Guides

Dashboard and health score

55 views
One screen with a project's size, duplication, complexity and dead code from jscpd --dashboard, and the health badge alone from jscpd --health.

A clone report answers one question. When you open an unfamiliar codebase, or check where a project stands before a refactoring, you want several at once: how big it is, how much of it is copied, where the complexity sits, and what nothing runs. --dashboard answers all four in one run and prints them on one screen, under the project's health score. --health prints that score alone, for a README badge or a CI gate.

Available from jscpd 5.3.0. The dead-code section covers JavaScript, TypeScript and Python, including Vue, Svelte and Astro components, and Rust when you pass --rust-diagnostics (5.3.2+).

Run it

Terminal
jscpd . --dashboard
jscpd src --dashboard --summary-top 10
jscpd src --dashboard --ignore-identifiers

The second line prints ten rows per list instead of five, the third counts renamed copies too. On fixtures/dashboard-demo of the jscpd repository, a five-file TypeScript service with a README:

Health  B   74/100  █████████████████▊░░░░░░  93 lines of code (XS)
  duplication   74  ████████▉░░░  6.5% in typescript (no text)
  dead code     72  ████████▋░░░  14.0%
  complexity    76  █████████▏░░  0.0% in complex files

── Project ─────────────────────────────────────────────────
  7 files · 324 lines · 2.1K tokens · 3 formats
  largest: markdown 163, typescript 93, bash 68 lines
  Largest code files:
    LINES  TOKENS  SIZE  PATH
       35     182   757  src/rates.ts
       21     169   665  src/checks.ts
       16      97   423  src/index.ts
       11     109   409  src/labels.ts
       10     113   430  src/legacy/manifest.ts

── Duplication ─────────────────────────────────────────────
  1.85% duplicated lines · 1 clone (1 exact)
  By format:
    DUP%  LINES  CLONES  FORMAT
     6.5      6       1  typescript

── Complexity ──────────────────────────────────────────────
  25 total · 5.0 mean per file
  Most complex files:
    CX  LINES  SIZE  PATH
    11     35   757  src/rates.ts
     6     21   665  src/checks.ts
     3     11   409  src/labels.ts
     3     10   430  src/legacy/manifest.ts
     2     16   423  src/index.ts

── Dead code ───────────────────────────────────────────────
  13.98% unused lines · 2 findings in 5 files
  1 unused-file · 1 unused-export
  Largest findings:
    LINES  CATEGORY       WHERE
       10  unused-file    src/legacy/manifest.ts
        3  unused-export  src/checks.ts:19 checkBatch

time: 10.149ms

Reading the output

SectionWhat it reports
HealthOne 0-100 score with a grade, from duplication, dead code and complexity, and under it each sub-score with what it was measured from; see The health score
ProjectFiles, lines and tokens analyzed, the largest formats by lines, and the largest code files. Only code is ranked there, so the README, the longest file of this project, stays out of the list
DuplicationThe duplicated-lines percentage, the clones per kind (exact, renamed, similar) and a breakdown by format. Prose, data and markup formats are left out of the breakdown, since the health score does not count them either
ComplexityTotal and mean complexity per file, and the most complex files. Complexity is one path per function plus one per branch, counted from the token stream; prose and data files score 0
Dead codeFor the languages the run could read: the share of lines nothing runs, the findings per category and the largest findings

In this project src/checks.ts and src/labels.ts share one address block, one exact clone; src/rates.ts scores 11 with one function, three weight tiers, three case labels and two boolean operators; src/legacy/manifest.ts is reached by no import from src/index.ts, and checkBatch is exported but never imported. The two percentages of the duplication section differ on purpose: 1.85% counts every line the run read, 6.5% the TypeScript alone, and the health score reads the second.

The options of a normal run apply: --min-tokens, --ignore, --format, --ignore-identifiers, --max-gap-lines, --similarity and --kind shape the duplication section, and --entry, --dead-code-categories, --min-confidence and --rust-diagnostics shape the dead-code section. --summary-top N sets the rows per list (default 5).

The health badge alone

Terminal
jscpd . --health
Health  B   74/100  █████████████████▊░░░░░░  93 lines of code (XS)
  duplication   74  ████████▉░░░  6.5% in typescript (no text)
  dead code     72  ████████▋░░░  14.0%
  complexity    76  █████████▏░░  0.0% in complex files

Each sub-score is followed by what it was measured from: the share of code lines that are duplicated, that nothing runs, and that sit in complex files. (no text) says that the project has prose files and that they were left out. A dimension that cannot be measured is named instead of scored as perfect:

Health  B   75/100  ██████████████████▏░░░░░  30 lines of code (XS)
  duplication   76  █████████░░░  0.0% in rust (no data)
  complexity    75  █████████░░░  0.0% in complex files
  dead code    n/a  (no JavaScript, TypeScript, Python or compiler-checked Rust files)

Reporters

Reporter--health--dashboard
consoleThe badgeThe screen above
aiOne line: health 74 B (duplication 74, dead-code 72, complexity 76; 93 code lines)
jsonjscpd-health.json: score, grade, size, and for each dimension its value, adjusted value, lines, half-life, weight and scorejscpd-dashboard.json: every section of the screen
badgejscpd-health-badge.svgjscpd-health-badge.svg
markdown, htmljscpd-health.md, jscpd-health.html: the score and the dimension tablejscpd-dashboard.md, jscpd-dashboard.html: every section of the screen

The files go to --output. The Markdown report is ready for a PR comment:

Terminal
jscpd . --health --reporters markdown
# Project health

**Score:** B (74/100) — 93 lines of code (XS)

| Dimension | Score | Measured |
|---|---:|---|
| duplication | 74 | 6.5% in typescript (no text) |
| dead code | 72 | 14.0% |
| complexity | 76 | 0.0% in complex files |

Clone lists and dead-code findings still come from the regular reporters, in a clone run or a --dead-code run.

Metrics from other tools

--health-input FILE (config key healthInput) adds what jscpd cannot measure. A metric is either a ready 0-100 score, or a value with the halfLife that turns it into one; "direction": "higher" scores the distance to max (default 100). The demo ships health-metrics.json with 81% test coverage and a clean security scan:

health-metrics.json
{
  "metrics": [
    { "id": "coverage", "value": 81, "direction": "higher", "halfLife": 40 },
    { "id": "security", "score": 100 }
  ]
}
Terminal
jscpd . --health --health-input health-metrics.json
Health  B   78/100  ██████████████████▊░░░░░  93 lines of code (XS)
  duplication   74  ████████▉░░░  6.5% in typescript (no text)
  dead code     72  ████████▋░░░  14.0%
  complexity    76  █████████▏░░  0.0% in complex files
  coverage      72  ████████▋░░░  81
  security     100  ████████████

With halfLife: 40, 81% coverage is 19 short of 100 and scores 72. A CI job typically writes this file from its coverage and audit steps, then runs the command.

Tuning

The same object can live under health in .jscpd.json, together with the tuning of the built-in dimensions:

.jscpd.json
{
  "health": {
    "duplication": { "halfLife": 5, "weight": 2 },
    "deadCode": { "weight": 0 },
    "complexFile": 80,
    "metrics": [{ "id": "security", "score": 100 }]
  }
}

A weight of 0 leaves a dimension out. complexFile moves the complexity from which a file counts as complex (50 by default). An unknown key, or a metric that cannot be scored, is an error. Two scores are comparable only when they are built from the same dimensions with the same tuning, so keep the tuning in the config file where every run reads it.

Exit codes

A dashboard or health run is gated like a clone run: --threshold compares the duplication percentage, --exit-code is returned when clones were found, and --fail-on-empty fails a scan that analyzed nothing, so jscpd . --dashboard --threshold 3 works in CI. The baseline options are the exception: --baseline, --baseline-from-ref and --update-baseline need the clone report these runs do not write, so they warn and are ignored, and --fail-on-new-clones is refused:

Warning: --dashboard and --health ignore --baseline, --baseline-from-ref and --update-baseline
Error: --fail-on-new-clones needs a baseline, which --dashboard and --health do not build

A bad dead-code option, such as an unknown category, is refused as in a --dead-code run. Exit codes lists every case.

Cost

The dashboard runs a clone scan and a dead-code scan side by side. On a normal project that takes well under a second. On a very large tree, such as a 565 MB node_modules, it takes close to the two scans combined, because they share the CPU. --workers N is a budget for the whole run, the two scans take half of it each, and --workers 1 runs them one after the other.

When you only need one part

QuestionCommand
How healthy is the project, in one number?jscpd src --health
Which files are the most complex?jscpd src --complexity (no clone detection)
Where is the duplication?jscpd src --summary
Which clones are renamed copies?jscpd src --ignore-identifiers --kind renamed
What does nothing run?jscpd src --dead-code
  • The health score explains the formula, its constants and a worked example on this demo.
  • Dead code covers the findings, their confidence and entry points.
  • Use jscpd in CI shows where a badge or a Markdown report goes in a pipeline.
  • Reporters lists the reporters of a clone run.