checkJs
Reports type errors in JavaScript files, using JSDoc comments as the type annotations.
"checkJs": boolean // default: false (requires allowJs)- Added in TypeScript 2.3
Parameters
| Parameter | Type | Description |
|---|---|---|
$checkJsoptional | boolean= false | Report errors in JavaScript files. Works in tandem with allowJs, which must also be enabled. |
Return value
booleanType diagnostics reported in .js files, with types taken from JSDoc comments and from inference.
Errors
Reports the same diagnostics as in TypeScript files, including TS7006 for implicit any parameters when strict or noImplicitAny is on.
Changelog
- PHP 2.3Introduced together with the // @ts-check and // @ts-nocheck per-file comments.
allowJs lets JavaScript into the program; checkJs makes the compiler complain about it. Types come from two places: inference over the JavaScript itself, and JSDoc comments, which TypeScript reads as real annotations — @param, @returns, @type, @typedef, @template. You get most of TypeScript's checking without changing a single file extension.
Per-file control
Turning checkJs on across a large codebase produces thousands of errors at once, which is why the per-file comments exist. // @ts-nocheck at the top of a file exempts it entirely — the escape hatch for the files you have not got to yet. The inverse, // @ts-check, opts a single file in while checkJs stays off, which is how most teams start: enable it file by file, fix the errors, then flip the global flag once the list is short.
What trips people up
Under strict, every undocumented parameter is an implicit any (TS7006), so an unannotated file can go from clean to a hundred errors. Also, checkJs alone does nothing — allowJs must be on for the file to be in the program at all — and // @ts-ignore or // @ts-expect-error silence one line where a whole-file exemption is too blunt.
Example
// src/cart.js — checked because checkJs is on (or because of this comment)
// @ts-check
/**
* @param {{ price: number, qty: number }[]} lines
* @returns {number}
*/
function subtotal(lines) {
return lines.reduce((sum, l) => sum + l.price * l.qty, 0);
}
console.log(subtotal([{ price: 250, qty: 2 }, { price: 99, qty: 1 }])); // 599
// subtotal("nope") would be TS2345: argument of type 'string' is not
// assignable to the annotated array parameter — reported in the .js file.
console.log(subtotal([]));When to use it
- Type-checking a JavaScript codebase with JSDoc instead of committing to a rewrite.
- Opting individual legacy files in with // @ts-check before turning the flag on globally.
- Silencing a generated or vendored file with // @ts-nocheck while the rest stays checked.
- Producing accurate .d.ts files from JSDoc-annotated JavaScript for publication.
Frequently asked questions
How do I type-check JavaScript files without converting them to TypeScript?
Enable "allowJs": true and "checkJs": true, then describe your types in JSDoc comments. TypeScript reads @param, @returns, @type, @typedef and @template as real annotations and reports the same errors it would in a .ts file. Add // @ts-check at the top of individual files if you want to opt in gradually instead of enabling checkJs globally.
How do I disable type checking for one JavaScript file?
Put // @ts-nocheck as the first line of the file. Every diagnostic in that file is suppressed while checkJs stays on everywhere else. For a single line rather than a whole file, use // @ts-ignore, or // @ts-expect-error which additionally errors if the next line turns out to be fine.
Why does checkJs report no errors at all?
Most likely allowJs is off, so the .js files are not in the program and there is nothing to check. Confirm allowJs is true and that include actually matches the files. If a specific file is silent, check whether it starts with // @ts-nocheck.
More examples
Turning it on without drowning in errors
strict and noImplicitAny stay off at first, so undocumented parameters do not each become a TS7006. That leaves only real mistakes — wrong property names, bad argument counts, impossible comparisons. Once those are fixed, noImplicitAny goes on and the JSDoc gets written. noEmit keeps tsc purely as a checker so the existing build is untouched.
{
"compilerOptions": {
"allowJs": true,
"checkJs": true,
"noEmit": true,
"strict": false,
"noImplicitAny": false,
"target": "es2022"
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.generated.js"]
}JSDoc that carries real type information
@typedef declares a reusable type, @type annotates a variable, and @template gives a generic function real inference — findOne returns Job | undefined here, not any. The logged value is the pushed job object, which is exactly the shape the @typedef enforces at check time.
// src/queue.js
// @ts-check
/** @typedef {{ id: string, attempts: number }} Job */
/** @type {Job[]} */
const jobs = [];
/**
* @template T
* @param {T[]} items
* @param {(item: T) => boolean} pred
* @returns {T | undefined}
*/
function findOne(items, pred) {
return items.find(pred);
}
jobs.push({ id: "a", attempts: 0 });
// jobs.push({ id: "b" }); // TS2345: 'attempts' is missing
console.log(findOne(jobs, (j) => j.attempts === 0));
Discussion