Skip to content
OpenAgentsbeta
text
1# TypeScript Style Rules
2
3Rules are ordered by how much damage breaking them does. Each one names the failure it
4prevents, because a rule without a reason gets discarded the first time it is
5inconvenient.
6
7## Types must be honest
8
91. **Never use `any` to silence an error.** If the type is genuinely unknown, use
10 `unknown` and narrow it. `any` does not fix the problem, it hides it until runtime.
112. **Never use a non-null assertion (`!`) to silence an error.** Either the value can
12 be null and you must handle it, or it cannot and the type is wrong. Fix the type.
133. **A function's return type must describe every path.** If it can return undefined,
14 say so. A signature that lies is worse than no signature.
154. **Do not widen a type to make a call site compile.** Narrow at the boundary instead.
165. **Parse external data, do not cast it.** Anything from a network, a file, or a
17 database is `unknown` until validated. A cast on untrusted input is a runtime crash
18 waiting for the right payload.
19
20## Errors
21
226. **Never write an empty catch.** If a failure is genuinely ignorable, say why in a
23 comment. An unexplained empty catch is indistinguishable from a bug.
247. **Catch narrowly.** Wrap the call that can fail, not the whole function body.
258. **Do not convert an error into a falsy return value** unless every caller checks it.
26 Silent failure propagates further than a throw.
279. **Preserve the cause.** When rethrowing, use `{ cause: err }` rather than dropping
28 the original and its stack.
29
30## Structure
31
3210. **Do not create an abstraction for one caller.** Two callers is a coincidence,
33 three is a pattern. The premature interface costs more than the duplication.
3411. **Do not add a config option nobody asked for.** Every option is a branch that must
35 be tested and a decision the reader must understand.
3612. **Keep the module boundary narrow.** Export what callers need. An exported internal
37 becomes someone's dependency the moment it is visible.
3813. **Match the file you are editing.** Its naming, its error style, its import order.
39 Consistency inside a file beats your preference.
40
41## Async
42
4314. **Never leave a promise unawaited** unless you deliberately want fire-and-forget,
44 in which case attach a catch and say so in a comment.
4515. **Do not use `Promise.all` where one failure should not cancel the rest.** Use
46 `allSettled` and handle each result.
4716. **Do not `await` in a loop when the calls are independent.** Collect the promises
48 and await once.
49
50## Comments
51
5217. **Comment the why, never the what.** The code says what it does. Explain the
53 constraint, the workaround, the reason for the surprising choice.
5418. **Do not narrate a change.** `// changed to fix bug` is noise the moment it lands.
55 That belongs in the commit message.
5619. **Delete commented-out code.** Version control already has it.
57
58## What good looks like
59
60```ts
61// The upstream API returns 200 with an error body when the key is expired,
62// so a status check alone is not enough here.
63const parsed = ResponseSchema.safeParse(await res.json());
64if (!parsed.success) {
65 throw new Error(`malformed response from ${url}`, { cause: parsed.error });
66}
67```
68
69Honest types, narrow catch, external data parsed, and a comment that explains a thing
70the reader could not have guessed.
71

Keyboard shortcuts

Focus search
/
Go to Explore
ge
Go to Home
gh
Go to Tags
gt
Go to Collections
gc
Show this help
?
Close suggestions or this dialog
Esc