Typescript Strict
v1.0.0Deep TypeScript strictness workflow—incremental enablement, compiler flags, typing boundaries, narrowing, generics, utility types, and safe refactors. Use wh...
Like a lobster shell, security has layers — review code before you run it.
TypeScript Strict Mode (Deep Workflow)
Strictness is a gradient, not a single switch. The goal is fewer runtime surprises without blocking delivery—via incremental migration and clear typing boundaries at IO edges.
When to Offer This Workflow
Trigger conditions:
- Enabling
strict,strictNullChecks,noImplicitAny, ornoUncheckedIndexedAccess - Legacy codebase full of
anyand@ts-ignore - Runtime bugs that types would have caught (undefined, wrong shapes)
- Library authoring needing accurate
.d.tsexports
Initial offer:
Use six stages: (1) baseline & goals, (2) compiler flags roadmap, (3) boundary typing, (4) narrowing & exhaustiveness, (5) generics & patterns, (6) verify & guardrails). Confirm TS version, build tool (tsc, esbuild, etc.), and monorepo layout.
Stage 1: Baseline & Goals
Goal: Know current pain and target strictness with metrics.
Actions
- Capture
tsc --noEmiterror count and top files by errors - Identify critical packages vs leaf apps for sequencing
- Define non-goals (e.g., “no perfect types for untyped third-party JSON this quarter”)
Exit condition: Baseline error count + priority directories.
Stage 2: Compiler Flags Roadmap
Goal: Enable flags incrementally—fix clusters, not the whole repo at once.
Typical order (adapt)
strictNullChecks(often highest value)noImplicitAnyon new files + ratchet oldstrictFunctionTypes,strictBindCallApplynoUncheckedIndexedAccess(verbose—plan forundefinedunions)exactOptionalPropertyTypes(sharp edges—later)
Techniques
// @ts-expect-errorwith ticket over blind@ts-ignore- Path-specific
tsconfigextends for stricter packages - CI gate: no new
anyin changed lines (eslint@typescript-eslint/no-explicit-any)
Exit condition: Flag timeline with owners per package.
Stage 3: Boundary Typing (IO Edges)
Goal: Validate external data once; internal code trusts narrowed types.
Patterns
zod/io-ts/valibotfor runtime parse at API boundarysatisfiesfor config objects—keeps literal types- Avoid casting from
unknownwithout validation
APIs
- Generated types from OpenAPI/graphql-codegen when possible
- Branded types for IDs (
type UserId = string & { __brand: 'UserId' }) to prevent mix-ups
Exit condition: New IO code has parse → typed pipeline documented.
Stage 4: Narrowing & Exhaustiveness
Goal: Control flow analysis works for you—discriminated unions, never checks.
Practices
- Discriminated unions for states:
{ status: 'loading' } | { status: 'ok', data: T } switchwithassertNever(x)for compile-time exhaustiveness- Optional chaining vs explicit undefined handling—be consistent in module
Nullability
- Prefer
undefinedvsnullone convention per codebase section
Exit condition: Representative modules refactored with fewer optional footguns.
Stage 5: Generics & Patterns
Goal: Reuse without any soup—constraints and defaults.
Guidance
extendsconstraints; avoid unconstrained generics unless truly generic- Conditional types sparingly—readability over cleverness in app code
ReturnType/Parametersfor inference glue- Avoid mutable array inference surprises—explicit element types when needed
Performance
- Huge mapped types can slow
tsc—split types or simplify
Exit condition: Style guide section for generics in shared libraries.
Stage 6: Verify & Guardrails
Goal: Strictness sticks—regressions caught in CI.
CI
tsc --noEmitrequired green for affected projects- Type-aware ESLint rules; pre-commit optional
dtslintor tsd for library typings tests when authoring packages
Refactor safety
- Small PRs per module; runtime tests still required—types don’t replace tests
Final Review Checklist
- Baseline metrics and roadmap for flags
- IO boundaries validated with schemas or codegen
- Discriminated unions / exhaustiveness for key state machines
- Generics style documented for shared code
- CI enforces typecheck on merge
Tips for Effective Guidance
- Prefer
unknownoverany—forces narrowing at use site. - When user fights
noUncheckedIndexedAccess, show helper getters or Record patterns. - Monorepo: align TypeScript version across packages—version skew causes phantom errors.
Handling Deviations
- AllowJS mixed codebase: typed wrappers for critical paths first.
- Third-party untyped libs: local
d.tsor wrapper module with narrow exports.
Comments
Loading comments...
