SARIF reporter
-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
jscpd . -r sarif --output report
SARIF report saved to report/jscpd-report.sarif
{
"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:
{
"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.
| Rule | Clone | Needs | Since |
|---|---|---|---|
jscpd/duplicate-code | Token-for-token copies (Type-1) | nothing | |
jscpd/renamed-code | Copies that match once identifiers, literals or annotations are normalized (Type-2) | --ignore-identifiers, --ignore-literals or --ignore-annotations | 5.2.0+ |
jscpd/similar-code | Exact matches merged across a gap of changed lines (Type-3) | --max-gap-lines | 5.2.0+ |
jscpd/similar-function | JavaScript and TypeScript functions whose syntax trees overlap (Type-3) | --similarity | 5.4.0+ (filed under jscpd/similar-code before) |
jscpd/semantic-code | Functions that do the same job written differently (Type-4) | --semantic | 5.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
| Property | Meaning |
|---|---|
token_count | Matched tokens |
similarity, similarity_method | Present for similar and semantic clones; the method is gap or ast |
clone_hash | The 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+) |
blame | sha, 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:
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:
- 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 when | Flag |
|---|---|
| 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
| Flag | Effect on the file |
|---|---|
-o, --output | The directory of jscpd-report.sarif |
--sarif-error-tokens N | Level error from N tokens up |
-t, --threshold P | Every result at level error when the run is above P |
--baseline, --baseline-from-ref | Level error for new clones |
-b, --blame | Adds the blame property |
-a, --absolute | Absolute paths in reports |
Related
- Clone types for the kinds behind the rules and the flags that enable them.
- CI and the GitHub Action.
- CodeClimate reporter for GitLab.