eslint-plugin-unicorn 规则解析:prefer-array-slice —— 用Array#slice()替代只读场景下的Array#splice()
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
本篇技术指南围绕 eslint-plugin-unicorn 中的prefer-array-slice规则展开,讲解它在何时检测、为何推荐的Array#slice()与Array#splice()的差异,并结合 规则源码 与 测试用例 剖析其判定逻辑与边界行为。读完本文,你将掌握该规则的触发条件、类型感知过滤机制、可用的编辑器自动修复(suggestion),以及如何在自己的项目中正确使用这一规则。
规则概览:只读意图的代码表达
prefer-array-slice的核心诉求是:当调用Array#splice()只是为了读取其返回的数组元素时,应当改用Array#slice()。其规则描述为:
Prefer
Array#slice()overArray#splice()when reading from the returned array.
从规则 meta 信息(见 rules/prefer-array-slice.js)可以看出:
- 规则类型:
suggestion(仅产生建议性告警,不判定为错误级别的严重问题); - 推荐配置:默认在 ✅
recommended配置中启用,在 ☑️unopinionated(无观点)配置中禁用——因为该规则属于有明确编码观点的建议,不适合追求零主观约束的团队; - 可自动修复:支持 ESLint 的 editor suggestions 机制,即编辑器可提示一键将
splice改写为slice(手动触发的修复,而非自动应用); - 适用语言:
js/js(同时覆盖 JavaScript 与 TypeScript 场景,后者依赖类型信息)。
该规则已通过 rules/index.js 注册进规则索引,作为项目 300+ 条规则中的一员随插件一同发布。
为什么推荐:可变与不可变语义的取舍
Array#splice()会就地修改源数组(mutate):删除元素的同时返回被删除的元素组成的新数组。而Array#slice()是纯读取操作,不改变源数组,返回的是原数组的一段浅拷贝。
当代码形如array.splice(2)[0]时,开发者真正的意图往往只是“取出第 2 个元素之后的第一个元素”,并不想破坏源数组。此时splice的副作用属于无意的隐式变更,可能引发难以排查的 bug——例如后续代码再次读取array时发现元素已被删除。
prefer-array-slice正是在这类“读取返回值”场景下,将可变操作改写为不可变操作,让只读意图在代码层面被显式表达,既消除副作用风险,也提升可读性。
触发条件:何时报告
该规则只对满足以下全部条件的splice调用报告问题(见 rules/prefer-array-slice.js):
- 必须是标准方法调用:
splice作为方法名、非可选调用(splice?.()不算)、非可选成员(?.splice()不算)、非计算属性(array["splice"]()不算)、且恰好只有1 个参数(splice(index, deleteCount)不算); - 返回结果必须被“只读访问”:满足以下任一形态:
- 下标索引访问:
splice(index)[0]、splice(index)[offset],即紧接着被[n]读取; .at()调用:splice(index).at(0),即紧接着被.at(n)读取;
- 下标索引访问:
- 接收者(receiver)不能是已知的非数组:通过类型信息判断,若接收者明确不是数组则跳过(见下文)。
为什么限定为 1 个参数?因为array.splice(index, deleteCount)的第二个参数deleteCount明确表达了“我要删除若干个元素”的变更意图,此时splice是合理的,规则不介入。同理,array.splice(index)单独调用(不读取返回值)也在规则放行之列——文档中的正例也印证了这两点。
例外:赋值与删除操作不报告
“只读访问”的判定对赋值类操作保持克制。从 rules/utils/is-left-hand-side.js 的实现看,以下场景会被判定为“左值”而非“读取”,从而不报告:
array.splice(index)[0] = value(写入元素)delete array.splice(index)[0](删除元素)- 解构、更新表达式等出现在赋值目标位置的访问
这些场景虽然也立即消费了返回值,但语义上是写操作,替换为slice会产生行为差异,因此规则谨慎地将其排除(对应 测试用例 中的 valid 用例)。
官方示例速览
文档给出了四组典型示例(见 docs/rules/prefer-array-slice.md):
// ❌ 报告:读取返回值但会污染 process.argv const foo = process.argv.splice(2)[0]; // ✅ 推荐 const foo = process.argv.slice(2)[0];// ❌ 报告:.at(0) 同样属于只读读取 const foo = array.splice(index).at(0); // ✅ 推荐 const foo = array.slice(index).at(0);// ✅ 放行:不读取返回值,splice 的变更语义是操作本身的一部分 array.splice(index);// ✅ 放行:带 deleteCount,明确表达删除意图 array.splice(index, deleteCount)[0];最后一行体现了规则的边界哲学:当有意的变更(mutation)本身就是操作目的时,请继续使用Array#splice()。
源码级解析:三处关键判定
1. 下标访问判定isIndexedAccess
rules/prefer-array-slice.js 中的isIndexedAccess要求父节点是计算属性成员表达式(computed: true,如[0]而非.at)、非可选链(optional: false),且splice调用本身是该访问的对象(node.parent.object === node),同时该访问不能是左值。
2..at()调用判定isAtCall
rules/prefer-array-slice.js 中的isAtCall要求.at是普通成员访问,且外层是参数个数恰为1的at()方法调用。因此array.splice(index).at()(无参数)和array.splice(index).at(0, extra)(多余参数)都不会触发报告,对应测试中的 valid 用例(见 test/prefer-array-slice.js)。
3. 非数组接收者过滤shouldReportReceiver
rules/prefer-array-slice.js 使用isKnownNonArray判断接收者是否为“已知非数组”,并显式指定了接收者类型检查选项:
const receiverTypeOptions = { checkClassHeritage: true, // 检查类继承链 checkClassSyntax: true, // 检查类语法声明 treatMixedUnionAsNonTarget: true, // 混合联合类型视为非目标 };这意味着规则在报告前会谨慎排除以下“非数组”情况,避免误报:
- 自定义类实例:
const object = {splice() { return []; }}; object.splice(index)[0]; - 声明为
Set、string等非数组类型的变量; - 具有自定义
splice方法的类型。
而一个值得注意的实现细节是:规则并未使用共享的shouldSkipKnownNonArrayReceiver工具,而是自带接收者选项。其源码注释说明:把 typed array(如Uint8Array)视为非数组在这里并无影响——因为typed array 本身没有splice()方法,根本不会被报告。该接收者判定能力由 rules/utils/is-array.js 导出的isKnownNonArray提供,它基于createTypeCheckers实现,覆盖表达式形态(ObjectExpression、FunctionExpression、NewExpression等)与类型标注形态(如TSArrayType)两类判断。
类型感知:TypeScript 场景下的精确过滤
得益于项目统一使用的类型检查基础设施,该规则在 TypeScript 项目(开启@typescript-eslint解析器与类型信息)中表现出更精确的行为,测试文件 test/prefer-array-slice.js 完整覆盖了这些场景:
报告(invalid)的 TS 用例:
- 显式类型标注:
declare const array: string[]; array.splice(index)[0] - 类型别名:
type Strings = string[]; declare const array: Strings; ... - 类型断言:
(array as string[]).splice(index)[0] - 索引类型参数:
array.splice(index as number)[0]、array.splice(index!)、array.splice(index satisfies number) - 未知类型:
declare const value: unknown; value.splice(index)[0] - 继承自
Array的子类:class ArraySubclass extends Array {} new ArraySubclass().splice(0)[0]、class ArraySubclass extends Array<number> {...} this类型:function method(this: string[]) { return this.splice(0)[0]; }- 动态构造:
declare const ArrayConstructor: typeof Array; new ArrayConstructor().splice(0)[0]
放行(valid)的 TS 用例:
- 明确非数组:
declare const set: Set<string>; set.splice(index)[0]、declare const string: string; ... - 自定义接口:
interface Custom { splice(index: number): string[]; } declare const value: Custom; ... - 混合联合类型(
treatMixedUnionAsNonTarget生效):declare const value: string[] | Custom; value.splice(index)[0](类型信息下) - 静态方法中的
this:class ArraySubclass extends Array { static method() { return this.splice(0)[0]; } }——静态上下文中this是构造函数而非数组实例
其中class ArraySubclass extends Array { method() { return this.splice(0)[0]; } }在非类型感知下是 invalid(会报告),而在类型感知下取决于能否确认继承链指向Array——测试第 93 行的用例显示,当value的运行时类型被推断为自定义Custom时则放行,体现了类型信息对误报的抑制作用。
自动修复与编辑器建议
该规则声明了hasSuggestions: true(见 rules/prefer-array-slice.js),并提供一条建议修复(suggestion):
- 错误消息:
Prefer Array#slice() over Array#splice() when reading from the returned array. - 建议消息:
Use Array#slice(). - 修复动作:
fixer.replaceText(node.callee.property, 'slice')——仅将方法名splice替换为slice,不改动参数与接收者。
由于splice与slice在此场景下的参数签名完全兼容(单参数start),这个替换是行为安全的:array.splice(index)返回[index, 之后所有元素]的新数组,与array.slice(index)在只读场景下返回相同的内容,而后者不产生副作用。在支持 ESLint suggestions 的编辑器(VS Code 等)中,你可以手动触发快速修复一键应用。
使用与验证
在项目中启用该规则的方式与其他规则一致——使用recommended配置即默认开启:
// eslint.config.js(flat config) import unicorn from 'eslint-plugin-unicorn'; export default [ unicorn.configs['flat/recommended'], // 或仅启用单条规则: // {plugins: {unicorn}, rules: {'unicorn/prefer-array-slice': 'error'}}, ];若团队希望自行约束,可通过规则名单独配置开关与级别:
{ "rules": { "unicorn/prefer-array-slice": ["error"] } }运行npx eslint --fix-dry-run可以预览建议修复,--fix-type suggestion则可自动应用所有 suggestion 类修复。
规则的完整行为矩阵均可通过 test/prefer-array-slice.js 中的快照测试验证:其 valid 数组列出 25 个放行用例,invalid 数组列出 12 个报告用例,并额外覆盖 TypeScript 解析器(scripts/parsers.js 提供typescriptEslintParser)与类型感知(projectService)两类测试模式,是理解该规则边界的首选参考资料。
总结
prefer-array-slice通过“方法名 + 参数个数 + 后续访问形态 + 接收者类型”四重判定,精准识别出“用可变操作表达只读意图”的代码模式:
- 报告条件:单参数
splice()的返回值紧接着被[n]下标或.at(n)只读消费; - 放行条件:带
deleteCount、不读取返回值、赋值/删除类左值访问、接收者确认为非数组类型; - 修复方式:手动触发的 editor suggestion,将方法名安全替换为
slice。
将该规则与recommended配置一起使用,可以在不改变行为的前提下,让代码的只读意图更明确,并消除splice副作用带来的潜在隐患。
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考