Files
atelier/languages/ts-tooling.md
T
Jon Chery 4e433158cd docs(P03): complete language-derived extension — v0.4
---ci---
project: atelier
phase: 3
milestone: v0.4
status: complete
phase_role: execution
phase_tag: v0.3.3
requirements:
  covered: [ATELIER-102, ATELIER-103, ATELIER-104, ATELIER-105]
  partial: []
---/ci---
2026-08-05 16:07:21 +00:00

114 lines
5.6 KiB
Markdown

# TypeScript Tooling — Derived Application
> Applies Atelier's domain principles to TypeScript tooling specifically.
> Derives from `domains/` docs; introduces no new P-rules (D-063).
> See `languages/typescript.md` for the language first-principles stub.
## tsc and tsconfig Discipline (DevOps P2 Automation, DevOps P1 Reproducibility)
- **`strict: true` is the floor, not the ceiling:** it enables `strictNullChecks`, `noImplicitAny`, `strictFunctionTypes`, and more. Disable sub-flags only with a justification comment.
- **`tsc --noEmit` in CI:** type-checking is a build gate; emission is the bundler's job. Separate the two so a type error fails CI even when the bundler would have succeeded.
- **`tsconfig` is per-project, not inherited verbatim:** a shared base (`extends`) encodes org defaults; each project overrides the deltas it needs. Avoids the "one monoreto-config-fits-all" trap.
- **`noUncheckedIndexedAccess` for safety:** `arr[i]` becomes `T | undefined`, forcing narrowing. Costs little, prevents a class of out-of-bounds deref bugs.
- **Applies `devops/P1` (reproducibility):** pinned `typescript` version in `package.json` and `lockfile` ensure every CI run type-checks against the same compiler.
```jsonc
// tsconfig.json — base
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noEmit": true,
"moduleResolution": "bundler",
"isolatedModules": true
}
}
```
## ESLint and @typescript-eslint (DevOps P2 Automation, Documentation P9 Living Documents)
- **ESLint with `@typescript-eslint` strict ruleset:** `recommended-type-checked` enables rules that require the type checker (`no-floating-promises`, `no-misused-promises`).
- **Rules encode decisions, not taste:** every custom rule in the config has a one-line `// reason:` comment linking to the principle it enforces. This makes the config a living document (Documentation P9).
- **Format is Prettier's job; ESLint lints:** `eslint-config-prettier` disables conflicting format rules. Do not relitigate formatting in code review.
- **`no-floating-promises` enforces `concurrency/P8` (timeout discipline):** an un-awaited `Promise` is a fire-and-forget that swallows errors and timeouts. The rule forces `.catch()` or `await`.
```jsonc
// .eslintrc.json
{
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended-type-checked",
"prettier"
],
"parserOptions": { "project": "./tsconfig.json" },
"rules": {
// reason: enforce Concurrency P8 — no un-awaited promises
"@typescript-eslint/no-floating-promises": "error",
// reason: enforce Data P7 — no `any` escaping the type checker
"@typescript-eslint/no-explicit-any": "error"
}
}
```
## Project References and ts-jest (DevOps P2 Automation, C6 Composability)
- **Project references for monorepos:** `composite: true` + `references` let `tsc --build` incrementally type-check only changed projects, and enforce the dependency graph at the type level.
- **`paths` aliases mirror the import structure:** `@app/*``src/*`. Configure once in `tsconfig`, mirror in the bundler and the test runner so all three agree.
- **`ts-jest` (or `vitest`) with `isolatedModules: true`:** each test file is type-checked in isolation, matching how the bundler transpiles. Catches the "passes in `tsc` but fails in the bundler" gap.
- **Applies `devops/P2`:** the build pipeline (tsc → lint → test → bundle) is automated; a developer never runs a manual sequence.
```jsonc
// tsconfig.references.json
{
"files": [],
"references": [
{ "path": "./packages/core" },
{ "path": "./packages/api" },
{ "path": "./packages/web" }
]
}
```
## Lockfile and Reproducible Install (DevOps P1 Reproducibility)
- **`npm ci` in CI, not `npm install`:** `ci` reads the lockfile exactly and fails on drift. `install` mutates the lockfile.
- **Lockfile committed for applications:** for libraries, commit `package-lock.json` for CI reproducibility even though consumers resolve their own tree.
- **No floating ranges in `package.json`:** `^` and `~` are CI's job to resolve; pin the resolved version in the lockfile. An unpinned `*` is a supply-chain attack surface.
```bash
# CI install step — deterministic
npm ci
# Type-check gate
npx tsc --noEmit
# Lint gate
npx eslint .
```
## Documentation in the Pipeline (Documentation P1 Documentation is Code, DevOps P9 Documentation in the Pipeline)
- **Type-checked JSDoc:** `typedoc` (or `TypeDoc`) generates API docs from `tsdoc` comments. The compiler enforces that `@param` names match real parameters.
- **`@example` blocks are compiled:** a `tsdoc` `@example` fenced block is type-checked as part of the doc build. Stale examples fail the pipeline (Documentation P1 — docs are code).
- **README badges reflect CI status:** the build/lint/test/type-check gates are the source of truth; badges surface them. Do not hand-edit status tables.
```typescript
/**
* Fetch a user by ID.
*
* @param id - a branded UserId (see ts-types.md).
* @throws {NotFoundError} if the user does not exist.
* @example
* ```ts
* const u = await getUser(userId('abc'));
* ```
*/
async function getUser(id: UserId): Promise<User> { /* ... */ }
```
## Cross-References
- `domains/devops/ci-cd.md` — the pipeline gates that host tsc/ESLint/ts-jest.
- `domains/devops/first-principles.md` — DevOps P1 Reproducibility, P2 Automation.
- `domains/documentation/first-principles.md` — Documentation P1 Documentation is Code.
- `languages/ts-types.md` — the type rules ESLint enforces reference this doc.
- `languages/ts-testing.md` — the test-runner config (`ts-jest`/`vitest`) detailed here.