npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

crap4ts

v1.0.1

Published

CRAP score analyzer for TypeScript — find complex, under-tested functions

Readme

crap4ts

npm version CI License: GPL-3.0-or-later Node >= 18

CRAP score analyzer for TypeScript — find functions that are too complex and too poorly tested.

Unlike hosted services, crap4ts runs locally and in CI with zero configuration, combining cyclomatic complexity and test coverage into a single actionable score.

Coverage alone can hide risky branching logic, and complexity alone cannot tell you whether that risk is tested. crap4ts combines both so you can find the functions most likely to hurt refactors, reviews, and releases.

$ npx crap4ts --top 5

 crap4ts v1.0.0 — CRAP Score Analysis

 Function                        CC   Cov%   CRAP   Risk
 ────────────────────────────────────────────────────────
 parseLegacyConfig               12    20%    85.73  High
 resolveImports                   8    45%    18.65  Moderate
 validateSchema                   6    70%     6.97  Acceptable
 transformOutput                  3    85%     3.03  Low
 formatResult                     2   100%     2.0   Low

 Summary: 42 functions | 2 above threshold (16) | worst: 85.73 | FAIL

Install

npm install --save-dev crap4ts

Or run directly with npx:

npx crap4ts

Requirements: Node.js >= 18, TypeScript >= 5.0 (peer dependency). Works in both ESM and CommonJS projects.

Quick Start

# 1. Run your tests with coverage
vitest run --coverage          # or: jest --coverage

# 2. Analyze
npx crap4ts

crap4ts reads an existing coverage report, so step 1 must produce coverage JSON before step 2 runs.

If you want a config file first, scaffold one up front:

npx crap4ts init

crap4ts auto-discovers coverage files (coverage/coverage-final.json, coverage/coverage-v8.json) and source directories (via tsconfig.json or src/).

What is CRAP?

CRAP(m) = CC(m)^2 * (1 - cov(m)/100)^3 + CC(m)

A function with high complexity and low coverage gets a high CRAP score. High complexity but high coverage gets a moderate score. Low complexity gets a low score regardless of coverage.

| CRAP Score | Risk Level | |:----------:|------------| | ≤ 5 | Low | | 5 < x ≤ 8 | Acceptable | | 8 < x ≤ 30 | Moderate | | > 30 | High |

GitHub Action

The action is designed for pull request quality gates: it reads an existing coverage report, comments on the PR, uploads a JSON artifact, and fails the workflow if functions exceed your threshold.

name: Quality

on:
  pull_request:
  push:
    branches: [main]

permissions:
  contents: read
  pull-requests: write

jobs:
  crap:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: 20

      - run: npm ci
      - run: npm run test:coverage

      - uses: breezy-bays-labs/crap4ts@v1
        with:
          threshold: 16
          changed-only: true
          post-comment: true
          upload-artifact: true

Notes:

  • The action does not generate coverage for you. Run your test command with coverage before invoking it.
  • post-comment requires pull-requests: write permissions.
  • changed-only uses the PR base SHA when available and falls back to full analysis outside PR workflows.
  • Self-hosted runners must provide git, jq, and gh on PATH.
  • Use working-directory for monorepos or nested packages.

| Input | Default | Description | |-------|---------|-------------| | threshold | 16 | CRAP score threshold | | changed-only | true | Only analyze functions changed in PR | | post-comment | true | Post/update PR comment with results | | upload-artifact | true | Upload JSON report as artifact | | coverage-path | auto | Path to coverage JSON | | src | auto | Source directories (space-separated) | | coverage-metric | delegated to CLI (line) | Coverage metric: line or branch | | version | latest | crap4ts version to install via npx | | working-directory | . | Directory to run analysis from |

src is split on spaces inside the composite action. Paths containing spaces are not currently supported there.

| Output | Description | |--------|-------------| | passed | Whether all functions passed threshold | | total | Total functions analyzed | | exceeding | Count of functions exceeding threshold | | exit-code | Raw exit code (0=pass, 1=threshold, 2=config, 3=parse) |

If you only want a machine-readable report in CI, set post-comment: false and consume crap4ts-report.json from the uploaded artifact.

CLI

npx crap4ts                           # zero-config analysis
npx crap4ts --top 10                  # 10 worst functions
npx crap4ts --strict                  # threshold 8 (strict mode)
npx crap4ts --lenient                 # threshold 30 (gradual adoption)
npx crap4ts --diff main              # only changed files since main
npx crap4ts -f json -t 12            # JSON output, threshold 12
npx crap4ts --sort complexity --top 5 # top 5 by complexity
npx crap4ts --summary                # one-line summary only
npx crap4ts --breakdown all -f json  # CC contributor breakdown (JSON)
npx crap4ts -q                       # quiet — exit code only

Scaffold a config file:

npx crap4ts init    # creates crap4ts.config.ts with sensible defaults

Run crap4ts --help for the full option reference.

Mutual exclusions: --strict, --lenient, and --threshold cannot be combined. --quiet and --verbose cannot be combined.

Exit Codes

| Code | Meaning | |------|---------| | 0 | All functions below threshold | | 1 | One or more functions above threshold | | 2 | Configuration/input error | | 3 | Parse error |

Output Formats

Use -f / --format to choose output format: table (default), json, or markdown.

The --breakdown flag (JSON only) adds per-function cyclomatic complexity contributor maps. Values: all, exceeding (default when flag is present), or off.

Configuration

// crap4ts.config.ts
import { defineConfig } from "crap4ts";

export default defineConfig({
  threshold: 12,
  coverageMetric: "line",
  exclude: ["**/*.test.*", "**/*.spec.*"],
  thresholds: {
    "src/domain/**": 8,    // strict for domain layer
    "src/legacy/**": 30,   // lenient for legacy code
  },
});

Config file discovery (first match wins): crap4ts.config.ts > .js > .mjs > package.json "crap4ts" field.

Config priority: defaults < config file < environment variables < CLI flags.

All Config Fields

| Field | Type | Description | |-------|------|-------------| | threshold | number | Default CRAP threshold | | coverageMetric | "line" \| "branch" | Coverage metric to use | | include | string[] | File include globs | | exclude | string[] | File exclude globs | | thresholds | Record<string, number> | Per-path threshold overrides | | format | "table" \| "json" \| "markdown" | Output format | | src | string \| string[] | Source directories | | breakdown | "off" \| "exceeding" \| "all" | CC contributor breakdown | | sort | "crap" \| "complexity" \| "coverage" \| "name" | Sort field | | top | number | Show N worst functions | | summary | boolean | Show summary line only |

Environment Variables

| Variable | Description | |----------|-------------| | CRAP4TS_THRESHOLD | CRAP threshold (number) | | CRAP4TS_FORMAT | Output format: table, json, markdown | | CRAP4TS_COVERAGE | Path to coverage JSON | | NO_COLOR | Disable colors (no-color.org convention) |

Coverage Metric

  • line (default) — Uses line coverage percentages.
  • branch — Uses branch coverage. Functions with no branches default to 100% coverage.

Programmatic API

Primary API

import { analyze, RiskLevel } from "crap4ts";

const result = await analyze({
  coverage: "./coverage/coverage-final.json",
  threshold: 12,
});

console.log(result.passed);              // true if all functions pass
console.log(result.summary.crapLoad);    // total excess over threshold
console.log(result.summary.maxCrap);     // worst CRAP score

for (const fn of result.functions) {
  if (fn.scored.crap.riskLevel === RiskLevel.High) {
    console.log(`${fn.scored.identity.qualifiedName}: ${fn.scored.crap.value}`);
  }
}

result.functions is the canonical scored result set used for result.summary and result.passed. If a function has complexity but no matching coverage entry, crap4ts includes it there as a worst-case 0% coverage verdict. The lower-level result.unmatched field is diagnostic mismatch detail and should not be added on top of the summary totals.

Also available: analyzeFile() for single-file analysis, defineConfig() for typed configuration, and constants like PRESETS and createThresholdConfig().

Sub-path Exports

For building custom tooling, crap4ts exposes composable primitives:

// Pure CRAP formula — no I/O, no dependencies
import { computeCrap, classifyRisk } from "crap4ts/formula";
const { value, riskLevel } = computeCrap(12, 45); // CC=12, coverage=45%

// Complexity extraction from source text
import { extractComplexity } from "crap4ts/complexity";
const functions = extractComplexity(sourceCode, "file.ts");

// Coverage parsing — sync (in-memory) or async (from file)
import { parseCoverage, parseCoverageFile } from "crap4ts/coverage";
const result = await parseCoverageFile("./coverage/coverage-final.json");

Upgrading From 0.x

  • Functions with complexity but no matching coverage entry are now scored at 0% coverage and included in result.functions, result.summary, and threshold decisions.
  • Treat result.functions as the canonical scored result set. result.unmatched remains diagnostic mismatch detail and should not be added on top of summary totals.
  • GitHub Action users should pin to uses: breezy-bays-labs/crap4ts@v1 on the stable major release line.

Coverage Format Support

| Format | Source | Status | |--------|--------|--------| | Istanbul JSON | jest, vitest (istanbul), nyc | Supported | | V8 JSON | vitest (v8), c8 | Supported | | LCOV | various | Planned |

FAQ

I get "0 functions analyzed" — crap4ts couldn't match coverage data to source files. Check that your coverage path is correct and your source files are under the discovered src/ directory. Use --verbose to see discovery details.

All functions show 100% coverage — You may be pointing at the wrong coverage file format. If using vitest with V8 provider, ensure you're using the V8 JSON output, not the Istanbul-format summary.

CRAP scores seem too high — The CRAP formula is sensitive to low coverage on complex functions. A function with CC=10 and 50% coverage scores 22.5. The same function at 80% coverage scores 10.8. Focus on improving coverage for your most complex functions first.

Contributing

See CONTRIBUTING.md for development workflow, local quality gates, and PR expectations.

Additional project docs:

License

GNU General Public License v3.0 or later (GPL-3.0-or-later)