Skip to content

Repository files navigation

crap4swift

CI crap4swift License: MIT

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.

Install

brew install JordanCoin/tap/crap4swift     # macOS, universal binary

Or grab a binary from Releases, or build from source with swift build -c release. See the CHANGELOG for what's new.

Quickstart

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 MyAppTests
CRAP    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.

PR review — the point

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 }]
  }]
}

Drop-in CI

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 2 if a changed function is over threshold (0 clean, 1 usage 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.

Coverage sources & options

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.

How it works

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 Coverage type.
  • 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).

Credits

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.

License

MIT — see LICENSE.

About

Change Risk Anti-Patterns (CRAP) for Swift — find complex, untested code and hand an AI reviewer a measured risk map. SwiftSyntax + llvm-cov/xccov. PR-native, Linux-ready.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages