microsoft / TypeScript

TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
https://www.typescriptlang.org
Apache License 2.0
100.76k stars 12.46k forks source link

Assertion call error for property typed via JSDoc qualified name #42172

Open ajafff opened 3 years ago

ajafff commented 3 years ago

TypeScript Version: 4.2.0-dev.20210101

Search Terms: assertion jsdoc

Expected behavior:

No error, assertion call narrows v.

Actual behavior:

Assertions require every name in the call target to be declared with an explicit type annotation.(2775)
input.js(8, 4): 'assert' needs an explicit type annotation.

Assertions require every name in the call target to be declared with an explicit type annotation.(2775)
input.js(3, 4): 'assert' needs an explicit type annotation.

isDeclarationWithExplicitTypeAnnotation doesn't handle JSDocParameterTag and JSDocPropertyTag

Code

/**
 * @typedef Foo {object}
 * @property assert {(v: any) => asserts v}
 */

/**
 * @param p {object}
 * @param p.assert {(v: any) => asserts v}
 * @param v {boolean}
 */
function doesntWork(p, v) {
    p.assert(v); // Error here
    v;
}

/**
 * @param p {Foo}
 * @param v {boolean}
 */
function alsoDoesntWork(p, v) {
    p.assert(v); // Error here
    v;
}

/**
 * @param assert {(v: any) => asserts v}
 * @param v {boolean}
 */
function works(assert, v) {
    assert(v);
    v;
}

/**
 * @param p {{assert(v: any): asserts v}}
 * @param v {boolean}
 */
function alsoWorks(p, v) {
    p.assert(v);
    v;
}
Compiler Options ```json { "compilerOptions": { "noImplicitAny": true, "strictFunctionTypes": true, "strictPropertyInitialization": true, "strictBindCallApply": true, "noImplicitThis": true, "noImplicitReturns": true, "alwaysStrict": true, "esModuleInterop": true, "checkJs": true, "allowJs": true, "declaration": true, "experimentalDecorators": true, "emitDecoratorMetadata": true, "moduleResolution": 2, "target": "ES2017", "module": "ESNext" } } ```

Playground Link: Provided

opiation commented 10 months ago

Hey there! I'm not 100% certain this is exactly the same but the error is the same and I'm using JSDoc for type annotations with assertions. Essentially, even with every name in the call target having an explicit type annotation, I still get the aforementioned error. Despite making the JSDoc annotations even more verbose, explicit type annotations still don't make the error go away.

Note, there is a somewhat cumbersome workaround but I would hate to have to use that everywhere. You can inline the type annotation in the @param or @typedef. Here's a playground with both examples and the workaround also inlined below for convenience:

/**
 * @param {unknown} value
 * @returns {asserts value is number}
 */
function assertNumber(value) {
  if (typeof value !== "number") throw "a ball in the air";
}

/**
 * @param {{ num: typeof assertNumber }} inlinedAssert
 * @param {object} jsdocObjectAssert
 * @param {typeof assertNumber} jsdocObjectAssert.num
 */
function paramExample(inlinedAssert, jsdocObjectAssert) {
  // This works with no compiler error πŸŽ‰!
  inlinedAssert.num("asdf");

  // This should probably work 🀷? but throws the error mentioned above 😒.
  jsdocObjectAssert.num("zxcv");
}

/** @typedef {{ num: typeof assertNumber }} InlinedAssert */
/**
 * @typedef ObjectAssert
 * @property {typeof assertNumber} num
 */

function typedefExample() {
  /** @type {InlinedAssert} */
  const inlinedAssert = { num: assertNumber }
  // This works with no compiler error πŸŽ‰!
  inlinedAssert.num("qwerty")

  /** @type {ObjectAssert} */
  const objectAssert = { num: assertNumber }
  // Same error as above 😒.
  objectAssert.num("πŸ€”")
}

Interesting that the object literal inlined notation in @param and @typedef are treated differently than the expanded JSDoc form.