microsoft / microsoft/TypeScript
JSDoc `@type` on a function: the type in a type predicate is never checked (unused `@import` reported, missing names not reported)
- Lingua principale
- Go
- Stelle
- 111k
- Fork
- 14.3k
- Merge medio
- 2g 4h
- PR unite (30g)
- 132
Descrizione
> [!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
```jsonc
// tsconfig.json
{
"compilerOptions": {
"module": "nodenext",
"allowJs": true,
"checkJs": true,
"noEmit": true,
"noUnusedLocals": true
}
}
```
```ts
// types.d.mts
export type Foo = Error & { code: 'X' };
```
```js
// 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):
```js
// 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](https://github.com/microsoft/TypeScript/blob/57d9528db25b8dc8375e18468a870ec3f4277d62/tsc/internal/checker/checker.go#L3464-L3468), [checker.go#L10320-L10324](https://github.com/microsoft/TypeScript/blob/57d9528db25b8dc8375e18468a870ec3f4277d62/tsc/internal/checker/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.
Guida per i contributori
Apri la guida per i contributori
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Valutazione
Questa issue non è ancora stata valutata.