Strictness and the Compiler
Lookup sheet for stage 3. The question it exists to answer: which flag catches which failure, where did a setting come from, and will this import resolve?
The strict family
Seven flags default to true unless strict is false. An eighth is named by tsc --all but uncatalogued here.
| Flag | Catches | Diagnostic |
|---|---|---|
noImplicitAny | unannotated parameter, nothing to infer | TS7006 |
strictNullChecks | null/undefined where not declared possible | TS2322 |
strictFunctionTypes | callback narrower than its function type | TS2322 |
strictBindCallApply | mismatch via call, bind, apply | TS2345 |
strictPropertyInitialization | class field never assigned in the constructor | TS2564 |
noImplicitThis | untyped this in a plain nested function | TS2683 |
useUnknownInCatchVariables | caught value used as a known shape | TS2322 |
strictBuiltinIteratorReturn | named in the family, uncatalogued this stage | none given |
strictNullChecks is load-bearing: off, null and undefined join every type rather than merely going unchecked. See lesson 15.
Checks strict omits
| Flag | Catches | Diagnostic | Needs strictNullChecks |
|---|---|---|---|
noUncheckedIndexedAccess | index read past the end | TS2322 | yes, else inert |
exactOptionalPropertyTypes | undefined written into an optional property | TS2375 | yes, else refused |
noImplicitReturns | a branch falling out under a declared return type | TS7030 | no |
noFallthroughCasesInSwitch | a case with statements and no break | TS7029 | no |
noPropertyAccessFromIndexSignature | dot access on an index signature | TS4111 | no |
noUnusedLocals | a dead local | TS6133 | no |
tsc --init turns on only the two needing strictNullChecks. See lesson 16.
Flag interaction, the three tiers
| Tier | Flags | What happens |
|---|---|---|
Refused with TS5052 | strictPropertyInitialization, exactOptionalPropertyTypes | rejected as configuration, no file read |
| Accepted but inert | noUncheckedIndexedAccess without strictNullChecks | compiles; no union forms, so a mismatch names the bare type |
| Working | any of the three, strictNullChecks on | reports honestly: a union, TS2564, or TS2375 |
An inert flag reads as covered when nothing ran. The table above gives each tier a row; the columns show why the middle one is the dangerous tier rather than merely the useless one. It agrees with the working tier on both questions a reader can actually put to the compiler, and disagrees on the only one they cannot. A green build is evidence for either.
See lesson 16.
unknown against any
| Operation | unknown | any |
|---|---|---|
| assign in | compiles | compiles |
| assign out to a typed variable | TS2322 until narrowed | compiles, no check |
| read a property or call it | TS18046 until narrowed | compiles regardless of shape |
narrow with typeof, instanceof | behaves like any narrowed type | nothing to narrow |
| chain a deep property read | blocked at the first step | stays any, no diagnostic |
| default catch variable type | unknown | only with catch (e: any) explicit |
| annotate catch as a specific type | TS1196 refused | same refusal |
See lesson 17.
Escape hatches
| Mechanism | Costs | Can go stale |
|---|---|---|
as, between overlapping types | turns off the one overlap check | yes, silently |
as unknown as | defeats the overlap guard entirely | yes, looks like honest unknown use |
! | removes null/undefined without checking absence | yes, a lie when the value is absent |
@ts-ignore | suppresses whatever lands on the next line | yes, forever, no signal once dead |
@ts-expect-error | suppresses a diagnostic only if one exists | no; TS2578 once its claim is false |
See lesson 18.
tsconfig anatomy
| Field | Decides | The gotcha |
|---|---|---|
include | glob seed set, relative to the config file | not the whole program |
exclude | narrows what include found | an import overrides it |
files | exact allow list, no globs | must exist; exclude cannot veto it |
extends | merges a base config, extender wins ties | resolves relative paths against the base file |
tsc --showConfig | the fully merged, effective configuration | implied settings show; strict never expands |
skipLibCheck | whether .d.ts files are checked | hides a mismatch between duplicate type packages |
types | which @types packages contribute globals | defaults to none since TypeScript 6.0 |
bare files beside tsconfig.json | refused outright | TS5112; --ignoreConfig restores the old behaviour |
See lesson 19.
target against lib
| Setting | Governs | The gotcha |
|---|---|---|
target | emitted JavaScript, e.g. a private field lowered to a WeakMap | never decides whether code type-checks |
lib | declarations available to the checker | naming it replaces the default bundle, never adds to it |
| default | target chooses a lib bundle when none is named | raising target is usually safer than naming lib |
| DOM | part of every target's default bundle | on by default, even off a browser |
target: es5 | removed in TypeScript 7 | TS5108, refused before any source file is read |
See lesson 20.
Module resolution
Identical options, opposite verdicts, keyed on package.json, for a specifier resolving to dep.ts on disk.
package.json | Import in main.ts | Verdict |
|---|---|---|
{"type": "module"} | from "./dep" | TS2835, extension required |
{"type": "module"} | from "./dep.js" | compiles, the future emitted extension |
{"type": "module"} | from "./dep.ts" | TS5097, unless allowImportingTsExtensions |
{}, or no type | from "./dep" | compiles, CommonJS keeps extensions optional |
Write the extension the emitted file will have, not the one on disk. Module system order: .mts is always ESM, .cts always CommonJS, else the nearest package.json's type, else CommonJS. verbatimModuleSyntax refuses a silently elided type-only import: a plain import of a type-only T gives TS1484 unless written import { type T }. See lesson 21.
Diagnostics seen in this stage
TS number | Meaning | Usual cause |
|---|---|---|
TS7006 | parameter implicitly any | no annotation, nothing to infer |
TS2322 | not assignable | unhandled null/undefined, narrower callback, or unnarrowed value |
TS2345 | argument not assignable | mismatch via call, bind, apply |
TS2564 | no initialiser | class field never set in the constructor |
TS2683 | this implicitly any | plain function reading this, no receiver |
TS5052 | option needs another option | flag needs strictNullChecks first |
TS2375 | optional property assignment fails | undefined under exactOptionalPropertyTypes |
TS7030 | not all paths return | branch falls out, declared return type |
TS7029 | fallthrough case in switch | statements with no break |
TS4111 | index signature accessed by dot | m.anything instead of m["anything"] |
TS6133 | declared but never used | dead local |
TS18046 | value is unknown | used before narrowing |
TS1196 | catch type must be any/unknown | specific type named on catch |
TS2352 | conversion may be a mistake | as between types sharing nothing |
TS2578 | unused @ts-expect-error | expected error gone |
TS5108 | option removed | target: es5 |
TS2550 | property missing, check target library | method newer than current lib |
TS2584 | cannot find name, check target library | lib named, dropped a default global |
TS2304 | cannot find name | undeclared global, or unnamed ambient package |
TS2835 | relative import needs an extension | extensionless specifier, ESM package, nodenext |
TS5097 | import path can only end in .ts | source extension instead of the emitted one |
TS5096 | allowImportingTsExtensions needs a no-emit setting | enabled while still emitting |
TS5112 | tsconfig.json ignored, files given on the command line | files passed with a real config present |
TS2353 | excess property on a fresh literal | silenced by as |
TS1484 | type needs a type-only import | verbatimModuleSyntax on, type keyword missing |