Skip to content

Code style

Half of the style here is decided by a tool and needs no discussion. Review carries the other half. A reviewer who has to re-derive a rule from the diff every time will eventually stop asking for it, so that half is written down. Each section says which half it is.

Prettier owns formatting, and .prettierrc at the root holds every option it was given. Nobody argues about a brace here. The format lane runs pnpm format:check, and pnpm format rewrites files in place.

But one of those options is worth knowing by heart, because it governs prose as much as code. proseWrap is set to never, so Prettier joins a hand-wrapped Markdown paragraph back onto one line. Write a full thought on one physical line however long it runs, and let diffs be about sentences, not about where the column ran out.

Imports come in a fixed order with a blank line between the groups and alphabetical order inside each: Node builtins, then external packages, then @supolka/*, then relative paths. @ianvs/prettier-plugin-sort-imports enforces it from the same .prettierrc, so a wrong import order fails the format lane and never goes unnoticed. pnpm format is the fix.

A statement that pulls in values alongside types marks the types inline with { type X }. A statement that pulls in only types uses import type. The lint lane enforces both. verbatimModuleSyntax is the reason, and tsconfig.base.json turns it on: under it, an all-type import written with inline specifiers still emits a live import {} and loads the module at runtime for no reason.

Modules a neighbor borrows only a type from must not appear in that neighbor’s runtime import graph. Extract the pure part into its own module instead. Nothing enforces this one except review.

Put a blank line before every return and between logical blocks. Code that runs unbroken from a function’s first line to its last is harder to read than the same function split into steps a reader can see. The compiler is indifferent either way. But the person who arrives next is not. Review is the only enforcement, and not for want of trying, because no lint rule here covers blank lines between statements. Blank lines between test blocks are the exception, and the lint lane covers those.

Linting is oxlint through the root .oxlintrc.json, never ESLint, for the reasons in decision 0007. Two categories run at error, correctness and suspicious, so any hit fails the lint lane. No machine can check how a lint conflict gets resolved, so the resolution is written down.

  • Never make a suppression the answer. Move the code until the rule and the semantics agree, or change the rule tree-wide. Per-line disables freeze a disagreement into the one file nobody will ever revisit.
  • Never weaken a healthy default to make a check pass. Fix the artifact that tripped it.

Fix a type collision by changing the structure, never by casting. A cast records a lie, and the compiler then repeats that lie to every reader who comes after. Review is the only enforcement here: the typecheck lane cannot see through a cast, and that blindness is the whole reason a cast is worth banning. Prefer the platform idiom wherever the platform owns the concern. Hand-rolled equivalents have to be maintained and eventually debugged, and Design states that rule with its standing example.

Rules a tool checks, and rules review carries

Section titled “Rules a tool checks, and rules review carries”
Rule Tool Lane
Formatting, prose included Prettier format
Import grouping and order @ianvs/prettier-plugin-sort-imports format
Inline versus statement type imports oxlint lint
Correctness and suspicious categories oxlint lint
Blank lines between test blocks oxlint jest rules lint
Type safety TypeScript typecheck

Every rule this page did not put in that table is one review has to carry. Tooling carries the full lane list, including the lanes that have nothing to do with style.