ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

eslint-plugin-unicorn 的 prefer-abort-signal-any 规则:用 `AbortSignal.any()` 替代手动转发 abort 事件

eslint-plugin-unicorn 的 prefer-abort-signal-any 规则:用 `AbortSignal.any()` 替代手动转发 abort 事件 eslint-plugin-unicorn 的 prefer-abort-signal-any 规则用AbortSignal.any()替代手动转发 abort 事件【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn导读prefer-abort-signal-any是 eslint-plugin-unicorn 提供的一条代码风格规则rule type 为suggestion它检测代码中手动创建AbortController并逐个监听输入信号的abort事件来转发中止信号的桥接模式建议改用标准 APIAbortSignal.any()组合多个信号。本文基于 docs/rules/prefer-abort-signal-any.md 展开并结合 rules/prefer-abort-signal-any.js 的源码实现与 test/prefer-abort-signal-any.js 的测试用例深入说明该规则的触发条件、忽略场景、编辑器建议修复suggestion的生成逻辑及其在 JavaScript / TypeScript 下的行为差异帮助你在真实项目中理解并安全地启用这条规则。规则背景为什么推荐AbortSignal.any()AbortSignal.any()是 Web 平台提供的一个静态方法它会创建一个新的AbortSignal当传入的任意一个输入信号被中止时该组合信号也随之中止。相比手写桥接逻辑它有两大优势省去多余的AbortController不再需要中转控制器来统一管理多个来源的中止省去手动转发事件不再需要为每个输入信号分别addEventListener(abort, ...)并把事件转发到控制器。在AbortController/AbortSignal成为浏览器与 Node.js 通用基础设施fetch、流、定时器等的今天AbortSignal.any()是组合多个中止信号的标准做法。启用状态该规则在 ✅recommended配置中启用在 ☑️unopinionated配置中禁用。这条配置事实与规则元数据一致——源码 rules/prefer-abort-signal-any.js 中docs.recommended为true。修复方式本规则不提供自动修复fix而是通过编辑器建议suggestion手动触发修复对应元数据中的hasSuggestions: true。规则触发的典型模式规则针对的是源码中getDirectBridge直接桥接与getForOfBridge循环桥接两种可识别的桥接结构。下面先看规则文档给出的核心示例。循环转发for-of 桥接——应报告// ❌ const abortController new AbortController(); for (const signal of signals) { signal.addEventListener(abort, () abortController.abort()); } await fetch(url, {signal: abortController.signal});// ✅ const abortSignal AbortSignal.any(signals); await fetch(url, {signal: abortSignal});逐条转发直接桥接——应报告// ❌ const abortController new AbortController(); firstSignal.addEventListener(abort, () abortController.abort()); secondSignal.addEventListener(abort, () abortController.abort()); await fetch(url, {signal: abortController.signal});// ✅ const abortSignal AbortSignal.any([firstSignal, secondSignal]); await fetch(url, {signal: abortSignal});注意两种模式的修复产物略有差异for-of 模式直接使用循环所迭代的数组变量AbortSignal.any(signals)而直接桥接模式则把逐条监听的对象收集成数组字面量AbortSignal.any([firstSignal, secondSignal])。从源码看这一行为由 getForOfBridge 与 getDirectBridge 分别构造 replacement 文本实现。带其他行为的控制器被有意忽略规则非常保守只有当AbortController的唯一用途就是通过.signal把合并结果传给消费方时才值得替换。文档明确给出了一个控制器还有其他用途的忽略示例// ✅ const abortController new AbortController(); signal.addEventListener(abort, () abortController.abort()); button.addEventListener(click, () abortController.abort()); await fetch(url, {signal: abortController.signal});这里控制器除了响应signal的 abort 事件外还被按钮点击事件主动调用abort()因此被规则视为有其他行为不会报告。这与源码中getSignalMembers的检查一致它要求变量abortController的所有引用要么是桥接事件里的abort()调用要么是.signal成员访问getSignalMembers一旦出现其他引用方式如赋值写入、别名、abortController.abort()的直接调用就会放弃报告。报告的限定条件只处理简单模式规则文档强调它只报告控制器除此之外仅通过.signal使用的简单模式。具体而言以下情况会被忽略对应源码中的getSignalMember守卫见 rules/prefer-abort-signal-any.js检查组合信号的 abort reason如读取abortController.signal.reason或调用abortController.signal.throwIfAborted()。原因在于AbortSignal.any()保留的是第一个被中止的输入信号的 reason而手动桥接通常不会等价地保留 reason甚至可能把 reason 丢失。源码用reasonSensitiveProperties集合reason、throwIfAborted识别这类读取reasonSensitiveProperties对信号做别名如const signal abortController.signal;后再使用规则无法可靠地追踪别名后续的读写对.signal成员做写入或作为 for-in/for-of 左值在控制器声明或.signal相关代码附近存在注释源码通过hasCommentInRange、isStatementCommentFree等检查确保被删除/改写的代码中没有可能承载语义的注释hasCommentBetween输入信号无法被确认为AbortSignal例如对普通EventTarget实例注册abort监听器或对AbortSignal.abort()这类已知已中止的信号再转发后者在组合结果中会改变AbortSignal.any()的 reason 语义均不会报告相关检查见 hasKnownAlreadyAbortedSignal 与 isDirectBridgeSource。建议修复suggestion的生成细节当规则命中时它不会自动改写代码而是抛出一条带建议的报错报错信息Prefer AbortSignal.any() over manually forwarding abort events between signals.建议信息Replace with AbortSignal.any().修复逻辑在 createProblem 的suggest.fix中完成大致包括四步用AbortSignal.any(...)表达式替换new AbortController()初始化如果声明带有 TypeScript 类型注解则把类型改为: AbortSignal变量名abortController→abortSignal、controller→signal的重命名会先通过hasNameConflict检查新名字是否与作用域内已有变量冲突冲突则保留原名见 getReplacementName删除所有桥接的addEventListener语句removeStatementGroup会连同整行空白一起清理见 removeStatementGroup。测试快照 test/snapshots/prefer-abort-signal-any.js.md 展示了实际修复输出例如对下面的输入const abortController new AbortController(); firstSignal.addEventListener(abort, () abortController.abort()); secondSignal.addEventListener(abort, () abortController.abort()); fetch(url, {signal: abortController.signal});建议修复后的代码为const abortSignal AbortSignal.any([firstSignal, secondSignal]); fetch(url, {signal: abortSignal});可被识别的输入信号来源为了让替换在语义上安全规则只接受确定是 AbortSignal的输入来源。从源码可以归纳出以下几类被接受的形式标识符包括变量名以signal结尾或恰为signal例如firstSignal、secondSignalisSignalLikeNameAbortSignal静态调用AbortSignal.timeout(...)这类调用可直接作为输入快照 invalid(2) 即验证了AbortSignal.any([AbortSignal.timeout(1000), secondSignal])的修复数组字面量 /Array.of/new Array/Array(...)/ 单参数Array.fromfor-of 桥接中只要迭代源是这些可静态展开的数组形态且元素均为直接桥接源就可以被合并相关实现见 getKnownArrayElements 与 isAllowedArrayCompositionSource。反之以下来源会导致放弃报告测试的 valid 用例中有大量对应覆盖迭代源是Set、Array.from带映射回调、new Set等不可静态分析的集合元素个数为 0 或 1组合没有意义数组中混入abortController.signal自身形成自引用信号来自函数调用如getSignal()等。TypeScript 支持类型感知与只读数组规则对 TypeScript 提供了两层支持语法层在未开启类型检查parser 无program时通过类型注解推断例如signals as AbortSignal[]、readonly AbortSignal[]、元组[AbortSignal, AbortSignal]、as const断言等都会被识别。当类型注解明确是EventTarget[]时如 valid 用例function compose(signals: EventTarget[])则判定输入不是 AbortSignal不会误报类型感知层当通过parserOptions.projectService等开启类型信息后测试中的typeAware用例会调用 TypeScript 的getTypeChecker沿类型图联合类型、交叉类型、类型约束、基类递归判断某表达式是否为AbortSignal或AbortSignal[]核心实现在 isAbortSignalType 与 isReadonlyArrayType。此外针对只读数组readonly AbortSignal[]、ReadonlyArrayAbortSignal、元组、as const修复时会生成数组拷贝[...signals]以避免AbortSignal.any()的规范行为它不持有输入数组引用但拷贝能防御只读语义下的后续变异。这一逻辑集中在 needsArrayCopyForAbortSignalAny 与 getAbortSignalAnyArgumentText并由 isForOfArray 决定 for-of 桥接是否成立。桥接回调的形态约束规则对addEventListener(abort, callback)的回调做了严格限定只有满足以下条件才被认为是纯转发getCallbackExpression 与 getAbortReference回调必须是箭头函数或普通函数表达式不能是 async 或 generator 函数回调不能有参数带event参数的回调不匹配回调体只能是单个表达式或只含一条表达式语句的块表达式必须是abortController.abort()可选地携带sourceSignal.reason如abortController.abort(firstSignal.reason)——规则认可这种显式转发 reason的形式因为它在语义上等价于AbortSignal.any()的 reason 行为监听器的第三个参数options只能是布尔字面量或只含capture/once/passive布尔字面量的对象或直接省略isAllowedListenerOptions。这些约束在 test/prefer-abort-signal-any.js 的 valid / invalid 用例中都有体现例如async () abortController.abort()、event abortController.abort()、回调内附带cleanup()、firstSignal?.addEventListener(...)可选链等均被视为不安全而放行。如何在项目中使用启用规则该规则已包含在recommended配置中直接使用 unicorn 的推荐配置即可// eslint.config.jsflat config import unicorn from eslint-plugin-unicorn; export default [ unicorn.configs[flat/recommended], ];或在传统.eslintrc中{ extends: [plugin:unicorn/recommended] }如果你不希望启用该规则例如项目需要兼容不支持AbortSignal.any()的老环境可以显式关闭{ rules: { unicorn/prefer-abort-signal-any: off } }该规则无任何可配置项schema: []默认即开启。需要说明的是AbortSignal.any()是较新的 Web API启用前请确认目标运行环境支持该静态方法。规则不适用于哪些场景综合文档与源码以下场景不会被报告可放心使用控制器还被按钮、定时器等其他来源主动调用abort()组合信号的 reason 或throwIfAborted()被消费信号被别名引用、赋值给新变量或写入成员桥接语句之间或控制器声明处存在注释规则宁可放过也不破坏注释语义输入信号无法被静态确认为AbortSignal尤其是类型感知模式下被明确标注为EventTarget的情况已知已中止的信号AbortSignal.abort()结果被作为桥接输入。小结prefer-abort-signal-any是 eslint-plugin-unicorn 中一条少即是多的现代化规则它只瞄准一个非常具体的反模式——用AbortController手工桥接多个AbortSignal的 abort 事件——并以编辑器建议的形式安全地改写为AbortSignal.any()。从 源码实现 与 测试快照 可以看到它在识别桥接模式、处理 reason 语义、支持 TypeScript 类型推断以及生成安全修复方面做了大量保守而精细的守卫因此在启用recommended配置的仓库中可以放心让这条规则帮你持续清理手写的中止转发样板代码。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表