TypeScript Strict Refactoring
Incrementally converts JavaScript or loose TypeScript codebases to strict TypeScript with proper types, removing any/unknown usage.
TypeScript Strict Refactoring
Incrementally migrate a TypeScript codebase to strict mode by systematically resolving any types, null checks, and implicit coercions while maintaining runtime behavior.
When to Use This Template
Choose TypeScript Strict Refactoring when:
- You are enabling
strict: truein tsconfig.json and facing hundreds of type errors - Your codebase relies heavily on
anytypes and you want to add proper typing - You need to add null safety checks without changing business logic
- You want to incrementally tighten type checking file by file
Consider alternatives when:
- You are starting a new project (just enable strict mode from the beginning)
- You need to migrate from JavaScript to TypeScript (use a JS-to-TS migration tool)
- Type errors are caused by outdated library typings (update @types packages first)
Quick Start
# .claude/skills/typescript-strict-refactoring.yml name: typescript-strict-refactoring description: Refactor TypeScript code to pass strict mode checks prompt: | Refactor the specified TypeScript files to pass strict mode checks. Rules: 1. Replace `any` with proper types inferred from usage 2. Add null/undefined checks where strictNullChecks fails 3. Use type narrowing instead of type assertions 4. Add explicit return types to exported functions 5. Never change runtime behavior — only add type safety 6. Use `unknown` instead of `any` for truly dynamic values Prioritize: proper types > unknown > type assertions > any
Example invocation:
claude "Refactor src/services/userService.ts to pass strict TypeScript checks"
Before:
function getUser(id) { const user = cache.get(id); return user.name.toUpperCase(); }
After:
function getUser(id: string): string | null { const user = cache.get(id); if (!user?.name) return null; return user.name.toUpperCase(); }
Core Concepts
Strict Mode Flags Breakdown
| Flag | What It Catches | Typical Error Count |
|---|---|---|
strictNullChecks | Null/undefined access | 40-60% of errors |
noImplicitAny | Missing type annotations | 20-30% of errors |
strictFunctionTypes | Function parameter variance | 5-10% of errors |
strictPropertyInitialization | Uninitialized class props | 5-10% of errors |
noImplicitReturns | Missing return statements | 2-5% of errors |
noImplicitThis | Untyped this context | 1-3% of errors |
Incremental Migration Strategy
// tsconfig.json — Enable flags one at a time { "compilerOptions": { // Phase 1: Start here (biggest impact) "noImplicitAny": true, // Phase 2: After fixing all `any` types "strictNullChecks": true, // Phase 3: Function and class strictness "strictFunctionTypes": true, "strictPropertyInitialization": true, // Phase 4: Final strict mode "strict": true } }
Common Refactoring Patterns
// Pattern 1: Replace `any` with inferred types // Before function processData(data: any) { return data.items.map((item: any) => item.name); } // After interface DataPayload { items: Array<{ name: string; id: number }>; } function processData(data: DataPayload): string[] { return data.items.map(item => item.name); } // Pattern 2: Null narrowing // Before function getDisplayName(user: User) { return user.profile.displayName.trim(); // crashes if profile is null } // After function getDisplayName(user: User): string { return user.profile?.displayName?.trim() ?? user.email; } // Pattern 3: Discriminated unions instead of type assertions // Before function handleEvent(event: any) { if (event.type === 'click') { (event as MouseEvent).clientX; // unsafe assertion } } // After type AppEvent = | { type: 'click'; clientX: number; clientY: number } | { type: 'keypress'; key: string } | { type: 'scroll'; offset: number }; function handleEvent(event: AppEvent) { if (event.type === 'click') { event.clientX; // narrowed automatically } } // Pattern 4: Exhaustive switch with never function getStatusColor(status: OrderStatus): string { switch (status) { case 'pending': return 'yellow'; case 'shipped': return 'blue'; case 'delivered': return 'green'; default: { const _exhaustive: never = status; // compile error if case missed return 'gray'; } } }
Configuration
| Option | Type | Default | Description |
|---|---|---|---|
targetFlag | string | "noImplicitAny" | Which strict flag to target first |
filePattern | string | "src/**/*.ts" | Files to refactor (glob pattern) |
preserveAssertions | boolean | false | Keep existing as casts or replace with narrowing |
addExplicitReturns | boolean | true | Add return type annotations to exports |
unknownOverAny | boolean | true | Prefer unknown over any for dynamic values |
skipTests | boolean | true | Skip test files during refactoring |
Best Practices
-
Enable one strict flag at a time — Enabling
strict: trueall at once generates hundreds of errors across the codebase. EnablenoImplicitAnyfirst, fix all errors, commit, then enablestrictNullChecks, and so on. Each flag is independently valuable. -
Use
// @ts-expect-errorsparingly as a migration tool — When you cannot fix a type error immediately, use@ts-expect-errorwith a TODO comment explaining why. This is better thananybecause it will surface when the underlying issue is fixed (the comment becomes unnecessary and TS reports it). -
Infer types from usage, not just declarations — When replacing
any, trace how the variable is actually used in the code. Ifdata.items.map(i => i.name)works at runtime, thendatahas anitemsarray withnameproperties. Build the interface from actual usage patterns. -
Add type guards for external data boundaries — API responses, localStorage reads, and JSON.parse results are legitimately
unknown. Create type guard functions (function isUser(data: unknown): data is User) at these boundaries rather than casting withas. -
Run tests after every file change — Strict mode refactoring should never change runtime behavior. If a test fails after your refactoring, you changed logic (e.g., adding a null check that returns early when the old code would have thrown). Review every behavioral change carefully.
Common Issues
Hundreds of errors from a single flag — Enabling strictNullChecks in a large codebase generates errors in nearly every file because most code assumes values are never null. Use the // @ts-expect-error directive to suppress errors in files you have not refactored yet, and fix files one by one starting with leaf modules (no internal dependencies).
Third-party library types incompatible with strict mode — Some library typings use any extensively or have incorrect null annotations. Create a types/overrides.d.ts file with declare module blocks to patch specific library types. File issues upstream with the library maintainers for permanent fixes.
Generic function types become overly complex — Strict mode forces you to fully type generic utility functions, leading to type gymnastics. If a generic function's type signature is longer than its implementation, consider splitting it into multiple specific functions or using function overloads for the common use cases.
Reviews
No reviews yet. Be the first to review this template!
Similar Templates
Full-Stack Code Reviewer
Comprehensive code review skill that checks for security vulnerabilities, performance issues, accessibility, and best practices across frontend and backend code.
Test Suite Generator
Generates comprehensive test suites with unit tests, integration tests, and edge cases. Supports Jest, Vitest, Pytest, and Go testing.
Pro Architecture Workspace
Battle-tested skill for architectural, decision, making, framework. Includes structured workflows, validation checks, and reusable patterns for development.