typescript-strict-migrator

安装量: 42
排名: #26240

安装

npx skills add https://github.com/patricio0312rev/skills --skill typescript-strict-migrator

TypeScript Strict Migrator

Incrementally migrate to TypeScript strict mode for maximum type safety.

Core Workflow Audit current state: Check existing type errors Enable incrementally: One flag at a time Fix errors: Systematic approach per flag Add type guards: Runtime type checking Use utility types: Proper type transformations Document patterns: Team guidelines Strict Mode Flags // tsconfig.json - Full strict mode { "compilerOptions": { // Master flag (enables all below) "strict": true,

// Individual flags (enabled by strict)
"noImplicitAny": true,
"strictNullChecks": true,
"strictFunctionTypes": true,
"strictBindCallApply": true,
"strictPropertyInitialization": true,
"noImplicitThis": true,
"useUnknownInCatchVariables": true,
"alwaysStrict": true,

// Additional strict-ish flags (not in strict)
"noUncheckedIndexedAccess": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noImplicitOverride": true,
"exactOptionalPropertyTypes": true,
"noPropertyAccessFromIndexSignature": true

} }

Incremental Migration Strategy Phase 1: Basic Strict Flags // tsconfig.json - Phase 1 { "compilerOptions": { "strict": false, "noImplicitAny": true, "alwaysStrict": true } }

// Before: implicit any function processData(data) { return data.map(item => item.value); }

// After: explicit types function processData(data: DataItem[]): number[] { return data.map(item => item.value); }

interface DataItem { value: number; label: string; }

Phase 2: Strict Null Checks // tsconfig.json - Phase 2 { "compilerOptions": { "noImplicitAny": true, "strictNullChecks": true } }

// Before: potential null errors function getUserName(user: User) { return user.profile.name; // Error if profile is undefined }

// After: proper null handling function getUserName(user: User): string | undefined { return user.profile?.name; }

// With non-null assertion (use sparingly) function getUserNameOrThrow(user: User): string { if (!user.profile?.name) { throw new Error('User has no name'); } return user.profile.name; }

Phase 3: Function Types // tsconfig.json - Phase 3 { "compilerOptions": { "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true } }

// Before: contravariance issues type Handler = (event: Event) => void; const mouseHandler: Handler = (event: MouseEvent) => { console.log(event.clientX); // Error with strictFunctionTypes };

// After: proper variance type Handler = (event: T) => void; const mouseHandler: Handler = (event) => { console.log(event.clientX); };

Phase 4: Property Initialization // tsconfig.json - Phase 4 { "compilerOptions": { "strict": true, "noUncheckedIndexedAccess": true } }

// Before: uninitialized properties class UserService { private apiClient: ApiClient; // Error: not initialized

constructor() {} }

// After: definite assignment class UserService { private apiClient: ApiClient;

constructor(apiClient: ApiClient) { this.apiClient = apiClient; } }

// Or with definite assignment assertion class UserService { private apiClient!: ApiClient; // Initialized in init()

async init() { this.apiClient = await createApiClient(); } }

Type Guards Basic Type Guards // Type guard functions function isString(value: unknown): value is string { return typeof value === 'string'; }

function isNumber(value: unknown): value is number { return typeof value === 'number'; }

function isObject(value: unknown): value is Record { return typeof value === 'object' && value !== null; }

function isArray(value: unknown, itemGuard: (item: unknown) => item is T): value is T[] { return Array.isArray(value) && value.every(itemGuard); }

// Usage function processInput(input: unknown) { if (isString(input)) { return input.toUpperCase(); // input is string } if (isNumber(input)) { return input.toFixed(2); // input is number } throw new Error('Invalid input type'); }

Object Type Guards interface User { id: string; name: string; email: string; role: 'admin' | 'user'; }

interface ApiResponse { data: T; success: boolean; }

// Type guard for User function isUser(value: unknown): value is User { return ( isObject(value) && typeof value.id === 'string' && typeof value.name === 'string' && typeof value.email === 'string' && (value.role === 'admin' || value.role === 'user') ); }

// Type guard for API response function isApiResponse( value: unknown, dataGuard: (data: unknown) => data is T ): value is ApiResponse { return ( isObject(value) && typeof value.success === 'boolean' && 'data' in value && dataGuard(value.data) ); }

// Usage async function fetchUser(id: string): Promise { const response = await fetch(/api/users/${id}); const data: unknown = await response.json();

if (!isApiResponse(data, isUser)) { throw new Error('Invalid API response'); }

return data.data; }

Discriminated Unions // Discriminated union pattern type Result = | { success: true; data: T } | { success: false; error: E };

function createSuccess(data: T): Result { return { success: true, data }; }

function createError(error: E): Result { return { success: false, error }; }

// Type guard via discriminant function isSuccess(result: Result): result is { success: true; data: T } { return result.success === true; }

// Usage async function processRequest(): Promise> { try { const user = await fetchUser('123'); return createSuccess(user); } catch (error) { return createError(error instanceof Error ? error : new Error(String(error))); } }

const result = await processRequest(); if (isSuccess(result)) { console.log(result.data.name); // TypeScript knows data exists } else { console.error(result.error.message); // TypeScript knows error exists }

Utility Types for Migration // Making properties required type RequiredUser = Required;

// Making properties optional type PartialUser = Partial;

// Pick specific properties type UserCredentials = Pick;

// Omit specific properties type PublicUser = Omit;

// Make properties readonly type ReadonlyUser = Readonly;

// Deep readonly type DeepReadonly = { readonly [P in keyof T]: T[P] extends object ? DeepReadonly : T[P]; };

// NonNullable type DefiniteString = NonNullable; // string

// Extract and Exclude type AdminRole = Extract; // 'admin' type NonAdminRole = Exclude; // 'user'

// Record type type UserById = Record;

// Parameters and ReturnType type FetchParams = Parameters; // [input: RequestInfo, init?: RequestInit] type FetchReturn = ReturnType; // Promise

Common Migration Patterns Handling Optional Chaining // Before: unsafe access const userName = user.profile.settings.displayName;

// After: safe access with optional chaining const userName = user?.profile?.settings?.displayName;

// With nullish coalescing const userName = user?.profile?.settings?.displayName ?? 'Anonymous';

// With type narrowing function getDisplayName(user: User | null): string { if (!user?.profile?.settings?.displayName) { return 'Anonymous'; } return user.profile.settings.displayName; }

Assertion Functions // Assertion function function assertIsDefined(value: T): asserts value is NonNullable { if (value === undefined || value === null) { throw new Error('Value is not defined'); } }

function assertIsUser(value: unknown): asserts value is User { if (!isUser(value)) { throw new Error('Value is not a User'); } }

// Usage function processUser(maybeUser: unknown) { assertIsUser(maybeUser); // maybeUser is now User console.log(maybeUser.name); }

Error Handling // Before: any in catch try { await riskyOperation(); } catch (error) { console.error(error.message); // Error with useUnknownInCatchVariables }

// After: proper error handling try { await riskyOperation(); } catch (error) { if (error instanceof Error) { console.error(error.message); } else { console.error('Unknown error:', String(error)); } }

// Helper function function getErrorMessage(error: unknown): string { if (error instanceof Error) return error.message; if (typeof error === 'string') return error; return 'Unknown error occurred'; }

Index Signatures // Before: unsafe index access const users: Record = {}; const user = users['unknown-id']; console.log(user.name); // Error with noUncheckedIndexedAccess

// After: proper null check const user = users['unknown-id']; if (user) { console.log(user.name); }

// Or with assertion const user = users['known-id']!; // Only if you're certain

// Better: use Map const usersMap = new Map(); const user = usersMap.get('some-id'); // User | undefined by design

Migration Script // scripts/analyze-strict.ts import * as ts from 'typescript'; import * as path from 'path';

interface StrictAnalysis { noImplicitAny: number; strictNullChecks: number; strictFunctionTypes: number; strictPropertyInitialization: number; total: number; }

function analyzeProject(configPath: string): StrictAnalysis { const configFile = ts.readConfigFile(configPath, ts.sys.readFile); const parsedConfig = ts.parseJsonConfigFileContent( configFile.config, ts.sys, path.dirname(configPath) );

// Enable strict flags one by one const strictOptions = { noImplicitAny: true, strictNullChecks: true, strictFunctionTypes: true, strictPropertyInitialization: true, };

const analysis: StrictAnalysis = { noImplicitAny: 0, strictNullChecks: 0, strictFunctionTypes: 0, strictPropertyInitialization: 0, total: 0, };

for (const [flag, _] of Object.entries(strictOptions)) { const options = { ...parsedConfig.options, [flag]: true, };

const program = ts.createProgram(parsedConfig.fileNames, options);
const diagnostics = ts.getPreEmitDiagnostics(program);

analysis[flag as keyof StrictAnalysis] = diagnostics.length;
analysis.total += diagnostics.length;

}

return analysis; }

// Usage const analysis = analyzeProject('./tsconfig.json'); console.log('Strict mode analysis:', analysis);

Best Practices Incremental adoption: One flag at a time Start with noImplicitAny: Easiest to fix Add type guards: Runtime safety Use assertion functions: Fail fast Avoid non-null assertions: Use sparingly Document patterns: Team consistency CI enforcement: Prevent regression Use unknown over any: Better type safety Output Checklist

Every strict migration should include:

Baseline error count per flag Migration plan with phases Type guard utilities Assertion functions Error handling patterns Index access handling Optional chaining usage Updated tsconfig.json Team documentation CI strict checking

← 返回排行榜