废话4t
一个代码质量的门,你可以实际运送. 并不要求你修复遗产——只是阻止你把事情弄得更糟.
任何代码质量工具最难卖的是 "我们已经有14,000个发现 从我们尝试的最后一个灯, 你真的要求我 修复所有之前,我可以合并?"废话4ts说: 没有。 。 。 .
它计算了C.R.A.P的分数——麦凯比复杂度按测试覆盖度加权计算——对于您TypeScript / JavaScript 代码库中的每一个函数,然后根据基线从 main. . . . . 遗产热点在报道中保持可见,但绝不阻止合并.
CRAP(m) = comp(m)² × (1 − cov(m)/100)³ + comp(m)
最初的度量衡由Alberto Savoia & Bob Evans于2007年提出(请原谅我的法语,但这个代码是C. R.. crap4ts将其带入了TypeScript生态系统,并拥有一等支持的vitest, jest, bun test, 以及任何LCOV排放工具.
现场实例
打开公关,反对这回波, 每一个都行使一种情形 让机器人评论。 点击其中任何一个来查看实际粘贴的注释外加代码扫描注释的bot帖.
Diff 假设
| 公关 | 显示什么 |
|---|---|
| # 1 Demo - 清除现有函数的回归 | processOrder 倒退, --fail-regression 1号出口 |
| #2 Demo - 添加了新的高CRAP功能 | 新设 validateInvoice 标记为 新建遗产不要挡道 |
| #3 Demo - 改进CRAP的元素 | processOrder 分裂成帮助者,大负 QQ,QQ 判决 |
| #4 Demo - 纯移动/重命名检测 | git mv 报告为: 移动,而非新+删除 |
| # 7 Demo - SARIF 上传到代码扫描 | 粘贴注释 财务报告和审定财务报表 pR diff 中的内含错误注释 |
莫诺雷波布局
每一个公关都增加了一个自成一体的固定装置 examples/monorepos/ 在固定图中显示运行命令+预期每个包输出.
| 公关 | 工作空间配置 | 固定线 |
|---|---|---|
| #9 Demo - Monorepo (pnpm-workspace.yaml) (英语) | pnpm-workspace.yaml | examples/monorepos/pnpm/ |
| #10 Demo - Monorepo (package.json#workspaces) (英语) | package.json#workspaces (bun/纱线/npm) | examples/monorepos/npm/ |
| #11 Demo - 莫诺雷波 (图尔博) | turbo.json + 键 pnpm-workspace.yaml | examples/monorepos/turbo/ |
v0.6 特性
| 公关 | 显示什么 | 固定线 |
|---|---|---|
| #14 Demo - v. 6. 特性 | 认知复杂性、内源禁用评论、 超度、热点 | examples/v06/ |
30秒设置
cd my-project
bunx @danibram/crap4ts init --workflow
git add crap.config.json .github/workflows/crap.yml
git commit -m "ci: gate on CRAP regressions" && git push
crap4ts init 检测您的测试跑道( test / jest / bun test), 写 crap.config.json 带有合理默认值,并降低基线感知的 GitHub 工作流程:
- 从
main每个公关 - 在每个 PR 下与 diff 一起 贴出粘贴的 bot 注释
- 上传 SARIF 到代码扫描( 内置说明 + 安全标签)
- 当任何函数退后时退出非零
就是这样。 没有犹太考古学 没有"我该扫描哪条路径" 没有从这个README复制.
手动快速启动
# Without coverage data — pure complexity ranking
crap4ts src/
# With vitest v8 coverage
crap4ts src/ --coverage coverage/coverage-final.json
# Fail CI when any function exceeds CRAP 50
crap4ts src/ --coverage coverage/coverage-final.json --fail-on 50
# Markdown for PR comments
crap4ts src/ --reporter markdown > crap-report.md
# GitHub Actions inline annotations
crap4ts src/ --reporter github
# Compare a PR against a baseline from main and fail on regressions
crap4ts src/ --baseline crap-main.json --fail-regression
# Opinionated PR-bot comment (sticky marker for in-place updates)
crap4ts src/ --baseline crap-main.json --reporter pr-comment > comment.md
# Monorepo overview, grouped by package
crap4ts --workspace --report-by package
# Hotspots: rank by CRAP × git churn (risky AND frequently-changed)
crap4ts --hotspots --since 90d
# Cognitive complexity instead of cyclomatic (nesting-weighted)
crap4ts --complexity cognitive
# Merge per-package coverage before scanning a monorepo
crap4ts merge-coverage 'packages/*/coverage/coverage-final.json' -o merged.json
产出
Scanned 261 files. 910 functions analysed.
Coverage: v8 (coverage/coverage-final.json)
Above threshold (CRAP > 30): 133 (14.6%)
CRAP COMP COVERAGE LOCATION FUNCTION
─ ───── ──── ──────────────── ────────────────────────────────── ───────────────────────
▲ 2070.0 45 ░░░░░░░░░░ 0% src/billing/excel-validator.ts:108 validateBillingCostItem
▲ 1406.0 37 ░░░░░░░░░░ 0% src/billing/periods-feature.ts:84 getUnifiedPeriodsRows
✓ 18.0 18 ██████████ 100% src/config.ts:56 mergeConfig
...
状态图标 : ✗ 超过 --fail-on, (中文(简体) ), ▲ 超过 --threshold, (中文(简体) ), ✓ 干净.
记者
| 记者 | 用于 |
|---|---|
table | 默认 — 终端中的对齐列 |
json | 机器可读输出(CI脚本,仪表板) |
markdown | 公关机构 / GitHub 问题(GFM表) |
github | ::warning:: 编号 : ::error:: pR的说明 |
pr-comment | 有意见的PR-bot评论有粘性标记 |
sarif | SARIF 2.1.0 → GitHub 代码扫描 / VS代码 |
eslint | ESLint JSON – 评论狗和ESLint 认知工具 |
热点(v.6
一个无人触及的功能上的高CRAP分数是低优先级的——你不会因为没有编辑而打破. 实际被咬的功能是 既危险又不断改变那是热点四角星.
crap4ts --hotspots --since 90d
Scanned 25 files. 175 functions analysed.
Hotspots: CRAP × commits since 90 days ago (HOT column, sorted).
CRAP HOT CHURN COMP COVERAGE LOCATION FUNCTION
─ ────── ──── ───── ──── ───────────── ──────────────────── ───────────────
▲ 756.0 6804 9 27 ░░░░░░░░░░ n/a src/cli.ts:387 buildCliConfig
▲ 1560.0 6240 4 39 ░░░░░░░░░░ n/a src/config.ts:137 mergeConfig
▲ 650.0 5850 9 25 ░░░░░░░░░░ n/a src/cli.ts:141 run
HOT = CRAP × CHURN,其中churn是接触窗口中文件的承诺数量。 说明如何 buildCliConfig (9项承诺) mergeConfig 尽管生的CRAP较低——编辑次数超过两倍,因此是比较有风险的地方. --since 接受 90d, (中文(简体) ), 6w, (中文(简体) ), 3m, (中文(简体) ), 1y,或任何 git 日期表达式(默认) 90d。在git repo之外,churn会降解为0(没有崩溃).
认知复杂性(v0.6)
经典的 CRAP 度量法使用 cyclomatic 复杂度,它计算独立路径,但处理平面 switch 和一堆深沉的 if编号. 认知复杂性 (公制Biome和SonarJS移动到) 重量并读取更接近“这对人类来说有多难”:
crap4ts --complexity cognitive
- 巢穴受到处罚:a
if在两个循环中,成本超过顶级. else编号 :else if平地(+1,无筑巢处罚).- 布尔运行计数一次( O)
a && b && c= +1; 交替运算符增加更多(a && b || c= +2) (中文(简体) ). switch是+1 总计,不是一次一次.
这是一个"crap4ts"扩展——Canonic CRAP在"环相上下"的定义——但认知轨迹对深层嵌入式代码的感知风险更好. Recursion的+1还没有模型化. 所选的衡量标准记录为: complexityMetric 在JSON的信封.
压制职能
三级,外科手术越来越多:
| 机制 | 范围 | 何处 |
|---|---|---|
--ignore <glob> | 文件完全没有解析 | CLI / 配置 |
--allow <glob> | 文件解析后, 函数隐藏在报表中 | CLI / 配置 |
/* crap4ts-disable-next-function */ | 一个函数 | 来源 |
/* crap4ts-disable-file */ | 全文件 | 来源( 靠近上方) |
/* crap4ts-disable-next-function */
export function generatedMonster(/* ... */) { /* gnarly but generated */ }
内源评论是外科选项——他们住在他们豁免的代码旁边,因此豁免在审查中可见并随文件出行.
配置
crap4ts 读取 crap.config.json 从当前工作目录上行到最近的 git root(或文件系统 root) 。 跌回一个 crap 节 package.json (仅cwd). CLI 旗帜总是覆盖配置文件 .
{
"include": ["src/**/*.ts"],
"ignore": ["src/legacy/**", "**/*.gen.ts"],
"allow": ["generated/**", "render*"],
"threshold": 30,
"failOn": 100,
"missing": "pessimistic",
"min": 10,
"reporter": "table",
"top": 50,
"summary": false,
"coverage": "./coverage/coverage-final.json",
"coverageFormat": "auto",
"tsconfig": "./tsconfig.json",
"baseline": "./crap-main.json",
"failRegression": true,
"epsilon": 0.01,
"complexity": "cyclomatic",
"overrides": [
{ "paths": "legacy/**", "threshold": 200, "failOn": 500 },
{ "paths": ["src/generated/**", "**/*.gen.ts"], "failOn": null }
]
}
Per-path 覆盖
overrides 让您放松(或收紧)特定路径的大门,而不提高全球条,这是在已经拥有高CRAP角的代码库上采用crap4ts的关键. 每个条目都通过光标和覆盖来匹配 threshold 和(或) failOn 用于匹配的文件。 决议是 最后一场比赛 (压力) overrides 语义学:后入句比起更早入句. "failOn": null 明确清除这些路径的大门(区别于省略它,它继承了全球)。 因此,在上述例子中: legacy/** 可能会在失败前到达 CMRAP 500 , 生成的文件永远不会出故障 门, 其它所有的东西都会在环球上保持线条 failOn: 100。 。 。 .
CLI 旗帜
过滤
| 旗帜 | 默认 | 说明 |
|---|---|---|
-i, --ignore <glob> | 无 | 在行走时跳过文件( 可重复 ,), 未解析) (中文(简体) ). |
--allow <glob> | 无 | 解析文件但隐藏匹配函数 。 如果包含路径模糊 / 或者说 **; 否则匹配函数名( e)* 没有交叉 :: 或者说 .) (中文(简体) ). |
--min <score> | 无 | 从报表中隐藏此分数下的行 。 对 没有 影响 --fail-on。 。 。 . |
--top <n> | 50 | 只显示N最坏的罪犯. |
阈值
| 旗帜 | 默认 | 说明 |
|---|---|---|
-t, --threshold <n> | 30 | 标出一个函数的分数 . |
--fail-on <n> | 无 | 如果任何函数超过此值, 则退出代码 1 . |
覆盖范围
| 旗帜 | 默认 | 说明 |
|---|---|---|
-c, --coverage <file> | 无 | 报道路径. |
--coverage-format <fmt> | auto | auto | v8 | istanbul | lcov |
--missing <policy> | pessimistic | 如何在无覆盖数据的情况下对函数进行分分 : pessimistic (0%), optimistic (100% = CRAP = comp), skip (放下行来. |
产出
| 旗帜 | 默认 | 说明 |
|---|---|---|
-r, --reporter <name> | table | table | json | markdown | github | pr-comment | sarif |
--summary | 关 | 只打印汇总数据+最坏的罪犯(无表格). |
-o, --output <file> | 无 | 写入文件而不是 stdout . |
基线
| 旗帜 | 默认 | 说明 |
|---|---|---|
--baseline <file> | 无 | 与先前发布的JSON报告相比. 添加 QQ 列, 并将每个函数分类为新 / 移动 / 退后 / 改进 / 不变 . |
--fail-regression | 关 | 退出 1 如果任何函数出现退入 --epsilon。要求 --baseline。 。 。 . |
--epsilon <n> | 0.01 | 容忍"不变" CRAP diff. 职能 |
杂项
| 旗帜 | 默认 | 说明 |
|---|---|---|
--tsconfig <path> | 无 | Tsconfig.json的路径(非常需要). |
--config <path> | 无 | 胡说八道. |
-h, --help | ||
-v, --version |
覆盖范围格式
crap4ts 自动检测三种格式:
| 来源 | 格式 |
|---|---|
@vitest/coverage-v8 → coverage-final.json | v8 |
伊斯坦布尔/ @vitest/coverage-istanbul 编号 : nyc → coverage-final.json | istanbul |
bun test --coverage --coverage-reporter=lcov → lcov.info | lcov |
jest --coverage → coverage/lcov.info | lcov |
用vitest生成覆盖:
# vitest.config.ts:
# test: { coverage: { provider: 'v8', reporter: ['json'] } }
vitest run --coverage
解释分数
原始论文提供了一个软门槛: 30个 用于"此函数需要注意". 在~50以上,你有有意义的危险代码。 在 ~100 以上,您有代码, 其中一个改变 有很大的可能性 引入回归.
该公式有两个纯成分:
- 覆盖率=100% →
CRAP = comp(m). 即使经过充分测试的具有环形复杂度的代码 30+ 也很难解释;考虑拆分. - 覆盖率=0% →
CRAP = comp(m)² + comp(m). 复杂度5跳至30,复杂度10跳至110. 测试覆盖迅速降低分数——添加 任意 测试大约在八分之一的CRAP.
确定高CRAP值的最快方法几乎总是添加测试,然后自信地重构.
基线工作流程(v0.3)
密码质量门最难卖的是 "我们已经有 CMRAP 200 的功能 在这个repo, 你要去让我 修复这一切之前,我可以合并?"基线工作流程如下: 不,只是不要让它更糟.
-
打开
main,在每座建筑中发出一个基线JSON:crap4ts src/ --coverage coverage/coverage-final.json \ --reporter json --output crap-main.json史达什
crap-main.json您的 PRC 可以获取它( 艺术、 S3、 分支保护缓存等) . -
每一份公关记录上
crap4ts src/ --coverage coverage/coverage-final.json \ --baseline crap-main.json --fail-regression
crap4ts 然后打印出 QQ 列并分类每个函数:
| 状态 | 含义 |
|---|---|
regressed | 同样的功能, CRAP 上升超过 --epsilon |
new | 基线中不存在函数 |
moved | 函数正体散列匹配不同文件中或不同名称下的基线条目 |
improved | CRAP 下降超过 --epsilon |
unchanged | |
removed | 基线中存在函数,但现已消失 |
移动检测基于散列,所以重构的 PRs,在周围打乱代码不被报告为墙壁 new + 键 removed 双人 默认 --epsilon 0.01 吸收覆盖工具在运行之间引入的CRAP的子百分比紧张.
PR-bot 注释
crap4ts src/ --baseline crap-main.json \
--reporter pr-comment --output comment.md
pr-comment 发出粘相 <!-- crap4ts-report --> 在第一行标出。 连接您的 PR- comment 工作流程以找到该标记并更新现有的注释, 而不是发布每个运行的新注释 。 评论体崩溃 改进和现有的热点 <details> 因此,"什么变得更糟"的桌子 保持前和中.
单曲
覆盖工具根据运行地点以四个形状发送文件路径 :
- 绝对 :
/home/alice/proj/src/foo.ts - 相对于工作空间根 :
src/foo.ts - 相对于子包(当覆盖工具运行时)
packages/foo/:src/bar.ts - 与
./或者说../前缀
早先的版本 crap4ts 运行 path.resolve() 静悄悄地将相对路径与当前 process.cwd()。这在回收根上是好的,但在单体上却无声无息地失败了——子包中的每个功能都显示覆盖率为0%.
从v0.4开始,匹配者使用双等指数:
- 绝对报告路径进入由绝对路径键入的散列图 。 准确点击解析
O(1)。 。 。 . - 相对报告路径匹配 组件后缀 相对于查询的组件.
src/foo.ts匹配/repo/packages/foo/src/foo.ts· ;foo/bar.ts已经 没有 匹配/proj/oofoo/bar.ts因为匹配边界是路径分隔符,而不是字节边界.
如果多个相对条目相匹配,则最具体的(最长的匹配后缀)获胜. 跨机绝对路径( CI 报告运行于 /home/runner/work/... 本地询问 /Users/dani/...)故意不匹配——默默地将不同的绝对根相融合会掩盖出真正的bug. 一个未来 --path-prefix 旗帜会在您需要的时候重新显示.
crap4ts 还读取最近的 .gitignore 并将其图案添加到行走者中,从而生成目录(dist-temp/, (中文(简体) ), build-out/等)自动跳过而无需重复 crap.config.jsonB. 忽略模式!foo)并筑巢于地 .gitignore 树上更深层的文件还没有被尊重——如果击中它们就打开一个问题.
工作空间发现
crap4ts --workspace 自动检测您的工作空间配置( 按此顺序) :
pnpm-workspace.yaml– 报告packages:列表package.json#workspaces- npm / 纱线 / 包子形状, 既有平方阵形, 也有被线抹去的形式
当它找到包时,扫描会扩展到所有它们,报告中的每一个功能都带有它的包标签,以及 --report-by package 按软件包组合表格,排序最糟糕的软件包:
▸ @scope/billing (12 fns · Σcrap 870)
CRAP COMP COVERAGE LOCATION FUNCTION
─ ──── ──── ─────────────── ──────────────────────── ───────────
▲ 462.0 21 ░░░░░░░░░░ n/a src/recurring.ts:14 computeBill
▲ 156.0 12 ████░░░░░░ 40% src/discounts.ts:7 applyCode
…
▸ @scope/auth (3 fns · Σcrap 102)
…
这是单列波维护者在分解时实际使用的视图:"哪个团队需要重构什么". JSON记者总是带着 package 字段,当工作区活动时,所以仪表板也可以组合.
覆盖自动检测配置 --workspace-config path/to/pnpm-workspace.yaml 当从 repo 根外跑出.
合并每包的覆盖率
每个软件包(图尔博、Nx、平行CI)进行测试的单相机输出一个 coverage-final.json 每个包,但crap4ts会消耗一个覆盖文件. merge-coverage 组合它们——文件的结合,在不止一个报告中出现文件时,对每份报表的点击数进行总结:
crap4ts merge-coverage 'packages/*/coverage/coverage-final.json' -o coverage/merged.json
crap4ts --workspace --coverage coverage/merged.json
JSON(istanbul / v8)目前只有——每包输出模式几乎总是会出JSON. LCOV合并被推迟.
GitHub 代码扫描( SARIF)
crap4ts --reporter sarif 输出 a SARIF 2.1.0 (法语) gitHub 代码通过官方扫描摄入 upload-sarif 行动。 高级区域行动方案的职能随后出现:
- 在每个 PR 的 diff 中插入提示/ 错误说明到违法行范围旁边
- 在那个 安全 —— 代码扫描 选项卡作为持续结果
partialFingerprints所以重跑时不要重复 - 在读取SARIF(通过SARIF查看器扩展,IntelliJ等的VS代码)的编辑器中
发布了两项规则:
| 规则ID | 职等 | 触发当 |
|---|---|---|
crap4ts/threshold | 警告 | crap > --threshold (30个) |
crap4ts/fail-on | 错误 | crap > --fail-on |
最小工作流程 :
- name: Run crap4ts (SARIF)
run: bunx @danibram/crap4ts src/ \
--coverage coverage/coverage-final.json \
--threshold 30 \
--fail-on 100 \
--reporter sarif \
--output crap.sarif
- name: Upload to Code Scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: crap.sarif
category: crap4ts
category: crap4ts 将这些结果与其他代码扫描源(CodeQL,ESLint SARIF等)区分开来,因此每个工具都会在仪表板上得到自己的一栏.
CI 整合
GitHub 动作
- name: Run tests with coverage
run: vitest run --coverage
- name: Check CRAP
run: bunx @danibram/crap4ts src/ \
--coverage coverage/coverage-final.json \
--threshold 30 \
--fail-on 100 \
--reporter github
--reporter github 通过工作流程命令在 PR diff 上发布内置说明 .
一般 CI( 仅退出代码)
bunx @danibram/crap4ts src/ \
--coverage coverage/coverage-final.json \
--fail-on 100
回归退出 1 如果任何函数超过 --fail-on与 --reporter json > crap.json 保存机器可读文物.
如何运作
crap4ts 通过解析类型脚本 ts-morph (真正的AST——不是regex). 对每个类似功能的节点来说,它都是决定点 crap4j 原为:
if, (中文(简体) ),for, (中文(简体) ),for-of, (中文(简体) ),for-in, (中文(简体) ),while, (中文(简体) ),do-whilecase(c) 条款(每例一次;default是倒转)catch条款- 有条件的表达式
a ? b : c(中文(简体) ) - 逻辑运算符 :
&&, (中文(简体) ),||, (中文(简体) ),?? - 可选的链条(
?.)为 没有 决定点
嵌入功能内部的决定属于内在功能——它们不会出血.
覆盖范围计算为 covered_statements / total_statements 从覆盖报告中取出。 这是文件所指明的基础路径覆盖的实际近似值,足以推动确定优先顺序.
限制
- 说明范围,而不是基于路径范围. 对大多数代码来说,在实际操作上是等同的;对于密度条件来说,略微不足.
- 类型脚本
.ts编号 :.tsx编号 :.js编号 :.jsx仅限 ——还没有Vue / Svelte / MDX. - 内部分类方法
class { ... }仅限 ——取出TS命名空间方法和计算出的名称方法,但可能失去有用的命名.
鸣谢
- 阿尔贝托·萨沃亚和鲍勃·埃文斯 原始C.R.A.P.度量衡 财务报告和审定财务报表
crap4j(2007年). - 这个
ts-morph没有它,这个工具会是一堆雷克克斯.
许可证
麻省理工学院 丹尼尔·比德马