eslint-plugin-crap
v0.1.3
Published
CRAP (Change Risk Anti-Pattern) metric for TypeScript/JavaScript as an ESLint/oxlint plugin (aka crap4ts)
Maintainers
Readme
eslint-plugin-crap (aka crap4ts)
CRAP (Change Risk Anti-Pattern) metric for TypeScript/JavaScript, as an oxlint JS plugin.
Combines cyclomatic complexity with test coverage to flag functions that are both complex and under-tested — the riskiest code to change. A port of crap4clj to the TS ecosystem.
How it works
- You run your tests with an lcov coverage reporter (vitest, jest, etc.), producing
coverage/lcov.info. - The
crap/craplint rule computes cyclomatic complexity per function, looks up the function's line coverage in the lcov file, and reports any function whose CRAP score exceeds the threshold.
CRAP(fn) = CC² × (1 - coverage)³ + CC| Score | Risk | |-------|------| | 1-5 | Low — clean code | | 5-30 | Moderate — refactor or add tests | | 30+ | High — complex and under-tested |
Setup
Requires oxlint with JS plugin support (v1.78+). In .oxlintrc.json:
{
"jsPlugins": ["eslint-plugin-crap"],
"rules": {
"crap/crap": ["warn", { "maxCrap": 30, "lcovPath": "coverage/lcov.info" }]
}
}(oxlint strips the eslint-plugin- prefix, so rules are referenced as crap/....)
Prefer the classic crap4ts name? Alias it:
{
"jsPlugins": [{ "name": "crap4ts", "specifier": "eslint-plugin-crap" }],
"rules": {
"crap4ts/crap": ["warn", { "maxCrap": 30 }]
}
}Make sure your test runner emits lcov. For vitest:
// vitest.config.ts
export default defineConfig({
test: { coverage: { provider: 'v8', reporter: ['text', 'lcov'] } },
});Usage
npx vitest run --coverage # 1. generate coverage/lcov.info
npx oxlint . # 2. lint with CRAP scoresExample output:
src/rule.ts:33:10: warning crap(crap): 'functionName' has a CRAP score of 40.4
(complexity 13, coverage 45.5%) — max is 30. Add tests or simplify.Options
| Option | Default | Meaning |
|--------|---------|---------|
| maxCrap | 30 | Report functions scoring above this |
| lcovPath | coverage/lcov.info | lcov file, relative to the lint root |
| warnMissing | true | Warn when the lcov file is missing or a file has no entry in it |
Notes
- If the lcov file is missing, or a file has no entry in it, the rule reports a warning at the top of each linted file so the gap isn't silent. Set
warnMissing: falseto restore the old silent behaviour (e.g. in CI stages that lint before running coverage). - If a source file is newer than the lcov file, the rule warns that coverage may be stale but still reports CRAP scores from the existing data.
- Functions whose line range contains no instrumented lines are skipped.
- Complexity counts:
if,?:,for/for-in/for-of,while,do-while, non-defaultcase,catch,&&/||/??, and&&=/||=/??=, plus 1. Nested functions count toward their enclosing function too.
Why not ast-grep?
ast-grep rules are declarative pattern matchers — they can't count decision points, do arithmetic, or read a coverage file, so they can't compute CRAP. oxlint JS plugins run real JavaScript, so they can do all three.
Development
pnpm build # compile src/ to dist/ for oxlint consumers
pnpm test # unit + RuleTester tests
pnpm coverage # regenerate lcov
pnpm lint # build, then dogfood the rule on this repo