ARTICLE DETAIL

资讯详情

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

eslint-plugin-unicorn 的 no-top-level-assignment-in-function 规则:禁止函数内写入顶层变量,消除隐藏共享状态

eslint-plugin-unicorn 的 no-top-level-assignment-in-function 规则:禁止函数内写入顶层变量,消除隐藏共享状态 eslint-plugin-unicorn 的 no-top-level-assignment-in-function 规则禁止函数内写入顶层变量消除隐藏共享状态【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicornno-top-level-assignment-in-function是 eslint-plugin-unicornMore than 300 powerful ESLint rules中一条默认在recommended配置下启用的规则用于阻止在函数体内部直接为模块顶层Top-level绑定变量赋值。本文将结合该规则的 官方文档、规则源码 与 测试用例讲清它解决的问题、判定边界、源码实现原理以及实际配置与修复方法帮助你在项目中安全启用并正确使用这条规则。规则要解决的问题函数内改写顶层变量带来的隐藏共享状态在现代 JavaScript/ESM 模块中顶层声明的变量let/var/函数声明/import绑定等天然是模块级的共享状态。如果某个函数直接给这些变量赋值就会在函数与模块生命周期之间建立起隐式耦合产生文档中明确指出的三类风险改变后续调用的结果函数第一次调用写入缓存后第二次调用可能因为缓存已存在而走不同分支行为不可预期破坏递归、重入Reentrant与异步场景函数被递归调用或并发执行时多个执行上下文共享同一份顶层状态互相覆盖难以测试与推理函数是否修改了全局状态无法从函数签名上看出调用顺序会影响最终结果。规则文档给出的建议非常明确把状态保持在函数局部通过参数显式传入、通过返回值带出若确需共享就放进对象等显式的共享容器中只有当你真的需要模块级缓存之类的共享状态时才使用 ESLint disable 注释豁免。启用方式与配置说明该规则在插件预设配置中的启用情况如下见 readme.md 中的配置标记约定✅ 在recommended配置中默认启用源码 meta.docs.recommended 为true☑️ 在unopinionated配置中默认禁用属于带主观风格取向的规则。使用 flat config 时只要在配置中extends预设即可生效import unicorn from eslint-plugin-unicorn; import {defineConfig} from eslint/config; export default defineConfig([ // … { files: [**/*.js], plugins: { unicorn, }, extends: [ unicorn/recommended, ], rules: { // 如需关闭或降级可在此覆盖 unicorn/no-top-level-assignment-in-function: off, }, }, ]);该规则是纯“问题检测”型规则meta.type为problemschema为空数组rules/no-top-level-assignment-in-function.js#L67-L72不提供自动修复autofix因为将顶层状态迁移到函数局部通常需要结合业务逻辑手工重构。规则声明适用于js/js语言meta.languages因此也可配合 TypeScript 解析器使用见下文测试用例。典型示例什么会被报告什么不会文档给出了两组正反对照// ❌ 错误函数内给顶层变量赋值 let cache; function build() { cache new Map(); }// ✅ 正确状态局限在函数内部 function build() { const cache new Map(); }// ❌ 错误箭头函数内的更新表达式同样命中 let index 0; const next () index;// ✅ 正确把共享状态显式放进对象规则不拦截属性写入 const state { index: 0, }; const next () state.index;第二组示例揭示了规则的重要边界文档明确说明也由源码实现它只拦截对顶层绑定本身的直接写入赋值、/--、复合赋值等不拦截对对象属性的修改——因为state.index修改的是对象内部状态而非重新绑定state这个名字。规则的判定边界只报“顶层绑定 直接写入 位于函数内”从 规则源码 可以精确还原出它的三条判定条件三者必须同时满足才会报告目标是顶层作用域的变量通过sourceCode.getScope(program)拿到 Program 顶层作用域及其子作用域scope.childScopes只保留scope.block program的作用域再收集其中有实际声明variable.defs.length 0的变量rules/no-top-level-assignment-in-function.js#L26-L32。因此非顶层外层作用域的变量例如if块内声明的let cache不受影响通过/* global cache:writable */注释声明的全局变量不被纳入因为它们在当前文件没有defs。是直接写入而非初始化引用必须满足!reference.init reference.isWrite()rules/no-top-level-assignment-in-function.js#L47-L50。reference.init为真表示这是let x;声明处的初始化写入不报告isWrite()覆盖赋值、、||、??、/--、解构赋值、for...in/for...of循环变量等所有写引用。写入点位于函数内部isInsideFunction从写入节点的父节点开始向上遍历 AST若遇到FunctionDeclaration、FunctionExpression或ArrowFunctionExpression任一类型见 rules/ast/function-types.js即视为“在函数内”若先遇到Program则判定为不在函数内rules/no-top-level-assignment-in-function.js#L8-L24。最终报告信息为Do not assign to top-level variable \{{name}} from inside a function.其中{{name}} 是顶层变量名rules/no-top-level-assignment-in-function.js#L4-L6。注意类内部的写法也受该规则约束。虽然类静态块static { … }和类字段初始化器本身不是函数类型节点但字段值中的箭头函数或方法体内的写入会被判定为“在函数内”从而报告详见下文测试。测试用例对判定边界的全面验证规则配套的测试 test/no-top-level-assignment-in-function.js 使用快照断言test/snapshots/no-top-level-assignment-in-function.js.md其valid/invalid分组完整覆盖了上述所有边界可用于加深理解不会报告valid的情形顶层直接写入let cache; cache new Map();——在函数外放行函数内读取顶层变量return cache——只读不写放行函数内修改对象的属性state.cache new Map()——非绑定写入放行函数内写入函数内部声明的局部变量——不属于顶层绑定放行函数内写入外层块作用域if块内声明的变量——非顶层作用域放行函数参数遮蔽function build(cache) { cache new Map(); }——写入的是参数放行对import绑定只读引用return cache——放行类静态块/类字段中直接给顶层变量赋值static { cache new Map(); }、field (cache new Map())——写入点不位于函数节点内放行带/* global cache:writable */的全局写入——无顶层defs放行TypeScript 场景下仅将顶层变量用作类型type Cache typeof cache——不产生写引用放行。会报告invalid的情形函数声明、函数表达式、箭头函数内写入顶层变量三种函数形态全部覆盖嵌套函数内写入顶层变量function reset() { cache new Map(); }——向上遍历仍能找到函数祖先类方法内写入顶层变量class Builder { build() { cache new Map(); } }各种写操作符index、--index、count 1、cache || value、cache ?? value对函数声明的重新赋值function cache() {} … cache undefined——函数声明也是顶层绑定对import绑定赋值cache new Map()——导入绑定同样在顶层作用域且有defsif (enabled) { var cache; }中的var声明后函数内赋值——var提升到函数/模块顶层作用域命中类字段值中的箭头函数内写入field () { cache new Map(); }——箭头函数是函数节点命中解构赋值({cache} object)、[cache] array与for (key in object)/for (value of values)循环变量写入TypeScript 场景下函数内实际赋值cache value——使用 TypeScript 解析器parsers.typescript时同样生效。修复策略与实践建议当规则报告时按以下优先级重构与文档建议一致局部化把状态移入函数内部用局部变量承载const cache new Map()显式传递需要跨调用共享时通过参数传入、返回值带出让数据流在函数签名中可见显式共享容器确实需要模块级共享状态时放进对象如const state {…}通过属性读写而非重新绑定顶层变量豁免确需模块级缓存等共享状态的场景在写入处加 ESLint 注释禁用该行规则let cache; // eslint-disable-line no-top-level-assignment-in-function function build() { cache new Map(); }小结no-top-level-assignment-in-function通过“顶层绑定 直接写引用 写入点位于函数内”三条件精确识别隐藏共享状态的产生点是recommended配置下防止模块状态被函数隐式篡改的实用防线。它不提供自动修复但结合其清晰的边界放行属性写入、局部变量与只读引用配合 官方文档、规则源码 与 测试用例你可以放心启用并在团队内推广用显式的参数与返回值替代隐式的模块级状态变更。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表