Files

7.6 KiB

TypeScript Code Guidelines

  • Write clear, readable, maintainable code first.
  • Prefer modern TypeScript and current ECMAScript syntax.
  • Keep code simple and explicit.
  • Avoid clever or overly abstract solutions.
  • Follow the existing project style and architecture.
  • Enable strict TypeScript settings where possible.
  • Prefer strict: true.
  • Avoid any.
  • Prefer precise types over broad types.
  • Prefer unknown over any for untrusted or unknown values.
  • Narrow types explicitly before use.
  • Prefer strongly typed models over loose objects.
  • Avoid excessive type assertions.
  • Avoid non-null assertions unless safety is guaranteed.
  • Prefer discriminated unions for state and variant modeling.
  • Prefer literal unions over arbitrary strings.
  • Prefer enums only when they provide a clear benefit.
  • Prefer readonly where mutation is not required.
  • Prefer immutable data where practical.
  • Avoid unnecessary mutation.
  • Avoid global mutable state.
  • Keep functions small and focused.
  • Keep modules focused on a clear responsibility.
  • Prefer composition over inheritance.
  • Avoid unnecessary classes when functions and plain objects are sufficient.
  • Avoid unnecessary interfaces and abstractions.
  • Prefer concrete object types when no abstraction is required.
  • Use interfaces primarily when extension, implementation contracts, or declaration merging is actually useful.
  • Prefer type aliases for unions, compositions, mapped types, and local data models.
  • Avoid unnecessary wrapper types.
  • Avoid excessive generic complexity.
  • Keep generic constraints explicit and meaningful.
  • Avoid deeply nested conditional or mapped types unless they provide clear value.
  • Prefer readable types over type-system cleverness.
  • Avoid duplicated type definitions.
  • Derive types from existing sources when practical.
  • Do not duplicate backend contracts manually when generated or shared types are available.
  • Prefer explicit return types for public APIs and non-trivial functions.
  • Prefer inference for simple local variables.
  • Avoid unnecessary annotations when inference is obvious.
  • Prefer const by default.
  • Use let only when reassignment is required.
  • Avoid var.
  • Prefer optional chaining and nullish coalescing where appropriate.
  • Do not use || when 0, false, or empty strings are valid values.
  • Handle null and undefined intentionally.
  • Avoid mixing null and undefined without a clear convention.
  • Prefer early returns over excessive nesting.
  • Avoid deeply nested control flow.
  • Avoid overly large functions.
  • Avoid hidden side effects.
  • Keep pure logic pure where practical.
  • Prefer deterministic behavior.
  • Avoid implicit runtime coercion.
  • Use strict equality.
  • Avoid unnecessary object spreading in hot paths.
  • Avoid unnecessary array copies.
  • Avoid unnecessary intermediate arrays.
  • Avoid repeated .map(), .filter(), or .reduce() chains in performance-critical paths.
  • Use array methods when they improve readability and performance is not critical.
  • Prefer appropriate data structures such as Map and Set when lookup behavior requires them.
  • Avoid O(n²) patterns when a better structure is practical.
  • Measure performance before micro-optimizing.
  • Avoid unnecessary JSON serialization and parsing.
  • Avoid unnecessary deep cloning.
  • Avoid JSON.parse(JSON.stringify(...)) as a cloning strategy.
  • Prefer platform APIs such as structuredClone when deep cloning is actually required.
  • Avoid synchronous blocking operations in server-side hot paths.
  • Prefer asynchronous APIs for I/O.
  • Always handle rejected promises.
  • Avoid floating promises.
  • Use await when sequencing is required.
  • Use Promise.all for independent operations when safe.
  • Limit concurrency for large workloads.
  • Avoid uncontrolled Promise.all over very large collections.
  • Support cancellation with AbortSignal where applicable.
  • Clean up event listeners, timers, subscriptions, streams, and resources.
  • Avoid memory leaks caused by retained closures or listeners.
  • Do not swallow exceptions.
  • Preserve useful error context.
  • Use typed domain errors where they improve handling.
  • Avoid exceptions for normal control flow.
  • Validate external input at system boundaries.
  • Never trust API payloads, query parameters, environment variables, storage data, or user input based solely on TypeScript types.
  • Use runtime validation for untrusted external data.
  • Do not assume compile-time types provide runtime safety.
  • Prefer schema validation when external contracts are complex.
  • Never expose secrets, tokens, credentials, or sensitive data.
  • Do not log sensitive information.
  • Avoid unsafe dynamic property access.
  • Avoid eval, Function, and dynamic code execution.
  • Avoid unsafe HTML injection.
  • Sanitize untrusted HTML when rendering HTML is required.
  • Avoid weakening TLS, authentication, authorization, CSP, validation, or other security controls.
  • Treat all external data as untrusted.
  • Prefer secure defaults.
  • Do not disable security checks for convenience.
  • Avoid prototype pollution risks when merging untrusted objects.
  • Prefer allowlists over denylists for security-sensitive validation.
  • Keep dependencies minimal.
  • Prefer built-in platform APIs over adding small utility dependencies.
  • Do not add dependencies without a clear reason.
  • Avoid abandoned or unnecessary packages.
  • Keep dependencies current and supported.
  • Respect lockfiles.
  • Do not manually modify generated lockfile content.
  • Avoid broad dependency upgrades unrelated to the task.
  • Use ESM or the project's existing module system consistently.
  • Avoid mixing module systems without necessity.
  • Prefer named exports unless the project convention favors default exports.
  • Keep import paths consistent.
  • Remove unused imports and exports.
  • Avoid circular dependencies.
  • Keep frontend, domain, application, and infrastructure concerns separated where applicable.
  • Keep API and transport models separate from domain logic when necessary.
  • Do not leak implementation details through public APIs.
  • Avoid unnecessary public exports.
  • Keep module boundaries intentional.
  • Write code that is easy to test.
  • Prefer behavior-based tests.
  • Avoid tests coupled to implementation details.
  • Mock only external boundaries or expensive dependencies where practical.
  • Keep tests deterministic.
  • Avoid time-, network-, and environment-dependent tests without proper isolation.
  • Remove dead code instead of keeping it as commented-out code.
  • Do not add comments unless the user explicitly requests them.
  • Use descriptive naming.
  • Avoid vague names such as data, obj, item, temp, or value when more precise names are available.
  • Avoid abbreviations unless they are established domain terminology.
  • Keep naming conventions consistent.
  • Treat lint and TypeScript errors seriously.
  • Do not suppress ESLint or TypeScript diagnostics without a concrete reason.
  • Avoid @ts-ignore.
  • Prefer @ts-expect-error only when the error is intentional and documented.
  • Do not disable lint rules globally for local problems.
  • Prefer correctness, security, maintainability, and predictable behavior over micro-optimizations.

Performance Priority

Prioritize performance work in this order:

  • Architecture
  • Network and API usage
  • Database access
  • Algorithms and data structures
  • Rendering and state updates
  • Concurrency
  • Memory allocations
  • CPU micro-optimizations

Never optimize based purely on assumptions.

Safety Rule

  • Compile-time type safety is not runtime validation.
  • Validate all external and untrusted data.
  • Do not use any, type assertions, non-null assertions, or disabled compiler checks to bypass real type problems.
  • Prefer making invalid states unrepresentable where practical.