T

TypeScript Strict Refactoring

Incrementally converts JavaScript or loose TypeScript codebases to strict TypeScript with proper types, removing any/unknown usage.

SkillClipticsdevelopmentv1.0.0MIT
0 views0 copies

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: true in tsconfig.json and facing hundreds of type errors
  • Your codebase relies heavily on any types 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

FlagWhat It CatchesTypical Error Count
strictNullChecksNull/undefined access40-60% of errors
noImplicitAnyMissing type annotations20-30% of errors
strictFunctionTypesFunction parameter variance5-10% of errors
strictPropertyInitializationUninitialized class props5-10% of errors
noImplicitReturnsMissing return statements2-5% of errors
noImplicitThisUntyped this context1-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

OptionTypeDefaultDescription
targetFlagstring"noImplicitAny"Which strict flag to target first
filePatternstring"src/**/*.ts"Files to refactor (glob pattern)
preserveAssertionsbooleanfalseKeep existing as casts or replace with narrowing
addExplicitReturnsbooleantrueAdd return type annotations to exports
unknownOverAnybooleantruePrefer unknown over any for dynamic values
skipTestsbooleantrueSkip test files during refactoring

Best Practices

  1. Enable one strict flag at a time — Enabling strict: true all at once generates hundreds of errors across the codebase. Enable noImplicitAny first, fix all errors, commit, then enable strictNullChecks, and so on. Each flag is independently valuable.

  2. Use // @ts-expect-error sparingly as a migration tool — When you cannot fix a type error immediately, use @ts-expect-error with a TODO comment explaining why. This is better than any because it will surface when the underlying issue is fixed (the comment becomes unnecessary and TS reports it).

  3. Infer types from usage, not just declarations — When replacing any, trace how the variable is actually used in the code. If data.items.map(i => i.name) works at runtime, then data has an items array with name properties. Build the interface from actual usage patterns.

  4. 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 with as.

  5. 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.

Community

Reviews

Write a review

No reviews yet. Be the first to review this template!

Similar Templates