# TypeScript Style Guide **Authority**: This guide is MANDATORY for all TypeScript code. No exceptions without explicit approval. Based on Google TypeScript Style Guide with stricter enforcement. --- ## Core Principles 1. **Explicit over implicit** — Always declare types, never rely on inference for public APIs 2. **Specific over generic** — Use the narrowest type that works 3. **Safe over convenient** — Type safety is not negotiable --- ## Forbidden Patterns (NEVER USE) ### `any` Type — FORBIDDEN ```typescript // ❌ NEVER function process(data: any) { } const result: any = fetchData(); Record // ✅ ALWAYS define explicit types interface UserData { id: string; name: string; email: string; } function process(data: UserData) { } ``` ### `unknown` as Lazy Typing — FORBIDDEN `unknown` is only acceptable in these specific cases: 1. Error catch blocks (then immediately narrow) 2. JSON.parse results (then validate with Zod/schema) 3. External API responses before validation ```typescript // ❌ NEVER - using unknown to avoid typing function getData(): unknown { } const config: Record = {}; // ✅ ACCEPTABLE - error handling with immediate narrowing try { riskyOperation(); } catch (error: unknown) { if (error instanceof Error) { logger.error(error.message); } else { logger.error('Unknown error', { error: String(error) }); } } // ✅ ACCEPTABLE - external data with validation const raw: unknown = JSON.parse(response); const validated = UserSchema.parse(raw); // Zod validation ``` ### Implicit `any` — FORBIDDEN ```typescript // ❌ NEVER - implicit any from missing types function process(data) { } // Parameter has implicit any const handler = (e) => { } // Parameter has implicit any // ✅ ALWAYS - explicit types function process(data: RequestPayload): ProcessedResult { } const handler = (e: React.MouseEvent): void => { } ``` ### Type Assertions to Bypass Safety — FORBIDDEN ```typescript // ❌ NEVER - lying to the compiler const user = data as User; const element = document.getElementById('app') as HTMLDivElement; // ✅ USE - type guards and narrowing function isUser(data: unknown): data is User { return typeof data === 'object' && data !== null && 'id' in data; } if (isUser(data)) { console.log(data.id); // Safe } // ✅ USE - null checks const element = document.getElementById('app'); if (element instanceof HTMLDivElement) { element.style.display = 'none'; // Safe } ``` ### Non-null Assertion (`!`) — FORBIDDEN (except tests) ```typescript // ❌ NEVER in production code const name = user!.name; const element = document.getElementById('app')!; // ✅ USE - proper null handling const name = user?.name ?? 'Anonymous'; const element = document.getElementById('app'); if (element) { // Safe to use element } ``` --- ## Required Patterns ### Explicit Return Types — REQUIRED for all public functions ```typescript // ❌ WRONG - missing return type export function calculateTotal(items: Item[]) { return items.reduce((sum, item) => sum + item.price, 0); } // ✅ CORRECT - explicit return type export function calculateTotal(items: Item[]): number { return items.reduce((sum, item) => sum + item.price, 0); } ``` ### Explicit Parameter Types — REQUIRED always ```typescript // ❌ WRONG const multiply = (a, b) => a * b; users.map(user => user.name); // If user type isn't inferred // ✅ CORRECT const multiply = (a: number, b: number): number => a * b; users.map((user: User): string => user.name); ``` ### Interface Over Type Alias — PREFERRED for objects ```typescript // ✅ PREFERRED - interface (extendable, better error messages) interface User { id: string; name: string; email: string; } // ✅ ACCEPTABLE - type alias for unions, intersections, primitives type Status = 'active' | 'inactive' | 'pending'; type ID = string | number; ``` ### Const Assertions for Literals — REQUIRED ```typescript // ❌ WRONG - loses literal types const config = { endpoint: '/api/users', method: 'GET', }; // config.method is string, not 'GET' // ✅ CORRECT - preserves literal types const config = { endpoint: '/api/users', method: 'GET', } as const; // config.method is 'GET' ``` ### Discriminated Unions — REQUIRED for variants ```typescript // ❌ WRONG - optional properties for variants interface ApiResponse { success: boolean; data?: User; error?: string; } // ✅ CORRECT - discriminated union interface SuccessResponse { success: true; data: User; } interface ErrorResponse { success: false; error: string; } type ApiResponse = SuccessResponse | ErrorResponse; ``` --- ## Generic Constraints ### Meaningful Constraints — REQUIRED ```typescript // ❌ WRONG - unconstrained generic function merge(a: T, b: T): T { } // ✅ CORRECT - constrained generic function merge(a: T, b: Partial): T { } ``` ### Default Generic Parameters — USE SPECIFIC TYPES ```typescript // ❌ WRONG interface Repository { } // ✅ CORRECT - no default if type should be explicit interface Repository { } // ✅ ACCEPTABLE - meaningful default interface Cache { } ``` --- ## React/JSX Specific ### Event Handlers — EXPLICIT TYPES REQUIRED ```typescript // ❌ WRONG const handleClick = (e) => { }; const handleChange = (e) => { }; // ✅ CORRECT const handleClick = (e: React.MouseEvent): void => { }; const handleChange = (e: React.ChangeEvent): void => { }; const handleSubmit = (e: React.FormEvent): void => { }; ``` ### Component Props — INTERFACE REQUIRED ```typescript // ❌ WRONG - inline types function Button({ label, onClick }: { label: string; onClick: () => void }) { } // ✅ CORRECT - named interface interface ButtonProps { label: string; onClick: () => void; disabled?: boolean; } function Button({ label, onClick, disabled = false }: ButtonProps): JSX.Element { return ; } ``` ### Children Prop — USE React.ReactNode ```typescript interface LayoutProps { children: React.ReactNode; sidebar?: React.ReactNode; } ``` --- ## API Response Typing ### Define Explicit Response Types ```typescript // ❌ WRONG const response = await fetch('/api/users'); const data = await response.json(); // data is any // ✅ CORRECT interface UsersResponse { users: User[]; pagination: PaginationInfo; } const response = await fetch('/api/users'); const data: UsersResponse = await response.json(); // ✅ BEST - with runtime validation const response = await fetch('/api/users'); const raw = await response.json(); const data = UsersResponseSchema.parse(raw); // Zod validates at runtime ``` --- ## Error Handling ### Typed Error Classes — REQUIRED for domain errors ```typescript class ValidationError extends Error { constructor( message: string, public readonly field: string, public readonly code: string ) { super(message); this.name = 'ValidationError'; } } class NotFoundError extends Error { constructor( public readonly resource: string, public readonly id: string ) { super(`${resource} with id ${id} not found`); this.name = 'NotFoundError'; } } ``` ### Error Narrowing — REQUIRED ```typescript try { await saveUser(user); } catch (error: unknown) { if (error instanceof ValidationError) { return { error: error.message, field: error.field }; } if (error instanceof NotFoundError) { return { error: 'Not found', resource: error.resource }; } if (error instanceof Error) { logger.error('Unexpected error', { message: error.message, stack: error.stack }); return { error: 'Internal error' }; } logger.error('Unknown error type', { error: String(error) }); return { error: 'Internal error' }; } ``` --- ## ESLint Rules — ENFORCE THESE ```javascript { "@typescript-eslint/no-explicit-any": "error", "@typescript-eslint/explicit-function-return-type": ["error", { "allowExpressions": true, "allowTypedFunctionExpressions": true }], "@typescript-eslint/explicit-module-boundary-types": "error", "@typescript-eslint/no-inferrable-types": "off", // Allow explicit primitives "@typescript-eslint/no-non-null-assertion": "error", "@typescript-eslint/strict-boolean-expressions": "error", "@typescript-eslint/no-unsafe-assignment": "error", "@typescript-eslint/no-unsafe-member-access": "error", "@typescript-eslint/no-unsafe-call": "error", "@typescript-eslint/no-unsafe-return": "error" } ``` --- ## TSConfig Strict Mode — REQUIRED ```json { "compilerOptions": { "strict": true, "noImplicitAny": true, "strictNullChecks": true, "strictFunctionTypes": true, "strictBindCallApply": true, "strictPropertyInitialization": true, "noImplicitThis": true, "useUnknownInCatchVariables": true, "noUncheckedIndexedAccess": true, "noImplicitReturns": true, "noFallthroughCasesInSwitch": true, "noImplicitOverride": true } } ``` --- ## Summary: The Type Safety Hierarchy From best to worst: 1. **Explicit specific type** (interface/type) — REQUIRED 2. **Generic with constraints** — ACCEPTABLE 3. **`unknown` with immediate validation** — ONLY for external data 4. **`any`** — FORBIDDEN **When in doubt, define an interface.**