rule coding v1.0.0

TypeScript Strict Mode Rules

Author markeddown
License MIT
Min Context 4,096 tokens
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."

Compatibility

Compare
gpt-4o-mini 100% sanity-v1
claude-haiku-4-5 80% sanity-v1