Skip to content

scan

The scan command is the main entry point. It analyzes Go modules, computes CRAP scores, and outputs the results.

Usage

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

Arguments:

Argument Description Default
pattern Go package pattern(s) to scan. Accepts any number of patterns. ./... (entire module)

Patterns use the same syntax as go list / go build:

Pattern Meaning
. Current package
./... Current package and all sub-packages
./internal/score Single package
./internal/... All packages under internal
./internal/foo ./internal/bar Multiple specific packages

Examples:

# Scan entire current module (default)
go-crap scan

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

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

# Scan by import path
go-crap scan github.com/padiazg/go-crap/internal/...

Two-layer selection

go-crap uses two independent mechanisms to narrow what gets analyzed. They operate at different granularities:

  1. Patterns — select which Go packages are scanned. Operates at the package/directory level via go list syntax. Choosing whole packages is efficient because coverage tests are only run on matching packages.
  2. --exclude — filters files and functions within matched packages. Operates at the file-path and function-name level via regex. Cannot exclude whole packages from being tested, only from the final report.

They are designed to be used together:

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

# Scan only internal/foo, and within that package also exclude testdata files
go-crap scan ./internal/foo --exclude 'testdata/.*\.go'

# Narrow to specific packages first, then exclude a file pattern within them
go-crap scan ./internal/scan ./internal/coverage --exclude 'mock_'

Using patterns alone is the right choice when you know which packages to scan. Using --exclude is necessary when you need to skip individual files that live inside matched packages — something go list cannot do, since Go has no negative package patterns.

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
--format -f Output format: table, json, github, sarif, or pr-comment table
--top Show only the N worst offenders (0 = all). CoverageUntrusted entries always survive, even when N is small 0
--min Hide entries below this score. CoverageUntrusted entries are never hidden, regardless of their score 0
--missing Policy for functions without coverage: pessimistic, optimistic, or skip pessimistic
--coverage-profile Use an existing coverage profile (as produced by go test -coverprofile) instead of running go test "" (disabled)
--exclude Exclude files and functions matching this regex pattern (repeatable). _test.go files are excluded by default. e.g. pb/.*\.go to exclude protobuf files 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 "" (disabled)
--detailed Include mutation failure details (original/replacement code, line, type) in report output false
--timeout Timeout for the full scan (e.g. 30s, 5m, 1h30m) 10m0s
--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
--fail-regression-ignore-covered Exclude fully covered functions from regression failures (still shows them with ~) false
--show-unchanged In baseline mode, also show functions whose CRAP score did not change (requires --baseline) false

CoverageUntrusted has meaning only if --mutation-report was used.

Using an existing coverage profile

By default scan runs go test -coverprofile=<tmp> ./... in each discovered module to produce coverage data. The --coverage-profile flag supplies a profile that already exists (as produced by go test -coverprofile) and skips running go test entirely:

go test -coverprofile=coverage.out ./...
go-crap scan --coverage-profile coverage.out

This is useful when:

  • Coverage was already generated by a prior step or CI job — no need to re-run the suite.
  • The environment cannot build or run the tests, but a profile is available.

Notes:

  • If the profile path does not exist, scan fails immediately rather than producing an empty report.
  • The same profile is applied to every discovered module; entries whose paths do not belong to a module are skipped.
  • Duplicate block entries from merged coverage profiles (common with ./... test runs across multiple packages) are automatically deduplicated by (startLine, endLine) position using OR semantics — a block is covered if any test binary covered it.
  • When the scan path is a package subdirectory that has no go.mod of its own, the profile is resolved against the enclosing module (the nearest parent containing a go.mod). This lets you point scan at a single package:
go-crap scan --coverage-profile coverage.out ./internal/scan

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 ‼, footer lists unavailable modules with error messages
  • jsoncoverage is null, coverage_warning contains the error
  • github::warning annotation with module error
  • sarif — result with RuleID: "go-crap/coverage-unavailable"
  • pr-comment — "Coverage Unavailable" section

This is distinct from the --missing policy, which handles functions that have no coverage data because they were not exercised by tests. Coverage unavailable means the entire module's test run failed.

If the test run was killed because it exceeded --timeout, the error message says so explicitly (go test: timed out (increase --timeout to allow more time)) instead of the generic signal: killed.

Progress Indicators

During long scans, go-crap can show real-time progress bars on stderr:

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

Phases tracked: Discovering modules, Running coverage tests, Analyzing complexity, Processing results.

# Force progress in CI
go-crap scan --progress

# Suppress progress (e.g. in scripts that capture stderr)
go-crap scan --no-progress --format json

Examples

Scan all packages

go-crap scan

Scan a specific package

go-crap scan ./internal/scan

Scan multiple packages

go-crap scan ./internal/scan ./internal/coverage

Scan a project outside the current module

go-crap scan ~/go/src/github.com/padiazg/go-crap/...

Show only the top 20 worst offenders

go-crap scan --top 20

CI integration - fail on high CRAP scores

go-crap scan --fail-above --threshold 30 --format github

Filter by minimum score

go-crap scan --min 10

Exclude generated or test files

go-crap scan --exclude 'testdata/.*\.go'

Exclude protobuf and mock files at any depth

go-crap scan --exclude '\.pb\.go$' --exclude 'mock_'

Machine-readable JSON output

go-crap scan --format json > report.json

SARIF output

go-crap scan --format sarif > report.sarif

Outputs SARIF 2.1.0 compliant JSON for integration with code scanning tools, IDEs, and CI platforms that support SARIF.

Pull request comment output

go-crap scan --format pr-comment > pr-comment.md

Generates a markdown table with status symbols, CRAP scores, complexity, coverage, and file locations — formatted for pasting into pull request comments.

Write to file

go-crap scan --output report.json
go-crap scan -o report.json

Uses the --output / -o flag to write results to a file instead of stdout. Works with any format.

Verbose / debug logging

go-crap scan --verbose

Enables debug-level logging to help diagnose issues with module discovery, coverage parsing, or path matching.

Mutation report validation

go-crap scan --mutation-report gremlins-report.json

Validates coverage reliability by comparing mutation testing results against go-crap's coverage data. 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.

Unreliable coverage is indicated by:

  • A ⚠ warning next to the coverage percentage in table and pr-comment output
  • An additional coverage-untrusted SARIF result in sarif format
  • A mutation score in json output (mutation_score field)
  • A ::warning annotation in github format, emitted even when the CRAP score is below threshold
  • An "Unreliable Coverage" section in pr-comment output listing all affected functions (always rendered when mutation report is provided)

This is useful when you use gremlins or similar mutation testing tools to catch functions that appear well-tested but have blind spots.

Detailed mutation output

go-crap scan --mutation-report gremlins-report.json --format json --detailed

The --detailed flag includes full mutation failure details in the report output:

  • JSON: mutation_details array per entry with type, mutator_name, file, line, status, original_text, replacement_text
  • SARIF: survived mutations appended to warning messages with type, line, and code diff (e.g. "a < b" → "a >= b")
  • PR Comment: Survived Mutants column in the Unreliable Coverage section, with code snippets inline
  • Table: no change — still shows ⚠ for untrusted coverage

This is useful for debugging which specific mutants survived and what code transformations they represented.

Baseline comparison

go-crap scan --baseline baseline.json

Compare the current scan against a previous JSON report. Every formatter shows deltas:

  • pr-comment: header shows Combined/Average CRAP with deltas vs baseline, plus "New Functions" and "Regressions" sections
  • table: Δ column with per-function delta (e.g. +15 ↑), delta footer lines
  • json: per-entry baseline_crap and delta fields, summary object with baseline/compare stats
  • github: summary ::notice:: annotation when baseline provided

Regression enforcement

go-crap scan --baseline baseline.json --fail-regression

Exits with code 1 when any function's CRAP score has increased (regressed) compared to the baseline. Use --fail-regression-threshold to adjust the minimum delta that counts as a regression.

Ignoring fully covered regressions

go-crap scan --baseline baseline.json --fail-regression --fail-regression-ignore-covered

When --fail-regression-ignore-covered is set, fully covered functions (coverage ≥ 99.95%) that have regressed are excluded from triggering the exit code 1 failure. They are still reported in the PR comment output with a ~ symbol to indicate they were ignored.

This is useful in CI pipelines where you want to enforce regression detection on partially covered functions while acknowledging that fully tested code shouldn't count as a failure.

Combined threshold + regression

go-crap scan --fail-above --threshold 30 --baseline baseline.json --fail-regression

Fails if any function exceeds the threshold OR has regressed compared to baseline.

Creating a baseline

go-crap scan --format json > baseline.json
go-crap scan --baseline baseline.json

Generate a JSON report to use as a baseline. Subsequent scans can compare against it to track quality over time.

Docker

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

Mount the project directory at /code — the container analyses whatever directory you mount. Pass any flags directly:

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

To scan a specific package inside the mounted directory:

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

Images are available at docker.io/padiazg/go-crap and ghcr.io/padiazg/go-crap. Tags map to releases. Multi-arch images (linux/amd64, linux/arm64).