☰
jscpd 的 Rust 报告层 cpd-reporter 全解析:克隆检测的 12+ 种输出格式与实现原理
2026/10/9 1:58:25 网站建设 项目流程
  • 开发工具
  • 代码质量
  • 静态分析

【免费下载链接】jscpd

Copy/paste detector for source code. 220+ languages, Rust engine, SARIF/HTML/badge reporters, GitHub Action, MCP server for AI agents.

项目地址:https://gitcode.com/gh_mirrors/js/jscpd
点击查看免费下载

本指南以仓库内 rust/crates/cpd-reporter/README.md 为主线,结合其源码实现,系统讲解 jscpd(Rust 引擎)的报告器体系:从Reporter抽象与工厂、CLI 编排,到 Console、JSON、XML、CSV、HTML、Markdown、SARIF、Xcode、Badge、AI、Silent、Threshold 等每一种格式的产物结构、适用场景与底层实现证据。读完你将能按 CI、编辑器、Agent 等不同消费方准确选择报告器,并理解这些报告是如何从「克隆结果」一步步渲染成最终文件的。

一、cpd-reporter 在 jscpd 架构中的定位

jscpd 的 Rust 引擎(v5+)按职责拆分为一组 crate,cpd-reporter是其中的输出格式化层:

Crate职责
cpd-finder文件遍历、编排、git blame——入口层
cpd-core检测算法(Rabin–Karp 滚动哈希)、数据模型
cpd-tokenizer语言分词(223 种格式)
cpd-reporter输出格式化(报告器)

从 cpd/README.md 的架构表可以看出,检测引擎把结果交付给报告层,报告层再负责把CpdClone克隆集合 + 统计信息渲染成人类或机器可读的产物。

该 crate 的 lib.rs 暴露了 20 余个模块:console、console_full、json_reporter、xml_reporter、csv_reporter、html、markdown_reporter、sarif、badge、ai、silent、threshold、xcode、codeclimate、openmetrics、edn、deadcode、dashboard、health_render、history_render、summary_render等,并通过Reportertrait、ReportContext和create_reporter工厂对外提供服务。

注意:README 明确指出cpd-reporter 不打算被直接使用,完整的 CLI 入口在jscpdcrate。程序化使用请依赖cpd-finder等引擎 crate(cpdcrate 的库目标并非公共 API)。

二、统一抽象:Reporter trait、ReporterOptions 与 ReportContext

所有报告器都实现同一个 Reporter trait:

pub trait Reporter: Send { fn report( &self, clones: &[CpdClone], ctx: &ReportContext, output_dir: &Path, ) -> Result<(), ReporterError>; fn name(&self) -> &str; }

2.1 ReporterOptions:所有报告器的共享参数

ReporterOptions 是传入每个报告器构造函数的选项集合:

字段类型含义
output_dirPathBuf文件型报告器的输出目录
thresholdOption<f64>重复率阈值(百分比),供 threshold/sarif/codeclimate 使用
blamebool是否附带 git blame 信息
no_colorsbool是否禁用 ANSI 颜色
blame_dataBlameMapblame 数据表(按解析后的路径索引)
absolutebool是否输出绝对路径
tool_versionString写入 SARIFtool.driver.version与 HTML footer 的版本号
sarif_error_tokensOption<u32>达到该 token 数的克隆在 SARIF 中升级为error级别

其中tool_version的默认值取自 crate 自身版本(env!("CARGO_PKG_VERSION")),源码注释特别提醒:二进制必须覆盖为自己 crate 的版本,否则cpd --version打印的版本与报告内版本不一致——这是集成方容易踩的坑。

2.2 ReportContext:统计与运行时上下文

ReportContext 取代早期版本的&Statistics形参(v0.8.0 破坏性变更),携带:

  • stats:克隆检测统计(Statistics);
  • duration:检测耗时,按<1000ms显示ms、否则显示s自适应格式化;
  • summary:可选的代码库摘要(--summary时存在);
  • history:可选的 git 历史重复率趋势(--history);
  • similar:--similarity找到的结构相似函数对。

2.3 create_reporter:名称到实现的工厂

create_reporter 是报告器注册表,把字符串名称映射到具体实现,支持 16 个名称与若干别名:

"console" => ConsoleReporter "console-full" | "consoleFull" | "full" => ConsoleFullReporter "json" | "sarif" | "xml" | "csv" | "html" | "markdown" | "badge" "ai" | "xcode" | "threshold" | "silent" | "edn" | "openmetrics" "codeclimate" | "gitlab" => CodeClimateReporter // gitlab 是 codeclimate 的别名

未知名称返回None,由调用方决定告警。仓库内置的单元测试(reporter.rs 测试模块)验证了:未知名称返回None、full/consoleFull别名解析、gitlab别名解析、trait 的 object-safety、空克隆集不 panic 等关键行为。

三、CLI 侧如何选择与编排报告器

3.1 命令行参数与配置文件

CLI 通过--reporters(短选项-r,逗号分隔)选择报告器,定义在 cli.rs:

jscpd --reporters console,json,html --output report .

配置文件中对应reporters键(.jscpd.json或package.json的jscpd字段):

{ "minTokens": 50, "minLines": 5, "reporters": ["console", "json"], "output": "report", "threshold": 5 }

当 CLI 未显式指定时,从配置读取;配置也没有时默认回退到["console"](见 options.rs)。

3.2 报告器的三类编排

从 main.rs 的ReporterPlan构造可以看出,报告器被划分为三类,按固定顺序执行:

  1. 控制台类(ai、console、console-full、silent、xcode)——直接打印到 stdout;
  2. 文件类——写入--output指定目录;
  3. threshold——总是最后执行,因为只有当其他报告器都跑完、统计完整后才做阈值判定。
fn is_console_reporter(name: &str) -> bool { matches!( normalize_reporter_name(name), "ai" | "console" | "console-full" | "silent" | "xcode" ) }

run_reporters(main.rs)逐个调用create_reporter并执行reporter.report():未知报告器打印Warning: unknown reporter后跳过;ThresholdExceeded错误被特殊捕获并标记threshold_exceeded,最终影响进程退出码。

四、终端友好输出:console、console-full、xcode

4.1 Console(默认报告器)

console.rs 输出 jscpd 经典的终端格式,每个克隆打印两个位置(fragment A 为主行、fragment B 缩进显示),并带行数、token 数:

Found 1 exact clones with 85 tokens in the following files: - src/a.js [10:1 - 24:10] (15 lines, 85 tokens) src/b.js [4:1 - 18:10]

实现要点:行数用clone.matched_lines()计算(嵌入块只统计代码本身,而非宿主文本跨过的行,对应 issue #1090);no_colors选项通过Style::new(opts.no_colors)全局关闭颜色。README 提到 Console 支持可选 blame 信息,即通过ReporterOptions.blame打开。

4.2 Console-full:带源码片段与 blame 的详细视图

console_full.rs 是 README 未单独列出的增强型控制台报告器(通过别名full/consoleFull触发)。它除了打印克隆位置,还会读取磁盘上的源文件、渲染两侧代码片段(默认最多显示 20 行),并基于BlameMap(按解析后的绝对路径索引)逐行标注作者:

%02 author line code Alice 10 function hello() {

blame 键使用「source_root + clean source_id」解析后的路径,避免跨扫描根目录时 basename 歧义。

4.3 Xcode:一行一条 warning

xcode.rs 面向 Xcode 的issue解析约定,每条克隆输出一行:

MyFile.swift:5:3: warning: Found 10 lines (5-15) duplicated on file OtherFile.swift (10-20)

末尾追加Found N clones.。这让克隆检测结果能直接进入 Xcode 的 Issue Navigator。

五、文件型结构化报告:JSON / XML / CSV / Markdown / HTML

这五个报告器都以write_report_file写入--output目录,文件命名与内容如下。

5.1 JSON:jscpd-report.json

json_reporter.rs 输出两级结构:statistics(检测统计,camelCase 字段如duplicatedLines、percentageTokens、newDuplicatedLines、newClones)+duplicates(克隆数组)。每条 duplicate 的结构:

{ "format": "javascript", "lines": 15, "fragment": "function hello() { ... }", "tokens": 85, "firstFile": { "name": "src/app.js", "start": 10, "end": 24, "startLoc": {"line": 10, "column": 0, "position": 100}, "endLoc": {"line": 24, "column": 3, "position": 500} }, "secondFile": { "...": "..." }, "isNew": false, "kind": "exact" }
  • blame开启时,每个文件对象附带commitSha/author(include_blame);
  • --similarity结构相似对额外带有similarity(保留 3 位小数)与method(gap/ast)字段,--summary/--history开启时报告根节点按需追加summary/history键,不改变既有消费方的 schema;
  • 该格式与--lsp语言服务器的jscpd/clones应答共用clone_to_dup生成逻辑,保证编辑器内诊断与 JSON 报告一致。

5.2 XML:jscpd-report.xml(PMD CPD 兼容)

xml_reporter.rs 输出与 PMD CPD / TypeScript 版 jscpd 兼容的 XML:

<?xml version="1.0" encoding="UTF-8"?> <pmd-cpd> <duplication lines="15"> <file path="src/a.js" line="10"> <codefragment><![CDATA[function hello() { ... ]]></codefragment> </file> <file path="src/b.js" line="4"> <codefragment><![CDATA[...]]></codefragment> </file> <codefragment><![CDATA[...]]></codefragment> </duplication> </pmd-cpd>

实现细节体现工程严谨性(均有测试覆盖):

  • 源码片段放入 CDATA,但]]>会被拆分成两个 CDATA 段(escape_cdata);
  • XML 1.0 禁止的字符(NUL、ANSI 转义、换页符等)统一替换为 U+FFFD(sanitize_xml_text,对应 issue #375);
  • 路径属性由 quick-xml 转义,避免&被双重转义成&amp;amp;;
  • 为保持 TS 兼容,刻意不输出tokens=与endline=属性(见测试断言)。

5.3 CSV:jscpd-report.csv

csv_reporter.rs 输出统计表(不含克隆明细),首行为表头,随后每个格式一行、末尾Total:汇总行:

Format,Files analyzed,Total lines,Total tokens,Clones found,Duplicated lines,Duplicated tokens javascript,5,100,500,2,20,100 Total:,5,100,500,2,20,100

5.4 Markdown:jscpd-report.md

markdown_reporter.rs 生成可直接嵌入 README/文档的表格,以# Copy/paste detection report为标题、>引用行放摘要(检测时间等),然后按格式排序渲染 Markdown 表格:

| Format | Files analyzed | Total lines | Total tokens | Clones found | Duplicated lines | Duplicated tokens | |--------|---------------|-------------|--------------|--------------|------------------|-------------------| | javascript | 5 | 100 | 500 | 2 | 20 (20.00%) | 100 (20.00%) | | **Total:** | 5 | 100 | 500 | 2 | 20 (20.00%) | 100 (20.00%) |

5.5 HTML:jscpd-report.html

html.rs 使用 askama 模板引擎渲染templates/report.html(模板文件),页面布局与 TypeScript 版 jscpd 对齐(内嵌 CSS 仿 Tailwind v2 配色)。内容包含:总数(文件/行/克隆/重复行/重复 token/百分比)、按格式排序的统计表、按格式分组的克隆明细(两侧文件、行列范围与代码片段)。空结果时显示 "No duplicates" 文案;版本号从ReporterOptions.tool_version写入页面 footer。

六、CI 与平台集成格式:SARIF、Badge、Code Climate、OpenMetrics、EDN

6.1 SARIF:jscpd-report.sarif(GitHub Code Scanning 友好)

sarif.rs 输出SARIF 2.1.0规范报告,为 CI/安全扫描平台(如 GitHub Code Scanning)设计,实现细节:

  • 规则集:声明 5 条规则(rules.rs 定义了共享的规则 ID),按需声明——默认只声明jscpd/duplicate-code,只有出现对应克隆才附加renamed-code、similar-code、similar-function、semantic-code规则;
  • 级别升级逻辑(level()):当「整个运行超过 duplication 阈值」或「该克隆相对 baseline 是新增(is_new)」或「token 数 ≥sarif_error_tokens截断值」时为error,否则warning。阈值比较采用严格大于,与 ThresholdReporter 的失败语义完全一致(测试专门断言了「等于阈值不升级」);
  • 指纹:partialFingerprints.jscpdCloneHash/v1携带 16 位十六进制内容哈希(clone_hash),且与片段顺序无关(交换 A/B 哈希不变),保证跨运行结果身份稳定;properties中附带token_count、similarity、similarity_method、nodes、blame(sha/author/timestamp);
  • 多扫描根:同一相对路径出现在多个扫描根时分配独立 artifact,uriBaseId使用%SRCROOT%/%SRCROOT1%变量并在originalUriBaseIds中给出真实 URI;
  • 消息链接:主消息内嵌text指向relatedLocationsid 0——这是为了让 GitHub code scanning 正确关联重复位置;
  • tool.driver.version由ReporterOptions.tool_version注入(测试用9.9.9-test验证不硬编码)。

6.2 Badge:jscpd-badge.svg与jscpd-lines-badge.svg

badge.rs 生成 shields 风格的 SVG 徽章(不依赖外部图片服务,纯本地生成,适合私有 CI):

  • jscpd-badge.svg:duplication+ 百分比,颜色按阈值分级——>20%红#e74c3c、>10%橙#f39c12、否则绿#27ae60(duplication_color);
  • jscpd-lines-badge.svg:dup lines+ 重复行数(固定蓝#3498db);
  • 模块还提供health_badge(),可为健康评分生成health | B 73徽章(A/B 绿、C 黄、D 橙、E 红、未评分灰)。

仓库根目录的 assets/jscpd-badge.svg 即该报告器的产物示例。

6.3 Code Climate / GitLab:gl-code-quality-report.json

codeclimate.rs 遵循 GitLab Code Quality report 格式(artifacts:reports:codequality消费)。与 SARIF 不同,它把重复代码暴露为代码质量问题而非安全漏洞。关键设计:

  • 每个克隆生成两条 issue,分别锚定在两个片段上,这样 MR 无论改动哪一侧都能被注释;
  • 每个 issue 携带确定性 fingerprint(内容哈希 + 路径 + 起始行,经 xxh3_64 计算),不依赖运行顺序或绝对路径,保证跨 pipeline 身份稳定;
  • severity 与 SARIF 级别升级对齐:新增克隆或整体超阈值 →major,否则minor。

6.4 OpenMetrics:jscpd-metrics.txt

openmetrics.rs 输出 OpenMetrics 文本格式(GitLabartifacts:reports:metrics消费,对应 issue #422)。每项统计是一个 gauge 家族:先无标签的总量样本,再按格式名排序逐个输出format="..."标签样本,标签值按 ABNF 转义反斜杠、双引号与换行。

6.5 EDN:jscpd-report.edn

edn.rs 输出 Clojure EDN 格式(--similarity的忠实伴侣)::candidates存放结构相似函数对(:score为两棵子树指纹的 Jaccard 指数,:left-nodes/:right-nodes为归一化树规模),:clones存放其他 pass 的克隆(exact/renamed/gap-merged/semantic),无结果时均为空向量。

七、行为型报告器:ai、silent、threshold

7.1 AI:面向 Agent 与 LLM 的机器可读输出

ai.rs 是 README 强调的「machine-readable」报告器,专为 AI Agent 解析设计。每行克隆被压缩为紧凑格式:

Clones: src/ foo/a.js:10-20 ~ bar/b.js:5-15 src/billing/invoice.py 3-13 ~ 3-14 (renamed) src/a.py:10-24 ~ src/b.py:4-18 [~0.85 gap] --- 3 clones · 12.3% duplication

路径压缩算法(compress_clone_line):同一文件只写path range ~ range;共享目录前缀只写一次(src/ foo/a.js ... ~ bar/b.js);完全不同的根则完整列出两侧。后缀标记区分克隆类型:(renamed)、[~0.85 gap](similarity_method可为gap/ast)、[~0.78 semantic]。--summary/--history开启时继续追加紧凑摘要与历史趋势。全部输出无 ANSI 颜色依赖,便于管道与工具消费。

7.2 Silent:最小化输出

silent.rs 按 README 语义「不产生任何报告输出」,实现上仅打印一行加粗的汇总行(summary_line:克隆数/重复率/格式数)。适合只想要退出码或日志极简的 CI 场景;配合--silent标志或仅选 silent 时,CLI 还会跳过耗时统计输出(见 main.rs 的silent判定)。

7.3 Threshold:用退出码守卫重复率

threshold.rs 本身不产出任何报告文件,只做一件事:当stats.total.percentage > threshold(严格大于,等于阈值不失败,有专门测试)时返回ReporterError::ThresholdExceeded { actual, threshold }:

ERROR: jscpd found too many duplicates (25.5%) over threshold (10.0%)

main.rs 捕获该错误并最终使进程以失败退出,因此jscpd --threshold 5 .成为 CI 中「重复率超过 5% 即构建失败」的标准做法。threshold 报告器总是最后执行,保证判定基于完整统计。

八、如何验证:集成测试与工程建议

8.1 测试体系

reporters_integration.rs 覆盖了全部报告器的集成行为:文件型报告器(json/xml/csv/markdown/sarif/openmetrics/html)断言产物文件存在且包含关键内容;stdout 型(console/console-full/xcode)断言运行成功;行为型(silent/threshold/badge/ai)断言特殊语义。各报告器模块内还有大量单元测试,例如 SARIF 的 20 余个测试覆盖了规则声明、级别升级、指纹稳定性、blame 注入等边界。

8.2 选型建议(基于源码事实)

  • 本地人工排查:默认console,需要逐行 blame 详情用console-full/full,Xcode 用户用xcode;
  • 数据管道/工具链:json(字段最全,含片段、位置、blame、kind)、xml(PMD 生态兼容)、csv(统计表格)、edn(Clojure 生态与结构相似对);
  • 文档沉淀:markdown(直接贴进 MR/文档)、html(可视化报告页)、badge(README 徽章,纯本地 SVG 无需外网);
  • CI 门槛:threshold(退出码守门)+sarif(GitHub Code Scanning)、codeclimate(GitLab MR 注释)、openmetrics(GitLab 指标);
  • AI Agent:ai(紧凑机器可读行)+silent(日志最小化)。

组合使用示例:

# CI:控制台看细节、SARIF 进 code scanning、超 5% 即失败 jscpd --reporters console,sarif,threshold --threshold 5 --output report . # 文档与徽章 jscpd --reporters markdown,badge --output report . # Agent 友好 jscpd --reporters ai .

九、小结

cpd-reporter用「一个Reportertrait + 一个create_reporter工厂」把克隆检测的结果消费端做成了高度可插拔的体系:12 个 README 列出的格式覆盖人类、文件、CI、Agent 四类消费方,加上console-full、codeclimate、openmetrics、edn等扩展,实际工厂内注册了 16 个名称(含别名)。无论是jscpd-report.sarif里逐规则的级别升级与跨运行指纹,还是jscpd-badge.svg里 20%/10% 的色阶,每一处细节都能在 cpd-reporter 源码 与 集成测试 中找到对应实现——这也是把「检测结果」转化为「工程动作」(合并请求注释、代码扫描告警、构建失败)的最后一公里。

该 crate 遵循 MIT 协议(见 Cargo.toml),不直接对外发布(publish = false),随jscpdCLI 一起分发;完整 CLI 用法可进一步参考 cpd/README.md。

  • 开发工具
  • 代码质量
  • 静态分析

【免费下载链接】jscpd

Copy/paste detector for source code. 220+ languages, Rust engine, SARIF/HTML/badge reporters, GitHub Action, MCP server for AI agents.

项目地址:https://gitcode.com/gh_mirrors/js/jscpd
点击查看免费下载

相关推荐

上一篇:COLMAP图像预处理完全指南:从新手到专家的分辨率与畸变校正
下一篇:WordPress函数命名规范:Coding-Standards中的ValidFunctionName Sniff全面指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询