You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue was created by Claude (Anthropic's AI assistant), with direction from Jordan Harband (@ljharb). All compiler output below is from actual tsc runs.
🔎 Search Terms
"declared but never used" jsdoc, TS6196, TS6133, TS2304, @import unused, jsdoc type predicate unused import, jsdoc type predicate "Cannot find name", @type function declaration type predicate, asserts jsdoc, noUnusedLocals checkJs, FullSignature
🕗 Version & Regression Information
This changed between versions 6.0.3 and 7.0.2 (also reproduces on 7.1.0-dev.20260915.1)
⏯ Playground Link
No response (the repro needs a second file for the @import, plus a tsconfig)
// isFoo.mjs/** @import { Foo } from './types.d.mts' *//** @type {(e: unknown) => e is Foo} */exportfunctionisFoo(e){returneinstanceofError&&'code'ine&&e.code==='X';}
🙁 Actual behavior
isFoo.mjs(1,15): error TS6196: 'Foo' is declared but never used.
Foo is used, in the e is Foo type predicate of the @type tag.
🙂 Expected behavior
No error, as in 6.0.3.
Additional information about the issue
Each form below was tested in its own file with the same @import and tsconfig:
Form
7.1.0-dev.20260915.1
7.0.2
6.0.3
@type {(e: unknown) => e is Foo} on a function declaration (above)
TS6196
TS6196
no error
@type {(e: unknown) => asserts e is Foo} on a function declaration
TS6196
TS6196
no error
export default /** @type {(e: unknown) => e is Foo} */ (e) => …
TS6196
TS6196
TS6133
@param {unknown} e + @returns {e is Foo}
no error
no error
TS6133
JSDoc cast: /** @type {(e: unknown) => e is Foo} */ ((e) => …)
no error
no error
no error
@type {(e: Foo) => boolean} (Foo as a parameter type)
no error
no error
no error
the first form, plus a call in the same file that narrows with it
no error
no error
no error
Workaround: drop the @import and write e is import('./types.d.mts').Foo.
The predicate's type doesn't seem to be checked at all: names that don't exist aren't reported there either, while the same names in the parameter or return type are (same tsconfig, no imports):
// unchecked.mjs/** @type {(e: unknown) => e is Missing1} */exportfunctiona(e){return!!e;}/** @type {(e: unknown) => asserts e is Missing2} */exportfunctionb(e){if(!e){thrownewTypeError();}}/** @type {(e: Missing3) => boolean} */exportfunctionc(e){return!!e;}/** @type {(e: unknown) => Missing4} */exportfunctiond(e){return/** @type {never} */(e);}
7.1.0-dev.20260915.1 and 7.0.2:
unchecked.mjs(13,16): error TS2304: Cannot find name 'Missing3'.
unchecked.mjs(18,28): error TS2304: Cannot find name 'Missing4'.
6.0.3:
unchecked.mjs(1,33): error TS2304: Cannot find name 'Missing1'.
unchecked.mjs(6,41): error TS2304: Cannot find name 'Missing2'.
unchecked.mjs(13,16): error TS2304: Cannot find name 'Missing3'.
unchecked.mjs(18,28): error TS2304: Cannot find name 'Missing4'.
Possibly relevant, but I haven't confirmed it: when a @type tag supplies a function's FullSignature, the checker only uses it for the argument-count check (checker.go#L3464-L3468, checker.go#L10320-L10324) and never runs checkSourceElement on it. If so, the predicate's type is never checked or marked as referenced unless something else resolves the predicate, like the same-file call in the last row of the table. #64052 (the open fix for #63754) edits these same lines, but only to strip undefined from the @type's type, so it wouldn't change this.
Note
This issue was created by Claude (Anthropic's AI assistant), with direction from Jordan Harband (@ljharb). All compiler output below is from actual
tscruns.🔎 Search Terms
"declared but never used" jsdoc, TS6196, TS6133, TS2304,
@importunused, jsdoc type predicate unused import, jsdoc type predicate "Cannot find name",@typefunction declaration type predicate, asserts jsdoc, noUnusedLocals checkJs, FullSignature🕗 Version & Regression Information
⏯ Playground Link
No response (the repro needs a second file for the
@import, plus a tsconfig)💻 Code
🙁 Actual behavior
Foois used, in thee is Footype predicate of the@typetag.🙂 Expected behavior
No error, as in 6.0.3.
Additional information about the issue
Each form below was tested in its own file with the same
@importand tsconfig:@type {(e: unknown) => e is Foo}on a function declaration (above)@type {(e: unknown) => asserts e is Foo}on a function declarationexport default /** @type {(e: unknown) => e is Foo} */ (e) => …@param {unknown} e+@returns {e is Foo}/** @type {(e: unknown) => e is Foo} */ ((e) => …)@type {(e: Foo) => boolean}(Fooas a parameter type)Workaround: drop the
@importand writee is import('./types.d.mts').Foo.The predicate's type doesn't seem to be checked at all: names that don't exist aren't reported there either, while the same names in the parameter or return type are (same tsconfig, no imports):
7.1.0-dev.20260915.1 and 7.0.2:
6.0.3:
Possibly relevant, but I haven't confirmed it: when a
@typetag supplies a function'sFullSignature, the checker only uses it for the argument-count check (checker.go#L3464-L3468, checker.go#L10320-L10324) and never runscheckSourceElementon it. If so, the predicate's type is never checked or marked as referenced unless something else resolves the predicate, like the same-file call in the last row of the table. #64052 (the open fix for #63754) edits these same lines, but only to stripundefinedfrom the@type's type, so it wouldn't change this.