
ESLint no-useless-assignment 规则完全指南检测并消除死赋值让每一行赋值都物尽其用【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint本文以 ESLint 仓库中的no-useless-assignment规则文档docs/src/rules/no-useless-assignment.md为核心系统讲解死赋值dead store这一经典代码异味什么是死赋值、规则在什么情况下会报告、哪些写法会被放过、它与no-unused-vars如何分工以及规则底层的代码路径code path分析原理。读完本文你既能直接在项目中配置并运用该规则也能从源码层面理解其判定逻辑与已知局限。什么是死赋值Dead Store规则文档引用了维基百科对 dead store 的定义在计算机编程中一个局部变量被赋了值但该值没有被任何后续指令读取这样的赋值被称为死赋值dead store。死赋值会白白消耗处理时间与内存因此最好删除这些对变量的多余赋值。更关键的是如果作者本意是要使用该变量那么死赋值的出现往往意味着代码里藏着 bug常见原因有本应使用某个已赋的值但忘记使用了给要保存的变量写错了名字。一个典型例子let id x1234; // 这就是一个死赋值——这个值x1234从未被读取 id generateId(); doSomethingWith(id);第 1 行给id赋的初始值x1234从未被任何语句读取紧接着就被generateId()的返回值覆盖了。这个初始值要么是残留代码要么暗示作者原本想用它却忘了。no-useless-assignment规则的目标就是把这类赋了值却没人读的赋值语句找出来。规则详情报告值未被使用的赋值该规则旨在报告赋值后其值未被使用的变量赋值。文档示例中的错误代码/* eslint no-useless-assignment: error */启用规则function fn1() { let v used; doSomething(v); v unused; } function fn2() { let v used; if (condition) { v unused; return } doSomething(v); } function fn3() { let v used; if (condition) { doSomething(v); } else { v unused; } } function fn4() { let v unused; if (condition) { v used; doSomething(v); return } } function fn5() { let v used; if (condition) { let v used; console.log(v); v unused; } console.log(v); }逐一分析fn1v unused之后函数立即结束新值从未被读取fn2条件分支内v unused之后立即return新值无人读取fn3else分支的赋值之后没有任何读取fn4初始值unused在if分支内被新值覆盖初始赋值是死赋值fn5内层块作用域里的v unused之后是console.log(v)但注意内层块使用的是内层声明的v该赋值依旧未被读取。对应的正确代码示例function fn1() { let v used; doSomething(v); v used-2; doSomething(v); } function fn2() { let v used; if (condition) { v used-2; doSomething(v); return } doSomething(v); } function fn3() { let v used; if (condition) { doSomething(v); } else { v used-2; doSomething(v); } } function fn4() { let v used; for (let i 0; i 10; i) { doSomething(v); v used in next iteration; } }这些示例的共同点是每一次赋值之后都有对该变量的读取。特别值得注意fn4的循环场景——v used in next iteration的赋值虽然位于循环体末尾但它会在下一次迭代开始时被读取因此不是死赋值。测试文件 tests/lib/rules/no-useless-assignment.js 中也有大量同类用例如i; i; console.log(i)属正确写法因为自增结果最终被读取。报告位置与消息从源码 lib/rules/no-useless-assignment.js 可以看到规则通过context.report()把错误定位在被赋值的标识符节点上并使用messageId: unnecessaryAssignment对应消息模板为源码The value assigned to {{name}} is not used in subsequent statements.即赋给{{name}}的值在后续语句中未被使用。测试用例中对消息与行列号如line: 4, column: 17均有精确断言例如连续多次无用赋值v unused; v unused;会按行逐一报告。与 no-unused-vars 的分工从不报告从未被读取的变量规则文档特别强调本规则不会报告那些完全没人读过的变量——因为那显然是未使用变量应由no-unused-vars规则负责规则文档。下面的代码不会触发no-useless-assignment/* eslint no-useless-assignment: error */ function fn() { let v unused; v unused-2 doSomething(); }这里的v从头到尾没有任何读取属于未使用变量的范畴应通过启用no-unused-vars规则来报告。这一行为在源码中有明确对应verifyAssignmentIsUsed()首先过滤出变量的读取引用reference.isRead()若根本不存在任何读取引用直接返回不报告源码注释写明这不仅是多余赋值而是多余的未使用变量应交由 no-unused-vars 报告。两条规则是互补关系no-unused-vars管从未用过的变量no-useless-assignment管变量被用过但某次赋值是多余的。Options该规则没有选项no-useless-assignment没有配置选项只能以off、warn或error设置严重级别{ rules: { no-useless-assignment: error } }源码中meta.schema为空数组lib/rules/no-useless-assignment.js即不接受任何选项参数这印证了文档Rules has no options的说法。同时源码meta.docs.recommended为truelib/rules/no-useless-assignment.js说明该规则已被纳入官方推荐配置按默认推荐配置使用即可受益。规则如何工作基于代码路径Code Path分析的实现原理从源码实现看该规则深度依赖 ESLint 的**代码路径分析code path analysis**机制相关基础设施位于 lib/linter/code-path-analysis核心思路是把一次赋值记录下来然后沿着它之后可能执行的代码路径segment去查找是否存在读取。实现要点如下lib/rules/no-useless-assignment.js记录三类赋值监听VariableDeclarator[init!null]带初始化器的声明、AssignmentExpression赋值表达式和UpdateExpression/--自增自减见 源码通过eslint-scope的findVariable解析出对应的变量解析复杂模式通过extractIdentifiersFromPattern()递归提取解构模式对象解构、数组解构、Rest 元素、带默认值的AssignmentPattern中的所有标识符源码因此({ a, arr: [b, c,, ...d] } fn())这种复杂赋值同样会被逐标识符检查——测试用例中此类场景均被覆盖逐段追踪在每个代码路径段segment上记录该段内出现的首个与末个标识符first/last见 源码用于快速判断某标识符是否落在某段范围内后向遍历从赋值所在段的后续段nextSegments出发逐段检查是否存在对该变量的读取若发现后续段中有另一处赋值则不需要再往后探索源码通过缓存与Set记录已遍历段来避免死循环识别写后写otherAssignmentAfterTargetAssignment逻辑用于判断在当前段中目标赋值之后是否还有另一处赋值——若有说明目标赋的值确实未被使用源码例如v used; v unused; v used; console.log(v);中间那次v unused会被报告边界位置的读取判定isIdentifierEvaluatedAfterAssignment()与isIdentifierUsedBetweenAssignedAndEqualSign()处理x (x 1)、let { x, y x } obj这类同一表达式内先算后赋的边界情况源码确保不误报。哪些变量被跳过跨作用域、导出与全局变量源码中还列出了若干刻意跳过的情况理解它们有助于避免误用预期全局且无定义记录的变量无法得知全局变量在何处被使用直接跳过源码赋值所在代码路径作用域之外的变量如回调、异步场景中变量可能在当前代码路径之外被写入/读取无法可靠追踪跳过源码。测试用例证实setTimeout(() v 42, 1)之后再写v的赋值仍会被报告但规则对跨代码路径的写入保持保守eslintUsed变量被/* exported */注释或sourceCode.markVariableAsUsed()标记为已使用的变量跳过源码——测试中用自定义插件规则调用markVariableAsUsed(a, node)验证了这一点ESM 导出变量被export声明、export default或export { ... }引用的变量跳过源码因为外部模块可能读取其值非标识符引用存在插件产生的非Identifier/JSXIdentifier类型引用时跳过源码注释说明插件生成的引用无法在核心规则中可靠处理。已知限制try 块内的赋值不报告规则文档明确列出当赋值发生在try块内部时规则不会报告某些变量重赋值。这是有意为之——因为这样的赋值可能在对应的catch块中或整个try-catch结构之后被观察到取决于是否存在提前退出或错误处理逻辑function foo() { let bar; try { bar 2; unsafeFn(); return { error: undefined }; } catch { return { bar }; // bar 在 catch 块中被观察到 } } function unsafeFn() { throw new Error(); } function foo() { let bar; try { bar 2; // 如果 unsafeFn() 抛错这个赋值就是相关的 unsafeFn(); bar 4; } catch { // Error handling } return bar; } function unsafeFn() { throw new Error(); }第一个例子中若unsafeFn()抛错bar 2的值会在catch块的return { bar }中被读取第二个例子中bar 2在抛错路径上决定了最终return bar的值。由于代码路径分析无法完全确定异常何时发生规则选择对 try 块内的赋值一律保守处理。实现上规则通过TryStatement监听器把 try 块节点压入tryStatementBlocks栈在verifyAssignmentIsUsed()中若发现赋值标识符落在某个 try 块范围内直接跳过源码 与 源码。测试文件中也包含try { m 2; unsafeFn(); m 4; } catch { } return A prop{m} /;这类 JSX 用例验证了该保守策略。何时不使用此规则如果你不希望收到赋值后值未被读取的提醒可以安全地关闭该规则{ rules: { no-useless-assignment: off } }该规则属于**建议类suggestion**规则见规则文档 frontmatter 的rule_type: suggestion其报告属于代码质量改进建议而非功能性错误在代码清理、重构或遗留代码审查等场景下尤其有价值。实际使用中建议与no-unused-vars搭配启用一个负责未使用变量、一个负责多余赋值二者共同覆盖变量使用这一维度的两大常见问题。延伸阅读规则实现源码lib/rules/no-useless-assignment.js规则测试用例1717 行覆盖解构、循环、try-catch、JSX、导出、跨作用域等场景tests/lib/rules/no-useless-assignment.js规则在规则索引中的注册位置lib/rules/index.js姊妹规则负责未使用变量no-unused-vars 规则文档 与 no-unused-vars 实现代码路径分析基础设施lib/linter/code-path-analysis规则使用与配置方式总览docs/src/use【免费下载链接】eslintFind and fix problems in your JavaScript code.项目地址: https://gitcode.com/GitHub_Trending/es/eslint创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考