text
| 1 | # TypeScript Style Rules |
| 2 | |
| 3 | Rules are ordered by how much damage breaking them does. Each one names the failure it |
| 4 | prevents, because a rule without a reason gets discarded the first time it is |
| 5 | inconvenient. |
| 6 | |
| 7 | ## Types must be honest |
| 8 | |
| 9 | 1. **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. |
| 11 | 2. **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. |
| 13 | 3. **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. |
| 15 | 4. **Do not widen a type to make a call site compile.** Narrow at the boundary instead. |
| 16 | 5. **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 | |
| 22 | 6. **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. |
| 24 | 7. **Catch narrowly.** Wrap the call that can fail, not the whole function body. |
| 25 | 8. **Do not convert an error into a falsy return value** unless every caller checks it. |
| 26 | Silent failure propagates further than a throw. |
| 27 | 9. **Preserve the cause.** When rethrowing, use `{ cause: err }` rather than dropping |
| 28 | the original and its stack. |
| 29 | |
| 30 | ## Structure |
| 31 | |
| 32 | 10. **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. |
| 34 | 11. **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. |
| 36 | 12. **Keep the module boundary narrow.** Export what callers need. An exported internal |
| 37 | becomes someone's dependency the moment it is visible. |
| 38 | 13. **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 | |
| 43 | 14. **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. |
| 45 | 15. **Do not use `Promise.all` where one failure should not cancel the rest.** Use |
| 46 | `allSettled` and handle each result. |
| 47 | 16. **Do not `await` in a loop when the calls are independent.** Collect the promises |
| 48 | and await once. |
| 49 | |
| 50 | ## Comments |
| 51 | |
| 52 | 17. **Comment the why, never the what.** The code says what it does. Explain the |
| 53 | constraint, the workaround, the reason for the surprising choice. |
| 54 | 18. **Do not narrate a change.** `// changed to fix bug` is noise the moment it lands. |
| 55 | That belongs in the commit message. |
| 56 | 19. **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. |
| 63 | const parsed = ResponseSchema.safeParse(await res.json()); |
| 64 | if (!parsed.success) { |
| 65 | throw new Error(`malformed response from ${url}`, { cause: parsed.error }); |
| 66 | } |
| 67 | ``` |
| 68 | |
| 69 | Honest types, narrow catch, external data parsed, and a comment that explains a thing |
| 70 | the reader could not have guessed. |
| 71 |