refactor(rules): split TypeScript guidance by topic

This commit is contained in:
fraujulian committed 2026-09-24 21:47:29 +02:00
1 parent 27db155b85
commit 646e552ff1
10 files changed
+112 -160

No files matched your search

-160
View File
@@ -1,160 +0,0 @@
# 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.
+14
View File
@@ -0,0 +1,14 @@
# TypeScript Rules
For TypeScript or modern ECMAScript work, open only the topic rules relevant to the task. Load applicable framework and shared security rules as well.
| Topic | Codex rule | Claude skill |
| --- | --- | --- |
| Types and data modeling | `references/types-and-modeling.md` | `rules-typescript-types-and-modeling` |
| Coding style and control flow | `references/coding-style.md` | `rules-typescript-coding-style` |
| Runtime validation and browser safety | `references/runtime-safety.md` | `rules-typescript-runtime-safety` |
| Security-specific implementation | `references/security.md` | `rules-typescript-security` |
| Async work and resource lifetime | `references/async-and-resources.md` | `rules-typescript-async-and-resources` |
| Performance | `references/performance.md` | `rules-typescript-performance` |
| Modules, APIs, and dependencies | `references/modules-and-dependencies.md` | `rules-typescript-modules-and-dependencies` |
| Tests, diagnostics, and maintenance | `references/testing-and-quality.md` | `rules-typescript-testing-and-quality` |
@@ -0,0 +1,12 @@
# TypeScript Async Work and Resources
Apply these rules when implementing promises, I/O, streams, subscriptions, timers, or other long-lived work.
* Handle every rejected promise. Do not leave floating promises; for intentional fire-and-forget work, attach error handling and make its owner and lifetime explicit.
* Use `await` when sequencing or error handling depends on completion. Use `Promise.all` only for independent, bounded work that can safely run together.
* Limit concurrency for large workloads with a queue or limiter. Apply backpressure to streams and producer/consumer flows instead of buffering without a bound.
* Support cancellation with `AbortSignal` when callers need to stop I/O or long-running work. Propagate it through nested calls and release resources when cancellation occurs.
* Avoid synchronous blocking and long CPU-bound work on the Node.js event loop or browser main thread. Move substantial CPU work to a worker or split it into bounded tasks when measurements justify it.
* Remove event listeners, timers, subscriptions, streams, and abort handlers when their owner is done. Avoid retaining closures or resources beyond their intended lifetime.
* Keep shared mutable state safe and deterministic when asynchronous operations can overlap. Prevent stale responses from overwriting newer state when request ordering matters.
* Retry only failures known to be transient, with bounded attempts and delay. Preserve idempotency and cancellation; avoid multiplying retries across layers.
@@ -0,0 +1,12 @@
# TypeScript Coding Style
Apply these rules when changing TypeScript syntax, control flow, or everyday code organization.
* Follow the project's established architecture, formatter, lint rules, and module conventions.
* Prefer `const`; use `let` only when reassignment is needed and avoid `var`.
* Keep functions and modules focused. Prefer early returns and shallow control flow over deeply nested branches or oversized functions.
* Prefer explicit, predictable behavior over implicit coercion, hidden side effects, or overly abstract helpers.
* Use strict equality. Handle `null` and `undefined` intentionally and do not use `||` when `0`, `false`, or an empty string is a valid value.
* Use optional chaining and nullish coalescing where they clarify behavior; avoid mixing nullish conventions without a project reason.
* Keep naming descriptive and consistent. Avoid vague names and abbreviations unless they are established domain terms.
* Remove dead code and unused imports. Do not add comments unless the user asks or a project rule requires them.
@@ -0,0 +1,12 @@
# TypeScript Modules and Dependencies
Apply these rules when changing package dependencies, module structure, imports, or public exports.
* Follow the project's existing module system consistently; avoid mixing ESM and CommonJS without a compatibility need.
* Keep modules focused and boundaries intentional. Avoid circular dependencies and unnecessary public exports.
* Separate transport models from domain logic where that boundary exists. Do not leak implementation details through public APIs.
* Prefer built-in platform APIs or installed packages before adding a dependency. Keep dependencies minimal, supported, and compatible with the lockfile.
* Review package identity, maintenance, transitive dependencies, permissions, lifecycle scripts, and update behavior before adopting a package. Pin or constrain versions according to project policy and commit the package manager's lockfile.
* Avoid broad dependency upgrades unrelated to the task. Do not hand-edit generated lockfile content; regenerate it with the intended package manager and verify the resulting diff.
* Do not treat bundled frontend configuration as secret storage. Anything shipped to a browser is observable by users.
* Remove unused imports and exports, and keep path and export conventions consistent with the surrounding code.
@@ -0,0 +1,13 @@
# TypeScript Performance
Apply these rules when optimizing TypeScript runtime, rendering, or memory use.
* Establish a baseline using representative data, devices, concurrency, and runtime versions. Measure the user-visible path and its latency distribution, throughput, memory, and CPU cost as relevant.
* Prioritize architecture, network and API usage, database access, algorithms, rendering, concurrency, allocations, then CPU micro-optimizations. Confirm that the suspected layer dominates before changing it.
* Avoid quadratic scans and repeated full collection passes for large inputs. Choose `Map`, `Set`, or an indexed lookup when it reduces measured repeated work and its memory cost is acceptable.
* Avoid unnecessary object spreading, intermediate arrays, copies, deep clones, and serialization in hot paths. Do not replace clear code with opaque loops without measured benefit.
* In browsers, reduce unnecessary component updates and DOM work; virtualize large lists when rendering all rows is costly. Avoid memoization that adds stale-state or invalidation bugs.
* In Node.js, keep CPU-heavy synchronous work from blocking the event loop. Stream large payloads and bound queues, buffers, and concurrent requests.
* Prefer platform APIs over custom low-level code or dependencies. Use `structuredClone` only when a deep clone is required and its data constraints are met.
* Cache only repeated measurable work, with bounded size, explicit keys, expiry/invalidation, and safe treatment of user- or tenant-specific data.
* Do not add batching or parallelism without preserving ordering, cancellation, rate limits, and failure behavior. Measure end-to-end results after the change.
@@ -0,0 +1,13 @@
# TypeScript Runtime Safety
Apply these rules when handling API payloads, user input, browser data, or other values outside the type system.
* Treat external values as `unknown` until validated. TypeScript annotations and assertions do not validate runtime data.
* Validate untrusted data at system boundaries before storing it, using it for authorization, or passing it to sensitive operations. Use schema validation when external contracts are complex.
* Handle `null` and `undefined` intentionally, and narrow values before use. Avoid implicit coercion and unsafe dynamic property access.
* Prefer allowlists for security-sensitive input. Prevent prototype pollution when merging untrusted objects; avoid unsafe recursive merges and dynamic writes to ordinary object prototypes.
* Treat URL, path, header, query, and redirect destinations as untrusted. Server-side fetches must restrict schemes and destinations, validate every redirect, and enforce response size and timeout limits.
* Use framework-safe text binding and automatic output encoding. Avoid `innerHTML`, `dangerouslySetInnerHTML`, and equivalent escape hatches unless sanitized content is required and a maintained sanitizer is applied.
* For cookie-based authentication, preserve secure cookie attributes and the application's CSRF protections. Do not store authentication secrets in browser-accessible storage without an explicit threat model and security design.
* Never expose secrets or sensitive data in output, logs, or errors. Client-side validation and route guards do not replace server-side authentication, authorization, or validation.
* Bound input size, nesting, parsing work, and response size before processing untrusted JSON or other structured data.
@@ -0,0 +1,13 @@
# TypeScript Security
Apply these rules when implementing or reviewing security-sensitive TypeScript code. Load the matching shared security rule for the affected API, identity, data, browser, file, network, cryptographic, or dependency boundary as well.
* Validate external data at runtime before using it in authorization decisions, database operations, filesystem access, or privileged actions. A type assertion is not validation.
* Enforce authentication, authorization, tenant scoping, and input validation on the server. Browser checks, hidden controls, route guards, and TypeScript types do not create a security boundary.
* Keep credentials and private keys on trusted servers. Never embed secrets in frontend bundles, source maps, browser storage, or public runtime configuration.
* Use cryptographically secure platform randomness for tokens and security-sensitive identifiers. Do not use `Math.random()` for secrets, session values, or authorization codes.
* Use parameterized database APIs and allowlists for dynamic identifiers. Avoid constructing SQL, shell commands, HTML, or other interpreted input by string concatenation.
* For server-side URL fetching, restrict protocols and destinations, validate redirects, and bound time, response size, and concurrency to reduce SSRF and resource-exhaustion risk.
* Use the framework's safe output encoding and preserve CSP, TLS, cookie, and CSRF protections. Do not disable these controls to make a request or test pass.
* Review untrusted object merges for prototype pollution, unsafe deserialization, and mass assignment. Copy only fields the caller is allowed to control.
* Review dependency additions and upgrades for source, package identity, install scripts, transitive risk, and lockfile changes.
@@ -0,0 +1,11 @@
# TypeScript Testing and Quality
Apply these rules when changing tests, lint configuration, compiler diagnostics, or maintainability checks.
* Prefer behavior-based tests that assert externally observable results. Avoid coupling tests to private implementation details.
* Keep tests deterministic. Control time, randomness, filesystem state, and external services when they affect the result.
* Mock external boundaries or expensive dependencies only when needed; avoid mocks that merely restate implementation details.
* Treat TypeScript, compiler, and lint diagnostics seriously. Do not suppress errors globally or use `@ts-ignore` to hide a real type problem.
* Use `@ts-expect-error` only for an intentional, narrow case whose expected error is part of the test or contract.
* Keep code easy to test through clear module boundaries without adding test-only production exports or hooks without a concrete reason.
* Run focused tests, type checks, and lint for changed paths. Expand checks when shared contracts or generated types are affected.
@@ -0,0 +1,12 @@
# TypeScript Types and Modeling
Apply these rules when defining TypeScript types, public function contracts, or domain data models.
* Prefer `strict: true`, precise types, and `unknown` for values whose shape has not been validated. Avoid `any`, broad assertions, and non-null assertions that hide uncertainty.
* Model variants with discriminated unions and use literal unions for constrained values. Make invalid states difficult to represent.
* Prefer `readonly` and immutable data when mutation is unnecessary. Use interfaces for implementation contracts or declaration merging; use type aliases for unions and compositions.
* Give exported APIs, callbacks, and non-trivial functions explicit contracts; rely on inference for simple local values where it keeps code clearer.
* Derive types from trusted existing contracts or generated sources instead of duplicating backend schemas manually. Regenerate generated contracts from their source rather than hand-editing them.
* Keep generic constraints meaningful and types readable. Avoid deep conditional or mapped types when a simpler model expresses the contract.
* Keep compile-time types aligned with runtime schemas. If a schema changes, update its inferred types and tests so the two do not silently drift.
* Avoid representing authorization state, validation success, or trusted input using a type assertion alone. Construct trusted values only after the required checks succeed.