
eslint-plugin-unicorn 的 no-useless-compound-assignment从源码读懂两种建议如何分叉【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn重构后残留的x 0、x * 1这类恒等复合赋值是典型的死代码eslint-plugin-unicorn 的 no-useless-compound-assignment 规则专门负责清理它们且修复只走编辑器建议不自动改写。本文将回答三个问题为什么它不自动修判定边界划在哪命中之后删除整句还是替换为左值由什么决定设计哲学为什么只给建议、不动手修 ⚖️这条规则没有任何自动修复fix这不是功能缺失而是刻意选择——先讲这个最反直觉的点再讲它检测什么。x 0在数字上看起来是恒等操作但复合赋值的实际结果取决于运行时变量值。docs/rules/no-useless-compound-assignment.md#L18 列举了四种行为每种都会让静默删除或替换变成行为变更a 0产出字符串a0这是字符串拼接删掉这一句就丢掉了追加0的副作用。5 * 1会把字符串隐式转换成数字5变量的类型从 string 变成 number删除后所有依赖这次隐式转换的后续运算都会失效。5n * 1直接抛出TypeError原代码在这里存在一条异常路径删除后异常消失这是行为变化而非等价改写。x 0会把-0变成0Object.is(-0, 0)为 false任何区分有符号零的逻辑例如1 / x得到 ±Infinity都会被悄悄破坏。一句话一行看似无操作的代码实际藏着四类隐藏行为。所以规则元数据里只声明hasSuggestions: truerules/no-useless-compound-assignment.js#L109把修改动作留给开发者确认。这里要分清两个概念。autofix 是 lint 时或--fix时静默落盘的用户无感知而 editor suggestions 只是出现在编辑器的动作列表里用户必须显式点击才生效。规则文档头部用 标注的 manually fixable by editor suggestionsdocs/rules/no-useless-compound-assignment.md#L7说的就是后者。产品交互上的本质区别前者把判断权交给工具后者把判断权交给人——只要存在上述任何一种运行时语义差异后者才是安全的默认值。设计取舍讲完了那它到底检测什么命中地图哪些写法会被标记 规则只接受「算术运算符 纯数字字面量恒等值」的组合任何包装形式都不命中。实现上create只监听AssignmentExpressionrules/no-useless-compound-assignment.js#L34先查运算符是否在noOpAssignments映射表中再查右操作数是否对应该运算符的恒等值。完整映射见 rules/no-useless-compound-assignment.js#L20-L26运算符恒等值示例0x 0-0x - 0*1x * 1/1x / 1**1x ** 1字面量的等价写法同样命中0.0、1.0、0x1、0e3都会报错。判定依据是 rules/ast/literal.js#L29 的isNumericLiteralnode.type Literal typeof node.value number解析器会把上述各种记法在value上规范化成数字0或1因此 rules/no-useless-compound-assignment.js#L42-L44 的node.right.value ! noOpAssignments.get(node.operator)检查依然通过。测试 test/no-useless-compound-assignment.js#L68-L72 覆盖了x 0.0、x / 1.0、x * 0x1、x - 0e3四个用例快照 test/snapshots/no-useless-compound-assignment.js.snapshot#L508-L517 还记录了报错文案保留字面量原文 0.0——错误信息用的是node.right.raw不是规范化后的值。以下写法不会命中每项对应一个 AST 层面的原因BigIntx 0n、x * 1n是带bigint标记的Literalvalue为字符串而非 numbertest/no-useless-compound-assignment.js#L35-L38一元正负号包裹x -0、x * 1是UnaryExpression而非Literal且-0语义并非恒等test/no-useless-compound-assignment.js#L40-L44非字面量右值x y、x * zero、x foo()分别是Identifier/CallExpression运行时值无法静态确定test/no-useless-compound-assignment.js#L46-L49TypeScript 类型断言x 0 as number、x * (1 satisfies number)是TSAsExpression不是纯字面量test/no-useless-compound-assignment.js#L51-L53豁免清单哪些相似写法被刻意放过 这些不是规则漏报而是设计者明确选择放过的——源文件头部的注释就是最直接的证据位运算「deliberately excluded because they are deliberate int32/uint32 coercion idioms」% 1因为「x % 1is the fractional part, notx」rules/no-useless-compound-assignment.js#L17-L19。豁免对象豁免原因valid 测试位置位运算符配恒等值\| 0、 0、 -1、^ 0、 0、 0故意强转 int32/uint32 的惯用法不是失误test/no-useless-compound-assignment.js#L15-L21% 1x % 1取的是 x 的小数部分结果不是 x不属于无操作test/no-useless-compound-assignment.js#L23-L24逻辑赋值\|\| 0、 1、?? 0运算符不在noOpAssignments表中rules/no-useless-compound-assignment.js#L35 直接 returntest/no-useless-compound-assignment.js#L26-L29普通赋值x 0、x 1不是复合赋值赋值内容本身也不是无操作test/no-useless-compound-assignment.js#L31-L33恒等值与运算符不匹配x * 0、x / 0、x ** 0*/**//的恒等值是1不是0操作会真的改变值test/no-useless-compound-assignment.js#L55-L58注意x | 0不报错不是漏报。它是把值强转成 int32 的经典写法在 canvas、图像处理等代码里随处可见如果标记它反而是误伤——这正是「检测 vs 豁免」分界线所在同一份代码里 0是失误| 0是意图。建议分叉上下文如何决定修复方式 ️是否是独立语句决定了建议的形态独立语句给「删除整句」表达式内嵌给「替换为左值」注释无法保留则只报错不给建议。三个 messageId 定义在 rules/no-useless-compound-assignment.js#L8-L15。上下文触发条件建议 messageId建议文案独立语句父节点是ExpressionStatement且祖父节点属于Program/BlockStatement/StaticBlock/SwitchCaseno-useless-compound-assignment/removeRemove the useless assignment.表达式内嵌赋值结果被外层表达式消费no-useless-compound-assignment/replaceReplace the useless assignment with{{left}}.注释不可保留待删除/替换范围内存在注释无只报 error—分支一独立语句删除整句。触发条件在 rules/no-useless-compound-assignment.js#L51-L54parent.type ExpressionStatement且祖父节点在上述四种语句容器内。命中后调用 rules/fix/remove-statement.js#L14-L55 的removeStatement助手它会把语句连同自身行、缩进和行终止符一并吸走但保留语句之外的注释。测试覆盖顶层语句test/no-useless-compound-assignment.js#L62-L66、块内缩进语句test/no-useless-compound-assignment.js#L89-L90、switchcase 内test/no-useless-compound-assignment.js#L92-L93、class 静态块内test/no-useless-compound-assignment.js#L95-L96。x 0; // keep这条用例test/no-useless-compound-assignment.js#L102-L103专门验证尾随注释在删除后仍然保留。分支二表达式内嵌替换为左值。触发条件是不是独立语句即赋值结果被使用rules/no-useless-compound-assignment.js#L78-L94。实现分两步sourceCode.getText(node.left)rules/no-useless-compound-assignment.js#L79取左值原文fixer.replaceText(node, left)把整个赋值表达式替换成左值本身。测试 test/no-useless-compound-assignment.js#L79-L87 覆盖了函数实参、成员表达式左值、const y (x * 1)、赋值链a x 0、箭头函数体、逗号表达式、for更新表达式for (let i 0; i n; i 0) {}——这些上下文里删掉整行都会破坏语义只能收缩为左值。分支三放弃建议单独说明。关键判定在 rules/no-useless-compound-assignment.js#L56-L59sourceCode.getCommentsInside(isRemovableStatement ? parent : node).length 0时只返回 error、不带 suggest。这里有个容易被忽略的细节——检查范围随分支不同而不同走删除分支时查的是整个语句包括;前的尾随注释走替换分支时只查赋值表达式本身。测试用例印证了这一点x /* keep */ 0;、x /* keep */ * 1;test/no-useless-compound-assignment.js#L98-L100注释落在赋值内部删除或替换都会丢注释放弃建议x * 1/* keep */;位于函数块内test/no-useless-compound-assignment.js#L107-L108尾随注释落在语句内、表达式外删除分支同样放弃建议而if (a) x 0 /* keep */;test/no-useless-compound-assignment.js#L111-L112走的是替换分支注释在赋值表达式之外替换后注释安然无恙建议照给。实战对比三组 ❌→✅ ⚡第一组删除分支。// ❌ let total getTotal(); total 0;// ✅ let total getTotal();赋值是独立语句remove 建议调用removeStatement整行移除其余行为完全不变。第二组替换分支。// ❌ function f() { return count - 0; }// ✅ function f() { return count; }赋值结果就是返回值不能删行replace 建议把count - 0收缩为count数值上等价但因为有-0 → 0之类的语义差异它仍是建议而非自动修复。第三组边界情况放弃建议。x /* keep */ 0;只报 error不给任何建议。注释卡在左右操作数之间无论删除还是替换都会把它弄丢getCommentsInside命中后规则选择把决定权完整留给人。启用与元数据 ⚙️该规则默认在recommended与unopinionated两个配置中启用docs/rules/no-useless-compound-assignment.md#L5。元数据里type: suggestion、recommended: unopinionatedrules/no-useless-compound-assignment.js#L103-L108属于不掺杂强烈风格偏好的分级。规则元数据中没有 schema不接收任何自定义选项。readme.md#L292 的规则总表里该规则在建议列标注 与文档头部的 editor suggestions 标注一致可作为配置时的佐证。最小启用配置unicorn/no-useless-compound-assignment: error判定恒等五种算术运算符 纯数字字面量其余一律不碰、豁免惯用法位运算强转、% 1明确放过、区分上下文独立语句删整句、表达式内嵌收缩为左值、保守建议注释存疑就不动手判断权交给人。这套静态判定等价 上下文分叉 危险改写降级为建议的模式对任何需要安全清理冗余代码的静态分析工具都是可直接迁移的工程范式。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考