rule coding
TypeScript Strict Mode Rules
typescript strict-mode rules coding-standards
Targets
---
id: "c91186c7-38bf-4192-b874-e755cbd8d5d0"
name: "TypeScript Strict Mode Rules"
type: rule
category: coding
version: "1.0.0"
author: "markeddown"
license: MIT
min_context_tokens: 4096
target_frameworks:
- cursor
- windsurf
- opencode
recommended_models:
- anthropic/claude-sonnet-4-5
- openai/gpt-4o
tags:
- typescript
- strict-mode
- rules
- coding-standards
style_hints: {}
depends_on: []
deprecated: false
created: "2026-04-06"
---
## Project Context
TypeScript project with strict mode enabled (`strict: true` in tsconfig, including `noUncheckedIndexedAccess`, `exactOptionalPropertyTypes`, and `noImplicitOverride`). All code must compile with zero errors, zero `any` escapes, and zero suppression comments.
## Type Safety Rules
### The `any` ban
- Never use `any`. No exceptions. No "just for now."
- When a type is genuinely unknown, use `unknown` and narrow it with type guards, `instanceof`, or discriminated unions.
- When a third-party library exports `any` (common with older packages), wrap the call in a typed helper function at the boundary. Do not let `any` leak into application code.
- `JSON.parse()` returns `any`. Always follow it with a Zod parse or a type guard: `const data = schema.parse(JSON.parse(raw))`.
### Type vs Interface
- Use `type` for unions, intersections, mapped types, and utility types.
- Use `interface` for object shapes that will be implemented by classes or extended by declaration merging (e.g., augmenting `Window` or a library's types).
- When in doubt, use `type`. It covers more cases and is more predictable.
### Enums vs Unions
- Do not use `enum`. Use `as const` objects with a derived union type:
```typescript
const STATUS = { ACTIVE: 'active', INACTIVE: 'inactive' } as const;
type Status = (typeof STATUS)[keyof typeof STATUS]; // 'active' | 'inactive'
```
- This is tree-shakeable, works with `satisfies`, and doesn't produce runtime artifacts that TypeScript enums do.
### `satisfies` vs `as const`
- Use `as const` when you want the literal type preserved (config objects, lookup tables).
- Use `satisfies` when you want to check a value conforms to a type without widening it:
```typescript
const config = { port: 3000, host: 'localhost' } as const satisfies ServerConfig;
```
- Never use `as` for type assertions on data. `as` is for cases where you provably know more than the compiler (e.g., DOM element casts after a null check). If you need `as` on API data, the real fix is a runtime validation layer.
### Generics
- Name generic parameters descriptively when they appear more than once: `TItem`, `TResponse`, not `T`, `U`.
- Single-use generics in simple utility functions are fine as `T`.
- Constrain generics: `<T extends Record<string, unknown>>` is better than `<T>` when you know the shape.
## Code Style
- `const` by default. `let` only when reassignment is required. Never `var`.
- Boolean variables and functions use `is`, `has`, `can`, or `should` prefixes: `isLoading`, `hasPermission`, `canSubmit`, `shouldRetry`.
- Early returns to reduce nesting. Maximum two levels of `if` nesting. If you hit three, extract a function.
- Named exports only. No default exports — they make refactoring and auto-import less reliable.
- Explicit return types on all exported functions. Inferred return types are fine for private/local functions.
- Prefer `readonly` arrays and objects in function signatures when the function does not mutate its input.
- Destructure in function parameters when accessing more than two properties:
```typescript
// Instead of: function createUser(opts: CreateUserOpts) { opts.name ... opts.email ... }
function createUser({ name, email, role = 'member' }: CreateUserOpts): User {
```
## Error Handling
- Do not use `// @ts-ignore` or `// @ts-expect-error` to suppress errors. Fix the underlying type issue. If it is genuinely a compiler bug, link to the TypeScript issue in a comment next to a `// @ts-expect-error`.
- Do not use `as any` casts. Ever.
- Do not remove error handling, even if it appears redundant. If it was added, there was a reason.
- Catch blocks must type the error: `catch (err: unknown)`. Narrow before accessing properties.
- Prefer `Result` patterns (return `{ ok: true, data }` / `{ ok: false, error }`) over thrown exceptions for expected failure modes (validation, not-found). Reserve `throw` for genuinely exceptional conditions.
## Response Rules
- Provide minimal, targeted edits. Do not rewrite working code unless asked.
- Always explain the "why" behind a type change in one sentence. "Changed to `string | null` because the API returns `null` when the field is unset" — not just "fixed the type."
- When a type error has multiple valid fixes, state the tradeoffs briefly and recommend one.
## Constraints
- Do not suggest adding new dependencies without flagging it explicitly with `[NEW DEP]` and stating the bundle size impact.
- Do not introduce breaking changes to exported types without noting `[BREAKING]` and listing which consumers are affected.
- When writing new functions, always include the return type annotation explicitly.
- Tests must be updated if behavior changes. Do not ship behavior changes without test coverage.
- Do not add JSDoc comments to functions where the types and name already communicate the intent. Comments are for "why", not "what."
Download
Compatibility
gpt-4o-mini 100% sanity-v1
claude-haiku-4-5 80% sanity-v1