Skip to content

20. TypeScript Strict Mode Enablement

Date: 2025-11-14
Status: Accepted
Deciders: Engineering Team
Technical Story: Architecture Refactoring - Enhancement #4 (November 14, 2025)

Sources: - Planning: 2025-11-14-architecture-refactoring

Context and Problem Statement

TypeScript was already in use [ADR-0001] but with lenient compiler settings [Problem: weak type checking]. After the architecture refactoring [ADR-0016], the codebase was well-structured enough to enable strict mode for maximum type safety [Opportunity: enforce strictness].

The Core Problem: Should we enable all TypeScript strict compiler options to catch more errors at compile-time? [Planning: 11-typescript-strict-mode-plan.md]

Decision Drivers: * Catch more errors at compile-time [Benefit: error prevention] * Prevent common bugs (null/undefined, implicit any) [Safety: type safety] * Better IDE support and autocomplete [DX: IntelliSense] * Industry best practice for TypeScript [Standard: strict recommended] * Clean architecture enables strict mode [Context: post-refactoring]

Alternative Options

  • Option 1: Enable All Strict Options - Full strict mode
  • Option 2: Gradual Strict Enablement - Enable options one-by-one
  • Option 3: Keep Lenient Settings - Stay with current permissive config
  • Option 4: Selective Strict - Only some strict options
  • Option 5: Strict for New Code Only - Gradual adoption

Decision Outcome

Chosen option: "Enable All Strict Options (Option 1)", because the codebase was already well-typed post-refactoring, and full strict mode provides maximum type safety with manageable effort (22 errors to fix).

Strict Options Enabled

TypeScript Configuration: [Source: tsconfig.json; 12-typescript-strict-mode-complete.md, lines 9-19]

{
  "compilerOptions": {
    "strict": true,              // Master flag (enables all below)
    "noImplicitAny": true,       // No implicit 'any' types
    "strictNullChecks": true,    // Null/undefined checking
    "strictFunctionTypes": true, // Function type checking
    "strictBindCallApply": true, // bind/call/apply checking
    "strictPropertyInitialization": true,  // Class property init
    "noImplicitThis": true,      // 'this' must be typed
    "alwaysStrict": true,        // Emit "use strict"
    "noUnusedLocals": true,      // Catch unused variables
    "noUnusedParameters": true,  // Catch unused parameters
    "noImplicitReturns": true,   // All paths must return
    "noFallthroughCasesInSwitch": true  // Switch exhaustiveness
  }
}

Errors Fixed

Total: 22 errors across 16 files [Source: 12-typescript-strict-mode-complete.md, line 21]

Error Categories: [Source: lines 25-77] 1. noImplicitAny (8 errors): Added explicit types 2. strictNullChecks (6 errors): Added null checks and non-null assertions 3. noImplicitReturns (4 errors): Added return statements 4. noUnusedLocals (2 errors): Removed unused variables 5. strictFunctionTypes (2 errors): Fixed function signatures

Example Fixes:

// Before: Implicit any
function process(data) { }

// After: Explicit type
function process(data: ConceptMetadata): void { }

// Before: Possible null
const concept = await repo.findByName(name);
concept.category // Error: might be null

// After: Null check
const concept = await repo.findByName(name);
if (!concept) throw new Error('Not found');
concept.category // OK: null checked

Consequences

Positive: * Type safety: Catches null/undefined bugs [Benefit: 12-typescript-strict-mode-complete.md, lines 97-101] * Better IDE: Improved autocomplete and IntelliSense [DX: better tooling] * Fewer runtime errors: Type errors caught at compile-time [Quality: prevention] * Self-documenting: Types serve as documentation [Documentation: implicit] * Refactoring confidence: Type system guides refactoring [Safety: guided changes] * 22 bugs prevented: Errors that would be runtime bugs [Impact: quality improvement] * Zero test failures: All 37 tests still passing [Result: line 82]

Negative: * Development friction: More type annotations required [Trade-off: explicitness] * Learning curve: Developers must understand strict checks [Effort: education] * Refactoring effort: 22 errors fixed across 16 files [Investment: ~2 hours] [Source: line 106] * Null checking verbosity: More if-checks for null/undefined [Code: verbosity]

Neutral: * One-time cost: Fix errors once, benefit ongoing [Investment: upfront] * TypeScript recommended: Strict mode is TypeScript best practice [Standard: official recommendation]

Confirmation

Validation: [Source: 12-typescript-strict-mode-complete.md, lines 79-88] - ✅ Build: Zero TypeScript errors - ✅ Tests: 37/37 passing (unit + integration) - ✅ Runtime: No new runtime errors - ✅ IDE: IntelliSense improved - ✅ Effort: ~2 hours actual vs. ~2.5 hours estimated [Source: line 106]

Pros and Cons of the Options

Option 1: Enable All Strict Options - Chosen

Pros: * Maximum type safety * 22 bugs caught [Result: line 21] * Better IDE support * Industry best practice * All tests still pass [Validated: line 82] * ~2 hours effort manageable [Source: line 106]

Cons: * 22 errors to fix immediately * Some development friction * Null checking verbosity * Learning curve

Option 2: Gradual Strict Enablement

Enable one option at a time.

Pros: * Smaller incremental changes * Easier to absorb * Less overwhelming

Cons: * Takes longer: Multiple sessions to complete [Effort: extended timeline] * Partial benefits: Don't get full safety until end * More context switching: Return to strict mode multiple times * All-at-once chosen: Faster to fix all at once [Decision: efficiency]

Option 3: Keep Lenient Settings

Stay with permissive TypeScript config.

Pros: * Zero effort * No errors to fix * Status quo

Cons: * Misses type errors: 22 bugs could become runtime errors [Risk: quality] * Weaker IDE: Less helpful autocomplete [DX: worse] * Against best practices: TypeScript docs recommend strict [Standard: ignored] * Rejected: After refactoring, codebase ready for strict [Decision: opportune time]

Option 4: Selective Strict

Enable only some options (cherry-pick).

Pros: * Can avoid hardest checks * Partial improvement * Less work

Cons: * Incomplete safety: Still vulnerable to some bug types * Configuration complexity: Must decide which options * Why not all?: If doing some, might as well do all [Logic: marginal effort] * All-or-nothing better: Full strict mode or none

Option 5: Strict for New Code Only

Use // @ts-check comments on new files only.

Pros: * No existing code changes * New code gets benefits * Gradual improvement

Cons: * Inconsistent: Different rules for different files [Problem: confusion] * Technical debt: Old code stays loose [Debt: accumulates] * Hard to enforce: Relies on discipline [Risk: forgotten] * Whole codebase fixed: Better to fix everything [Decision: consistency]

Implementation Notes

Error Fixing Strategy

By Option: [Source: 11-typescript-strict-mode-plan.md, implementation approach] 1. Enable one option in tsconfig 2. Fix all errors for that option 3. Commit 4. Repeat for next option

Actual Approach: [Source: 12-typescript-strict-mode-complete.md] - Enabled all options at once - Fixed errors by category - Single comprehensive update

Common Fixes

Implicit Any:

// Before
function parseData(input) { }

// After
function parseData(input: unknown): ParsedData { }

Strict Null Checks:

// Before
const concept = concepts.find(c => c.name === query);
return concept.category;  // Error: might be undefined

// After
const concept = concepts.find(c => c.name === query);
if (!concept) throw new Error('Concept not found');
return concept.category;  // OK: undefined handled

References


Confidence Level: HIGH Attribution: - Planning docs: November 14, 2024 - Documented in: 12-typescript-strict-mode-complete.md

Traceability: 2025-11-14-architecture-refactoring