news 2026/9/18 4:13:20

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 中,getset访问器的核心特征是:读写属性时会自动调用对应的访问器函数。因此,如果在 getter 内部再次读取同名属性:

const foo = { get bar() { return this.bar; } };

当代码执行foo.bar时,读取操作会再次触发 getter,getter 又读取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}}会被替换为getset):

// ❌ 对象字面量 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访问的键名与访问器键名是否一致。具体分为三步:

第一步:找到最近的函数作用域。工具函数getClosestFunctionScope(rules/no-accessor-recursion.js)从当前节点沿scope.upper向上遍历,一旦遇到class作用域立即终止(避免穿透到外层类),并跳过箭头函数——因为箭头函数没有自己的this,它内部的this属于外层函数。因此,getter 内嵌套的箭头函数里写this.bar同样会被识别为递归。

第二步:确认该函数体属于合法的访问器。isValidProperty(rules/no-accessor-recursion.js)要求父节点是Property(对象字面量成员)或MethodDefinition(类成员),kindgetset,且键名不是计算属性(!property.computed)。

第三步:按访问器种类分别判定"读"与"写"。

  • getter 中调用isPropertyRead(rules/no-accessor-recursion.js),它由两条路径组成:isMemberAccess(点号访问,如this.bar)与isRecursiveDestructuringAccess(解构访问,如const {bar} = this)。
  • setter 中调用isPropertyWrite(rules/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))。

值得一提的实现细节是isSameKey(rules/no-accessor-recursion.js)通过比较节点的typename两个字段判断键名是否相同,因此它天然支持私有字段PrivateIdentifier,如this.#bar)的同名递归检测,同时isIdentifier(rules/no-accessor-recursion.js)将IdentifierPrivateIdentifier一并纳入点号访问的判断范围。

规则的检测边界:哪些"看似递归"的写法不会误报

通过 test/no-accessor-recursion.js 中的valid用例列表,可以清晰还原规则精心设计的放行边界,避免对合理写法产生误报:

  1. 普通方法不受影响bar()方法体内this.bar = void 0; return this.bar;合法(方法与访问器是两套键名空间之外的行为)。
  2. 键名不同的访问:getter 内读this._barthis.baz等不同名属性合法;同一类中 getterbar读私有字段this.#bar(且#bar是独立 getter)也合法。
  3. 深属性链:setter 内写this.bar.baz = value合法——它只修改bar的深层子属性,不会再次触发bar的 setter(测试用例注释为 "Deep property setter")。
  4. this的别名const self = this; return self.bar;合法——规则只追踪字面量this表达式,不追踪引用别名(测试用例注释为 "Define this to alias")。
  5. 计算属性:getter 键名为[bar]的访问器、或内部用this[bar]访问(方括号内不是标识符键)均合法,因为计算键无法静态确定是否同名。
  6. 函数作用域隔离:getter 内嵌套的普通函数function baz() { return this.bar; }合法——该函数有自己的this;getter 内嵌套的对象字面量里的其他 getter 互不影响。
  7. setter 右侧读取:setter 内a = this.bar合法——等号右侧是读取bar,而 setter 递归只关心对bar写入
  8. 静态块与类字段: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 })、静态 getter(static 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),在插件配置中按需声明即可;由于它默认包含在recommendedunopinionated两个预设中,多数用户无需显式书写。如果你希望单独显式开启它:

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、CommonJS(sourceType: '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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 4:11:46

鸿蒙预览场景模拟:@ohos/hamock 模拟框架详解与源码级实践指南

鸿蒙预览场景模拟:ohos/hamock 模拟框架详解与源码级实践指南 【免费下载链接】cann-recipes-harmony-infer 本项目为鸿蒙开发者提供基于CANN平台的业务实践案例,方便开发者参考实现端云能力迁移及端侧推理部署。 项目地址: https://gitcode.com/cann/…

作者头像 李华
网站建设 2026/9/18 4:09:23

Agent-Reach实践:构建多智能体系统的统一触达层

1. 项目概述与核心问题1.1 从"Agent能做什么"到"Agent怎么被找到""Agent-Reach"这个名字乍一看有点抽象,但如果拆开理解就很直白:Agent代表智能体,Reach代表触达、覆盖、到达。合起来,它解决的问题…

作者头像 李华
网站建设 2026/9/18 4:09:19

当 Gemini 3.8 Live 全双工对话,TaoToken Key 如何分并发

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华