@omermohideen/react-crap 1.0.1
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/LICENSE +21 -0
- package/README.md +400 -0
- package/dist/bin/react-crap.d.ts +3 -0
- package/dist/bin/react-crap.d.ts.map +1 -0
- package/dist/bin/react-crap.js +67 -0
- package/dist/bin/react-crap.js.map +1 -0
- package/dist/src/cache.d.ts +13 -0
- package/dist/src/cache.d.ts.map +1 -0
- package/dist/src/cache.js +33 -0
- package/dist/src/cache.js.map +1 -0
- package/dist/src/complexity.d.ts +11 -0
- package/dist/src/complexity.d.ts.map +1 -0
- package/dist/src/complexity.js +279 -0
- package/dist/src/complexity.js.map +1 -0
- package/dist/src/config.d.ts +17 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +96 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/coverage.d.ts +14 -0
- package/dist/src/coverage.d.ts.map +1 -0
- package/dist/src/coverage.js +62 -0
- package/dist/src/coverage.js.map +1 -0
- package/dist/src/delta.d.ts +26 -0
- package/dist/src/delta.d.ts.map +1 -0
- package/dist/src/delta.js +102 -0
- package/dist/src/delta.js.map +1 -0
- package/dist/src/index.d.ts +26 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +388 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/merge.d.ts +20 -0
- package/dist/src/merge.d.ts.map +1 -0
- package/dist/src/merge.js +83 -0
- package/dist/src/merge.js.map +1 -0
- package/dist/src/report/github.d.ts +3 -0
- package/dist/src/report/github.d.ts.map +1 -0
- package/dist/src/report/github.js +14 -0
- package/dist/src/report/github.js.map +1 -0
- package/dist/src/report/html.d.ts +3 -0
- package/dist/src/report/html.d.ts.map +1 -0
- package/dist/src/report/html.js +75 -0
- package/dist/src/report/html.js.map +1 -0
- package/dist/src/report/human.d.ts +10 -0
- package/dist/src/report/human.d.ts.map +1 -0
- package/dist/src/report/human.js +126 -0
- package/dist/src/report/human.js.map +1 -0
- package/dist/src/report/json.d.ts +5 -0
- package/dist/src/report/json.d.ts.map +1 -0
- package/dist/src/report/json.js +20 -0
- package/dist/src/report/json.js.map +1 -0
- package/dist/src/report/markdown.d.ts +3 -0
- package/dist/src/report/markdown.d.ts.map +1 -0
- package/dist/src/report/markdown.js +16 -0
- package/dist/src/report/markdown.js.map +1 -0
- package/dist/src/report/pr-comment.d.ts +3 -0
- package/dist/src/report/pr-comment.d.ts.map +1 -0
- package/dist/src/report/pr-comment.js +62 -0
- package/dist/src/report/pr-comment.js.map +1 -0
- package/dist/src/report/sarif.d.ts +3 -0
- package/dist/src/report/sarif.d.ts.map +1 -0
- package/dist/src/report/sarif.js +50 -0
- package/dist/src/report/sarif.js.map +1 -0
- package/dist/src/score.d.ts +29 -0
- package/dist/src/score.d.ts.map +1 -0
- package/dist/src/score.js +52 -0
- package/dist/src/score.js.map +1 -0
- package/dist/src/walker.d.ts +7 -0
- package/dist/src/walker.d.ts.map +1 -0
- package/dist/src/walker.js +75 -0
- package/dist/src/walker.js.map +1 -0
- package/package.json +72 -0
- package/schemas/delta-v2.json +41 -0
- package/schemas/report-v1.json +26 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Omer Mohideen
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
# react-crap
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@omermohideen/react-crap)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
|
|
6
|
+
Compute the **CRAP** (Change Risk Anti-Patterns) metric for React TypeScript projects.
|
|
7
|
+
|
|
8
|
+
CRAP combines cyclomatic complexity and test coverage into a single number that is high when code is both hard to understand and poorly tested — i.e. where bugs love to hide. The metric was introduced by Savoia & Evans in 2007 and was originally implemented for Java (Crap4j) and .NET (NDepend). `react-crap` brings it to the TypeScript / React ecosystem.
|
|
9
|
+
|
|
10
|
+
```
|
|
11
|
+
CRAP(m) = comp(m)² × (1 − cov(m)/100)³ + comp(m)
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
A few properties worth internalizing before you use the output:
|
|
15
|
+
|
|
16
|
+
- A trivial function (CC=1, 100% covered) scores exactly 1.0. That's the lower bound.
|
|
17
|
+
- At 100% coverage the quadratic term collapses and **CRAP equals CC**. When you see matching values in those two columns, that function is fully covered — tests are capping the damage, but the complexity itself remains. It's a good sign, not a bug.
|
|
18
|
+
- Above CC ≈ 30 no amount of coverage keeps you under the default threshold of 30. That's not a bug in the formula — it's the formula saying "this function is too big to certify as clean, regardless of tests."
|
|
19
|
+
|
|
20
|
+
## Install
|
|
21
|
+
|
|
22
|
+
**Via `npx`** (no install):
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npx @omermohideen/react-crap --lcov coverage/lcov.info --path src
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Via `npm`** (global):
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm install -g @omermohideen/react-crap
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Via `npm`** (local dev dependency):
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
npm install --save-dev @omermohideen/react-crap
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Quick start
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 1. Generate an LCOV coverage report.
|
|
44
|
+
npx vitest run --coverage
|
|
45
|
+
|
|
46
|
+
# 2. Score every function.
|
|
47
|
+
npx react-crap --lcov coverage/lcov.info --path src
|
|
48
|
+
|
|
49
|
+
# 3. Gate CI on the threshold.
|
|
50
|
+
npx react-crap --lcov coverage/lcov.info --fail-above
|
|
51
|
+
|
|
52
|
+
# 4. Whole-workspace analysis (monorepos).
|
|
53
|
+
npx react-crap --lcov coverage/lcov.info --path . --workspace --top 20
|
|
54
|
+
|
|
55
|
+
# 5. Quick aggregate summary (no table).
|
|
56
|
+
npx react-crap --lcov coverage/lcov.info --summary
|
|
57
|
+
|
|
58
|
+
# 6. Watch mode during local development.
|
|
59
|
+
npx react-crap --lcov coverage/lcov.info --path src --watch --verbose
|
|
60
|
+
|
|
61
|
+
# 7. Generate an HTML report.
|
|
62
|
+
npx react-crap --lcov coverage/lcov.info --path src --format html --output crap-report.html
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Example output:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
┌───┬───────┬────┬───────────────────┬──────────┬───────────────┐
|
|
69
|
+
│ │ CRAP │ CC │ Coverage │ Function │ Location │
|
|
70
|
+
╞═══╪═══════╪════╪═══════════════════╪══════════╪═══════════════╡
|
|
71
|
+
│ ✗ │ 156.0 │ 12 │ ░░░░░░░░░░ 0.0% │ crappy │ src/lib.ts:24 │
|
|
72
|
+
│ ▲ │ 6.7 │ 4 │ ████░░░░░░ 44.4% │ moderate │ src/lib.ts:12 │
|
|
73
|
+
│ ✓ │ 1.0 │ 1 │ ██████████ 100.0% │ trivial │ src/lib.ts:8 │
|
|
74
|
+
└───┴───────┴────┴───────────────────┴──────────┴───────────────┘
|
|
75
|
+
✗ 1/3 function(s) exceed CRAP threshold 30.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Flags
|
|
79
|
+
|
|
80
|
+
| Flag | Default | Purpose |
|
|
81
|
+
|------|---------|---------|
|
|
82
|
+
| `--lcov <FILE>` | `coverage/lcov.info` | LCOV file from your test runner (Vitest, Jest, etc.). |
|
|
83
|
+
| `--path <DIR>` | `src` | Root to walk for `.ts` / `.tsx` files (respects `.gitignore`). |
|
|
84
|
+
| `--threshold <N>` | `30` | Score above which a function is flagged. |
|
|
85
|
+
| `--min <SCORE>` | — | Hide entries **below** this CRAP score. |
|
|
86
|
+
| `--max <SCORE>` | — | Hide entries **above** this CRAP score. |
|
|
87
|
+
| `--top <N>` | — | Show only the **N worst** offenders. |
|
|
88
|
+
| `--only-failures` | — | Only show functions **exceeding** the threshold. |
|
|
89
|
+
| `--missing {pessimistic,optimistic,skip}` | `pessimistic` | How to score a function with no coverage data. |
|
|
90
|
+
| `--exclude <GLOB>` | — | Skip files matching this pattern (repeatable). `**` crosses directories. |
|
|
91
|
+
| `--allow <GLOB>` | — | Suppress matching functions (repeatable). An entry containing `/` or `**` is a path glob and matches the file the function is in (e.g. `src/generated/**`); otherwise it matches the function name and `*` is a wildcard (e.g. `use*`). |
|
|
92
|
+
| `--format {human,json,github,markdown,html,pr-comment,sarif}` | `human` | Output format. `json` emits a versioned envelope (see [JSON output schema](#json-output-schema) below). `github` emits `::warning` annotations. `markdown` emits a GFM table (exhaustive). `html` emits a self-contained styled HTML page. `pr-comment` is the opinionated PR-bot variant: hides unchanged rows, caps each section, collapses non-critical info into `<details>` blocks. `sarif` emits SARIF 2.1.0 JSON for upload to GitHub Code Scanning, VS Code, and other static-analysis tooling (see [SARIF output](#sarif-output) below). |
|
|
93
|
+
| `--summary` | off | Print only aggregate stats (total, crappy count, worst offender) — no per-function table. In `--workspace` mode this becomes the per-package summary plus the aggregate line. |
|
|
94
|
+
| `--workspace` | off | Analyze all workspace packages (discovered via `package.json` workspaces or `pnpm-workspace.yaml`). Ignores `--path`. Adds a *Per-package summary* table to human and markdown output, and a `package` field to JSON entries. |
|
|
95
|
+
| `--verbose` | off | Print step-by-step progress to stderr (file discovery, analysis progress, merge/scoring steps). |
|
|
96
|
+
| `--watch` | off | Re-run automatically when source files or LCOV change. Uses 1-second polling. Press Ctrl+C to stop. |
|
|
97
|
+
| `--fail-above` | off | Exit 1 if any function exceeds `--threshold`. |
|
|
98
|
+
| `--baseline <FILE>` | — | JSON from a previous `--format json` run. Enables delta mode (shows Δ column). Functions that moved between files (same name, body unchanged) are detected and reported as `Moved` rather than as separate New + Removed entries; renderers show `← <previous_file>` next to the new location. |
|
|
99
|
+
| `--fail-regression` | off | Exit 1 if any function's score increased since `--baseline`. `Moved` (pure relocation, no score change) is not a regression. |
|
|
100
|
+
| `--epsilon <VALUE>` | `0.01` | Tolerance for the regression detector. Score deltas with absolute value at or below this count as `Unchanged`. Set to `0.0` to flag every increase, or higher to tolerate noisy coverage. Must be non-negative. |
|
|
101
|
+
| `--jobs <N>` | host CPUs | Cap parallel source-file analysis at `N` threads. Useful in memory-constrained CI/Docker environments. Must be a positive integer. |
|
|
102
|
+
| `--output <FILE>` | — | Write output to FILE instead of stdout (useful for saving JSON baselines). |
|
|
103
|
+
| `--no-color` | — | Disable colored output. |
|
|
104
|
+
|
|
105
|
+
### Filtering order
|
|
106
|
+
|
|
107
|
+
Flags are applied in this order:
|
|
108
|
+
|
|
109
|
+
1. `--min` — filter out low CRAP scores
|
|
110
|
+
2. `--max` — filter out high CRAP scores
|
|
111
|
+
3. `--only-failures` — keep only functions above threshold
|
|
112
|
+
4. `--top` — slice to N worst remaining
|
|
113
|
+
|
|
114
|
+
## Configuration file
|
|
115
|
+
|
|
116
|
+
Any flag can be set persistently in `.react-crap.json` at the project root (or any parent directory — the tool walks up until it finds one). CLI flags always take precedence.
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"threshold": 30,
|
|
121
|
+
"top": 10,
|
|
122
|
+
"min": 10,
|
|
123
|
+
"max": 5000,
|
|
124
|
+
"onlyFailures": false,
|
|
125
|
+
"missing": "pessimistic",
|
|
126
|
+
"exclude": ["**/*.test.ts", "**/*.test.tsx"],
|
|
127
|
+
"allow": ["src/generated/**"],
|
|
128
|
+
"failAbove": true,
|
|
129
|
+
"workspace": false,
|
|
130
|
+
"verbose": false
|
|
131
|
+
}
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
All keys are optional. Unknown keys are rejected to catch typos.
|
|
135
|
+
|
|
136
|
+
## Inline annotations
|
|
137
|
+
|
|
138
|
+
You can control individual functions directly in your source code with leading comments:
|
|
139
|
+
|
|
140
|
+
```typescript
|
|
141
|
+
// react-crap-ignore
|
|
142
|
+
export function legacyHelper() {
|
|
143
|
+
// This function will be excluded from analysis entirely.
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// @crap-threshold 50
|
|
147
|
+
export function parser(input: string) {
|
|
148
|
+
// This function is allowed a higher threshold (50 instead of the global default).
|
|
149
|
+
}
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
- `// react-crap-ignore` — excludes the next function from analysis
|
|
153
|
+
- `// @crap-threshold N` — overrides the global threshold for the next function
|
|
154
|
+
|
|
155
|
+
## Context-aware function naming
|
|
156
|
+
|
|
157
|
+
Anonymous arrow functions and function expressions are resolved from their surrounding context, so you never see generic `<anonymous>` spam:
|
|
158
|
+
|
|
159
|
+
| Pattern | Displayed name |
|
|
160
|
+
|---------|----------------|
|
|
161
|
+
| `return () => {}` | `handleAuthErrors return` |
|
|
162
|
+
| `useEffect(() => {})` | `useEffect callback` |
|
|
163
|
+
| `dedupePromise(() => {})` | `dedupePromise callback` |
|
|
164
|
+
| `<Sheet>{() => ...}</Sheet>` | `Sheet child` |
|
|
165
|
+
| `(async () => {})()` | `useEffect callback IIFE` |
|
|
166
|
+
| `const dropSpec = () => () => {}` | `dropSpec nested` |
|
|
167
|
+
|
|
168
|
+
If a name cannot be resolved, the tool walks up the AST to the nearest named parent function to provide useful context.
|
|
169
|
+
|
|
170
|
+
## Caching
|
|
171
|
+
|
|
172
|
+
Complexity analysis results are cached in `.react-crap-cache.json` (created next to `.react-crap.json`). Only files whose content has changed are re-analyzed. This makes repeated runs near-instant on large codebases. The cache is automatically invalidated when the file hash changes.
|
|
173
|
+
|
|
174
|
+
## JSON output schema
|
|
175
|
+
|
|
176
|
+
`--format json` produces a versioned envelope with a `$schema` URL pointing at the published JSON Schema. Consumers can validate output offline or generate types directly from the schema.
|
|
177
|
+
|
|
178
|
+
| Variant | Schema |
|
|
179
|
+
|---------|--------|
|
|
180
|
+
| Absolute (no `--baseline`) | [`schemas/report-v1.json`](https://raw.githubusercontent.com/OmerMohideen/react-crap/master/schemas/report-v1.json) |
|
|
181
|
+
| Delta (with `--baseline`) | [`schemas/delta-v2.json`](https://raw.githubusercontent.com/OmerMohideen/react-crap/master/schemas/delta-v2.json) |
|
|
182
|
+
|
|
183
|
+
```json
|
|
184
|
+
// react-crap --format json
|
|
185
|
+
{
|
|
186
|
+
"$schema": "https://raw.githubusercontent.com/OmerMohideen/react-crap/master/schemas/report-v1.json",
|
|
187
|
+
"version": "0.1.0",
|
|
188
|
+
"entries": [
|
|
189
|
+
{
|
|
190
|
+
"file": "src/lib.ts",
|
|
191
|
+
"function": "doThing",
|
|
192
|
+
"line": 12,
|
|
193
|
+
"cyclomatic": 4,
|
|
194
|
+
"coverage": 75.0,
|
|
195
|
+
"crap": 5.6,
|
|
196
|
+
"package": "my-pkg"
|
|
197
|
+
}
|
|
198
|
+
]
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// react-crap --format json --baseline baseline.json
|
|
202
|
+
{
|
|
203
|
+
"$schema": "https://raw.githubusercontent.com/OmerMohideen/react-crap/master/schemas/delta-v2.json",
|
|
204
|
+
"version": "0.1.0",
|
|
205
|
+
"entries": [],
|
|
206
|
+
"removed": []
|
|
207
|
+
}
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
`--baseline` only reads files in this envelope shape; bare-array baselines from older runs must be regenerated.
|
|
211
|
+
|
|
212
|
+
## SARIF output
|
|
213
|
+
|
|
214
|
+
`--format sarif` emits a [SARIF 2.1.0](https://docs.oasis-open.org/sarif/sarif/v2.1.0/sarif-v2.1.0.html) JSON document — the format consumed by GitHub Code Scanning, VS Code, and most static-analysis tooling.
|
|
215
|
+
|
|
216
|
+
- Each crappy function (entry above `--threshold`) becomes one `result` with `level: "warning"` and a physical location pointing at the function's start line.
|
|
217
|
+
- Functions below the threshold are not included.
|
|
218
|
+
- An empty result set still produces a valid SARIF document with the full `runs[0].tool.driver` envelope.
|
|
219
|
+
- `--baseline` is rejected with `--format sarif`; SARIF describes findings, not deltas. Use `--format json` for delta output.
|
|
220
|
+
|
|
221
|
+
## Design
|
|
222
|
+
|
|
223
|
+
The tool has six orthogonal modules. Each is testable in isolation; the join between them has its own integration test.
|
|
224
|
+
|
|
225
|
+
```
|
|
226
|
+
vitest --coverage typescript
|
|
227
|
+
(LCOV file) (TS AST)
|
|
228
|
+
│ │
|
|
229
|
+
▼ ▼
|
|
230
|
+
┌───────────┐ ┌────────────┐
|
|
231
|
+
│ coverage │ │ complexity │
|
|
232
|
+
│ module │ │ module │
|
|
233
|
+
└─────┬─────┘ └──────┬─────┘
|
|
234
|
+
│ │
|
|
235
|
+
└──────────┬───────────────────┘
|
|
236
|
+
▼
|
|
237
|
+
┌──────────┐
|
|
238
|
+
│ merge │ ← path normalization lives here
|
|
239
|
+
└─────┬────┘
|
|
240
|
+
▼
|
|
241
|
+
┌──────────┐ ┌───────┐
|
|
242
|
+
│ score │ ──▶ │ delta │ ← baseline comparison (optional)
|
|
243
|
+
└─────┬────┘ └───────┘
|
|
244
|
+
▼
|
|
245
|
+
┌──────────┐
|
|
246
|
+
│ report │ ← human / JSON / GitHub / Markdown
|
|
247
|
+
└──────────┘
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### The path-matching problem
|
|
251
|
+
|
|
252
|
+
This is where silent failures happen. Complexity analysis produces absolute paths (whatever was passed to the walker). LCOV files contain whatever the coverage tool decided to write:
|
|
253
|
+
|
|
254
|
+
1. Absolute paths — `/home/alice/project/src/foo.ts`
|
|
255
|
+
2. Project-relative paths — `src/foo.ts`
|
|
256
|
+
3. Workspace-relative paths in a monorepo — `packages/core/src/foo.ts`
|
|
257
|
+
4. Paths with `./` or `../` components
|
|
258
|
+
|
|
259
|
+
A naïve `Map<string, _>` lookup silently returns `None` for 100% of files when the two don't agree, and every function reports as 0% covered. `react-crap` handles this with a two-level index:
|
|
260
|
+
|
|
261
|
+
- Absolute coverage paths → direct canonical-path hash lookup.
|
|
262
|
+
- Relative coverage paths → suffix match on path components (not bytes — `/foo/bar.ts` must not match `oofoo/bar.ts`).
|
|
263
|
+
|
|
264
|
+
Relative paths are **never** canonicalized against the process's CWD, which would otherwise silently bind them to whatever file happened to exist under the tool's working directory.
|
|
265
|
+
|
|
266
|
+
### The `--missing` policy
|
|
267
|
+
|
|
268
|
+
Some functions have complexity data but no coverage data — the coverage tool didn't instrument them, or they were excluded via `test` files, or the coverage run was scoped to a subset of the workspace. Three policies:
|
|
269
|
+
|
|
270
|
+
- **pessimistic** (default): treat as 0% covered. Surfaces unmapped code as a red flag. Correct for CI gates.
|
|
271
|
+
- **optimistic**: treat as 100% covered. Useful during local development when you're iterating on a specific module.
|
|
272
|
+
- **skip**: drop the row entirely.
|
|
273
|
+
|
|
274
|
+
## Integrating with CI
|
|
275
|
+
|
|
276
|
+
### Absolute threshold gate
|
|
277
|
+
|
|
278
|
+
```yaml
|
|
279
|
+
- run: npx vitest run --coverage
|
|
280
|
+
- run: npx react-crap --lcov coverage/lcov.info --fail-above --threshold 30
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
### Regression gate (recommended for teams)
|
|
284
|
+
|
|
285
|
+
Save a baseline on `master`, then fail on any PR that makes a score go up. This works regardless of the absolute threshold and catches regressions as they are introduced, not weeks later.
|
|
286
|
+
|
|
287
|
+
```yaml
|
|
288
|
+
# On master branch — upload baseline as a CI artifact
|
|
289
|
+
- run: npx vitest run --coverage
|
|
290
|
+
- run: npx react-crap --lcov coverage/lcov.info --format json --output baseline.json
|
|
291
|
+
- uses: actions/upload-artifact@v4
|
|
292
|
+
with:
|
|
293
|
+
name: crap-baseline
|
|
294
|
+
path: baseline.json
|
|
295
|
+
|
|
296
|
+
# On pull requests — download baseline and compare
|
|
297
|
+
- uses: actions/download-artifact@v4
|
|
298
|
+
with:
|
|
299
|
+
name: crap-baseline
|
|
300
|
+
path: baseline
|
|
301
|
+
- run: npx vitest run --coverage
|
|
302
|
+
- run: npx react-crap --lcov coverage/lcov.info --baseline baseline/baseline.json --fail-regression
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
### GitHub Code Scanning (SARIF)
|
|
306
|
+
|
|
307
|
+
Upload `--format sarif` output to surface crappy functions in the repository's **Security → Code scanning** tab. The job needs `security-events: write`.
|
|
308
|
+
|
|
309
|
+
```yaml
|
|
310
|
+
self_score:
|
|
311
|
+
permissions:
|
|
312
|
+
security-events: write
|
|
313
|
+
steps:
|
|
314
|
+
- run: npx vitest run --coverage
|
|
315
|
+
- run: npx react-crap --lcov coverage/lcov.info --format sarif --output crap.sarif
|
|
316
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
317
|
+
with:
|
|
318
|
+
sarif_file: crap.sarif
|
|
319
|
+
category: react-crap
|
|
320
|
+
```
|
|
321
|
+
|
|
322
|
+
### PR comment bot
|
|
323
|
+
|
|
324
|
+
`--format pr-comment` produces a sticky comment that surfaces regressions and new functions in the primary table and tucks improvements / removed functions / above-threshold hot-spots into collapsed `<details>` blocks. A hidden marker (`<!-- react-crap-report -->`) lets the script update an existing comment instead of posting duplicates. The job needs `pull-requests: write`.
|
|
325
|
+
|
|
326
|
+
```yaml
|
|
327
|
+
self_score:
|
|
328
|
+
permissions:
|
|
329
|
+
pull-requests: write
|
|
330
|
+
steps:
|
|
331
|
+
# ...generate coverage and download baseline as above...
|
|
332
|
+
|
|
333
|
+
- name: Generate PR comment
|
|
334
|
+
if: github.event_name == 'pull_request'
|
|
335
|
+
run: |
|
|
336
|
+
npx react-crap \
|
|
337
|
+
--lcov coverage/lcov.info \
|
|
338
|
+
--baseline baseline.json \
|
|
339
|
+
--format pr-comment \
|
|
340
|
+
--output crap-comment.md
|
|
341
|
+
|
|
342
|
+
- name: Post or update PR comment
|
|
343
|
+
if: github.event_name == 'pull_request'
|
|
344
|
+
uses: actions/github-script@v7
|
|
345
|
+
with:
|
|
346
|
+
script: |
|
|
347
|
+
const fs = require('fs');
|
|
348
|
+
const body = fs.readFileSync('crap-comment.md', 'utf8');
|
|
349
|
+
const marker = '<!-- react-crap-report -->';
|
|
350
|
+
const { data: comments } = await github.rest.issues.listComments({
|
|
351
|
+
owner: context.repo.owner,
|
|
352
|
+
repo: context.repo.repo,
|
|
353
|
+
issue_number: context.issue.number,
|
|
354
|
+
});
|
|
355
|
+
const existing = comments.find(c => c.body.startsWith(marker));
|
|
356
|
+
const args = {
|
|
357
|
+
owner: context.repo.owner,
|
|
358
|
+
repo: context.repo.repo,
|
|
359
|
+
body,
|
|
360
|
+
};
|
|
361
|
+
if (existing) {
|
|
362
|
+
await github.rest.issues.updateComment({ ...args, comment_id: existing.id });
|
|
363
|
+
} else {
|
|
364
|
+
await github.rest.issues.createComment({ ...args, issue_number: context.issue.number });
|
|
365
|
+
}
|
|
366
|
+
```
|
|
367
|
+
|
|
368
|
+
## What this tool is not
|
|
369
|
+
|
|
370
|
+
- It is not a replacement for engineering judgment.
|
|
371
|
+
- It does not understand your business domain.
|
|
372
|
+
- It does not prove that your tests are good.
|
|
373
|
+
|
|
374
|
+
Coverage can execute a line without asserting the right behavior. A function can be fully covered and still poorly tested.
|
|
375
|
+
|
|
376
|
+
So the CRAP score should not be treated as absolute truth. It is a signal — a useful one.
|
|
377
|
+
|
|
378
|
+
The best use of the tool is to ask better questions:
|
|
379
|
+
|
|
380
|
+
- Why is this function so complex?
|
|
381
|
+
- Is this complexity essential or accidental?
|
|
382
|
+
- Do the tests cover the important branches?
|
|
383
|
+
- Can we split this into smaller pieces?
|
|
384
|
+
- Should this logic be modeled more explicitly?
|
|
385
|
+
|
|
386
|
+
Good tools do not replace thinking. They make thinking easier to focus.
|
|
387
|
+
|
|
388
|
+
## Prior art and references
|
|
389
|
+
|
|
390
|
+
- [Savoia, A. & Evans, B. (2007). *The CRAP Metric.*](https://www.artima.com/weblogs/viewpost.jsp?thread=210575)
|
|
391
|
+
- [Crap4j](http://www.crap4j.org/) — the original Java implementation.
|
|
392
|
+
- [cargo-crap](https://github.com/minikin/cargo-crap) — Rust implementation of the CRAP metric for Cargo projects by [minikin](https://minikin.me/blog/cargo-crap).
|
|
393
|
+
|
|
394
|
+
## Contributing
|
|
395
|
+
|
|
396
|
+
See [CONTRIBUTING.md](./CONTRIBUTING.md) for the commit convention, development setup, and release process.
|
|
397
|
+
|
|
398
|
+
## License
|
|
399
|
+
|
|
400
|
+
MIT
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-crap.d.ts","sourceRoot":"","sources":["../../bin/react-crap.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
4
|
+
const commander_1 = require("commander");
|
|
5
|
+
const index_js_1 = require("../src/index.js");
|
|
6
|
+
function collect(value, previous) {
|
|
7
|
+
return previous.concat([value]);
|
|
8
|
+
}
|
|
9
|
+
const program = new commander_1.Command();
|
|
10
|
+
program
|
|
11
|
+
.name("react-crap")
|
|
12
|
+
.description("Change Risk Anti-Patterns (CRAP) metric for React TypeScript projects")
|
|
13
|
+
.version("0.1.0");
|
|
14
|
+
program
|
|
15
|
+
.option("--lcov <file>", "LCOV coverage report file")
|
|
16
|
+
.option("--path <dir>", "Root directory to analyze", "src")
|
|
17
|
+
.option("--threshold <n>", "CRAP score threshold", "30")
|
|
18
|
+
.option("--min <score>", "Hide entries below this CRAP score")
|
|
19
|
+
.option("--max <score>", "Hide entries above this CRAP score")
|
|
20
|
+
.option("--top <n>", "Show only the N worst offenders")
|
|
21
|
+
.option("--only-failures", "Only show functions exceeding the CRAP threshold")
|
|
22
|
+
.option("--missing <policy>", "Missing coverage policy: pessimistic, optimistic, skip", "pessimistic")
|
|
23
|
+
.option("--exclude <glob>", "Skip files matching pattern (repeatable)", collect, [])
|
|
24
|
+
.option("--allow <glob>", "Suppress matching functions (repeatable)", collect, [])
|
|
25
|
+
.option("--format <fmt>", "Output format: human, json, github, markdown, pr-comment, sarif", "human")
|
|
26
|
+
.option("--summary", "Print only aggregate stats")
|
|
27
|
+
.option("--fail-above", "Exit 1 if any function exceeds threshold")
|
|
28
|
+
.option("--baseline <file>", "JSON baseline for delta comparison")
|
|
29
|
+
.option("--fail-regression", "Exit 1 if any score increased since baseline")
|
|
30
|
+
.option("--epsilon <value>", "Regression detector tolerance", "0.01")
|
|
31
|
+
.option("--jobs <n>", "Cap parallel analysis threads")
|
|
32
|
+
.option("--workspace", "Analyze all workspace packages")
|
|
33
|
+
.option("--verbose", "Print detailed progress information")
|
|
34
|
+
.option("--watch", "Re-run automatically when files change")
|
|
35
|
+
.option("--no-color", "Disable colored output")
|
|
36
|
+
.option("--output <file>", "Write output to file instead of stdout");
|
|
37
|
+
program.parse();
|
|
38
|
+
const options = program.opts();
|
|
39
|
+
const noColor = options.noColor || !!process.env.NO_COLOR;
|
|
40
|
+
(0, index_js_1.run)({
|
|
41
|
+
lcov: options.lcov,
|
|
42
|
+
path: options.path,
|
|
43
|
+
threshold: parseFloat(options.threshold),
|
|
44
|
+
min: options.min ? parseFloat(options.min) : undefined,
|
|
45
|
+
max: options.max ? parseFloat(options.max) : undefined,
|
|
46
|
+
top: options.top ? parseInt(options.top, 10) : undefined,
|
|
47
|
+
onlyFailures: options.onlyFailures ?? false,
|
|
48
|
+
missing: options.missing,
|
|
49
|
+
exclude: options.exclude,
|
|
50
|
+
allow: options.allow,
|
|
51
|
+
format: options.format,
|
|
52
|
+
summary: options.summary ?? false,
|
|
53
|
+
failAbove: options.failAbove ?? false,
|
|
54
|
+
baseline: options.baseline,
|
|
55
|
+
failRegression: options.failRegression ?? false,
|
|
56
|
+
epsilon: parseFloat(options.epsilon),
|
|
57
|
+
jobs: options.jobs ? parseInt(options.jobs, 10) : undefined,
|
|
58
|
+
workspace: options.workspace ?? false,
|
|
59
|
+
verbose: options.verbose ?? false,
|
|
60
|
+
watch: options.watch ?? false,
|
|
61
|
+
output: options.output,
|
|
62
|
+
noColor,
|
|
63
|
+
}).catch((err) => {
|
|
64
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
65
|
+
process.exit(1);
|
|
66
|
+
});
|
|
67
|
+
//# sourceMappingURL=react-crap.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"react-crap.js","sourceRoot":"","sources":["../../bin/react-crap.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { ComplexityEntry } from "./complexity.js";
|
|
2
|
+
export interface CacheEntry {
|
|
3
|
+
hash: string;
|
|
4
|
+
functions: ComplexityEntry[];
|
|
5
|
+
}
|
|
6
|
+
export interface Cache {
|
|
7
|
+
version: string;
|
|
8
|
+
files: Record<string, CacheEntry>;
|
|
9
|
+
}
|
|
10
|
+
export declare function loadCache(projectPath: string): Cache;
|
|
11
|
+
export declare function saveCache(projectPath: string, cache: Cache): void;
|
|
12
|
+
export declare function hashFile(content: string): string;
|
|
13
|
+
//# sourceMappingURL=cache.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAC;AAEvD,MAAM,WAAW,UAAU;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,eAAe,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,KAAK;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CAClC;AAKD,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,GAAG,KAAK,CAYpD;AAED,wBAAgB,SAAS,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,IAAI,CAGjE;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEhD"}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.loadCache = loadCache;
|
|
4
|
+
exports.saveCache = saveCache;
|
|
5
|
+
exports.hashFile = hashFile;
|
|
6
|
+
const node_crypto_1 = require("node:crypto");
|
|
7
|
+
const node_fs_1 = require("node:fs");
|
|
8
|
+
const node_path_1 = require("node:path");
|
|
9
|
+
const CACHE_FILE = ".react-crap-cache.json";
|
|
10
|
+
const CACHE_VERSION = "1";
|
|
11
|
+
function loadCache(projectPath) {
|
|
12
|
+
const path = (0, node_path_1.resolve)(projectPath, CACHE_FILE);
|
|
13
|
+
if ((0, node_fs_1.existsSync)(path)) {
|
|
14
|
+
try {
|
|
15
|
+
const raw = (0, node_fs_1.readFileSync)(path, "utf-8");
|
|
16
|
+
const parsed = JSON.parse(raw);
|
|
17
|
+
if (parsed.version === CACHE_VERSION)
|
|
18
|
+
return parsed;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// invalid cache, ignore
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return { version: CACHE_VERSION, files: {} };
|
|
25
|
+
}
|
|
26
|
+
function saveCache(projectPath, cache) {
|
|
27
|
+
const path = (0, node_path_1.resolve)(projectPath, CACHE_FILE);
|
|
28
|
+
(0, node_fs_1.writeFileSync)(path, JSON.stringify(cache, null, 2), "utf-8");
|
|
29
|
+
}
|
|
30
|
+
function hashFile(content) {
|
|
31
|
+
return (0, node_crypto_1.createHash)("sha256").update(content).digest("hex").slice(0, 16);
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":";;AAkBA,8BAYC;AAED,8BAGC;AAED,4BAEC;AAvCD,6CAAyC;AACzC,qCAAkE;AAClE,yCAAoC;AAapC,MAAM,UAAU,GAAG,wBAAwB,CAAC;AAC5C,MAAM,aAAa,GAAG,GAAG,CAAC;AAE1B,SAAgB,SAAS,CAAC,WAAmB;IAC5C,MAAM,IAAI,GAAG,IAAA,mBAAO,EAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IAC9C,IAAI,IAAA,oBAAU,EAAC,IAAI,CAAC,EAAE,CAAC;QACtB,IAAI,CAAC;YACJ,MAAM,GAAG,GAAG,IAAA,sBAAY,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;YACxC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAU,CAAC;YACxC,IAAI,MAAM,CAAC,OAAO,KAAK,aAAa;gBAAE,OAAO,MAAM,CAAC;QACrD,CAAC;QAAC,MAAM,CAAC;YACR,wBAAwB;QACzB,CAAC;IACF,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAC9C,CAAC;AAED,SAAgB,SAAS,CAAC,WAAmB,EAAE,KAAY;IAC1D,MAAM,IAAI,GAAG,IAAA,mBAAO,EAAC,WAAW,EAAE,UAAU,CAAC,CAAC;IAC9C,IAAA,uBAAa,EAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AAED,SAAgB,QAAQ,CAAC,OAAe;IACvC,OAAO,IAAA,wBAAU,EAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACxE,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ComplexityEntry {
|
|
2
|
+
file: string;
|
|
3
|
+
function: string;
|
|
4
|
+
line: number;
|
|
5
|
+
endLine: number;
|
|
6
|
+
cyclomatic: number;
|
|
7
|
+
bodyHash: string;
|
|
8
|
+
threshold?: number;
|
|
9
|
+
}
|
|
10
|
+
export declare function analyzeComplexity(files: string[], tsPath?: string, jobs?: number, onProgress?: (current: number, total: number, file: string) => void): Promise<ComplexityEntry[]>;
|
|
11
|
+
//# sourceMappingURL=complexity.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"complexity.d.ts","sourceRoot":"","sources":["../../src/complexity.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,eAAe;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,wBAAsB,iBAAiB,CACtC,KAAK,EAAE,MAAM,EAAE,EACf,MAAM,CAAC,EAAE,MAAM,EACf,IAAI,CAAC,EAAE,MAAM,EACb,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,KAAK,IAAI,GACjE,OAAO,CAAC,eAAe,EAAE,CAAC,CAqB5B"}
|