eslint-plugin-unicorn 规则实战:prefer-set-size —— 用Set#size替代Array#length
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
本篇文章以 eslint-plugin-unicorn 项目中prefer-set-size规则的文档、源码实现与快照测试为依据,系统讲解该规则的核心原理、自动修复行为与边界处理。读完你将掌握:什么代码会被该规则报告、[...set].length与Array.from(set).length两种模式的修复逻辑、哪些特殊情况会被安全跳过,以及如何通过 ESLint 的--fix一键完成替换。
规则概览
prefer-set-size是 eslint-plugin-unicorn(GitHub 推荐项目精选 / es / eslint-plugin-unicorn,提供 300+ 条 ESLint 规则)中一条专注于 JavaScriptSet用法的规则。其核心主张来自官方文档 docs/rules/prefer-set-size.md:
Set#size是获取Set中元素数量的直接方式。为了读取.length而把Set转成数组是低效的,也违背了使用 Set 的初衷。size属性是 O(1) 的,且不需要任何转换。
从规则元信息(rules/prefer-set-size.js)可以看到它的定位:
- 类型:
suggestion(建议型) - 可自动修复:
fixable: 'code',支持 ESLint 的--fixCLI 选项 - 推荐配置:
recommended(✅)与unopinionated(☑️) - 适用语言:
js/js - 报错信息:
Prefer using \Set#size` instead of `Array#length`.(消息 ID 为prefer-set-size`)
典型反模式:两种会被报告的写法
根据规则源码中的getSetNode函数(rules/prefer-set-size.js),该规则只针对两种具体的 AST 形态:
1. 展开语法 +length:[...set].length
// ❌ 触发规则 function isUnique(array) { return [...new Set(array)].length === array.length; } // ✅ 正确写法 function isUnique(array) { return new Set(array).size === array.length; }// ❌ 触发规则 const items = new Set([1, 2, 3, 4, 5]); if ([...items].length > 3) { // do something } // ✅ 正确写法 const items = new Set([1, 2, 3, 4, 5]); if (items.size > 3) { // do something }// ❌ 触发规则 const uniqueCount = [...new Set(userIds)].length; // ✅ 正确写法 const uniqueCount = new Set(userIds).size;2.Array.from+length:Array.from(set).length
// ❌ 触发规则 Array.from(new Set(array)).length; // ✅ 正确写法 new Set(array).size;检测条件非常严格:Array.from必须恰好只有一个参数(argumentsLength: 1),且不允许可选调用/可选成员访问(optionalCall: false、optionalMember: false)。因此Array.from(new Set(array), mapFn).length、Array?.from(...).length、Array.from?.(...).length都不会被报告。
触发条件与识别机制
外层必须是成员表达式.length
规则的create函数监听MemberExpression节点(rules/prefer-set-size.js),要求:
property严格为length;- 不允许可选访问(
optional: false),因此[...set]?.length会被跳过; - 必须是字面属性访问,因此
[...set]["length"]、[...set][length](计算成员)也都会被跳过。
内部必须是真正的Set
仅凭语法形态还不够,规则通过isSet()工具(rules/utils/is-set.js)做类型判定。该工具基于createBuiltinTypeCheckers(rules/utils/type-helpers.js)实现,将类型收敛为三种:target(确定是 Set)、non-target(确定不是)、unknown(无法确定)。其识别能力包括:
- 字面构造:
new Set(...)直接判定为目标; - 变量追踪:
const foo = new Set([]); [...foo].length会沿作用域链追踪变量定义(resolveIdentifierName),确认foo是 Set; - TypeScript 类型标注:对
Set<string>、ReadonlySet<string>等类型注解同样识别(ReadonlySet被注册为Set的别名); - 排除反例:
new NotSet(...).length、[...Set(array)].length(函数调用而非构造)、let foo = new Set([]); [...foo].length(可重新赋值的变量)、解构绑定的foo、重复var声明等,均被判定为non-target或unknown而跳过。
数组元素必须是单一的展开元素
[...set].length形态还要求数组表达式elements.length === 1且唯一元素是SpreadElement。因此[...new Set(array), foo].length、[foo, ...new Set(array)].length不会被误报。
自动修复:快照测试逐项验证的边界行为
快照文件 test/snapshots/prefer-set-size.js.md 记录了 20 个 invalid 用例的完整「输入 → 报错 → 修复输出」对照,来源是测试文件 test/prefer-set-size.js。以下按修复要点分类说明。
基础替换:.length→.size
最简单的场景,直接把属性名替换并删除多余的数组展开:
输入: [...new Set(array)].length 输出: new Set(array).size输入: Array.from(new Set(array)).length 输出: new Set(array).size变量场景:保持赋值与引用结构
当 Set 已绑定到变量时,修复只替换读取处,不动声明处:
输入: const foo = new Set([]); console.log([...foo].length); 输出: const foo = new Set([]); console.log(foo.size);输入: function getSize(set: Set<string>) { return Array.from(set).length; } 输出: function getSize(set: Set<string>) { return set.size; }多余括号:修复后同样得到清理
规则能识别并剥离[...(( new Set(array) ))]、(( [...new Set(array)] )).length、(( Array.from(new Set(array)) )).length等冗余括号层级:
输入: [...(( new Set(array) ))].length 输出: new Set(array).size输入: (( [...new Set(array)] )).length 输出: (( new Set(array) )).size注意最后一例:最外层的括号属于数组表达式外层、与成员访问连写((( ... )).length),修复后保留,因为去掉会改变语义。
注释保留
数组内或new与Set之间的注释在修复中会被完整保留:
输入: [/* comment */...new Set(array)].length 输出: new Set(array).size 输入: [...new /* comment */ Set(array)].length 输出: new /* comment */ Set(array).size实现上,createFix会对比array与set内部包含的注释数量(sourceCode.getCommentsInside,rules/prefer-set-size.js):若被删除的数组表达式内部注释多于set内部的注释,则放弃自动修复(只报错不修),避免丢失注释。
括号补全:TypeScript 类型断言场景
修复的核心动作是「把set文本替换为成员表达式的对象」。当set是 TypeScript 类型断言(如set as Set<string>)时,直接拼成set as Set<string>.size会解析出错,因此规则调用shouldAddParenthesesToMemberExpressionObject(rules/utils/should-add-parentheses-to-member-expression-object.js)判断是否需要加括号:
输入: function getSize(set: unknown) { return [...(set as Set<string>)].length; } 输出: function getSize(set: unknown) { return (set as Set<string>).size; } 输入: function getSize(set: unknown) { return Array.from(set as Set<string>).length; } 输出: function getSize(set: unknown) { return (set as Set<string>).size; }该工具按节点类型分策略:Identifier、CallExpression、TemplateLiteral等作为成员对象本就安全,返回false;NewExpression仅在已有括号时安全;其他类型(含 TS 断言)默认需要补括号。这条规则同样适用于no-unused-builtin-method-return、no-loop-iterable-mutation等复用isSet的其他规则。
关键字空格修复
在[...set].length场景中,array被替换为new Set(...)后,可能出现return new Set(array).size这种关键字(return)与新表达式紧贴的情况。因此createFix在数组表达式分支额外调用fixSpaceAroundKeyword(rules/fix/fix-space-around-keywords.js):检测替换范围前后的 token,若前/后是纯小写关键字(或of、await)且与范围紧邻,就插入一个空格。
输入: function isUnique(array) { return[...new Set(array)].length === array.length } 输出: function isUnique(array) { return new Set(array).size === array.length }语句开头场景
当数组表达式出现在语句开头(前面有分号)时,修复同样正确衔接:
输入: foo ;[...new Set(array)].length 输出: foo ;new Set(array).size如何在自己的项目中使用
该规则属于插件的一部分,无需单独配置。在你的 ESLint 配置中启用unicorn/prefer-set-size即可:
{ "plugins": ["unicorn"], "rules": { "unicorn/prefer-set-size": "error" } }由于规则fixable: 'code',可以直接用 ESLint 的--fix自动修复:
npx eslint . --fix如果你使用插件提供的recommended或unopinionated预设配置,该规则会自动启用。执行前建议先运行不带--fix的检查(npx eslint .)预览报告,再决定是否一键修复。
总结
prefer-set-size规则针对 JS 开发中常见的「把 Set 转成数组数长度」反模式,提供了完备的检测与安全自动修复:它严格限定.length成员访问与单一展开元素/单参数Array.from两种形态,通过isSet类型推理避免误报,并在修复时妥善处理括号、注释、关键字空格与 TypeScript 类型断言。其 20 个快照测试用例(test/snapshots/prefer-set-size.js.md、test/prefer-set-size.js)完整覆盖了这些边界行为,可以作为理解规则行为边界的权威参考,也方便你在接入该规则前评估其影响范围。
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考