ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

jscpd 复杂度统计完全指南:用 `--complexity` 与 `--summary-by` 读懂 5 种语言的圈复杂度计数原理

jscpd 复杂度统计完全指南:用 `--complexity` 与 `--summary-by` 读懂 5 种语言的圈复杂度计数原理 开发工具代码质量静态分析【免费下载链接】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点击查看免费下载jscpdCopy/paste detector不仅是支持 220 语言的重复代码检测器还内置了一套基于 token 流的圈复杂度cyclomatic complexity估算引擎。本文以仓库中的 summary-demo 示例目录 为主线逐行拆解 jscpd 如何用每个函数一条路径 每条分支一条路径的规则统计 C、Rust、Swift、Python、TypeScript 五个文件的复杂度并深入 Rust 源码验证其计数原理、与 lizard 的差异来源以及--complexity免检测快速模式的使用方法。读完本文你将能独立复现示例中的全部命令行输出并理解 jscpd 复杂度数值背后的判定逻辑。一、示例目录五个可手算的文件summary-demo 下放着五个短文件每种语言一个全部设计为可以用手算出圈复杂度用于验证 jscpd 的计数结果。命令在仓库根目录、使用默认阈值执行jscpd fixtures/summary-demo --summary --summary-by complexity --no-colors --no-tips原文档给出的每个文件考察点对照表如下文件考察的技术点路径数c/checkout.c无关键字可识别的函数、、\|\|、case13rust/status.rsmatch分支、match guard、static生命周期9swift/Profile.swiftString?、?.、??、guard、真正的三元表达式8python/report.py全是if/or/for的 docstring名为case和when的参数5typescript/form.ts可选属性city?:、?.、??、箭头函数4其中c/checkout.c与rust/status.rs对应源码分别为 c/checkout.c 与 rust/status.rs。注意这个 README 本身也会被扫描按 markdown 格式但由于正文是散文而非代码其if、or、for只是单词不是分支所以复杂度为 0——数据格式JSON、YAML、TOML、lock 文件同理得 0。二、按复杂度排名完整命令与输出解读在仓库根目录运行jscpd fixtures/summary-demo --summary --summary-by complexity --no-colors --no-tips # Found 0 clones. # # Summary (by complexity; 6 files, 6 folders analyzed) # Top files: # TOKENS LINES SIZE CX DUP% PATH # 176 32 710 13 0.0 c/checkout.c # 112 24 481 9 0.0 rust/status.rs # 88 21 446 8 0.0 swift/Profile.swift # 103 19 584 5 0.0 python/report.py # 96 19 441 4 0.0 typescript/form.ts # 1142 84 4.3K 0 0.0 README.md命令中每个参数的含义--summary开启代码库摘要输出默认关闭--summary-by complexity指定排序指标为复杂度CX列可用值还有tokens、lines、size对应源码中的SummaryMetric枚举见 rust/crates/cpd-core/src/summary.rs--no-colors关闭 ANSI 颜色--no-tips关闭结尾的使用提示。输出中CX列的值与上表的手算路径数完全一致而DUP%为 0.0 说明这五个文件彼此没有重复。摘要行的 6 files 包含了README.md本身——它按 markdown 扫描、复杂度为 0印证了散文不是分支的规则。从源码看摘要表的每个文件行都由 FileSummary 结构 承载tokenstoken 总数、lines最大 token 起始行、bytes文件字节数、duplicated_lines/duplicated_tokens按显示路径归并的重复量、complexity圈复杂度估算。排序逻辑在 compute_summary按主指标降序、同值按路径升序并用--summary-top截断为 Top-N文件夹行按直接父目录聚合每个文件只归入一个目录行不做祖先累加。三、--complexity不做克隆检测的快速模式如果只关心复杂度、不关心重复代码可以跳过克隆检测jscpd fixtures/summary-demo --complexity --summary-top 3 --no-colors --no-tips # Complexity (by complexity; 6 files, 6 folders analyzed) # Top files: # TOKENS LINES SIZE CX PATH # 176 32 710 13 c/checkout.c # 112 24 481 9 rust/status.rs # 88 21 446 8 swift/Profile.swift--complexity与--summary的区别在于它只做文件遍历与 token 化从不启动检测阶段因此在大代码库上明显更快且没有DUP%列。--summary-top 3只保留前三行-r ai输出紧凑形式-r json会把结果写入jscpd-complexity.json。对应的实现位于 rust/crates/cpd/src/complexity.rsscan()用RunConfig { similarity: 1.0, .. }强制跳过检测函数签名只服务克隆检测该模式永远不检测所以也不必为语法树付费随后调用compute_summary(sources, [], opts.summary_top, by, …)——克隆列表传空重复相关指标自然为 0。run()还会对与克隆相关的选项给出警告设置--threshold或--exit-code时警告没有重复率或克隆数可用来门控设置--baseline、--baseline-from-ref、--update-baseline、--fail-on-new-clones时警告基线族选项被忽略设置--history时警告没有克隆检测就没有重复趋势设置--kind时警告它过滤克隆类型而本模式不检测克隆。可用 reporter 仅限console/console-full、ai、json其他 reporter 会被警告忽略。四、逐文件计数原理详解核心规则一句话圈复杂度 每个函数 1 条路径 每条分支 1 条路径语言没有可靠函数标记时退化为全文件基线 1。下面按文件逐个验证。4.1c/checkout.c— 13C 语言给函数没有任何关键字。double line_total(…) {是靠其左括号前面紧跟一个名字来识别的以此与if (…) {、switch (…) {结尾的) {区分开。计数明细line_totalif、||、if、→ 4 条分支shipping_zone3 个case标签default不计为分支→ 3 条分支free_shipping、||、→ 3 条分支3 个函数 → 3 条路径。合计 3 4 3 3 13。源码佐证在 rust/crates/cpd-core/src/summary.rs 中c/cpp/java/clike等格式启用了braced_declarations: true由scan_complexity记录每个(前的头再用PARENTHESISED_STATEMENTSswitch、try、while、if等排除语句头从而把真正的函数{与分支{区分开。case属于共享决策 token 表而default不在表中。4.2rust/status.rs— 9Rust 的match对每个分支没有关键字只有。每个分支都计一次同时每个match反扣一次因为 N 个分支是 N 条路径、即 N-1 条分支与switch计case标签、不计default同理。计数明细status_for5 个分支 1 个 guardMethod::Post if authorized→ 6其中match反扣 1所以 1 4 1 61 个函数路径 4 净分支 1 guarddescribe3 个分支 → 3match反扣 1 后为 1 2 3。合计 6 3 9。此外static str里的static是孤立的单引号绝不能被误认作字符串起点。源码佐证rust格式的规则在 summary.rsextra: []每个计 1 分支、declarations: [fn]每个fn计 1 函数、arm_groups: [match]每个match在最后decisions.saturating_sub(groups)中反扣 1。static里的是字符串字面量的一部分不会被计入。4.3swift/Profile.swift— 8在 Swift 中?通常表示可选类型而非三元运算符String?与manager?.nickname中的?归属于前面的 token、不是分支而age 18 ? adult : minor把?放在开放位置上是真正的三元表达式计 1。??是分支guard也是分支。三个函数合计 2 3 3 8displayName??→ 1 条分支 1 函数 2ageLabelguard 三元?→ 2 条分支 1 函数 3managerName两个??→ 2 条分支 1 函数 3。源码佐证swift规则在 summary.rsextra: [guard]、question: QuestionMark::Optional、declarations: [func]。QuestionMark::Optional意味着裸?只在未附着前面 token 与它不相邻时才计为分支?.永不分支?:在可选语义下也不分支仅 Kotlin/Groovy 的 Elvis 语义才计。4.4python/report.py— 5summarize含for、if、elif→ 4 条分支 1 函数 5headline无分支 → 1。合计 5 1 - 1 5。关键难点在于看起来像分支但不是的两类情况docstring 正文…If a courier misses the slot, or the parcel is held…中的If、or、while、for、when只是注释文字用作名字的关键字headline(case, when)把case、when两个分支关键字当作参数名——文件自己列为参数、赋过值或从对象上读取的名字就归该文件所有无论另一种语言是否保留它。源码佐证docstring 的处理在 triple_quoted 函数tokenizer 按行处理会把三引号字符串内容当作普通单词流入因此需要识别/的开关来跳过字符串体python等格式由 has_triple_quoted_strings 判定。名字归属由 locally_bound 处理obj.case点后成员、case 3赋值且非/、f(case, when)参数/实参列表中关键字后紧跟逗号或右括号三种形态都会把该词标记为本文件自己的名字在scan_complexity中直接跳过防止误计分支。4.5typescript/form.ts— 4city?: string是可选属性customer.address?.city是可选访问都不算分支shippingLabel里的??是分支 → 21 函数 1 分支箭头函数hasContact里的||是分支 → 21 函数 1 分支。合计 2 2 4。源码佐证typescript规则在 summary.rsquestion: QuestionMark::Optional?:可选属性不计分支、declarations: [function, ]——注意在这里是箭头函数声明而非 Rust 的match分支计入函数数而非分支数。五、与 lizard 的对照两处分歧的根源summary-demo 的 README 提供了与 lizard 的对照验证方式pip install lizard lizard fixtures/summary-demo| 文件 | CX | lizard | 差异原因 | | ---- | -- | ------ | ------------------------------------------------- | |c/checkout.c| 13 | 13 | | |rust/status.rs| 9 | 5 | lizard 对match只计 1 次无论多少分支 | |swift/Profile.swift| 8 | 5 | lizard 不把??计为分支 | |python/report.py| 5 | 5 | | |typescript/form.ts| 4 | 4 | |结论lizard 与 jscpd 在除两行外的所有行上一致而仅有的两处差异都是 lizard 数少了路径——这正是 CHANGELOG 中记录的设计取舍lizard counts amatchonce regardless of arm count, and doesnt count??。换言之jscpd 的计数在语义上更贴合N 个分支就是 N 条路径的圈复杂度定义。六、从示例到实战通用判定规则速查综合以上五个文件与源码实现jscpd 的复杂度估算遵循以下规则共享决策 tokenASCII 大小写不敏感覆盖 SQL、PL/SQL、Fortran、COBOL、BASIC、Pascal 等大写关键字语言if、elif/elsif/elseif、unless、for/foreach、while/until、case/cond/when、catch/rescue/except、andalso/orelse、、||、and、or、?、??——完整列表见 is_decision_token双字符运算符合并通用 tokenizer 会把拆成两个joined_token 在扫描时把相邻的单个标点重新拼成、||、??、?.、?:、否则非 JS 语言的短路运算符永远不可达散文与数据格式零复杂度has_control_flow 与 is_markup 共同定义is_code——markdown、JSON、YAML、TOML、CSV、diff、gettext 等格式永远得 0HTML/CSS/模板同理if 在 HTML 属性里、and在媒体查询里都是单词语言专属规则language_rules 按格式配置extraRust 的、Swift 的guard、Go 的select、Erlang 的receive、declarationsfn/func/def/function/、question语义Ternary / Optional / OptionalWithElvis与braced_declarationsC 系。掌握这四条规则后你就可以在真实项目上运行jscpd --complexity --summary-top 20快速定位复杂度最高的文件或用--summary --summary-by complexity在常规克隆检测的同时输出复杂度排名作为代码健康度评估与重构优先级的量化依据。更完整的 JSON 输出可配合-r json与 summary-render 中的渲染逻辑集成进 CI 报告。赞分享开发工具代码质量静态分析【免费下载链接】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点击查看免费下载相关推荐StickySwitch最佳实践避免常见陷阱和错误配置的10个技巧StickySwitch最佳实践避免常见陷阱和错误配置的10个技巧 StickySwitch是Android开发中一款功能强大的开关控件库以其独特的粘性动画Foundry Forge Lint 规则解析cyclomatic-complexity圈复杂度检测Foundry Forge Lint 规则解析cyclomatic complexity圈复杂度检测 本文是 Foundry 仓库内置静态分析工具 for区块链开发工具PHP 代码复杂度计算实战sebastian/complexity 在 ShowDoc 仓库中的原理与使用指南PHP 代码复杂度计算实战sebastian/complexity 在 ShowDoc 仓库中的原理与使用指南 导读 sebastian/complexity文档知识库后端前端上一篇7周通关大厂面试Coding Interview University终极学习指南下一篇GIMP Resynthesizer完全指南5大智能图像处理技巧让新手快速成为高手创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表