- 开发工具
- 代码质量
- 静态分析
【免费下载链接】jscpd
Copy/paste detector for source code. 220+ languages, Rust engine, SARIF/HTML/badge reporters, GitHub Action, MCP server for AI agents.
本指南以仓库内 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_dir | PathBuf | 文件型报告器的输出目录 |
threshold | Option<f64> | 重复率阈值(百分比),供 threshold/sarif/codeclimate 使用 |
blame | bool | 是否附带 git blame 信息 |
no_colors | bool | 是否禁用 ANSI 颜色 |
blame_data | BlameMap | blame 数据表(按解析后的路径索引) |
absolute | bool | 是否输出绝对路径 |
tool_version | String | 写入 SARIFtool.driver.version与 HTML footer 的版本号 |
sarif_error_tokens | Option<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构造可以看出,报告器被划分为三类,按固定顺序执行:
- 控制台类(
ai、console、console-full、silent、xcode)——直接打印到 stdout; - 文件类——写入
--output指定目录; - 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;; - 为保持 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,1005.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.
相关推荐
深入解析 jscpd v5 的 cpd-finder:Rust 引擎中的文件遍历与克隆检测编排层
深入解析 jscpd v5 的 cpd finder:Rust 引擎中的文件遍历与克隆检测编排层 导读 cpd finder 是 jscpd v5(Rust 引
开发工具代码质量静态分析如何用 Go 后台任务队列 River 把耗时逻辑全部交给异步
如何用 Go 后台任务队列 River 把耗时逻辑全部交给异步 大促当晚,订单量是平日的十倍。如果你在下单接口里同步发邮件、同步生成对账单,接口平均耗时会从 8
任务调度后端jscpd 的 Markdown 嵌入式代码检测:围栏解析、子格式克隆与源码级原理
jscpd 的 Markdown 嵌入式代码检测:围栏解析、子格式克隆与源码级原理 本文围绕 jscpd 仓库中 markdown_embedded 测试样本(
开发工具代码质量静态分析
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考