 到 assert.ok() 的自动修复行为全览)
eslint-plugin-unicorn consistent-assert 规则测试快照深度解析assert() 到 assert.ok() 的自动修复行为全览【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn本篇文章以 eslint-plugin-unicorn 仓库中consistent-assert规则的测试快照test/snapshots/consistent-assert.js.md为主体系统拆解该规则如何识别node:assert系列模块导入、如何定位assert(...)函数调用、如何生成错误消息与自动修复输出并串联规则源码、测试用例与文档帮助你完整理解一个 ESLint 可修复规则从报告到快照验证的闭环。读完你将掌握快照测试文档的阅读方法、12 类非法用例的行为边界、自动修复的底层实现原理以及如何在本仓库中运行与更新此类快照。一、快照文件是什么AVA 生成的规则行为体检报告test/snapshots/consistent-assert.js.md是 AVA 测试框架为 test/consistent-assert.js 自动生成的快照测试报告。文件头部明确说明了两点关键信息该 Markdown 文件是快照的可读渲染版本真正的快照数据保存在同目录下的consistent-assert.js.snap文件中实测为 AVA Snapshot v3 格式的二进制压缩内容无法直接以文本读取。这份快照报告共记录了12 个 invalid非法测试用例对每个用例输出三部分内容Input输入源码含行号标注MessageESLint 报告的错误消息使用babel/code-frame的 code frame 精确定位到出错的标识符并给出高亮列范围Output应用--fix自动修复后的输出代码。以最基础的一个用例为例快照中invalid(1)// 输入 import assert from assert; assert(foo) // 报告 Prefer assert.ok(…) over assert(…). // 错误定位到第 2 行的 assert 标识符本身而非整个调用表达式 // 自动修复输出 import assert from assert; assert.ok(foo)这份快照本质上就是consistent-assert规则行为规范的精确契约任何对规则报告逻辑或修复逻辑的改动只要导致上述输出变化运行测试时就会产生快照差异从而被开发者在提交前发现。二、规则背景为什么强制使用 assert.ok()要理解快照中反复出现的Prefer \assert.ok(…) over assert(…) 消息需要先看规则本身的定位。在 docs/rules/consistent-assert.md 中规则的设计意图被明确阐述优先使用assert.ok()而不是assert()因为前者意图明确、可读性更好。它与其他 assert 方法保持一致确保风格统一、代码更易维护和理解。该规则被包含在recommended配置中meta.docs.recommended: true在unopinionated配置中禁用meta.fixable: code表明它可由--fix命令行选项自动修复。从 rules/consistent-assert.js 的元数据可以看到meta: { type: problem, docs: { description: Enforce consistent assertion style with node:assert., recommended: true, }, fixable: code, messages, languages: [js/js], }type: problem表示这类代码很可能导致错误或混淆直接调用assert(value)本质上是assert.ok(value)的简写但可读性差且容易与普通函数调用混淆。文档中的标准示例import assert from node:assert/strict; assert.strictEqual(actual, expected); assert.deepStrictEqual(actual, expected); // ❌ assert(divide(10, 2) 5); // ✅ assert.ok(divide(10, 2) 5);assert.strictEqual、assert.deepStrictEqual等命名方法不受影响规则只针对直接作为函数调用的 assert 对象本身。三、快照生成机制从测试代码到快照文档的完整链路快照并非手工编写而是由测试基础设施自动产出。链路如下1. 测试用例声明test/consistent-assert.jsimport outdent from outdent; import {getTester, parsers} from ./utils/test.js; const {test} getTester(import.meta); test.snapshot({ valid: [...], invalid: [...], });getTester(import.meta)通过测试文件名自动推导出被测规则 IDconsistent-assert并绑定 rules/consistent-assert.js 的规则对象。2. 快照测试器执行test/utils/snapshot-rule-tester.jstest.snapshot()调用SnapshotRuleTester其核心流程见run()方法对每个 invalid 用例通过 ESLintLinter.verify()校验输入代码得到 ESLint 消息数组断言非法用例必须至少产生一个错误将输入代码渲染为快照段Input对每条消息用babel/code-frame的codeFrameColumns将消息绘制成带行号与^^^^高亮定位的代码块Message段若规则可修复且消息带fix则执行applyFix()一种简化版SourceCodeFixer.applyFixes仅按fix.range切片拼接文本生成修复后代码并再次verify修复结果确保修复输出不会再触发错误Output段。这里体现了快照测试的一个重要特性快照中展示的 Output 并非随意生成而是修复后必须通过校验的结果防止规则产生修复一次仍报错的死循环式输出。3. 快照落盘t.snapshot()将上述内容序列化后写入consistent-assert.js.snap二进制格式同时 AVA 提供 Markdown 渲染版本即本文分析的consistent-assert.js.md。测试编写约定可参见 docs/write-tests.md 与测试工具入口 test/utils/test.js。四、12 个 invalid 用例逐类解析快照中 12 个用例按导入方式与调用形态可分为六类下面逐一还原其输入、报告与修复结果。4.1 默认导入的四种模块写法invalid 1–4这组用例验证规则能覆盖assert模块的四种等价导入路径用例导入语句报告消息invalid(1)import assert from assert;Prefer assert.ok(…) over assert(…).invalid(2)import assert from node:assert;同上invalid(3)import assert from assert/strict;同上invalid(4)import assert from node:assert/strict;同上四个用例的错误定位完全一致指向第 2 行assert(foo)中的assert标识符高亮为^^^^^^6 个字符修复输出均为assert.ok(foo)。注意node:协议前缀node:assert、node:assert/strict与裸模块名assert、assert/strict均被同等对待。4.2 自定义标识符invalid 5–6当默认导入使用自定义名字时规则跟随导入的本地绑定// 输入 import customAssert from assert; customAssert(foo) // 报告 Prefer customAssert.ok(…) over customAssert(…). // 修复 import customAssert from assert; customAssert.ok(foo)错误消息中的{{name}}占位符会被替换为实际的本地标识符名见 rules/consistent-assert.js 的data: {name: identifier.name}证明规则基于变量引用跟踪而非硬编码assert这个名字。4.3 同一文件的多个引用invalid 7import assert from assert; assert(foo) assert(bar) assert(baz)快照中该用例产生Error 1/3、2/3、3/3 共三条错误每条错误独立定位并独立修复修复第一个错误后输出assert.ok(foo)其余两行保持原样快照分别展示了三次修复的中间态最终全部修复为三个assert.ok(...)。这说明规则对同一导入变量的每次调用引用都单独报告且每个 fix 都是最小化替换。4.4 命名导入 strictinvalid 8–9Node.js 的assert模块允许通过命名导入获取 strict 版本// 输入 import {strict} from assert; strict(foo) // 报告 Prefer strict.ok(…) over strict(…). // 修复 import {strict} from assert; strict.ok(foo)带别名时同理import {strict as assert} from assert; assert(foo) // → assert.ok(foo)这是快照中很重要的一组用例规则不仅要识别assert默认导出还要识别从assert模块注意不是assert/strict导入的strict命名导出——因为在assert/strict中strict并不存在只有assert与assert/strict两个模块的默认导出才指向 strict 断言函数。4.5 全场景综合用例invalid 10这是信息量最大的一个用例一次性覆盖 10 种导入形态与调用import a, {strict as b, default as c} from node:assert; import d, {strict as e, default as f} from assert; import g, {default as h} from node:assert/strict; import i, {default as j} from assert/strict; a(foo); b(foo); c(foo); d(foo); e(foo); f(foo); g(foo); h(foo); i(foo); j(foo);快照逐条展示Error 1/10 至 Error 10/10每个错误定位到对应调用语句的标识符a、b、c…j修复后依次变为a.ok(foo)、b.ok(foo)…j.ok(foo)。从中可以归纳出规则识别的完整导入形态集合默认导入import a from node:assert、import d from assert、import g from node:assert/strict、import i from assert/strict命名为defaultimport {default as c} from node:assert、{default as f} from assert、{default as h} from node:assert/strict、{default as j} from assert/strict命名为strict仅限assert模块{strict as b} from node:assert、{strict as e} from assert。对照源码中的isAssertFunction判定函数rules/consistent-assert.js可以确认这套规则ImportDefaultSpecifier始终匹配ImportSpecifier需满足imported.name default或者moduleName assert且imported.name strict。4.6 可选链调用invalid 11import assert from node:assert; assert?.(foo)规则同样覆盖可选链调用CallExpression的可选形式修复结果为assert.ok?.(foo)快照中的错误定位与普通调用一致说明identifier.parent.type CallExpression的判定对可选调用同样成立而 fix 只插入.ok、保留?.语法。4.7 复杂括号嵌套与注释invalid 12(( /* comment */ (( /* comment */ assert /* comment */ )) /* comment */ (/* comment */ typeof foo string, foo must be a string /** after comment */) ));这是对定位精度与注释保留能力的极端测试assert被多层括号包裹、周围散布大量块注释且调用带两个参数断言表达式 消息字符串。快照显示错误精确定位到assert标识符本身第 6 行^^^^^^高亮而不是整个表达式修复输出仅将assert替换为assert.ok所有括号、注释、参数全部原样保留assert.ok(/* comment */ typeof foo string, foo must be a string /** after comment */)这一用例直接验证了修复策略fixer.insertTextAfter(identifier, .ok)rules/consistent-assert.js的最小插入特性只追加文本不触碰任何其他 token。五、报告与修复的源码级实现原理快照中的每一个定位和修复行为都能在规则源码 rules/consistent-assert.js 中找到对应实现。5.1 处理入口与模块过滤规则在create中监听ImportDeclaration事件L41-L55context.on(ImportDeclaration, function * (importDeclaration) { if (!isValueImport(importDeclaration)) { return; } let moduleName importDeclaration.source.value; if (moduleName.startsWith(NODE_PROTOCOL)) { moduleName moduleName.slice(NODE_PROTOCOL.length); } if (moduleName ! assert moduleName ! assert/strict) { return; } // ... });要点isValueImportL9过滤掉import type等非 value 导入TypeScript 场景先剥离node:前缀再做模块名匹配因此四种写法归一到assert与assert/strict两个模块名规则是一个 generator通过yield逐个产出 ESLint 报告对象。5.2 变量引用跟踪而非文本匹配快照中自定义标识符和别名导入都能正确报告关键在于规则不使用字符串匹配而是通过作用域分析L57-L74for (const specifier of importDeclaration.specifiers) { if (!isValueImport(specifier) || !isAssertFunction(specifier, moduleName)) { continue; } const variables context.sourceCode.getDeclaredVariables(specifier); // ... for (const {identifier} of variable.references) { if (!(identifier.parent.type CallExpression identifier.parent.callee identifier)) { continue; } // yield report } }getDeclaredVariables(specifier)拿到该导入绑定的变量遍历variable.references只处理该标识符的父节点是 CallExpression 且自身正是 callee的引用——这正是快照中所有错误都定位到作为调用目标的标识符而非任意出现位置的原因因此console.log(assert)之类仅引用不调用的场景不会被报告对应 valid 用例。5.3 修复策略最小文本插入修复函数只有一行L83fix: fixer fixer.insertTextAfter(identifier, .ok),即在标识符之后插入.ok不删除、不重排任何内容。这解释了快照中观察到的全部修复形态assert(foo)→assert.ok(foo)assert?.(foo)→assert.ok?.(foo)?.在标识符之后插入点不变带注释的复杂表达式只改标识符、注释与括号原样保留多引用场景下每个错误各自独立插入。六、快照反向验证的边界哪些代码不会被报告快照文档只记录 invalid 用例但测试文件中配套的 valid 用例test/consistent-assert.js从反面定义了规则的边界值得一并了解未导入直接调用裸assert(foo)无 import 语句不报告只导入不调用import assert from node:assert; assert;不报告非 assert 模块绑定import customAssert from node:assert后调用assert(foo)另一个名字不报告——规则只跟变量绑定走不猜名字本地遮蔽函数参数function foo(assert) { assert(bar); }不报告即使同时存在 assert 导入命名空间导入import * as assert from node:assert; assert(foo)不报告namespace 对象不是断言函数re-exportexport * as assert from node:assert、export {default as assert} from node:assert不报告错误模块的 strict 命名导入import {strict} from node:assert/strict不报告strict命名导出只存在于assert模块字符串字面量属性import {strict as assert} from assert不报告imported是Literal而非IdentifierTypeScript type-only 导入import type assert from node:assert/strict、import {type strict as assert} from node:assert/strict不报告测试中通过parsers.typescript指定了 TypeScript 解析器。这些 valid 用例与快照中的 invalid 用例共同构成了规则的完整行为契约。七、如何运行与更新快照测试在本仓库中运行consistent-assert的测试参见 docs/write-tests.mdnpx ava test/consistent-assert.js如果规则实现发生变化导致报告或修复输出改变需要同步更新快照npx ava test/consistent-assert.js -u-u即--update-snapshots会重新生成consistent-assert.js.snap及 Markdown 渲染版本。聚焦单个用例可在测试文件中使用test.only或only: true提交前需移除。八、总结通过这份快照文档我们可以完整还原consistent-assert规则的工程全貌功能定位强制以assert.ok()替代assert()直接调用提升断言意图的可读性属recommended配置下的可自动修复规则识别范围assert/assert/strict模块的默认导入、default命名导入、assert模块的strict命名导入支持node:前缀与自定义/别名标识符报告机制基于作用域变量引用跟踪仅报告作为CallExpressioncallee 的标识符错误精确定位到标识符本身修复机制insertTextAfter(identifier, .ok)最小插入兼容普通调用、可选链调用、复杂括号与注释场景多引用逐个修复质量保障快照测试自动记录每次报告与修复输出并二次校验修复结果为规则行为提供可审计、可回归的契约。从 docs/rules/consistent-assert.md 了解规则设计初衷从 rules/consistent-assert.js 阅读完整实现从 test/consistent-assert.js 查看测试用例全貌再回到本文的快照解析即可对这条规则乃至 eslint-plugin-unicorn 的规则开发模式形成闭环认知。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考