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.
- package/README.md +202 -0
- package/dist/cli.js +57 -0
- package/dist/cli.js.map +1 -0
- package/dist/hash.js +11 -0
- package/dist/hash.js.map +1 -0
- package/dist/incoming.js +34 -0
- package/dist/incoming.js.map +1 -0
- package/dist/index.js +60 -0
- package/dist/index.js.map +1 -0
- package/dist/io.js +39 -0
- package/dist/io.js.map +1 -0
- package/dist/meta.js +27 -0
- package/dist/meta.js.map +1 -0
- package/dist/paths.js +57 -0
- package/dist/paths.js.map +1 -0
- package/dist/prompt.js +64 -0
- package/dist/prompt.js.map +1 -0
- package/dist/updatePolicy.js +18 -0
- package/dist/updatePolicy.js.map +1 -0
- package/package.json +39 -0
- package/ts-reviewer/SKILL.md +351 -0
- package/ts-reviewer/references/async-patterns.md +64 -0
- package/ts-reviewer/references/code-quality.md +74 -0
- package/ts-reviewer/references/fix-workflow.md +358 -0
- package/ts-reviewer/references/modernization.md +94 -0
- package/ts-reviewer/references/security.md +75 -0
- package/ts-reviewer/references/tsconfig.md +59 -0
- package/ts-reviewer/references/type-safety.md +101 -0
|
@@ -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**.
|