A CI/CD quality gate for Swift. Fail the build when a changed function is too complex for its test coverage — and hand your AI reviewer a measured risk map instead of a blind diff. Any Swift package or iOS app.
(That crap4swift badge above is generated by crap4swift, on its own code, in its own
CI — green means no function is over threshold.)
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 }]
}]
}crap4swift is built to gate a pipeline. The included GitHub Action (Linux runner) does
four things on every PR, and re-checks the whole repo on every push to main:
- Gates the build — exit
2if a changed function is over threshold (0clean,1usage error), so a red check blocks the merge. - Comments the risk map on the PR — CRAP + SwiftLint for just the changed functions, ready for a human or an AI reviewer to act on.
- Uploads SARIF — findings appear as inline annotations in the PR's Files tab (GitHub Code Scanning).
- Publishes the status badge above — generated by crap4swift, on its own code.
Copy .github/workflows/crap4swift-pr.yml, or
wire the gate into a pre-push hook or an "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|shields |
text · JSON · agent map · Code Scanning · dashboard · badge |
--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.