Find the code that's actually risky to change — complex and untested — in any Swift package or iOS app. Then hand your AI reviewer a measured risk map, not a blind diff.
CRAP (Change Risk Anti-Patterns) scores each function by combining cyclomatic complexity with test coverage:
CRAP(f) = CC(f)² · (1 − coverage(f))³ + CC(f)
Fully tested code scores its complexity; untested complexity blows up super-linearly — a
function with 5 branches and no tests is CRAP 30; add tests and it drops to 5. The
canonical threshold is 30; --strict uses 8 (Uncle Bob's stricter bar).
There was no CRAP tool for Swift. This is it.
brew install JordanCoin/tap/crap4swift # macOS, universal binaryOr grab a binary from Releases, or
build from source with swift build -c release. See the
CHANGELOG for what's new.
crap4swift can run your tests itself and score the result in one command:
# Swift package / server-side Swift (Linux-friendly)
crap4swift --spm .
# iOS app — runs `xcodebuild test` on a simulator for you
crap4swift --xcodeproj MyApp.xcodeproj --scheme MyApp --only-testing MyAppTestsCRAP CC COV FUNCTION
132.0 11 0% routeCheckout(…) Services/Checkout.swift:5 ⚠️
3.1 3 78% classify(…) Utils/Classify.swift:1
2 functions · 1 over threshold 30
Exit codes are 0 clean · 1 usage error · 2 a function exceeded the threshold — so
it gates CI out of the box.
Scope to the functions a branch changed, fuse in SwiftLint, and emit a risk map an AI reviewer can reason about instead of guessing from a raw diff:
crap4swift --spm . --changed main --format review{
"summary": { "count": 1, "overThreshold": 1, "worst": "routeCheckout(…)" },
"functions": [{
"function": "routeCheckout(…)", "crap": 132, "complexity": 11, "coverage": 0,
"overThreshold": true,
"lint": [{ "rule": "force_cast", "severity": "Error", "line": 6 }]
}]
}Wire it into CI with the included GitHub Action, a pre-push hook, or the "AI creates
the PR" flow — see docs/pr-review.md.
Two "doors": crap4swift runs the tests itself, or you feed it coverage you already have.
| Coverage flag | Source |
|---|---|
--spm <dir> |
runs swift test --enable-code-coverage |
--xcodeproj <p> --scheme <s> |
runs xcodebuild test on a simulator |
--llvm-json <f> |
llvm-cov export JSON |
--xccov-json <f> / --xcresult <p> |
Xcode xccov report / result bundle |
| Option | Effect |
|---|---|
--threshold N / --strict |
gate threshold (default 30 / 8) |
--changed <base> |
only report functions touched vs <base> (PR scope) |
--missing pessimistic|optimistic|skip |
how to score functions with no coverage |
--baseline <f> + --fail-regression |
fail only if a function got worse |
--format human|json|review|sarif|html |
text · JSON · agent map · Code Scanning · dashboard |
--exclude <substr> · --only-testing <t> |
skip paths · scope test targets |
SwiftUI note: Xcode omits declarative View bodies from coverage, so the default
--missing skip drops them rather than flagging every body; use --missing pessimistic to surface them.
Two language-specific halves feed a pure, dependency-free core:
- Complexity — SwiftSyntax (the compiler's own parser), so result builders, macros,
and SwiftUI DSL parse exactly. Counting matches SwiftLint's
cyclomatic_complexity. - Coverage — llvm-cov (true per-line) and xccov (per-function) normalized behind one
Coveragetype. - Core — joins complexity to coverage by
(file, line-range), never by symbol name; applies the missing-coverage policy, scores, sorts, gates.
Built test-first: parser tests validate against real captured llvm-cov / xccov
output, matching their own summary numbers to the digit. Runs on a Linux CI runner for
SPM / server-side Swift — no Mac required (iOS coverage needs a Mac to generate).
The CRAP metric is by Alberto Savoia & Bob Evans (crap4j, 2007). crap4swift builds on
unclebob/crap4java — the direct inspiration and
the --strict (8) threshold — and
minikin/cargo-crap, whose module split and
--missing policy shaped this design. Complexity counting follows
SwiftLint.
MIT — see LICENSE.