
开发工具代码质量静态分析【免费下载链接】jscpdCopy/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点击查看免费下载导读cpd-finder 是 jscpd v5Rust 引擎中负责文件发现 → 分词分发 → 克隆检测 → git blame 溯源 → 统计聚合五大核心环节的编排 crate。本文将围绕 rust/crates/cpd-finder/README.md 展开结合其源码与集成测试说明每个--ignore、--cross-formats、--skip-local等 CLI 参数在底层对应的 WalkConfig/RunConfig 配置如何被真实执行帮助读者理解 jscpd 一次扫描背后的完整流水线并掌握以 Rust 编程方式驱动该 crate 的方法。一、crate 定位不是给终端用户直接用的编排层cpd-finder 是 jscpd v5 Rust 工作区中的一个内部 crateCargo.toml 中声明为publish false描述为 File walking and clone detection for cpdrust/crates/cpd-finder/Cargo.toml。它的职责原文明确列出了五条带忽略规则.gitignore、glob 排除的目录遍历按文件格式分发分词tokenization dispatch克隆检测与匹配重复来源的 git blame 溯源增强统计聚合其 README 特别强调This crate is not intended to be used directly; see thejscpdcrate for the full CLI.该 crate 不打算被直接使用完整 CLI 见jscpdcrate。也就是说终端用户通过jscpd/cpd命令间接获得它的能力而想在 Rust 中编程调用检测流水线时才直接依赖 cpd-finder。整个 jscpd v5 的架构见 rust/jscpd/README.md为jscpd (CLI binary) ├── cpd-core — Detection algorithm (Rabin-Karp rolling hash) ├── cpd-tokenizer — Language tokenization (224 formats) ├── cpd-finder — File walking, orchestration, git blame └── cpd-reporter — Output formatting (15 reporters)cpd-finder 依赖cpd-core检测模型与 Rabin-Karp 算法、cpd-tokenizer分词、cpd-similarity结构化相似度自身不直接面向报告输出——它把RunResultclones、statistics、sources、similar交给上层。模块结构rust/crates/cpd-finder/src/lib.rs 暴露五个模块恰好一一对应五条职责模块文件职责对应walkersrc/walker.rs目录遍历、格式识别、忽略规则orchestratesrc/orchestrate.rs编排全流水线、RunConfig、分词分发passsrc/pass.rs整文件级克隆检测 pass 抽象blamesrc/blame.rsgit blame 溯源statisticssrc/statistics.rs统计聚合二、目录遍历WalkConfig 与忽略规则2.1 WalkConfig一次扫描的文件发现配置walker模块定义WalkConfigsrc/walker.rs#L15-L26是文件发现阶段的核心数据结构pub struct WalkConfig { pub paths: VecPathBuf, // 扫描根路径可多个 pub extensions: VecString, // 格式过滤空 支持全部格式 pub ignore_patterns: VecString, // 文件级 ignore glob pub max_size: Optionu64, // 超过该字节数的文件被跳过 pub follow_symlinks: bool, // 是否跟随符号链接 pub no_gitignore: bool, // 是否忽略 .gitignore/.git/info/exclude pub formats_exts: HashMapString, VecString, // 扩展名→格式 自定义映射 pub formats_names: HashMapString, VecString,// 文件名→格式 自定义映射 pub pattern: OptionString, // 正向 glob 过滤如 src/**/*.ts }这些字段与 CLI 参数的对应关系paths↔jscpd path...、extensions↔--format、ignore_patterns↔--ignore、max_size↔--max-size、follow_symlinks↔--follow-symlinks、no_gitignore↔--no-gitignore、pattern↔--pattern。orchestrate中的walk_config()src/orchestrate.rs#L348-L360负责把RunConfig裁剪成WalkConfig。2.2 底层遍历实现walk()/walk_excluding()基于ignorecrate 的WalkBuilder并行遍历src/walker.rs#L103-L119并行遍历通过mpsc::channel收集结果比ArcMutexVec更省锁开销git_ignore(!no_gitignore)默认尊重.gitignore--no-gitignore关闭follow_links(follow_symlinks)跟随链接时会对每个文件做canonicalize并在最后dedup_by_real_path按真实路径去重避免同一物理文件被重复计数或把自己报告成自己的克隆issue #1059文件大小限制只读取元数据metadata不真正读文件内容零开销过滤超大文件。build_ignore_glob_setsrc/walker.rs#L86-L101把--ignore的每个 pattern 预编译成GlobSet并额外生成**/pattern变体使裸目录名如node_modules能在任意深度匹配无法解析的 pattern 被跳过并记 debug 日志。这正是 README 所述glob exclusions的精度保证——相比 v4 的子串包含匹配glob 语义严格更精确。2.3 格式识别的三级优先级detect_formatsrc/walker.rs#L382-L434按三级优先级确定文件格式文件名匹配formats_names如Dockerfile、Makefile这类无扩展名文件扩展名匹配formats_exts自定义映射优先内置格式库cpd_tokenizer::formats::get_format_by_extension无扩展名时读取首行若以#!开头则通过 shebang 判定如#!/usr/bin/env python3。--format过滤在每一级命中后生效格式无法识别则直接跳过该文件。2.4 面向 LSP 的 accepts 与 ignored_by_filessrc/walker.rs#L282-L309 的accepts()供语言服务器--lsp询问编辑器打开的某个文件是否会被扫描、以何格式文件可能尚未落盘。它综合了--pattern、格式、--ignore、大小限制与忽略文件判定。ignored_by_files()src/walker.rs#L318-L380)精确复刻了遍历器的忽略语义逐层检查root到path之间的每个目录就近的.ignore优先于.gitignoregit 仓库内还会查询.git/info/exclude且root自身永不被忽略。单元测试 walker.rs#L440-L464 验证了更近的规则胜出与no_gitignore行为。三、分词分发FilePreparer 与多格式文件3.1 内存映射与行数预过滤orchestrate中prepare_files_insrc/orchestrate.rs#L364-L417把遍历结果按 rayon 线程池并行处理每个 worker 用memmap2打开文件不把内容存入DiscoveredFile保证同时存活的内存映射数不超过线程数任意仓库规模都不会撞上vm.max_map_count先用memchr做 O(n) 行数过滤fits_lines再 UTF-8 解码交给FilePreparer::prepare。3.2 单格式路径对普通文件preparesrc/orchestrate.rs#L489-L677按RunConfig编译一次TokenizeOptionsmodemild/weak/strict、ignore_case、ignore_identifiers/ignore_literals/ignore_annotationsType-2 归一化issue #998、以及从--ignore-pattern编译出的code_ignore_regexes。其中code_ignore_ranges把正则匹配到源码文本上凡是与匹配区间重叠的 token 在检测时全部跳过——这与 v4 语义一致集成测试 tests/ignore_pattern_integration.rs#L147-L177 专门验证了这一点并证明非法正则会被静默跳过而非崩溃。3.3 多格式路径Markdown / Vue / Svelte / AstroMULTI_FORMAT_EXTS [md, markdown, mkd, vue, svelte, astro]src/orchestrate.rs#L444这类文件按块分词每个嵌入语言如 Markdown 代码栅栏里的 TypeScript、Vue 的script产生独立的PreparedSource其 id 形如guide-a.md:typescript。这支撑了 jscpd 的跨格式检测能力且host_file()src/orchestrate.rs#L715-L736负责把嵌入块归回宿主文件。测试 tests/embedded_statistics_integration.rs 验证了块与块之间的散文不会被误计为重复代码且统计只按各自语言的块计行。3.4 线程池与深层嵌套防护build_thread_poolsrc/orchestrate.rs#L164-L173为分词/检测建立 64 MiB 栈的 rayon 局部线程池OXC 解析 JS/TS 是递归下降Bun 的lots-of-for-loop.js这类极端深嵌套文件在默认 8 MiB 栈下会爆栈64 MiB 提供足够余量且局部池不会污染调用方持有的全局池。--workers对应num_threads。四、克隆检测与匹配检测池与流水线阶段4.1 run() 的五阶段流水线orchestrate::run()src/orchestrate.rs#L179-L294串起完整流水线遍历 分词prepare_files_in产出SourceFile展示用与PreparedSource哈希 token 流分组建池build_pools按--cross-formats分组——同组的格式共享一个检测池未分组的格式各自独立src/orchestrate.rs#L787-L815顺序确定性排序保证结果可复现Token 级克隆检测detect_prepared在池内用 Rabin-Karp 滚动哈希找克隆--skip-local/--skip-isolated通过PathFilters在目录/隔离组层面过滤配对近失配合并与相似度扩展--max-gap-lines开启merge_gapped_clones把同一文件对中间隔不超过 N 行的克隆合并为 similar 克隆issue #999--similarity阈值开启find_similar结构化函数相似搜索--min-nodes控制比较单元的最小归一化节点数收尾--semantic等ClonePass追加检测结果--kind过滤克隆类型discount_token_lines保证一行只计入一次最后statistics::compute汇总。4.2 cross-formats 与类型剥离--cross-formats的核心逻辑在build_poolsTS/JS 共享池后strip_types_formatssrc/orchestrate.rs#L837-L850在混合了 JS 家族与 TS 家族的组中剥离 TypeScript 专属语法。集成测试 tests/cross_formats_integration.rs 证明默认配置下 TS 与内容相同的 JS 孪生文件不产生克隆配置cross_formats: [[javascript,typescript]]后检出 1 个克隆且 TS 片段的起始行位于被剥离的interface块之后——位置仍指向原始源码。这正是 README 中clone detection and matching与跨格式能力的底层实现。4.3 ClonePass整文件级检测抽象src/pass.rs 定义trait ClonePass--semantic语义检测作为其中一个 pass 实现finder 对它一无所知只知道name/reads/read/find四个接口。pass 在 finder 持有文件内容期间读取文件token 阶段结束后运行一次并合并结果。RunError也只为 pass 失败设计——token 阶段永不失败。五、git blame 溯源enrich()README 的第四条职责在 src/blame.rs 实现。enrich(clones, repo_root)src/blame.rs#L66-L111)为每个克隆片段调用git blame --porcelain解析出逐行的 commit SHA、author、author-time写入fragment.blame。要点非 git 目录或git命令不存在时安全返回空BlameMap测试 blame.rs#L137-L143 验证不 panic多扫描根下相同的相对source_id会解析出不同文件因此以resolve_fragment_pathsource_root source_id为键避免不同根相互污染测试 blame.rs#L146-L159。这正是 CLI 中--blameconsole-full侧边栏作者对比、以及报告按行署名能力的数据来源。六、统计聚合statistics::compute()README 的第五条职责由 src/statistics.rs 承担。compute()src/statistics.rs#L10-L88做三件事按格式累计sources/lines/tokens用CpdClone::matched_lines()累加重复行与重复 tokenpercentage duplicated_lines / total_lines × 100、percentage_tokens同理--max-gap-lines合并出的 gap 行不计入重复测试 statistics.rs#L180-L193生成detection_date时间戳。StatRow的总量与按格式两个维度支撑了--threshold的 CI 判定超过阈值退出码 1与 console/json/html/sarif 等报告中的百分比展示。整个检测过程的统计输入是source_files的迭代器只读一遍适合长驻服务持有文件索引的场景。七、以 Rust 编程方式驱动 cpd-finder尽管 CLI 是主入口cpd-finder 同样暴露了干净的编程接口。以 rust/jscpd/README.md 的示例为准use cpd_finder::orchestrate::{RunConfig, run}; let config RunConfig { paths: vec![./src.into()], min_tokens: 50, ..Default::default() }; let result run(config).unwrap(); println!(Found {} clones, result.clones.len()); println!(Analyzed {} files, result.statistics.total.sources);RunConfigsrc/orchestrate.rs#L20-L69的默认值与 CLI 对齐min_tokens: 50、min_lines: 5、mode: Mode::Mild其余开关默认关闭。想精细控制可组合字段例如use cpd_finder::orchestrate::{RunConfig, run}; use cpd_tokenizer::tokenizer::Mode; let config RunConfig { paths: vec![./src.into(), ./lib.into()], min_tokens: 30, min_lines: 3, max_lines: Some(200), mode: Mode::Mild, formats: vec![javascript.into(), typescript.into()], ignore: vec![**/node_modules/**.into(), **/*.min.js.into()], code_ignore_patterns: vec![r//\s*cpd-disable.into()], max_size: Some(1024 * 1024), no_gitignore: false, follow_symlinks: false, skip_local: false, blame: true, workers: Some(8), ignore_case: false, cross_formats: vec![vec![javascript.into(), typescript.into()]], ..Default::default() }; let result run(config).unwrap(); for clone in result.clones { println!({}:{} → {}:{} ({} tokens), clone.fragment_a.source_id, clone.fragment_a.start.line, clone.fragment_b.source_id, clone.fragment_b.start.line, clone.token_count); }对需要长驻服务如 MCP server 的片段检查、--lsp的场景prepare_scan_in/FilePreparer允许把遍历分词与检测拆开重复使用已准备的 sourceRunResult.similar在all_similar开启时保留--similarity找到的全部配对供 edn 报告使用。八、质量保障集成测试矩阵cpd-finder 的tests/目录对每条职责都有回归测试测试验证点cross_formats_integration.rs跨格式池合并、TS 类型剥离、无关格式保持隔离ignore_pattern_integration.rs文件级 ignore glob、代码级 ignore-pattern 减少重复、非法正则静默跳过embedded_statistics_integration.rsMarkdown 嵌入块的行号与统计正确性issue #1090follow_symlinks_integration.rs符号链接去重issue #1059skip_local_integration.rs / skip_isolated_integration.rs同目录/隔离组克隆过滤markdown_prose_integration.rs / txt_comment_style_integration.rs多格式与注释风格边界此外 walker.rs 内嵌单元测试覆盖了*.jsglob 任意深度匹配、相对 pattern 在绝对根下的匹配issue #811、max_size0全排除、嵌套扫描根去重等边界。结语cpd-finder 作为 jscpd v5 的编排中枢把遍历、分词、检测、blame、统计五件事以可复现、并行化、内存安全的方式组织起来。理解它的WalkConfig/RunConfig与五个模块的对应关系就能精准预判jscpd每一个 CLI 参数的实际行为而它的模块化接口与完备测试矩阵也使其成为在 Rust 中集成克隆检测能力的可靠基础层。License 为 MIT见 rust/crates/cpd-finder/README.md#L16-L18。赞分享开发工具代码质量静态分析【免费下载链接】jscpdCopy/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 中嵌入 jscpd v5 检测引擎cpd-finder crate 实战指南在 Rust 中嵌入 jscpd v5 检测引擎cpd finder crate 实战指南 jscpd 是支持 220 语言的代码复制粘贴检测器其 v5开发工具代码质量静态分析jscpd v5 的 npm 分发全指南cpd 与 jscpd 包、Rust 引擎与 224 种语言重复代码检测jscpd v5 的 npm 分发全指南cpd 与 jscpd 包、Rust 引擎与 224 种语言重复代码检测 jscpd v5Rust 引擎通过 np开发工具代码质量静态分析jscpd 嵌套扫描路径的去重机制深入解析 jscpd . src 场景下的文件计数与自克隆问题jscpd 嵌套扫描路径的去重机制深入解析 jscpd . src 场景下的文件计数与自克隆问题 当一条扫描路径包含另一条扫描路径时例如命令行传入 jscp开发工具代码质量静态分析上一篇7-Zip免费压缩软件终极指南如何用开源工具实现文件管理革命下一篇5分钟上手Windmill零代码构建企业级内部工具的超能力创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考