ARTICLE DETAIL

资讯详情

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

eslint-plugin-unicorn no-subtraction-comparison 规则详解:36 个快照测试用例、自动修复边界与源码实现

eslint-plugin-unicorn no-subtraction-comparison 规则详解:36 个快照测试用例、自动修复边界与源码实现 eslint-plugin-unicorn no-subtraction-comparison 规则详解36 个快照测试用例、自动修复边界与源码实现【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn本文以no-subtraction-comparison规则的 AVA 快照测试报告为主体完整解读其 36 个无效代码用例的输入、报错与修复结果并逐条对照规则源码rules/no-subtraction-comparison.js分析「模式匹配 → 操作符翻转 → autofix/suggestion 三级判定」的实现机制。读完你能准确掌握这条规则的触发条件、自动修复与仅建议的边界划分以及isNumber静态数字判定在其中的关键作用。快照报告的来龙去脉测试快照报告 是 AVA 测试框架针对 test/no-subtraction-comparison.js 中test.snapshot调用自动生成的行为基线文档报告头部明确说明「实际快照保存在no-subtraction-comparison.js.snap中由 AVA 生成」。它与快照文件 test/snapshots/no-subtraction-comparison.js.snap 一一对应每当规则对某个输入产生的报错消息Message、自动修复输出Output或建议Suggestion发生变化时快照测试就会失败从而把这条规则的行为锁定为可回归的契约。这份快照报告本身就是规则的「行为规格说明书」它完整记录了 36 个被报告为无效的表达式每一个都包含三要素——Input触发的原始代码Message统一为Prefer comparing the values directly over comparing the difference with0.修复方式要么是Output--fix自动改写结果要么是Suggestion 1/1: Replace with …需人工确认的建议个别情况则只报错、不给任何修复。规则本身在 rules/index.js 中以no-subtraction-comparison名称导出其meta声明rules/no-subtraction-comparison.js为type: suggestion、fixable: code、hasSuggestions: true、recommended: unopinionated、仅适用于js/js语言。官方规则文档docs/rules/no-subtraction-comparison.md进一步说明该规则同时启用于recommended与unopinionated两套配置并且「可通过--fix自动修复也可通过编辑器建议手动修复」。36 个无效用例全景输入、消息与修复结果以下将快照报告中的 36 个用例按行为模式分组还原。所有消息文案相同故下表聚焦「输入 → 输出/建议」。分组一右侧与零比较两侧为裸标识符仅建议这是规则最典型的触发形态。由于两侧只是标识符无法证明为数字规则给出 suggestion建议而非 autofix#输入建议替换为1if (a - b 0) {}a b2if (a - b 0) {}a b3if (a - b 0) {}a b4if (a - b 0) {}a b5a - b 0a b6a - b ! 0a ! b7a - b 0a b8a - b ! 0a ! b分组二零在左侧关系操作符被镜像翻转0 op (a - b)等价于(a - b) flip(op)。等号族是对称的映射到自身大小于族则翻转。同样只给建议#输入建议替换为90 a - ba b100 a - ba b110 a - ba b120 a - ba b130 a - ba b140 ! a - ba ! b分组三操作数可证明为数字 → 自动修复#输入自动修复输出151 - 2 01 216foo.length - bar.length 0foo.length bar.length17Number(a) - Number(b) 0Number(a) Number(b)18Number.POSITIVE_INFINITY - Number.POSITIVE_INFINITY 0Number.POSITIVE_INFINITY Number.POSITIVE_INFINITY191 - 2 01 2201 - 1 01 1210 foo.length - bar.lengthfoo.length bar.length26Math.round(a) - Math.round(b) 0Math.round(a) Math.round(b)28(foo.length) - (bar.length) 0(foo.length) (bar.length)保留原有括号这里有两个值得注意的细节用例 18 中两侧是Number.POSITIVE_INFINITY。由于是严格比较x - y 0与x y即使在无穷值或NaN参与时也同真同假Infinity - Infinity为NaN两者均为false所以严格比较允许放宽到「可证明是数字」即可 autofix。用例 28 展示了getParenthesizedText的作用操作数原本的括号被完整保留在修复结果中。分组四降格为建议的边界场景以下用例同样被报告但只给 suggestion不给 autofix——它们正是理解规则保守策略的关键#输入建议替换为降格原因22Number.POSITIVE_INFINITY - Number.POSITIVE_INFINITY 0Number.POSITIVE_INFINITY Number.POSITIVE_INFINITY非严格比较遇到无穷值Infinity - Infinity 0为false而Infinity Infinity为true改写会改变行为23Number.POSITIVE_INFINITY - Number.POSITIVE_INFINITY 0Number.POSITIVE_INFINITY Number.POSITIVE_INFINITY同上NaN 0为falseInfinity Infinity为true24foo.length - bar.length 0foo.length bar.length非严格比较要求两侧是静态可知的有限数字length不是25foo.length - bar 0foo.length bar严格比较要求两侧都可证明是数字bar无法证明27a.length - b.length - c 0a.length - b.length c嵌套减法右侧0实际对应的是c改写保留左侧减法29const modes new Set([foo]); modes.clear(); (modes.size ? 1 : x) - (modes.size ? 1 : x) 0(modes.size ? 1 : x) (modes.size ? 1 : x)条件表达式两分支类型不一致1/x无法静态定值30const modes new Set([foo]); modes.clear(); ((modes.size 1) || value) - 1 0((modes.size 1) || value) 1逻辑表达式静态值不可推断31const object {value: true}; Object.defineProperty(object, value, {get() { return false; }}); (object.value ? 1 : value) - 1 0(object.value ? 1 : value) 1getter 定义无法被静态值分析穿透32const modes new Set([foo]); modes.clear(); ((modes.size ? 1 : value) as number) - 1 0((modes.size ? 1 : value) as number) 1TypeScriptas number断言提供的是类型信息而非静态值非严格比较不采信33(a - b) 0a b整个减法被括号包裹两侧仍是标识符34a?.b - c?.d 0a?.b c?.d可选链结果可能为undefined非数字36(a as number) - (b as number) 0(a as number) (b as number)自动修复严格比较中TSAsExpression断言为number被isNumber采信分组五表达式内含注释 → 只报错、不修复#输入结果35a - /* comment */ b 0仅报告错误既无 autofix 也无 suggestion规则源码中有一句注释说明了原因rules/no-subtraction-comparison.js「为避免丢失注释表达式内部存在任何注释时不提供修复」。因为fixer.replaceText(node, replacement)会整段替换节点文本会连带删除夹在操作符之间的注释所以干脆放弃修复。规则源码实现三级判定链理解了用例分布后再回看 rules/no-subtraction-comparison.js 的实现逻辑非常紧凑可以拆成四步。第一步模式匹配「比较表达式的一侧是减法、另一侧是 0」规则监听BinaryExpression第 40 行起要求节点操作符属于 8 个比较操作符之一且「一侧是减法BinaryExpression且操作符为-、另一侧是字面量0」let subtraction; let operator; if (isZero(node.right) isSubtraction(node.left)) { subtraction node.left; operator node.operator; } else if (isZero(node.left) isSubtraction(node.right)) { subtraction node.right; operator invertedOperator[node.operator]; } else { return; }两个判定点的细节都体现在用例中isZero node isLiteral(node, 0)只认数字字面量0。因此测试的 valid 用例里a - b 0nBigInt 零和a - b -0一元负号表达式不是0字面量都不会被报告。零在左侧时通过invertedOperator表翻转操作符第 11-22 行↔、↔而、!、、!对称映射到自身——这正是分组二六个用例的来源。第二步注释保护if (sourceCode.getCommentsInside(node).length 0 { return problem; // 只报错 }对应快照用例 35。第三步构造替换文本并决定修复级别替换文本由两侧操作数加翻转后的操作符拼接而成第 67-68 行const replacement ${getParenthesizedText(left, context)} ${operator} ${getParenthesizedText(right, context)};其中 getParenthesizedText 会连同包裹操作数的括号一起切片源码文本保证了用例 28 中(foo.length) (bar.length)这样的输出。随后是核心的三级判定第 75-89 行const canAutofix strictOrderingOperators.has(operator) ? isNumber(left, context) isNumber(right, context) : isFiniteStaticNumber(left, context) isFiniteStaticNumber(right, context);严格比较/翻转后同理要求两侧都通过isNumber判定类型层面可证明是数字→ autofix。覆盖用例 15-18、21、26、28、36。非严格比较、、、!、、!要求两侧都能通过 getStaticValueForControlFlow 求出有限数字静态值typeof value number Number.isFinite(value)→ autofix。覆盖用例 19、20。其余一律给 suggestionproblem.suggest携带Replace with {{replacement}}消息和同一个fix函数由用户确认后应用。覆盖分组一、二、四的大部分用例。之所以对非严格比较加「静态有限数字」的更严限制官方文档docs/rules/no-subtraction-comparison.md的 Caveats 一节给出了反例Infinity - Infinity是NaN导致Infinity - Infinity 0为false而Infinity Infinity为true同样10 - 5 0为true而字符串按字典序10 5为false——重写只对数字才保证行为等价。第四步isNumber——「可证明是数字」的判定清单autofix 与 suggestion 的分野很大程度取决于 rules/utils/is-number.js 中的isNumber。从源码结构看它按 AST 形式白名单式地认可以下「数字证据」数字字面量、Math静态属性PI、E等与静态方法调用Math.round(...)等、Number(...)调用、Number.EPSILON等静态属性、Number.parseInt/parseFloat调用、字符串的数字返回方法charCodeAt、indexOf、localeCompare等且接收方需可证明是字符串、.length属性、算术运算-、*、/、%、**等至少一侧为数字与一元恒为数字、条件/逻辑/序列表达式的传播、带: number注解的标识符以及 TypeScript 的as number/satisfies number/ 非空断言。兜底再走getStaticValueForControlFlow静态求值。把这份清单与快照用例对读每一条降格都能找到出处用例 25 的裸标识符bar无类型注解、无可静态值用例 34 的a?.b因可选链可能短路为undefined不在白名单内用例 26 的Math.round(a)恰好在mathMethods白名单里因此得以 autofix。值得再引用一条非快照用例佐证控制流敏感性的边界test/no-subtraction-comparison.js{ code: const alias condition; var condition true; (alias ? 1 : value) - 1 0, errors: [{messageId: no-subtraction-comparison/error, suggestions: 1}], },断言suggestions: 1且没有output说明尽管condition后面被赋值为true静态分析仍无法证明alias的真值条件表达式(alias ? 1 : value)得不到静态数字于是只能给建议。这与用例 29、30、31 的降格原因一脉相承。合法用例哪些写法不会被报告test/no-subtraction-comparison.js 的valid列表对应快照报告中不存在的报告记录同样重要它划定了规则的负边界输入不报告原因a b/a b/a b已经是直接比较a - b减法本身不是比较表达式a - b 1/a - b c/a - b c比较对象不是0a b 0/a * b 0/a % b 0一侧不是减法a 0/0 a/0 0两侧都不是减法a - b 0n0n是 BigInt 字面量isLiteral(node, 0)不成立a - b -0-0是一元表达式而非0字面量a - b instanceof cinstanceof不在 8 个受支持的操作符集合内使用与验证方式在 ESLint 中启用该规则flat config 示例规则前缀为unicorn// eslint.config.js export default [ { rules: { unicorn/no-subtraction-comparison: error, }, }, ];如果直接采用插件提供的recommended或unopinionated配置集该规则已默认开启见 docs/rules/no-subtraction-comparison.md 头部的配置说明。对严格比较场景运行--fix即可自动改写其余场景在编辑器中确认建议即可。行为回归方面本规则的全部 36 个无效用例与若干合法用例由 test/no-subtraction-comparison.js 中的test.snapshot定义基线即本文分析的 test/snapshots/no-subtraction-comparison.js.md 与配套的 test/snapshots/no-subtraction-comparison.js.snap只要规则的报错文案、修复级别或改写结果发生任何变化快照测试都会暴露出来。小结no-subtraction-comparison是 eslint-plugin-unicorn 中一条「小规则、深功夫」的典型模式匹配只区分两种形态零在左/在右和八种比较操作符但修复策略却精细到三级——严格比较要求isNumber双证、非严格比较要求静态有限数字双证、其余一律降级为 suggestion并对表达式内注释彻底弃修。快照报告以 36 个用例把这些边界全部固化成了可检索、可回归的文档这也是研究该仓库其他建议类规则 autofix 设计思路时值得参照的样本。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表