go-crap

command module
v0.5.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 17, 2026 License: MIT Imports: 1 Imported by: 0

README

go-crap

Go Reference CRAP analysis

CRAP score calculator for Go projects. Calculates the CRAP score (cyclomatic complexity × coverage) for every function in a Go module. Inspired by cargo-crap for Rust.

Installation

curl -fsSL https://padiazg.github.io/go-crap/install.sh | sh

Or build from source:

git clone https://github.com/padiazg/go-crap.git
cd go-crap
go build -o go-crap .

Or use Brew:

brew tap padiazg/go-crap 
brew install go-crap

Docker

Pull and run a pre-built image:

docker run --rm -v "$PWD:/code" ghcr.io/padiazg/go-crap scan

Registries: docker.io/padiazg/go-crap, ghcr.io/padiazg/go-crap

Available tags correspond to releases. Multi-arch images (linux/amd64, linux/arm64).

Build locally:

make docker-build
docker run --rm -v "$PWD:/code" go-crap:local scan

The image runs go-crap scan /code by default — it analyses whatever directory you mount at /code. Pass any flags directly:

docker run --rm -v "$PWD:/code" ghcr.io/padiazg/go-crap scan --top 10 --format table

Usage

go-crap scan [pattern ...] [flags]

Scans Go packages matching the given patterns (defaults to ./... — the whole module) and outputs a ranked table of functions by CRAP score.

Patterns use the same syntax as go list / go build: ./... (recursive), ./internal/score (single package), github.com/x/y (import path). Multiple patterns are accepted (e.g. ./internal/foo ./internal/bar).

Two-layer selection

go-crap uses two independent mechanisms to narrow what gets analyzed:

  1. Patterns select which Go packages are scanned. Operates at the package/directory level — coverage tests are only run on matching packages.
  2. --exclude filters files and functions within matched packages via regex. Cannot exclude whole packages from being tested, only from the final report.

They are designed to work together:

# Scan all packages, but skip generated protobuf files inside them
go-crap scan --exclude '\.pb\.go$'

# Narrow to specific packages, then exclude files within them
go-crap scan ./internal/scan ./internal/coverage --exclude 'mock_'
Example
# Scan entire current module (default)
go-crap scan

# Scan specific package
go-crap scan ./internal/score

# Scan multiple packages
go-crap scan ./internal/score ./internal/coverage

# Show only the 10 worst offenders
go-crap scan --top 10

# Fail CI if any function exceeds threshold
go-crap scan --fail-above --threshold 30

# Exclude protobuf files (test files excluded by default)
go-crap scan --exclude 'pb/.*\.go'
Flags
Flag Short Description Default
--threshold -t Score above which a function is marked as problematic 30.0
--fail-above Exit with code 1 if any function exceeds the threshold false
--fail-regression-ignore-covered Exclude fully covered functions from regression failures (still shows them with ~) false
--format -f Output format: table, json, github, sarif, or pr-comment table
--top Show only the N worst offenders (0 = all) 0
--min Hide entries below this score 0
--missing Policy for functions without coverage: pessimistic, optimistic, or skip pessimistic
--exclude Exclude files and functions matching this regex (repeatable). Use .* for any path depth. _test.go files are excluded by default none
--include-tests Include _test.go files in analysis (overrides default exclude) false
--verbose Enable verbose (debug-level) logging false
--progress Show progress indicators (default: auto-detect terminal) false
--no-progress Disable progress indicators false
--output -o Output file path (default: stdout) stdout
--mutation-report Path to gremlins JSON mutation report to validate coverage reliability ""
--detailed Include mutation failure details (original code, replacement, line) in report output false
--timeout Timeout for the full scan (e.g. 30s, 5m, 1h30m) 10m0s
--coverage-profile Supply an existing coverage profile instead of running go test ""
--coverpkg Pass -coverpkg to go test to measure cross-package coverage (e.g. ./...) ""
--baseline Path to a previous JSON report for baseline comparison ""
--fail-regression Exit code 1 when functions regressed vs baseline false
--fail-regression-threshold Minimum delta to consider regression 0.01
--show-unchanged In baseline mode, also show functions whose CRAP score did not change (requires --baseline) false
--help -h Help for scan —
Progress Indicators

go-crap can show progress bars on stderr during long scans:

  • Default — auto-detects terminal: enabled when stderr is a TTY, disabled when piped
  • --progress — force-enable progress indicators
  • --no-progress — force-disable progress indicators (useful in CI or when redirecting stderr)

Phases tracked: "Discovering modules", "Running coverage tests", "Analyzing coverage profile", "Analyzing complexity", "Processing results". The "Running coverage tests" phase is replaced by "Analyzing coverage profile" when --coverage-profile is set.

# Force progress in a non-interactive CI step
go-crap scan --progress

# Suppress bars when stderr is a TTY but you want clean output
go-crap scan --no-progress
Output Formats
Format Description
table Human-readable terminal output with status symbols and coverage bars
json Structured output with $schema URL, suitable for CI pipelines
github GitHub Actions workflow annotations (::warning)
sarif SARIF 2.1.0 compliant JSON for static analysis tools
pr-comment Markdown table formatted for pull request comments
Coverage Unavailable Warning

When a Go module fails to build or run tests, go-crap still parses the coverage profile from any passing tests. The error is reported in all output formats; functions exercised by passing tests appear with their real coverage, while functions with no coverage data get a warning.

  • table — coverage column shows N/A ‼ with a footer listing unavailable modules
  • json — coverage is null and coverage_warning contains the error message
  • github — ::warning annotation with the module error
  • sarif — result with RuleID: "go-crap/coverage-unavailable"
  • pr-comment — separate "Coverage Unavailable" section
Example: SARIF output
go-crap scan --format sarif > crap.sarif
Example: PR comment output
go-crap scan --format pr-comment > pr-comment.md
Example: Mutation report validation
go-crap scan --mutation-report gremlins-report.json

When a function has lived mutants (mutations that survived because tests didn't catch them), go-crap marks the coverage as unreliable (⚠) and recalculates the CRAP score assuming 0% coverage. This catches functions that appear well-tested but have blind spots.

Example: Detailed mutation output
go-crap scan --mutation-report gremlins-report.json --format json --detailed

The --detailed flag includes full mutation failure details in the output: type, line, original_code, and replacement_code for each survived mutant. In json format, these appear as a mutation_details array per entry. In sarif and pr-comment formats, survived mutations with code snippets are appended to the warning messages.

Example: Baseline comparison
# Run a scan and save it as a baseline
go-crap scan --format json > baseline.json

# Compare against the baseline
go-crap scan --baseline baseline.json --format table

# Show all functions including unchanged ones
go-crap scan --baseline baseline.json --show-unchanged

# Enforce regression thresholds in CI
go-crap scan --baseline baseline.json --fail-regression --fail-regression-threshold 0.01

When a baseline is provided, every formatter shows deltas: the PR comment header displays combined/average CRAP with deltas, the table adds a Δ column, and JSON includes per-entry baseline_crap and delta fields.

What is CRAP?

CRAP = Cyclomatic Readability And Predictability. It measures how expensive a function is to test.

$CRAP(CC, coverage) = CC^2 × \left(1 - \frac{coverage}{100}\right)^3 + CC$

A function with high cyclomatic complexity and low coverage scores the worst. A simple, fully tested function scores the best.

CRAP Range Meaning
0 – 10 Well-tested, simple function
10 – 30 Moderate complexity, should be tested
30 – 50 High CRAP — refactoring or more tests needed
50+ Critical — likely hard to test, complex logic

How It Works

go-crap scan
  │
  ├── scan.Scan()           — unified pipeline, discovers modules, filters, and ranks
  │   ├── coverage.Scan()   — discover Go modules, run go test -cover
  │   ├── complexity.Analyze() — walk AST, compute cyclomatic complexity
  │   ├── merge.Merge()     — join by (filepath, funcname), propagate coverage warnings
  │   ├── score.Score()     — apply CRAP formula + missing policy
  │   ├── mutation.Annotate() — validate coverage with mutation testing (optional)
  │   └── report.Format()   — table / json / github / sarif / pr-comment
  │
  └── pkg/utils/            — regex helpers for --exclude patterns
  • internal/scan — unified pipeline orchestrating the full scan flow (coverage → complexity → merge → score → filter → output)
  • internal/complexity — AST walking to compute cyclomatic complexity (adapted from gocyclo, BSD-3-Clause)
  • internal/coverage — module discovery + go test -cover profiling (adapted from test-finder, MIT)
  • internal/merge — double-index join of coverage and complexity data, propagates coverage-unavailable warnings from errored modules
  • internal/score — CRAP formula + missing coverage policy + EntryList wrapper
  • internal/mutation — parses gremlins JSON mutation reports and annotates CRAP entries with coverage reliability
  • internal/report — output formatters (table, JSON, GitHub, SARIF, PR comment)
  • pkg/logger — Logger interface and configuration types
  • pkg/slogger — slog-backed Logger implementation
  • pkg/utils — regex helper functions for exclude patterns

CI Integration

# .github/workflows/crap.yml
- run: go-crap scan --fail-above --threshold 30 --format github
  • --fail-above exits with code 1 when any function exceeds the threshold
  • --fail-regression exits with code 1 when functions regressed vs baseline
  • --fail-regression-ignore-covered excludes fully covered functions from regression failures (still shows them with ~)
  • --format github emits ::warning annotations that render as PR comments
  • --format sarif outputs SARIF 2.1.0 for integration with code scanning tools
  • --format pr-comment generates a markdown table for pull request comments
  • --output -o writes results to a file instead of stdout
  • --mutation-report validates coverage reliability against mutation testing results
  • --detailed includes mutation failure details (code, line, type) in report output
  • --baseline loads a previous JSON report for delta analysis
  • --show-unchanged includes functions with no CRAP change in baseline output
  • Coverage-unavailable warnings are emitted for modules where go test fails
Badge

Add a status badge to your README.md to show the latest master analysis:

[![CRAP analysis](https://github.com/YOUR_ORG/YOUR_REPO/actions/workflows/crap.yml/badge.svg?branch=master)](https://github.com/YOUR_ORG/YOUR_REPO/actions/workflows/crap.yml)

Requires the workflow to trigger on push: branches: [master] (not branches-ignore).

Prior art and references

License

This project is licensed under the MIT License.

Full Documentation

For a complete guide covering all flags, examples, and the CRAP formula in detail, see the documentation site.

Documentation

The Go Gopher

There is no documentation for this package.

Directories

Path Synopsis
internal
pkg

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL