ARTICLE DETAIL

资讯详情

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

eslint-plugin-unicorn 的 no-accessor-recursion 规则:彻底拦截 getter/setter 中的递归访问

eslint-plugin-unicorn 的 no-accessor-recursion 规则:彻底拦截 getter/setter 中的递归访问 eslint-plugin-unicorn 的 no-accessor-recursion 规则彻底拦截 getter/setter 中的递归访问【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn导读no-accessor-recursion是 eslint-plugin-unicorn 提供的一条问题型problem规则专门用来禁止在对象与类的 getter/setter 访问器中通过this递归访问自身同名属性从源头避免无限递归导致的栈溢出RangeError: Maximum call stack size exceeded。阅读本文后你将掌握该规则的全部触发场景与放行边界、其基于 ESLint 作用域分析的底层实现原理以及如何在项目配置中启用它。为什么要禁止访问器内的this递归访问在 JavaScript 中get与set访问器的核心特征是读写属性时会自动调用对应的访问器函数。因此如果在 getter 内部再次读取同名属性const foo { get bar() { return this.bar; } };当代码执行foo.bar时读取操作会再次触发 gettergetter 又读取this.bar……如此循环往复直到调用栈被耗尽抛出RangeError: Maximum call stack size exceeded。setter 同样如此const foo { set bar(value) { this.bar value; } };foo.bar 1会反复触发 setter形成死循环。这类 bug 在大型代码库中一旦混入往往要在运行时才能暴露且错误信息对定位根因帮助有限。该规则的职责就是在静态分析阶段把这种写法直接标记出来。规则的启用状态与定位规则类型problem问题型用于发现可能导致运行时错误的代码。默认启用配置✅recommended、☑️unopinionated两个预设配置中均默认开启源码见 rules/no-accessor-recursion.js 中的meta.docs.recommended声明。默认无任何可配置选项defaultOptions: []开箱即用属于零配置规则。支持语言js/js当前仓库中绝大多数规则均面向 JavaScript。由于它属于默认开启的规则使用官方recommended配置集或unopinionated配置集的用户会自动获得该检查无需额外配置。项目中配置预设的入口可参考 configs 目录下的配置文件。规则会报错的反模式示例以下四组代码分别覆盖了对象 getter、类 getter、对象 setter、类 setter四种最典型的错误写法均为该规则会报告no-accessor-recursion/error的场景消息文本为Disallow recursive access tothiswithin {{kind}}ters.其中{{kind}}会被替换为get或set// ❌ 对象字面量 getter const foo { get bar() { return this.bar; } };// ❌ 类 getter class Foo { get bar() { return this.bar; } }// ❌ 对象字面量 setter const foo { set bar(value) { this.bar value; } };// ❌ 类 setter class Foo { set bar(value) { this.bar value; } }四段代码的共同特征完全一致访问器内部对this的成员访问键名与访问器自身键名相同。这正是判定递归的充要条件。从源码看检测原理三步定位递归规则实现位于 rules/no-accessor-recursion.js核心思路清晰且工程化监听每个ThisExpression节点沿作用域链向上找到最近的函数作用域再判断该函数是否正好是某个 getter/setter 的函数体最后核对this访问的键名与访问器键名是否一致。具体分为三步第一步找到最近的函数作用域。工具函数getClosestFunctionScoperules/no-accessor-recursion.js从当前节点沿scope.upper向上遍历一旦遇到class作用域立即终止避免穿透到外层类并跳过箭头函数——因为箭头函数没有自己的this它内部的this属于外层函数。因此getter 内嵌套的箭头函数里写this.bar同样会被识别为递归。第二步确认该函数体属于合法的访问器。isValidPropertyrules/no-accessor-recursion.js要求父节点是Property对象字面量成员或MethodDefinition类成员kind为get或set且键名不是计算属性!property.computed。第三步按访问器种类分别判定读与写。getter 中调用isPropertyReadrules/no-accessor-recursion.js它由两条路径组成isMemberAccess点号访问如this.bar与isRecursiveDestructuringAccess解构访问如const {bar} this。setter 中调用isPropertyWriterules/no-accessor-recursion.js除了基础的点号访问外还显式覆盖了多种写语义赋值表达式与默认值模式this.bar value、[this.bar value] ...、自增自减this.bar、解构赋值目标[this.bar] arr、[...this.bar] arr、for...of/for...in循环左值、以及对象模式属性({property: this.bar} obj)。值得一提的实现细节是isSameKeyrules/no-accessor-recursion.js通过比较节点的type与name两个字段判断键名是否相同因此它天然支持私有字段PrivateIdentifier如this.#bar的同名递归检测同时isIdentifierrules/no-accessor-recursion.js将Identifier与PrivateIdentifier一并纳入点号访问的判断范围。规则的检测边界哪些看似递归的写法不会误报通过 test/no-accessor-recursion.js 中的valid用例列表可以清晰还原规则精心设计的放行边界避免对合理写法产生误报普通方法不受影响bar()方法体内this.bar void 0; return this.bar;合法方法与访问器是两套键名空间之外的行为。键名不同的访问getter 内读this._bar、this.baz等不同名属性合法同一类中 getterbar读私有字段this.#bar且#bar是独立 getter也合法。深属性链setter 内写this.bar.baz value合法——它只修改bar的深层子属性不会再次触发bar的 setter测试用例注释为 Deep property setter。this的别名const self this; return self.bar;合法——规则只追踪字面量this表达式不追踪引用别名测试用例注释为 Define this to alias。计算属性getter 键名为[bar]的访问器、或内部用this[bar]访问方括号内不是标识符键均合法因为计算键无法静态确定是否同名。函数作用域隔离getter 内嵌套的普通函数function baz() { return this.bar; }合法——该函数有自己的thisgetter 内嵌套的对象字面量里的其他 getter 互不影响。setter 右侧读取setter 内a this.bar合法——等号右侧是读取bar而 setter 递归只关心对bar的写入。静态块与类字段getter 内嵌套类里的static { this.bar }、类字段初始化baz this.bar均合法——其this属于嵌套类本身。在invalid用例中除了四种基础反模式还覆盖了更深层的变体getter 内return this.bar.baz深链读取、块语句if (true) { return this.bar; }内的递归、多层嵌套箭头函数内的递归、等号右侧的 getter 递归读取a this.bar、私有字段同名递归get #bar() { return this.#bar }、静态 getterstatic get bar() { return this.bar }、以及解构递归const {bar} this。setter 的写操作变体则以数组批量构造了 9 种用例包括自增自减、数组解构、rest 解构、for...of/for...in左值等见 test/no-accessor-recursion.js。如何修复被标记的代码修复原则只有一条让访问器内部不再读写自己的同名键。最直接的做法是访问一个不同的属性作为底层存储// ✅ 对象getter 返回底层字段 const foo { get bar() { return this._bar; } };// ✅ 类setter 写入底层字段 class Foo { set bar(value) { this._bar value; } }对 setter 的合法修复还需要注意setter 通常与同名的 getter 成对出现用于读写控制。若确实需要暴露同名属性并附带副作用业界惯例是引入_bar之类的私有底层字段或类私有字段#bar作为真实存储访问器只作为门面。这也是文档示例 (docs/rules/no-accessor-recursion.md) 中采用的修复范式。在你的项目中启用与验证该规则随 eslint-plugin-unicorn 一起安装启用方式与其他规则完全一致。若使用扁平化配置flat config在插件配置中按需声明即可由于它默认包含在recommended与unopinionated两个预设中多数用户无需显式书写。如果你希望单独显式开启它import unicorn from eslint-plugin-unicorn; export default [ unicorn.configs[flat/recommended], // 已包含 no-accessor-recursion // 或按需开启 { plugins: {unicorn}, rules: { unicorn/no-accessor-recursion: error, }, }, ];规则无任何配置选项error与warn的差异仅在于上报级别。仓库自带的测试test/no-accessor-recursion.js同时覆盖了 ES module、CommonJSsourceType: commonjs与脚本sourceType: script三种源码类型验证了规则在不同模块体系下的稳定性对应的快照文件位于 test/snapshots 目录可通过npm run test:js内部使用 AVA执行回归验证。小结no-accessor-recursion通过静态作用域分析在编译期就拦截了 getter/setter 中针对this的同名递归读写覆盖面包括对象与类、读与写、点号与解构、公有键与私有键、以及多种赋值左值形态同时借助键名相同 最近函数作用域的精确判定对别名、深属性链、计算属性、嵌套函数等易混淆场景保持了克制。对于维护大型面向对象风格代码库的团队它是一条低成本、高收益的默认防线。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表