Typing a plain-JavaScript function with JSDoc so that tsc checks it
A .js file in a project that has allowJs and checkJs enabled. The compiler currently infers any for both parameters, so a wrong call site is not reported at all.
Requirements: describe the user object shape once as a reusable named type, annotate both parameters and the return value, and do it without renaming the file to .ts and without importing anything.
// @ts-check
function formatUser(user, upper) {
return upper ? user.name.toUpperCase() : user.name;
}
formatUser({ name: 42 }, 'yes'); // must become an error
Write the implementation.
Declare the shape once with @typedef, then annotate the function with @param {Type} name and @returns. Under @ts-check the compiler reads those tags as real types, so formatUser({ name: 42 }, 'yes') fails to build — no rename needed.
- ✗Thinking JSDoc tags are documentation only and cannot type-check a
.jsfile - ✗Writing TypeScript annotation syntax inside a
.jsfile, which is a parse error - ✗Forgetting
@ts-checkorcheckJs, so the tags are parsed but nothing is enforced
- →How do you express a generic function in JSDoc, and which tag does that take?
- →What can a
.tsfile express that JSDoc inside a.jsfile cannot?
The solution
@typedef declares the shape once; @param and @returns attach the types to the signature. No rename, no extra build step — the file stays .js.
// @ts-check
/**
* @typedef {object} User
* @property {string} name
*/
/**
* @param {User} user
* @param {boolean} upper
* @returns {string}
*/
function formatUser(user, upper) {
return upper ? user.name.toUpperCase() : user.name;
}
formatUser({ name: 42 }, 'yes');
// ❌ Type 'number' is not assignable to type 'string'
// ❌ Argument of type 'string' is not assignable to parameter of type 'boolean'
Why it works
@ts-check turns the very same checker that runs over .ts onto this .js file. It reads the JSDoc tags as real annotations: User becomes an ordinary type rather than an editor hint, and both errors in the call are build failures, not just squiggles.
⚠️ TypeScript annotation syntax (user: User) is a parse error in a .js file. Everything goes through tags — which is also the approach's main cost.