Reporters

SARIF reporter

A SARIF 2.1.0 log with one result per clone, its counterpart as a related location and a stable fingerprint, for GitHub code scanning and other SARIF consumers.

-r sarif writes the format that GitHub code scanning, Azure DevOps and SARIF viewers read, so a clone appears as an annotation on the lines of a pull request with a link to its other copy. Pick it when findings belong in the code scanning view; for GitLab merge requests the CodeClimate reporter is the one GitLab shows as quality issues.

Run it

Terminal
jscpd . -r sarif --output report
SARIF report saved to report/jscpd-report.sarif
.jscpd.json
{
  "reporters": ["console", "sarif"],
  "output": "report",
  "sarifErrorTokens": 150
}

The output

One result from the repository's fixtures/mcp-demo folder scanned with --ignore-identifiers --ignore-literals --max-gap-lines 1:

report/jscpd-report.sarif
{
  "ruleId": "jscpd/similar-code",
  "level": "warning",
  "message": {
    "text": "Duplicated code block (197 tokens), duplicated at [src/print/invoice.js:1](0)"
  },
  "locations": [
    {
      "physicalLocation": {
        "artifactLocation": { "uri": "src/invoice.js", "uriBaseId": "%SRCROOT%", "index": 0 },
        "region": { "startLine": 1, "startColumn": 1, "endLine": 12, "endColumn": 2 }
      }
    }
  ],
  "relatedLocations": [
    {
      "id": 0,
      "message": { "text": "Duplicated at src/print/invoice.js:1" },
      "physicalLocation": {
        "artifactLocation": { "uri": "src/print/invoice.js", "uriBaseId": "%SRCROOT%", "index": 1 },
        "region": { "startLine": 1, "startColumn": 1, "endLine": 13, "endColumn": 2 }
      }
    }
  ],
  "partialFingerprints": { "jscpdCloneHash/v1": "c3ca1ec9e494c277" },
  "properties": {
    "token_count": 197,
    "similarity": 0.947,
    "similarity_method": "gap",
    "clone_hash": "c3ca1ec9e494c277"
  }
}

The primary location is the first copy; the second copy is related location 0, and the message links to it as [...](0), which is how GitHub shows both sides. Paths are relative to the scanned directory, declared once under originalUriBaseIds as %SRCROOT%, and tool.driver.version is the jscpd version.

Rules

ruleId names the kind of clone. jscpd/duplicate-code is always declared in tool.driver.rules; the other rules appear there only when a result uses them.

RuleCloneNeedsSince
jscpd/duplicate-codeToken-for-token copies (Type-1)nothing
jscpd/renamed-codeCopies that match once identifiers, literals or annotations are normalized (Type-2)--ignore-identifiers, --ignore-literals or --ignore-annotations5.2.0+
jscpd/similar-codeExact matches merged across a gap of changed lines (Type-3)--max-gap-lines5.2.0+
jscpd/similar-functionJavaScript and TypeScript functions whose syntax trees overlap (Type-3)--similarity5.4.0+ (filed under jscpd/similar-code before)
jscpd/semantic-codeFunctions that do the same job written differently (Type-4)--semantic5.3.3+

The same ids are the check_name of the CodeClimate report and the diagnostic codes of --lsp, so one clone has one name in code scanning, in a merge request and in the editor.

Properties

PropertyMeaning
token_countMatched tokens
similarity, similarity_methodPresent for similar and semantic clones; the method is gap or ast
clone_hashThe content hash of the pair, the same value as partialFingerprints.jscpdCloneHash/v1; it does not depend on which copy comes first, so code scanning keeps tracking a clone when line numbers shift (5.0.15+)
blamesha, author and timestamp of the first copy, with --blame

Where it shows up

With the GitHub Action, sarif among the reporters is enough: the action uploads the file to code scanning itself (upload-sarif defaults to true). The job needs the security-events: write permission, and contents: read as well in a private repository:

.github/workflows/jscpd.yml
name: Duplication
on: [push, pull_request]

permissions:
  contents: read
  security-events: write

jobs:
  jscpd:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: kucherenko/jscpd@v5
        with:
          reporters: console,sarif

With the binary in any workflow, upload the file yourself; if: always() uploads it when --threshold fails the step:

.github/workflows/jscpd.yml
      - run: jscpd . -r console,sarif --output report
      - uses: github/codeql-action/upload-sarif@v3
        if: always()
        with:
          sarif_file: report/jscpd-report.sarif
          category: jscpd

Results appear under Security, Code scanning, and as annotations on pull request diffs.

With gates

Every result is a warning unless one of three things raises it to error:

Level error whenFlag
The clone is new against the baseline--baseline or --baseline-from-ref (5.1.0+)
The clone has at least N tokens--sarif-error-tokens N, config sarifErrorTokens (5.0.15+)
The duplication of the whole run is above the threshold; then every result is an error--threshold, the same strictly-greater comparison that fails the build

On the sample run, --sarif-error-tokens 150 raises only the 197-token clone, and --threshold 5 raises all three because the run has 17.7% duplication. --kind keeps only the kinds you name.

Options

FlagEffect on the file
-o, --outputThe directory of jscpd-report.sarif
--sarif-error-tokens NLevel error from N tokens up
-t, --threshold PEvery result at level error when the run is above P
--baseline, --baseline-from-refLevel error for new clones
-b, --blameAdds the blame property
-a, --absoluteAbsolute paths in reports