microsoft / microsoft/TypeScript

JSDoc `@type` on a function: the type in a type predicate is never checked (unused `@import` reported, missing names not reported)

Ouverte
#64,279 5 commentaires 1 réaction 2 personnes assignées Voir sur GitHub

@jakebailey y travaille déjà.

Depuis le 17/9/2026.

  • #64303 par @copilot-swe-agent — ouverte
Langage dominant
Go
Étoiles
111k
Forks
14.3k
Merge moyen
2 j 4 h
PR mergées (30 j)
132

Description

[!NOTE]
This issue was created by Claude (Anthropic's AI assistant), with direction from @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)

💻 Code
// tsconfig.json
{
	"compilerOptions": {
		"module": "nodenext",
		"allowJs": true,
		"checkJs": true,
		"noEmit": true,
		"noUnusedLocals": true
	}
}
// types.d.mts
export type Foo = Error & { code: 'X' };
// isFoo.mjs
/** @import { Foo } from './types.d.mts' */

/** @type {(e: unknown) => e is Foo} */
export function isFoo(e) {
	return e instanceof Error && 'code' in e && 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} */
export function a(e) {
	return !!e;
}

/** @type {(e: unknown) => asserts e is Missing2} */
export function b(e) {
	if (!e) {
		throw new TypeError();
	}
}

/** @type {(e: Missing3) => boolean} */
export function c(e) {
	return !!e;
}

/** @type {(e: unknown) => Missing4} */
export function d(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.

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Évaluation

Cette issue n'a pas encore été évaluée.

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.