ts-reviewer 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,358 @@
1
+ # Fix Workflow Protocol
2
+
3
+ This document describes the complete fix mode protocol.
4
+ Read this before executing any fix or auto mode operation.
5
+
6
+ ## Prerequisites
7
+
8
+ - `code-smells.md` must exist in the project root.
9
+ If missing, stop with error: "No scan report found. Run scan first."
10
+ - The project must be in a git repository (for safe revert if needed).
11
+ - Recommend the user commit or stash uncommitted work before fix runs,
12
+ so they can `git diff` or `git checkout -- .` to revert all changes.
13
+
14
+ ## Step 1 — Parse the Report
15
+
16
+ Read `code-smells.md` and extract all issues into a work list.
17
+ Group issues by file path. Within each file, sort by line number **descending**
18
+ (fix from bottom of file upward so line numbers above don't shift).
19
+
20
+ Build the work plan:
21
+ ```
22
+ File: src/auth/token.ts
23
+ - Line 142: [High] Unsafe `as` cast — type-safety
24
+ - Line 87: [Medium] Floating promise — async
25
+ - Line 23: [Low] `enum` should be `as const` — modernization
26
+
27
+ File: src/api/handler.ts
28
+ - Line 201: [Highest] eval() with user input — security
29
+ - Line 55: [Medium] Missing exhaustive check — type-safety
30
+ ```
31
+
32
+ ## Step 2 — Detect Test Infrastructure
33
+
34
+ Check for test runner in this order:
35
+
36
+ | Signal | Runner | Command |
37
+ |---|---|---|
38
+ | `package.json` has `"scripts": { "test": "..." }` | npm script | `npm test` |
39
+ | `vitest.config.*` exists | vitest | `npx vitest run` |
40
+ | `jest.config.*` or `"jest"` in package.json | jest | `npx jest` |
41
+ | `*.test.ts` / `*.spec.ts` + `"mocha"` in devDeps | mocha | `npx mocha` |
42
+ | `deno.json` exists | deno | `deno test` |
43
+ | `bun.lockb` or `bun.lock` exists | bun | `bun test` |
44
+ | Node.js 18+ and `*.test.ts` files | node:test | `node --test` |
45
+
46
+ If no test infrastructure found:
47
+ - Warn the user: "No test runner detected. Fixes will be applied without test verification."
48
+ - Skip all test-related steps (baseline, regression tests, verification).
49
+ - Still run `tsc --noEmit` and linter after fixes.
50
+
51
+ Also detect the test file naming convention:
52
+ - `*.test.ts` vs `*.spec.ts`
53
+ - `__tests__/` directory vs co-located with source
54
+ - Test framework imports (`describe/it` vs `test` vs `Deno.test`)
55
+
56
+ ## Step 3 — Capture Test Baseline
57
+
58
+ Run the full test suite BEFORE any changes:
59
+ ```bash
60
+ <test_command> 2>&1 | tee .claude/ts-reviewer-baseline.log
61
+ ```
62
+
63
+ Record:
64
+ - Total tests: N
65
+ - Passing: N
66
+ - Failing: N (list these — they are pre-existing failures, NOT our responsibility)
67
+ - Test run command used
68
+
69
+ This baseline is critical. After our fixes, any NEW failures are regressions we caused.
70
+ Pre-existing failures that still fail are not our problem.
71
+
72
+ ## Step 4 — Apply Fixes File-by-File
73
+
74
+ Process one file at a time. For each file:
75
+
76
+ ### 4a. Apply all fixes in the file
77
+
78
+ Go through the file's issues sorted by line number descending.
79
+ For each issue:
80
+
81
+ 1. Read the current state of the file (lines may have shifted from previous fixes in same file).
82
+ 2. Apply the fix described in the report.
83
+ 3. If the fix involves replacing a pattern (e.g., `enum` -> `as const`), update ALL
84
+ references to that symbol across the codebase (imports, usages).
85
+
86
+ ### 4b. Write regression tests
87
+
88
+ For each fix, write a regression test if the issue is testable.
89
+
90
+ **Testable issues** (write a regression test):
91
+ - Type safety: incorrect cast → test that the function handles the correct type
92
+ - Security: eval/injection → test that the sanitized version rejects malicious input
93
+ - Async: floating promise → test that errors propagate correctly
94
+ - Bugs: missing null check → test with null/undefined input
95
+
96
+ **Non-testable issues** (skip regression test):
97
+ - Style changes (naming, formatting)
98
+ - Config changes (tsconfig flags)
99
+ - Modernization that doesn't change behavior (enum -> as const with same values)
100
+ - Complexity reduction (splitting a function — behavior unchanged)
101
+
102
+ Regression test conventions:
103
+ - Match the project's existing test naming convention and framework
104
+ - Place tests next to existing test files, or in `__tests__/` if that's the convention
105
+ - Name: `<original-file>.reviewer-fixes.test.ts` (or `.spec.ts` per convention)
106
+ - Each test should be clearly labeled with the issue title:
107
+ ```typescript
108
+ describe('ts-reviewer fixes: src/auth/token.ts', () => {
109
+ it('should not use unsafe cast for token payload (type-safety)', () => {
110
+ // test that validates the fix
111
+ });
112
+ });
113
+ ```
114
+ - Keep tests focused and minimal — test the specific fix, not the entire function
115
+
116
+ ### 4c. Run tsc after each file
117
+
118
+ ```bash
119
+ npx tsc --noEmit 2>&1
120
+ ```
121
+
122
+ If `tsc` reports NEW errors in the file we just fixed or in files affected by our changes:
123
+ - Analyze the errors
124
+ - Fix them immediately (they are likely caused by our refactoring — e.g., type changes
125
+ that affect consumers)
126
+ - Re-run `tsc` to confirm
127
+ - If unable to fix after 2 attempts on the same error, revert the last fix in this file
128
+ and mark the issue as `[FIX FAILED: caused type errors]` in the report
129
+
130
+ ### 4d. Move to next file
131
+
132
+ Repeat 4a-4c for each file in the work plan.
133
+
134
+ ## Step 5 — Linter Pass
135
+
136
+ After all files are fixed, run the linter:
137
+
138
+ ```bash
139
+ # ESLint
140
+ npx eslint [changed_files] --format json 2>/dev/null
141
+ # or Biome
142
+ npx biome check [changed_files] --reporter json 2>/dev/null
143
+ ```
144
+
145
+ If linter reports errors in files we changed:
146
+ - Auto-fix what's auto-fixable: `npx eslint --fix [files]` or `npx biome check --fix [files]`
147
+ - For remaining errors: fix manually
148
+ - Re-run linter to confirm clean
149
+
150
+ ## Step 6 — Test Verification
151
+
152
+ Run the full test suite:
153
+ ```bash
154
+ <test_command> 2>&1 | tee .claude/ts-reviewer-postfix.log
155
+ ```
156
+
157
+ Compare with baseline:
158
+
159
+ | Baseline | Post-fix | Verdict |
160
+ |---|---|---|
161
+ | PASS | PASS | OK — no regression |
162
+ | FAIL | FAIL | OK — pre-existing failure, not our problem |
163
+ | PASS | FAIL | REGRESSION — we broke this, must fix |
164
+ | FAIL | PASS | BONUS — we accidentally fixed a pre-existing failure |
165
+ | (new) | FAIL | Our new regression test fails — must fix |
166
+ | (new) | PASS | Our new regression test passes — good |
167
+
168
+ For each REGRESSION:
169
+ 1. Analyze the test failure and the stack trace.
170
+ 2. Identify which fix caused it (check git diff for the relevant file).
171
+ 3. Either fix the regression or revert the offending fix.
172
+ 4. Mark reverted fixes as `[FIX REVERTED: caused test regression in <test>]`.
173
+
174
+ ## Step 7 — Verification Loop
175
+
176
+ After fixing regressions, repeat:
177
+ 1. `tsc --noEmit`
178
+ 2. Linter check
179
+ 3. Full test suite
180
+
181
+ If new issues appear, fix them. **Maximum 5 iterations** of this loop.
182
+
183
+ After 5 iterations, if issues remain:
184
+ - Stop fixing
185
+ - Leave the code in its current state
186
+ - Update the report with a "Stabilization" section listing unresolved regressions
187
+ - The user will need to handle these manually
188
+
189
+ Iteration tracking:
190
+ ```
191
+ Iteration 1: Fixed 12/15 issues. 2 test regressions found.
192
+ Iteration 2: Fixed 2 regressions. 1 new tsc error.
193
+ Iteration 3: Fixed tsc error. All tests pass. Clean.
194
+ -> Done at iteration 3.
195
+ ```
196
+
197
+ ## Step 8 — Update Report
198
+
199
+ After fix completes, decide what to do with `code-smells.md`:
200
+
201
+ ### All issues fixed successfully
202
+
203
+ Delete `code-smells.md`. The report served its purpose and the codebase is clean.
204
+ Inform the user: "All N issues fixed. Report deleted. Run scan again to verify."
205
+
206
+ ### Some issues remain (failed, reverted, skipped, or not auto-fixable)
207
+
208
+ Keep `code-smells.md` as a complete audit trail. The file must show the full picture:
209
+ both what was fixed and what wasn't. This is important because:
210
+ - The user can trace regressions back to a specific fix
211
+ - The user can see at a glance what still needs manual attention
212
+ - A "fixed" issue that later causes problems can be identified and reverted
213
+
214
+ Rewrite the report with this structure:
215
+
216
+ ```markdown
217
+ # TypeScript Code Review Report
218
+
219
+ **Project:** <n>
220
+ **Scanned:** <original scan date>
221
+ **Fixed:** <fix date>
222
+ **Total issues found:** N
223
+ **Fixed:** N | **Failed:** N | **Skipped:** N | **Remaining:** N
224
+
225
+ ## Fix Summary
226
+
227
+ <1-2 sentences: what was done, what remains>
228
+
229
+ ## Fixed Issues
230
+
231
+ ### [TITLE] — [FIXED]
232
+
233
+ **Original severity:** High | **Category:** type-safety | **File:** `path` | **Line:** N
234
+
235
+ ```typescript
236
+ // BEFORE (original code)
237
+ ```
238
+
239
+ ```typescript
240
+ // AFTER (applied fix)
241
+ ```
242
+
243
+ **Regression test:** `path/to/file.reviewer-fixes.test.ts` (or "not applicable")
244
+
245
+ ---
246
+
247
+ ## Remaining Issues
248
+
249
+ ### Unfixed (failed/reverted/skipped)
250
+
251
+ ### [TITLE] — [FIX FAILED: reason] or [FIX REVERTED: reason] or [SKIPPED: reason]
252
+
253
+ **Severity:** High | **Category:** security | **File:** `path` | **Line:** N
254
+
255
+ ```typescript
256
+ // code snippet
257
+ ```
258
+
259
+ **Problem:** <explanation>
260
+ **Recommended fix:** <what should be done manually>
261
+ **Why auto-fix failed:** <specific reason>
262
+
263
+ ---
264
+
265
+ ### Not attempted (not auto-fixable or not in scope)
266
+
267
+ <these keep their original format from the scan report>
268
+
269
+ ## Config Issues
270
+ ## Recurring Patterns
271
+ ```
272
+
273
+ Status tags for each issue:
274
+ - `[FIXED]` — successfully applied and verified
275
+ - `[FIX FAILED: <reason>]` — attempted but couldn't complete (tsc errors, etc.)
276
+ - `[FIX REVERTED: <reason>]` — applied but caused test regression, rolled back
277
+ - `[SKIPPED: requires manual review]` — too risky or ambiguous to auto-fix
278
+ - No tag — not attempted (not auto-fixable or out of scope)
279
+
280
+ ### BEFORE/AFTER for fixed issues
281
+
282
+ Every `[FIXED]` issue must include both the original code and the replacement.
283
+ This serves as a diff the user can review, and if something breaks later,
284
+ they can identify which fix to revert by looking at the AFTER block.
285
+ Keep snippets minimal (3-7 lines) — just the changed portion.
286
+
287
+ ## Step 9 — Final State
288
+
289
+ After fix completes:
290
+ - All code changes are in the working tree, NOT staged, NOT committed
291
+ - The user can:
292
+ - `git diff` to review all changes
293
+ - `git add -p` to selectively stage
294
+ - `git checkout -- .` to revert everything
295
+ - Run tests themselves to verify
296
+ - New regression test files are also unstaged
297
+ - `code-smells.md` is either deleted (all clean) or updated with full audit trail
298
+
299
+ ---
300
+
301
+ ## Fix Safety Rules
302
+
303
+ 1. **NEVER commit or stage.** The user reviews and decides.
304
+ 2. **NEVER delete files** unless the file was entirely dead code flagged in the report.
305
+ 3. **NEVER modify files outside the scope** of issues in `code-smells.md`,
306
+ except for necessary cascading changes (e.g., updating imports after a type rename).
307
+ 4. **Preserve all existing behavior.** Fixes should not change what the code does,
308
+ only how it does it (safer types, modern syntax, proper error handling).
309
+ The exception is security fixes that intentionally change behavior (e.g., adding input
310
+ validation that rejects previously-accepted malicious input).
311
+ 5. **Keep changes minimal.** Don't refactor an entire file because of one issue.
312
+ Fix exactly what the report says, nothing more.
313
+ 6. **If uncertain, skip.** If a fix is ambiguous or risky, mark it as
314
+ `[SKIPPED: requires manual review]` and move on. Better to skip than to break.
315
+ 7. **Revert individual fixes with `git checkout -- <file>`** before the fix was applied,
316
+ then re-apply only the safe fixes. Use `git diff` to identify what changed in a file.
317
+
318
+ ---
319
+
320
+ ## Test Runner Quick Reference
321
+
322
+ ### vitest
323
+ ```bash
324
+ npx vitest run # run all tests once
325
+ npx vitest run --reporter json # JSON output for parsing
326
+ npx vitest run src/auth/ # run tests in directory
327
+ ```
328
+
329
+ ### jest
330
+ ```bash
331
+ npx jest # run all
332
+ npx jest --json # JSON output
333
+ npx jest --testPathPattern auth # filter by path
334
+ ```
335
+
336
+ ### mocha
337
+ ```bash
338
+ npx mocha # run all
339
+ npx mocha --reporter json # JSON output
340
+ ```
341
+
342
+ ### node:test
343
+ ```bash
344
+ node --test # run all *.test.* files
345
+ node --test --test-reporter spec # detailed output
346
+ ```
347
+
348
+ ### deno
349
+ ```bash
350
+ deno test # run all
351
+ deno test --filter "auth" # filter
352
+ ```
353
+
354
+ ### bun
355
+ ```bash
356
+ bun test # run all
357
+ bun test --bail # stop on first failure
358
+ ```
@@ -0,0 +1,94 @@
1
+ # Modernization Checklist — TypeScript 5.9+
2
+
3
+ Flag outdated patterns when a modern TypeScript equivalent exists.
4
+ All items here are **Medium** or **Low** severity.
5
+
6
+ ## Enums -> const Objects or Union Types (Medium)
7
+
8
+ ```typescript
9
+ // OUTDATED
10
+ enum Direction { Up, Down, Left, Right }
11
+ // MODERN
12
+ const Direction = { Up: 'up', Down: 'down', Left: 'left', Right: 'right' } as const;
13
+ type Direction = (typeof Direction)[keyof typeof Direction];
14
+ // Or simple union: type Direction = 'up' | 'down' | 'left' | 'right';
15
+ ```
16
+ Exception: `const enum` in library code for inlining.
17
+ Note: `const enum` is incompatible with `isolatedModules` (used by esbuild, SWC, Babel).
18
+ If the project uses a transpiler with `isolatedModules`, flag `const enum` as **Medium**
19
+ and recommend `as const` objects instead.
20
+
21
+ ## `namespace` -> ES Modules (Medium)
22
+
23
+ Flag `namespace` blocks that could be split into separate files with ES module exports.
24
+ Exception: declaration merging in `.d.ts` files.
25
+
26
+ ## `/// <reference>` -> import (Medium)
27
+
28
+ Flag `/// <reference path="..." />` that could be a regular `import`.
29
+ Exception: `/// <reference types="..." />` in global `.d.ts` files.
30
+
31
+ ## `satisfies` Operator (Medium)
32
+
33
+ Flag `as Type` or explicit type annotation where `satisfies` preserves literal types:
34
+ ```typescript
35
+ // BEFORE: loses literal type
36
+ const config: Config = { timeout: 5000 };
37
+ // MODERN: validates AND keeps literal type
38
+ const config = { timeout: 5000 } satisfies Config;
39
+ ```
40
+
41
+ ## Explicit Resource Management — `using` (Medium)
42
+
43
+ Flag manual try/finally cleanup for disposable resources:
44
+ ```typescript
45
+ // BEFORE
46
+ const handle = openFile('data.txt');
47
+ try { /* work */ } finally { handle.close(); }
48
+ // MODERN
49
+ using handle = openFile('data.txt');
50
+ ```
51
+ Only flag if the runtime supports `Symbol.dispose` (Node.js 20+, Deno 1.38+, Bun 0.6+).
52
+
53
+ ## `const` Type Parameters (Low)
54
+
55
+ ```typescript
56
+ // MODERN
57
+ function createRoute<const T extends string>(path: T) { ... }
58
+ ```
59
+
60
+ ## `import type` and Inline `type` (Low)
61
+
62
+ Flag type-only imports missing `import type` or inline `type` keyword.
63
+ If `verbatimModuleSyntax` is enabled, the compiler enforces this — don't double-flag.
64
+
65
+ ## `accessor` Keyword (Low)
66
+
67
+ Flag get/set pairs that simply read/write a backing field — use `accessor` instead.
68
+
69
+ ## `Promise` Constructor -> async/await (Low)
70
+
71
+ Flag `new Promise()` wrapping an already-async function or another promise.
72
+ The promise constructor anti-pattern adds unnecessary nesting.
73
+ Fix: use `async/await` directly.
74
+ Exception: wrapping callback-based APIs — this is the correct use of the constructor.
75
+
76
+ ## `Object.keys()` / `Object.entries()` Typing (Low)
77
+
78
+ Flag `as keyof` casts after `Object.keys()` — suggest a typed helper.
79
+
80
+ ## Module Resolution (Medium)
81
+
82
+ Flag `require()` in `.ts` files, `module.exports`, missing `.js` extensions
83
+ when `moduleResolution` is `nodenext`.
84
+
85
+ ## Deprecated Utility Types (Low)
86
+
87
+ - Custom `Awaited<T>` — built-in since TS 4.5.
88
+ - Custom `NoInfer<T>` — built-in since TS 5.4.
89
+
90
+ ## `@ts-expect-error` vs `@ts-ignore` (Low)
91
+
92
+ Flag `// @ts-ignore` without explanation — recommend `// @ts-expect-error` instead.
93
+ `@ts-expect-error` is preferred because it errors if the suppressed error disappears,
94
+ preventing stale suppressions from silently hiding future regressions.
@@ -0,0 +1,75 @@
1
+ # Security Checklist
2
+
3
+ For pure TypeScript code — no framework-specific issues.
4
+ Focus on patterns dangerous regardless of runtime (Node.js, Deno, Bun, browser).
5
+
6
+ ## Injection
7
+
8
+ - **`eval()` and `new Function()`** — executing dynamic strings.
9
+ Severity: **Highest**. Fix: lookup table, strategy pattern, safe parser.
10
+ - **Template literals in shell commands** — `exec(\`cmd ${userInput}\`)`.
11
+ Severity: **Highest**. Fix: `execFile` with argument arrays.
12
+ - **Dynamic `import()` with user-controlled paths**.
13
+ Severity: **Highest**. Fix: whitelist allowed module paths.
14
+ - **SQL/NoSQL injection** — string concatenation in queries.
15
+ Severity: **Highest**. Fix: parameterized queries.
16
+ - **RegExp from user input** — `new RegExp(userInput)`.
17
+ Severity: **High** (ReDoS + injection). Fix: escape input or use static regex.
18
+
19
+ ## Prototype Pollution
20
+
21
+ - `Object.assign(target, untrustedSource)` where source may contain `__proto__`.
22
+ Severity: **High**. Fix: `structuredClone()`, filter keys, or `Object.create(null)`.
23
+ - Recursive merge without guarding `__proto__`, `constructor`, `prototype`.
24
+ Severity: **High**.
25
+ - `obj[dynamicKey] = value` with external input key.
26
+ Severity: **High**. Fix: validate key or use `Map`.
27
+
28
+ ## Unsafe Deserialization
29
+
30
+ - `JSON.parse(untrusted)` without schema validation.
31
+ Severity: **Medium**. Fix: validate with Zod/io-ts/ajv after parsing.
32
+ Note: `JSON.parse` itself does not execute arbitrary code — the risk is accepting
33
+ malformed data that bypasses business logic, not injection.
34
+ - YAML/TOML from untrusted sources without safe parser. Severity: **High**.
35
+
36
+ ## Path Traversal
37
+
38
+ - File operations with user paths without normalization.
39
+ Severity: **Highest**. Fix: `path.resolve()` + verify within base dir.
40
+ - `path.join(base, userInput)` — does NOT prevent `..` traversal.
41
+ Severity: **Highest**. Fix: resolve full path, check `startsWith(baseDir)`.
42
+
43
+ ## Secrets and Credentials
44
+
45
+ - Hardcoded API keys, tokens, passwords in source. Severity: **Highest**.
46
+ - Secrets logged to console. Severity: **High**.
47
+ - Secrets in error messages. Severity: **High**.
48
+
49
+ ## Cryptography
50
+
51
+ - `Math.random()` for security-sensitive values. Severity: **Highest**.
52
+ Fix: `crypto.randomUUID()`, `crypto.getRandomValues()`.
53
+ - Hardcoded IVs, salts, seeds. Severity: **High**.
54
+ - Deprecated algorithms (MD5, SHA1 for security, DES). Severity: **High**.
55
+
56
+ ## Timing Attacks
57
+
58
+ - String comparison for secrets using `===`. Severity: **High**.
59
+ Fix: `crypto.timingSafeEqual()`.
60
+
61
+ ## Denial of Service
62
+
63
+ - **ReDoS** — nested quantifiers: `(a+)+`, `(a|a)+`. Severity: **High**.
64
+ - Unbounded data processing without size limits. Severity: **Medium**.
65
+ - Recursive functions without depth limits on untrusted input. Severity: **High**.
66
+
67
+ ## Information Disclosure
68
+
69
+ - Error messages exposing internals to end users. Severity: **Medium**.
70
+ - `console.log`/`console.debug` with sensitive data in production. Severity: **Medium**.
71
+
72
+ ## Race Conditions (security-relevant)
73
+
74
+ - TOCTOU in file operations. Severity: **High**.
75
+ - Auth checks separated from protected action by `await`. Severity: **High**.
@@ -0,0 +1,59 @@
1
+ # tsconfig.json Checklist
2
+
3
+ ## Strict Mode Flags
4
+
5
+ `"strict": true` enables all below. If off, check each individually.
6
+
7
+ | Flag | Severity if missing | Why |
8
+ |---|---|---|
9
+ | `strictNullChecks` | **Highest** | Without it, null/undefined assignable to everything |
10
+ | `strictFunctionTypes` | **High** | Contravariant parameter checking |
11
+ | `strictBindCallApply` | **Medium** | Type-checks bind/call/apply |
12
+ | `strictPropertyInitialization` | **High** | Catches uninitialized class properties |
13
+ | `noImplicitAny` | **High** | Prevents silent any inference |
14
+ | `noImplicitThis` | **Medium** | Catches untyped this |
15
+ | `useUnknownInCatchVariables` | **Medium** | catch(e) types as unknown not any |
16
+ | `alwaysStrict` | **Low** | Emits "use strict" |
17
+
18
+ If `strict: true` set but a sub-flag explicitly OFF — flag as that flag's severity.
19
+
20
+ ## Additional Safety Flags
21
+
22
+ | Flag | Recommended | Severity | Why |
23
+ |---|---|---|---|
24
+ | `noUncheckedIndexedAccess` | `true` | **Medium** | obj[key] returns T\|undefined instead of T |
25
+ | `exactOptionalPropertyTypes` | `true` | **Medium** | Prevents `{ key: undefined }` from satisfying optional `{ key?: string }` |
26
+ | `noFallthroughCasesInSwitch` | `true` | **Medium** | Prevents accidental fall-through |
27
+ | `noImplicitReturns` | `true` | **Medium** | Not all code paths return a value |
28
+ | `noImplicitOverride` | `true` | **Low** | Requires override keyword |
29
+ | `noPropertyAccessFromIndexSignature` | `true` | **Low** | Forces bracket notation |
30
+ | `allowUnreachableCode` | `false` | **Medium** | Should not be true |
31
+
32
+ ## Module System
33
+
34
+ | Setting | Recommendation | Severity |
35
+ |---|---|---|
36
+ | `module` | `"nodenext"` / `"preserve"` / `"esnext"` | **Medium** |
37
+ | `moduleResolution` | `"nodenext"` / `"bundler"` | **Medium** (avoid legacy `"node"` / `"node10"`) |
38
+ | `verbatimModuleSyntax` | `true` | **Medium** (enforces `import type`, removes unused imports) |
39
+ | `isolatedModules` | `true` | **Medium** (required by esbuild, SWC, Babel, TS transpile mode) |
40
+
41
+ ## Path Configuration
42
+
43
+ - Path aliases must match bundler/runtime config. Mismatches: **High**.
44
+ - Aliases pointing to non-existent dirs: **High**.
45
+
46
+ ## Deprecated Flags
47
+
48
+ Flag if present:
49
+ - `suppressImplicitAnyIndexErrors` — **Medium**
50
+ - `suppressExcessPropertyErrors` — **Medium**
51
+ - `noStrictGenericChecks` — **High**
52
+ - `importsNotUsedAsValues` — **Low** (replaced by verbatimModuleSyntax)
53
+ - `preserveValueImports` — **Low** (replaced by verbatimModuleSyntax)
54
+
55
+ ## Linter Config
56
+
57
+ - No linter configured at all: **Medium** (recommend adding one).
58
+ - TypeScript parser not configured in ESLint: **Medium**.
59
+ - Type-aware rules not enabled (e.g., `no-floating-promises`): **Medium**.
@@ -0,0 +1,101 @@
1
+ # Type Safety Checklist
2
+
3
+ ## Suppression Directives
4
+
5
+ - `// @ts-ignore` without explanation. Severity: **Medium**.
6
+ Recommend `// @ts-expect-error` with a comment explaining why the error is expected.
7
+ - `// @ts-expect-error` that no longer suppresses any error (stale). Severity: **Low**.
8
+ The directive should be removed — it was masking nothing.
9
+ - `// @ts-ignore` or `// @ts-expect-error` used to hide a type safety issue that could
10
+ be fixed properly. Severity: **Medium**. Fix: address the root cause instead.
11
+
12
+ ## `any` Abuse
13
+
14
+ - Explicit `any` in function parameters, return types, or variable declarations.
15
+ Severity: **Medium** (internal code), **High** (public API / exported functions).
16
+ - Implicit `any` from missing type annotations where TS cannot infer.
17
+ Severity: **Medium**.
18
+ - `any[]` where a typed array or generic is possible. Severity: **Medium**.
19
+ - `Record<string, any>` — usually should be `Record<string, unknown>` or a proper interface.
20
+ Severity: **Medium**.
21
+ - `Function` type — almost always wrong. Use a specific signature. Severity: **High**.
22
+ `Function` bypasses all type checking on arguments and return value.
23
+ - `object` type (lowercase) — too broad, prefer a specific interface. Severity: **Low**.
24
+
25
+ ## Unsafe Casts
26
+
27
+ - `as Type` that narrows a wider type without validation.
28
+ Severity: **High** — a runtime mismatch is a bug waiting to happen.
29
+ Fix: use a type guard, `satisfies`, or a validation function (e.g., Zod, io-ts, hand-written).
30
+ - `as unknown as Type` — double cast is almost always a red flag.
31
+ Severity: **High**.
32
+ - `<Type>value` (angle-bracket cast) — same issue as `as`, plus it conflicts with JSX.
33
+ Severity: **Medium** (prefer `as` syntax, but still flag the underlying safety issue).
34
+ - `as const` used correctly is NOT an issue — do not flag it.
35
+
36
+ ## Non-null Assertions (`!`)
37
+
38
+ - `value!` where `value` could genuinely be `null | undefined` at runtime.
39
+ Severity: **High**.
40
+ - `value!` right after a check that already narrowed the type — redundant, not harmful.
41
+ Severity: **Low** (remove the `!`, the narrowing already handles it).
42
+ - `document.getElementById('x')!` — acceptable in DOM code with known IDs,
43
+ but flag if it's in a library or server-side code. Severity: **Medium**.
44
+
45
+ ## Exhaustiveness
46
+
47
+ - `switch` on a discriminated union missing a `default: assertNever(x)` or equivalent.
48
+ Severity: **High** — adding a new variant to the union won't cause a compile error.
49
+ Fix: add an exhaustive check:
50
+ ```typescript
51
+ function assertNever(x: never): never {
52
+ throw new Error(`Unexpected value: ${x}`);
53
+ }
54
+ ```
55
+ - `if/else if` chains on union types without a final `else` that handles the rest.
56
+ Severity: **Medium**.
57
+
58
+ ## Generics
59
+
60
+ - Unnecessary generics — `function foo<T>(x: T): T` where `T` is never constrained
61
+ and the function doesn't actually use the generic relationship. Severity: **Low**.
62
+ - Missing constraints — `<T>` where `<T extends SomeBase>` is needed. Severity: **Medium**.
63
+ - Over-constrained generics that accept only one concrete type — just use that type.
64
+ Severity: **Low**.
65
+ - Generic with default that hides complexity: `<T = any>`. Severity: **Medium**.
66
+
67
+ ## Discriminated Unions
68
+
69
+ - Union types that should be discriminated but aren't (no shared literal field).
70
+ Severity: **Medium**.
71
+ - Discriminant field is `string` instead of a literal type. Severity: **Medium**.
72
+
73
+ ## Index Signatures
74
+
75
+ - `obj[key]` without checking if `key` exists — especially dangerous
76
+ when `noUncheckedIndexedAccess` is disabled. Severity: **Medium** if the flag is off.
77
+ - Using `in` operator or `hasOwnProperty` without narrowing. Severity: **Medium**.
78
+
79
+ ## Return Types
80
+
81
+ - Public/exported functions missing explicit return types. Severity: **Medium**.
82
+ (Internal functions can rely on inference — don't flag those unless the inferred type is `any`.)
83
+ - Functions that return different types in different branches without a union return type.
84
+ Severity: **High** — the inferred type might be wider than intended.
85
+
86
+ ## Type Predicates and Assertion Functions
87
+
88
+ - Type guard functions that return `boolean` instead of `x is Type`.
89
+ Severity: **Low** (works but loses narrowing at call site).
90
+ - Assertion functions (`asserts x is Type`) that don't actually throw on failure.
91
+ Severity: **High** — the compiler trusts the assertion.
92
+ - Type predicates that lie — the runtime check doesn't match the declared narrowing.
93
+ Severity: **Highest** — this silently causes type mismatches.
94
+
95
+ ## Utility Types
96
+
97
+ - Hand-rolled types that duplicate built-in utility types
98
+ (`Partial`, `Required`, `Pick`, `Omit`, `Record`, `Readonly`, `ReturnType`,
99
+ `Parameters`, `Awaited`, `NoInfer`). Severity: **Low**.
100
+ - `Omit` with a key that doesn't exist in the source type — TS silently allows this,
101
+ which may indicate a typo or stale code. Severity: **Low**.