eslint-plugin-unicorn 规则实战:`no-unnecessary-fetch-options` 详解
2026/9/18 21:19:02 网站建设 项目流程

eslint-plugin-unicorn 规则实战:no-unnecessary-fetch-options详解

【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn

unicorn/no-unnecessary-fetch-options是 eslint-plugin-unicorn(300+ 条 ESLint 规则集)中的一条自动修复型建议规则,用于检测并移除fetch()new Request()调用中多余、等价于省略的RequestInit选项。读完本文,你将掌握该规则的全部触发场景、其背后"输入是否为Request"的类型判断机制、自动修复策略与边界行为,并能在自己的 flat config 中正确启用并驾驭它。

规则概览:为什么要移除多余的 fetch 选项

fetch(url, {method: 'GET'})这类写法在语法上完全合法,但它把默认值显式写了出来。正如规则文档(docs/rules/no-unnecessary-fetch-options.md)所说:多余的选项会让请求更难一眼扫清。显式写出与默认值相同的选项,要么暴露写作者对 API 的不熟悉,要么是复制粘贴遗留的冗余,无助于可读性。

因此本规则的目标是:移除那些"省略等价"的选项——即去掉后行为完全不变。规则元数据(rules/no-unnecessary-fetch-options.js#L629-L644)显示:

  • 规则类型:suggestion(建议性,不涉及正确性问题);
  • 可修复:fixable: 'code',可通过 ESLint 的--fixCLI 选项 自动处理;
  • 无配置项:schema: [],即开即用,不需要也不支持任何参数;
  • 目标语言:js/js

安装与启用(flat config)

该插件要求ESLint>=10.4、flat config 以及 ESM项目(详见 readme.md)。安装:

npm install --save-dev eslint eslint-plugin-unicorn

本规则已默认包含在recommendedunopinionated两套预设配置中。如果使用预设,无需单独声明;若要单独启用,在eslint.config.js中配置:

import unicorn from 'eslint-plugin-unicorn'; import {defineConfig} from 'eslint/config'; import globals from 'globals'; export default defineConfig([ { files: ['**/*.js'], languageOptions: { globals: globals.builtin, }, plugins: { unicorn, }, rules: { 'unicorn/no-unnecessary-fetch-options': 'error', }, }, ]);

触发场景一:空选项对象

最简单的情况是传入一个空对象作为第二个参数,它完全等价于不传该参数:

// ❌ 错误 await fetch('/', {}); // ✅ 正确 await fetch('/');
// ❌ 错误 new Request(url, {}); // ✅ 正确 new Request(url);

源码中对应MESSAGE_ID_EMPTY_OPTIONS(rules/no-unnecessary-fetch-options.js#L31-L37),报错信息为"Remove unnecessary empty fetch options."

触发场景二:与默认值相等的选项

RequestInit中有一批选项,其取值与规范默认值相同时即可安全移除。源码维护了一张默认值表defaultValues(rules/no-unnecessary-fetch-options.js#L59-L69):

选项默认值触发示例
method'GET'fetch('/', {method: 'GET'})
credentials'same-origin'fetch('/', {credentials: 'same-origin'})
cache'default'fetch('/', {cache: 'default'})
mode'cors'fetch('/', {mode: 'cors'})
redirect'follow'fetch('/', {redirect: 'follow'})
referrer'about:client'fetch('/', {referrer: 'about:client'})
referrerPolicy''fetch('/', {referrerPolicy: ''})
integrity''fetch('/', {integrity: ''})
keepalivefalsefetch('/', {keepalive: false})

原文档示例:

// ❌ 错误 await fetch('/', {method: 'GET'}); // ✅ 正确 await fetch('/');
// ❌ 错误 await fetch('/', {credentials: 'same-origin'}); // ✅ 正确 await fetch('/');

关于method有一个细节:比较时大小写不敏感。源码isDefaultValue(rules/no-unnecessary-fetch-options.js#L433-L444)对method先做toUpperCase()再比较,因此'get''Get'等写法同样会被识别并移除:

// ❌ 错误(小写/混合大小写的 GET 也会被移除) await fetch(`https://example.com`, {method: 'get'}); await fetch(new URL(url), {method: 'Get'});

需要强调:默认值判定基于静态值分析。测试用例(test/no-unnecessary-fetch-options.js)验证了const method = "GET"; fetch("/", {method})fetch("/", {method:GET})fetch("/", {"method": "GET"})fetch("/", {["method"]: "GET"})等写法都会被报错,而fetch(url, {method})method为未定值标识符)则不会误报。

触发场景三:undefinednull的等价写法

对于RequestInit中列出的全部合法属性名(源码中的requestInitProperties集合,见 rules/no-unnecessary-fetch-options.js#L39-L57,共 17 个:attributionReportingbodybrowsingTopicscachecredentialsduplexheadersintegritykeepalivemethodmodepriorityredirectreferrerreferrerPolicysignalwindow),显式赋值为undefined与省略等价,一律移除:

// ❌ 错误 await fetch(url, {signal: undefined}); // ✅ 正确 await fetch(url);

同样地,body: null表示"无请求体",等价于省略,也会被移除:

// ❌ 错误 new Request(url, {body: null}); // ✅ 正确 new Request(url);

注意区分:body: undefined(命中属性名集合 + undefined 规则)和body: null(专门的isStaticNull分支,见 rules/no-unnecessary-fetch-options.js#L454-L459)都会被移除;但body: ""是合法的"空字符串请求体",不会被移除。

触发场景四:空的headers

当输入确定为非Request时,headers的三种空值写法(空对象{}、空数组[]、无参的new Headers())等价于省略(对应isEmptyHeaders判定,rules/no-unnecessary-fetch-options.js#L297-L313):

// ❌ 错误 fetch('/', {headers: {}}); fetch('/', {headers: []}); fetch('/', {headers: new Headers()});

关键边界:输入是否为Request决定默认值是否成立

这是本规则最核心的设计点。规则文档明确指出:

SomeRequestInitdefaults are only equivalent when the input is known not to be an existingRequest, because omitted options inherit from the input request.

fetch()/new Request()第一个参数本身是一个Request对象时,省略的选项会继承自该输入请求,而非采用规范默认值。因此"与默认值相等"这一判断只在输入确定不是Request时成立。源码用三态建模(rules/no-unnecessary-fetch-options.js#L71-L73):

  • request:输入确定是Request(例如new Request(url)字面量、类型标注为Request的变量);
  • non-request:输入确定不是Request(字符串字面量、无表达式的模板字符串、new URL(...)、TS 类型为string/URL/String等);
  • unknown:无法确定(普通标识符url、无 TS 类型信息等)。

判定逻辑见getInputState(rules/no-unnecessary-fetch-options.js#L387-L431),isUnnecessaryProperty(rules/no-unnecessary-fetch-options.js#L446-L473)中的规则是:

  • 无论输入状态如何:属性值静态为undefined、或bodynull,一律移除(因为继承也不会改变"无值"语义);
  • 仅当输入为non-request:空headers与"等于默认值"的选项才会被移除;
  • 输入为request{method: 'GET'}{mode: 'cors'}等默认值写法不会被移除,因为它们覆盖了继承自输入请求的值,是有意义的。

这正是原文档最后一个示例的含义:

// ✅ 正确(第一个参数是 Request,显式 method 会覆盖继承值,不能移除) await fetch(request, {method: 'GET'});

测试用例中也有大量对照验证:fetch(url, {method: "GET"})url未知)有效、fetch(request, {method: "GET"})有效、fetch(new Request(url, {method: "POST"}), {method: "GET"})有效;而fetch("https://example.com", {method: "GET"})new Request("https://example.com", {method: "GET"})无效。

类型感知(TypeScript)判定

当使用 TypeScript parser(如@typescript-eslint/parser并开启 project 服务)时,规则会借助类型信息深入判断。getInputTypeState(rules/no-unnecessary-fetch-options.js#L349-L385)会:

  • 展开联合类型 / 交叉类型,若其中含Request则保守地按request处理;
  • 递归取getBaseConstraintOfType与基类型;
  • string字面量、URLString等明确判为non-request
  • 对默认库符号(isDefaultLibrarySymbol)才采信其类型名,避免被用户自定义的Request类干扰。

测试中的typeAware用例(test/no-unnecessary-fetch-options.js#L7-L14)验证了:declare const request: Request; fetch(request, {method: "GET"})有效,而declare const url: string; fetch(url, {method: "GET"})无效、declare const input: Request | string; fetch(input, {method: "GET"})因含Request而保持有效(不误报)。

自动修复:注释保护与多种移除策略

规则的自动修复相当精细,核心目标是在不丢失注释、不引入副作用的前提下完成安全的代码变换。修复入口见getFixgetWholeOptionsFix(rules/no-unnecessary-fetch-options.js#L475-L533),并按优先级依次尝试:

  1. 整对象移除:当所有属性都不必要、且对象内无注释时,直接把整个 options 实参删掉。例如fetch('/', {method: 'GET', credentials: 'same-origin'})会被修复为fetch('/')(测试见 test/no-unnecessary-fetch-options.js#L185-L197),new Request('/', {method: 'GET', credentials: 'same-origin'})修复为new Request('/')。实参删除复用getArgumentRemovalRange(rules/fix/remove-argument.js#L14-L44),会一并处理尾随逗号、前后逗号及括号间隙。
  2. 部分属性移除:逐属性删除,支持整行移除(getPropertyLineRemovalRange)或行内移除(getPropertyInlineRemovalRange,rules/no-unnecessary-fetch-options.js#L197-L287),兜底使用通用工具removeObjectProperty(rules/fix/remove-object-property.js),自动处理属性前后的逗号。
  3. 仅剩一个属性时:如果对象中只剩一个可移除属性但对象还有其他"不可移除"内容(例如实参后面还有第三个参数),会退化为保留空对象{}fixer.replaceText(optionsNode, '{}'))。

修复过程有严格的安全护栏(isUnsafeToRemoveProperty,rules/no-unnecessary-fetch-options.js#L153-L159):

  • 副作用保护:属性值(或计算属性键)包含函数调用、成员访问等潜在副作用时,不自动修复。测试用例如fetch("/", {method: (sideEffect(), "GET")})虽然报错但不会自动修复;
  • 后置声明保护hasReferenceDeclaredAfter(rules/no-unnecessary-fetch-options.js#L118-L151)检测到属性值引用了声明在调用点之后的变量时,拒绝移除。测试用例fetch("/", {method, credentials: "same-origin"}); const method = "GET";只移除credentials,保留method(因为它引用的method在调用之后才声明);
  • 注释保护:选项对象内部含注释、属性行内有注释、实参后有注释等场景,要么跳过修复要么保留注释。例如fetch('/', {method: 'GET' /* keep */})报错但保留注释不自动修改,fetch(url, /* keep */ {})同理。

此外,静态值分析还会刻意绕过"可能被改写"的值:通过 getter、Object.definePropertySet等可变对象派生出的值不会被当作默认值移除(见测试中大量Object.definePropertymodes.clear()用例),确保修复绝不改变运行时行为。

不触发的情形与限制

以下写法规则不会报告,值得留意:

  • 第一个参数是标识符(fetch(url, {method: "GET"})fetch(url, {mode: "cors"})),因为url状态未知,无法证明默认值等价;
  • fetch(url, options)——options 不是对象字面量,无法静态分析;
  • 非全局的fetch/Request:源码通过isGlobalIdentifierNamed校验(rules/no-unnecessary-fetch-options.js#L596-L623),const fetch = () => {}; fetch(url, {})new NotRequest(url, {})等均不会误报;
  • 属性名含计算键、重复键、展开符(如fetch(url, {...options, method: "GET"})fetch(url, {[key]: value, method: "GET"}))时因无法确定属性名集合而跳过(getObjectPropertyNames,rules/no-unnecessary-fetch-options.js#L315-L330)。

结语

unicorn/no-unnecessary-fetch-options的价值在于把"与默认值相等"这种语义冗余从代码中系统性清除,同时通过"输入是否为Request"的类型三态判定、副作用与后置声明保护、注释感知的修复策略,将误报与破坏性修复降到最低。如果你已经在使用recommended预设,它已经在守护你的 fetch 调用;结合 TypeScript parser 使用时,还能获得更精确的类型级判断。

【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询