TypeScript 4.2 was announced on 23 February 2021. One of its breaking changes is about JavaScript files only: f<T>(100) in a .js file is now parsed the way JavaScript parses it, as two comparisons, (f < T) > 100. Type arguments were never allowed in JavaScript; now they are not even read as such.
The release also added --explainFiles, which says why each file is part of the program, and new checks that apply to .js files: an implicit any from a yield expression, and a function tested in a condition instead of called.
At a glance
| Release | TypeScript 4.2 |
|---|---|
| Released | 23 February 2021, the day of its announcement on the TypeScript blog |
| Source | TypeScript 4.2 release notes |
| Examples | Every example runs through TypeScript 5.9.3 when this site is built. |
| Previous release | TypeScript 4.1 |
| Next release | TypeScript 4.3 |
What changed for JavaScript
Type arguments in .js files are comparisons
JavaScript has no syntax for explicit type arguments, and since 4.2 TypeScript does not parse first<string>(x) as a call with one: it is first < string > x, which then fails as a comparison (TS2365, TS2693). Let inference choose the type, or give the argument a type with a JSDoc cast.
/**
* @template T
* @param {T[]} items
* @returns {T | undefined}
*/
export function first(items) {
return items[0];
}
/** @type {unknown[]} */
const rows = ["TEE-01"];
const sku = first<string>(rows);error TS2365: Operator '>' cannot be applied to types 'boolean' and 'unknown[]'.error TS2693: 'string' only refers to a type, but is being used as a value here.
export { sku };jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": ["*.js"]
} Implicit any from yield
Under noImplicitAny, keeping the result of a yield is now an error (TS7057) when nothing tells TypeScript its type. Give the generator a type with @returns {Generator<Yielded, Returned, Received>}.
export function* askLoose() {
const name = yield "Name?";error TS7057: 'yield' expression implicitly results in an 'any' type because its containing generator lacks a return-type annotation.
return name;
}
/** @returns {Generator<string, string, string>} */
export function* ask() {
const name = yield "Name?";
return name.trim();
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} Functions tested instead of called
Under strictNullChecks, a function used as a condition inside && or || is reported when it is always defined: isEnabled && item.visible is almost always a missing () (TS2774).
/**
* @param {{ visible: boolean }[]} items
* @param {() => boolean} isEnabled
*/
export function shown(items, isEnabled) {
return items.filter((item) => isEnabled && item.visible);error TS2774: This condition will always return true since this function is always defined. Did you mean to call it instead?
}jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": []
},
"include": [
"*.js"
]
} --explainFiles
Run tsc -p jsconfig.json --explainFiles to print every file in the program and the reason it is there: matched by include, imported by another file, or a lib or @types package. It is the quickest way to find out why a file outside your source folder is checked.
npx tsc -p jsconfig.json --explainFiles noPropertyAccessFromIndexSignature
A new option, outside strict, requires bracket access for keys that only an index signature declares, so a typo in a dotted property name cannot pass as a lookup.
/**
* @typedef {object} Settings
* @property {string} theme
*/
/** @type {Settings & Record<string, string>} */
const settings = { theme: "dark", locale: "en-US" };
console.log(settings.theme);
console.log(settings.locale);error TS4111: Property 'locale' comes from an index signature, so it must be accessed with ['locale'].
console.log(settings["locale"]);jsconfig.json for this example
{
"compilerOptions": {
"checkJs": true,
"noEmit": true,
"strict": true,
"target": "es2022",
"module": "nodenext",
"types": [],
"noPropertyAccessFromIndexSignature": true
},
"include": [
"*.js"
]
} What did not change
TypeScript still does not accept type arguments in JavaScript; the change is in how the text is parsed, which matters most to tools that read JavaScript through TypeScript's parser.
Upgrade notes
From the breaking changes:
- New TS7057 errors for generators under
noImplicitAny. - Uncalled-function checks in
&&and||understrictNullChecks. - An import path ending in
.d.tsis an error; import the module path instead. - The
inoperator's right side must be an object.
Gotchas
- An underscore prefix (
_first) now marks a destructured name as intentionally unused fornoUnusedLocals.
Sources
- TypeScript 4.2 release notes · TypeScript handbook
- Announcing TypeScript 4.2 · TypeScript blog
Last reviewed 20 October 2026.