☰
重构遗留的 `x += 0` 没人敢删?no-useless-compound-assignment 帮你定位并安全清理
2026/10/21 2:14:48 网站建设 项目流程

重构遗留的x += 0没人敢删?no-useless-compound-assignment 帮你定位并安全清理

【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn

一次例行重构之后,代码库里多出一行total += 0;。它不影响运行,但没人敢删——万一背后有玄机呢?其实这类「恒等复合赋值」绝大多数是纯粹的残留物。eslint-plugin-unicorn 提供的no-useless-compound-assignment规则就是为此而生:自动找出这类不改变数值的赋值语句,并告诉你安全清理的正确姿势。

能力全景:一张只有五张「罚单」的质检单

这条规则管的范围非常窄:只有当复合赋值的右操作数恰好是「该运算的恒等值」时,才判定为无意义操作。规则源码 里用一张noOpAssignments映射表锁定了全部命中组合:

运算符触发值命中示例
+=0total += 0
-=0count -= 0
*=1price *= 1
/=1ratio /= 1
**=1base **= 1

除此之外,下列写法全部豁免,一眼就能建立全局印象:

  • 所有位运算符(|=、^=、&=、<<=、>>=、>>>=)
  • 取模恒等值x %= 1
  • 逻辑赋值(||=、&&=、??=)与普通赋值(x = 0)
  • 右值不是纯数字字面量的所有情况(变量、函数调用、BigInt、带符号写法、TS 断言)

换个角度看,它就像一个只查五种证件的安检员:不在名单上的,一律放行。

判定边界拆解:四类被刻意放过的写法及原因

为什么豁免项这么讲究?每一条背后都有明确理由。

① 位运算强转惯用法。x |= 0、x >>>= 0、x &= -1这类写法在性能敏感代码里是刻意为之的 int32/uint32 强转手段,是「技巧」而不是「失误」。源码注释直接写明了排除动机。

②%= 1看起来像恒等,实际不是。x % 1得到的是 x 的小数部分,而不是 x 本身——除以 1 不变,取余 1 会「截」掉整数位,两者语义完全不同。

③ 非算术复合赋值不在射程内。x ||= 0、x &&= 1、x ??= 0以及普通赋值x = 0,无论值是否「看起来没意义」,规则一概不碰。

④ 右值必须能被静态确认为「纯数字字面量」。这是边界的核心,源码借助 isNumericLiteral(判断node.type === 'Literal' && typeof node.value === 'number')做硬校验,因此以下写法全部放行:

  • BigInt:x += 0n——类型不同,语义也不同;
  • 一元符号包裹:x += -0、x *= +1——在 AST 中是UnaryExpression而非Literal,且-0的行为并不总是恒等;
  • 变量与调用:x += y、x += foo()——运行时的值无从判断;
  • TypeScript 断言:x += 0 as number——节点是TSAsExpression,不是纯字面量。

值得注意的是,字面量的「等价形态」逃不掉:0.0、1.0、0x1、0e3一样会被标记。因为 AST 保存的是规范化后的数字0或1,只是报错信息里会原样展示你写的原始文本(比如+= 0.0)。

那为什么x *= 0、x /= 0不报?因为它们根本不是恒等操作——乘 0 会把值清零,除以 0 得到Infinity,值确实被改变了。

核心设计取舍:为什么只给建议、不直接帮你删

「既然恒等,删掉不就完了?」——不能。因为复合赋值的实际效果取决于左值在运行时的真实状态,存在一批看似无操作、实则改状态的极端场景:

'a' += 0; // 变成字符串拼接,产出 'a0' '5' *= 1; // 字符串被隐式转成数字 5 5n *= 1; // BigInt 与 Number 混算,直接抛 TypeError // x 为 -0 时,x += 0 会把 -0 翻成 +0(IEEE 754 有符号零)

💡 也就是说,规则只能确认「这大概率是死代码」,无法确认「删掉一定等价」。于是 规则元数据 只声明了hasSuggestions: true,不提供fix:错误照常上报,但清理动作以编辑器建议的形式摆出来,等你亲手点确认。这一步把「自动改写」降级为「人工确认」,是对运行时语义差异的保守让步。

修复形态:两种建议,各有一套触发条件

确认报错后,规则会看赋值表达式在 AST 中的上下文,二选一:

删除整句。当赋值是独立语句(ExpressionStatement),且直接挂在Program、BlockStatement、StaticBlock、SwitchCase之下时,建议「Remove the useless assignment」。底层调用 removeStatement 助手:连带吸收本行缩进与换行符,不留空行,且语句之外的尾随注释会保留——x += 0; // keep清理后注释还在。

替换为左值。当赋值结果被外部使用时,整行删除会破坏语义,此时建议把整个赋值表达式换成左值文本:

  • foo(x += 0)→foo(x)
  • return count -= 0→return count
  • const y = (x *= 1)→const y = x
  • for (let i = 0; i < n; i += 0) {}→ 更新式变为i

左值即使是成员表达式也没问题:this.count -= 0、foo.bar.baz *= 1都能正确取文本。

还有一个隐蔽的边界:注释卡在赋值内部时,规则只报错、不出建议。比如x += /* keep */ 0;,无论删句还是替换都会吞掉这段注释,于是规则宁可保守,把清理工作留给人。

实战对照:错 → 对

// ① 独立语句 → 采纳「删除整句」建议 let total = getTotal(); total += 0; // ✅ 清理后只剩上一行 // ② 结果被 return 使用 → 采纳「替换为左值」建议 function report() { return count -= 0; // ✅ 清理后为 return count; } // ③ 注释位于赋值内部 → 只报错、无建议,手动清理 x += /* keep */ 0;

对照 官方文档 的示例与 快照文件,可以逐条验证上面每个判定分支的真实输出。

如何启用:默认配置即生效

接入成本几乎为零:

  • 该规则同时启用在recommended与unopinionated两套预设中(文档头部以 ✅ / ☑️ 标注),使用插件预设配置时开箱即得;
  • 元数据为type: 'suggestion',定位是「不掺杂强烈风格偏好」的规则;
  • 不接收任何自定义选项——五种恒等组合是硬编码的,也不开放配置开关。

在 readme.md 的规则总表中,它以 💡 标记归类于「提供编辑器建议」的规则一族。

小结:一套可移植的「三步走」实现模式

这条规则的参考价值不在规则本身,而在它的工程范式,拆开来正好三步:

  1. 恒等判定窄化:把「什么是无操作」固化为一张小小的映射表,且右值只认纯字面量——判定面越窄,误报率越低;
  2. 上下文分诊:动手前先看父节点,决定「能删」还是「必须保留」,再选删除或替换策略,让建议天然适配所在语句;
  3. 保守落地:运行时语义可能不同的场景,一律降级为建议而非自动修复;注释无法保全时,宁可只报错不出修复。

「判定恒等 → 区分上下文 → 保守修复」这条流水线,对任何想做冗余代码安全清理的静态分析工具,都是可以直接抄作业的骨架。

【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询