Score Clojure, Java, Go, TypeScript, Rust, and Python with each language's complexity rules and coverage report.
5.2 KiB
crapper
CRAP scores for Clojure, Java, Go, TypeScript, Rust, and Python. One run detects the language of each source file, applies that language's complexity and coverage rules, and writes the snapshot uml-viewer already reads.
The formula is the one from crap4clj, crap4java, and crap4go:
CRAP = CC² × (1 − coverage)³ + CC
CC is cyclomatic complexity. coverage is the fraction of the function exercised by tests. A score of 1–5 is low risk, 5–30 is worth a look, and 30+ is complex and under-tested.
Run
./crapper
The first run creates .venv and installs the tool. From a project root it walks the tree, skips test, spec, vendor, node_modules, and target, and writes two results:
- a table on stdout, worst score first
.metrics/crap.edn, replaced on every successful analysis
./crapper --no-coverage # complexity only; coverage is N/A
./crapper --use-existing-coverage # read reports already on disk
./crapper src/demo/core.clj src/ui # these files and trees
./crapper --changed # git additions and edits
./crapper combat # path fragment, same idea as crap4clj
./crapper --threshold 30 # exit 2 when the worst score is higher
Snapshot
.metrics/crap.edn has the same shape crap4clj writes:
{:entries [{:name "place"
:namespace "demo.Board"
:complexity 3
:coverage 75.0
:crap 3.1}]}
uml-viewer groups entries by :namespace and joins each operation on :name. nil coverage means the function was found but no coverage data applied, and CRAP is nil too.
| Language | :namespace |
:name |
|---|---|---|
| Clojure | the ns |
the defn / defn- name |
| Java | package.Class, or package.Outer.Inner |
the method name |
| Go | the package import path, or import/path.Receiver |
the function or method name |
| TypeScript | the dotted module path, or module.Class |
the function or method name |
| Rust | crate::module, or crate::module::Type |
the function or method name |
| Python | the dotted module path, or module.Class |
the function or method name |
Rename or move is a new entry. There is no identity matching across runs.
What each language counts
Clojure follows crap4clj: if / when and their variants, and, or, loop, catch, and each clause of cond, condp, case, cond->, cond->>, some->, and some->>. Coverage prefers Cloverage's per-line form counts and falls back to LCOV.
Java follows crap4java: if, loops, catch, ?:, each switch label, and && / ||. Constructors and methods inside anonymous classes are omitted. Coverage is JaCoCo's INSTRUCTION counter.
Go follows crap4go: if, for, range, each switch and select clause, and && / ||. Coverage is go test -coverprofile.
TypeScript and TSX use the same structural decisions as Java, including && inside JSX. Top-level functions, class methods, and top-level arrow functions are entries. Nested callbacks stay inside the enclosing function. Coverage is LCOV. A coverage script is used as-is. A Vitest project runs vitest --coverage (installing @vitest/coverage-v8 into node_modules when it is missing, without editing package.json). Other test scripts are wrapped in c8.
Rust counts if, loops, each match arm, ?, and && / ||. mod tests is skipped. Coverage is LCOV from cargo llvm-cov or cargo tarpaulin, run in the nearest directory that contains Cargo.toml. When neither tool is installed, the run installs cargo-llvm-cov.
Python counts if, elif, for, while, except, each match case, a comprehension filter, a conditional expression, and each and / or. Nested functions stay inside the enclosing function. Coverage is LCOV from coverage.py, running pytest when the project uses it and unittest otherwise. coverage and pytest are installed into the project's interpreter when they are missing.
Coverage commands
By default a run deletes the previous report for each language it is about to measure and regenerates it:
| Language | Command | Report |
|---|---|---|
| Clojure | clj -M:cov --lcov (needs deps.edn or bb.edn) |
target/coverage/ |
| Java | JaCoCo Maven plugin 0.8.12 in each module with pom.xml |
target/site/jacoco/jacoco.xml |
| Go | go test ./... -coverprofile=... |
target/coverage/go/coverage.out |
| TypeScript | npm run coverage, or Vitest --coverage, or npx c8 ... npm test |
coverage/lcov.info or target/coverage/typescript/lcov.info |
| Rust | cargo llvm-cov or cargo tarpaulin, per Cargo package |
target/coverage/rust/lcov.info |
| Python | coverage run with pytest or unittest, then coverage lcov |
target/coverage/python/lcov.info |
A missing tool or a failed test run leaves that language at N/A and still writes the snapshot. Pass --coverage-command to replace those defaults with one command of your own.
Development
python3 -m venv .venv
.venv/bin/pip install -e '.[dev]'
.venv/bin/pytest