` / `Arrayunshift()` 的返回值)
eslint-plugin-unicorn 规则详解no-return-array-push —— 禁止使用Array#push()/Array#unshift()的返回值【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicornno-return-array-push是 eslint-plugin-unicorn 中一条开启即用的防错规则rule type 为problem核心目标是在静态分析阶段拦截Array#push()与Array#unshift()返回值的误用——这两类方法的返回值是数组新增后的长度既不是被加入的值也不是数组本身将其return或赋值几乎总是一种 bug。本指南以 docs/rules/no-return-array-push.md 为骨架结合 规则源码、测试用例 与快照 test/snapshots/no-return-array-push.js.md完整说明该规则的触发条件、推荐写法、豁免机制、编辑器建议修复与类型信息支持的实现原理帮助你准确配置并在真实代码库中落地。规则背景为什么返回值“几乎总是错误”Array.prototype.push()与Array.prototype.unshift()遵循 JavaScript 规范约定二者都返回操作完成后数组的新长度number而非加入的元素或数组本身。因此在如下场景中代码读起来仿佛这个长度值是有意义的实际上它对你“加了什么”毫无信息量// ❌ 你以为返回了 items实际上返回的是数字长度 function add(item) { return items.push(item); }这类写法最常见的两种意图与正确改法分别是只想追加元素然后退出先调用.push()/.unshift()再单独return确实有意使用长度在变更之后用显式的.length表达式取值。触发条件规则在什么时候报告从 规则入口 可以看到规则挂载在CallExpression上命中需要同时满足以下条件必须是成员方法调用调用形如xxx.push(...)/xxx.unshift(...)。经 is-method-call.js 与 call-or-new-expression.js 校验至少传入 1 个实参minimumArguments: 1因此array.push()、array.unshift()这类无参调用不在检查范围内见 测试用例必须是静态成员名属性为标识符push/unshiftarraypush、arraypush等计算属性调用被跳过测试用例调用结果被“使用”了返回值没有被丢弃详见下文豁免机制接收者不是已知的非索引集合例如对Map、Set、WeakMap、WeakSet等类型对象上的同名方法不误报测试用例。触发报告的错误信息为Do not use the return value of \.push(…).定位到调用处的属性名快照见 test/snapshots/no-return-array-push.js.md。完整的不合法场景清单以下场景均会被报告对应 测试用例场景示例说明赋值给变量const length array.push(value);最常见误用直接 returnreturn array.push(value);函数/箭头函数返回简写箭头函数体const add item items.push(item);简写体隐式返回表达式作为参数/awaitconsole.log(array.push(value));、const length await array.push(value);返回值进入其他表达式逻辑表达式condition array.push(value);结果参与/\|\|/??运算三元表达式condition ? array.push(value) : value;结果作为分支值逗号表达式(sideEffect(), array.push(value));结果作为整体值for 语句for (array.push(value); condition; ) {}初始化/测试/更新子句可选链调用return array?.push(value);、return array.push?.(value);可选链不豁免注意一个边界回调中的简写箭头同样会被报告。虽然像Array#forEach这样的回调会忽略返回值但source.forEach(item target.push(item))读起来仍像长度有意义且通常意味着循环可以改写得更直接因此规则依然标记它// ❌ 简写箭头体隐式返回 push 的结果 source.forEach(item target.push(item)); // ✅ 直接用展开语法批量追加 target.push(...source);正确的写法三个标准修复模式原文档给出了四组经典正反例归纳为三个标准修复模式模式一追加后单独 return退出函数// ❌ function add(item) { return items.push(item); } // ✅ function add(item) { items.push(item); return; }模式二先变更再取显式长度确实需要长度// ❌ const length items.unshift(item); // ✅ items.unshift(item); const length items.length;模式三把“变更”与“返回值”分离到不同语句// ✅ function getNextLength(item) { items.push(item); return items.length; }三条原则一致.push()/.unshift()永远作为独立的表达式语句执行需要长度时通过.length显式读取。豁免机制什么时候不报告规则设计了五类豁免避免对自定义 API 与真实业务代码造成误伤。1.void运算符显式的返回值丢弃前缀void是官方认可的“显式弃用”写法适用于调用返回一个你故意不await的Promise的场景// ✅ 明确丢弃返回值 void items.push(item);// ✅ 等价写法同样被豁免 void array.push(value); const length void array.push(value); // 明确声明“不要这个返回值” const foo value void array.push(value); function foo() { return void array.push(value); }源码中isReturnValueDiscarded()将UnaryExpression且operator void视为丢弃rules/no-return-array-push.js对应测试见 test/no-return-array-push.js。2. 独立表达式语句返回值被自然丢弃当调用作为ExpressionStatement单独成行时如array.push(value);返回值无人使用规则自然放行——这是最常规的正确形态。3. 常见流式 / 路由风格的.push()静态黑名单对于stream.push、router.push、this.$router.push、process.stdin.push等经典非数组接收者规则维护了一份静态黑名单ignoredCalleesrules/no-return-array-push.jsstream.push, router.push, this.push, this.router.push, this.$router.push, this.stream.push, process.stdin.push, process.stdout.push, process.stderr.push,匹配通过isStaticMemberPath()逐层校验成员链完成且支持可选链形态stream?.push(chunk)、this?.push(chunk)相关有效用例见 test/no-return-array-push.js。需要注意的是该黑名单只是“默认跳过”一旦类型信息表明接收者其实是数组例如function foo(this: string[]) { return this.push(value); }规则仍会报告测试用例。4. 链式访问调用结果自定义 API 的信号规则把“调用结果上的成员访问”视为自定义 API 的实用信号——典型如router.push(to).catch(...)此时push返回的是一个Promise而非长度。源码中isResultMemberAccessed()检查CallExpression的父节点是否为MemberExpressionrules/no-return-array-push.js因此以下写法全部豁免return router.push(to).catch(() {}); return router.push(to).then(onFulfilled); const promise router.push(to).finally(cleanup); return router.push(to)?.catch(() {}); // 可选链形态同样识别 array.push(value)[0]; // 计算属性访问同一信号对应测试见 test/no-return-array-push.js。注释中也解释了取舍“与其标记现实中几乎不存在的Number方法链例如array.push(value).toString()不如跳过这类自定义 API 的合理用法”。5. 类型信息跳过已知非数组接收者当启用了类型信息TypeScript parser 与类型检查isKnownNonIndexedCollection()会结合类型判断接收者是否为非索引集合。其实现位于 rules/utils/is-array.js维护了一份非索引集合类型名集合Map, ReadonlyMap, WeakMap, Set, ReadonlySet, WeakSet, CanvasRenderingContext2D, OffscreenCanvasRenderingContext2D,createTypeCheckers机制rules/utils/type-helpers.js负责三路判定语法推断ArrayExpression、Array.from/of调用、NewExpression等、TS 类型注解TSArrayType/TSTupleType、以及完整类型检查器checker.isArrayType/isTupleType同时识别 typed array。完整的类型感知测试见 test/no-return-array-push.js。语法级豁免示例仅凭 TS 注解就能判定接收者不是数组interface Router { push(to: string): Promisevoid; } function foo(router: Router) { return router.push(to); // ✅ 类型注解证明不是数组 }完整类型信息豁免示例projectService提供全量类型declare const router: {push(to: string): Promisevoid}; function foo() { return router.push(to); } // ✅ declare const queue: {unshift(value: unknown): number}; function foo() { return queue.unshift(value); } // ✅ // 而以下场景类型证明接收者就是数组仍然报告 declare const array: number[]; function foo() { return array.push(value); } // ❌注意测试配置中的languageOptions.parserOptions.projectService: {allowDefaultProject: [*.ts]}是启用完整类型信息的关键test/no-return-array-push.js。透明包装表达式类型断言等语法的穿透处理规则的“透明表达式类型”集合transparentExpressionTypesrules/no-return-array-push.js包含ChainExpression, TSAsExpression, TSSatisfiesExpression, TSNonNullExpression, TSTypeAssertiongetCallExpressionResultNode()会沿父链穿透这些不改变语义的包装节点从而正确识别“被包装后的返回值仍在使用”还是“已丢弃”。一个典型结果是array.push(value) as number;作为独立语句是合法的被类型断言包装后仍是表达式语句而return array.push(value) as number;依然非法穿透断言后仍能看到 return。完整覆盖见 TypeScript 专项测试array.push(value) as number; // ✅ 表达式语句 void (array.push(value) as number); // ✅ void 显式丢弃 return array.push(value) as number; // ❌ 穿透断言后仍返回长度 return (array.push(value) as Foo).bar; // ✅ 链式访问信号编辑器建议修复Suggestion自动拆分return规则声明了hasSuggestions: true并提供手动可应用的编辑器建议修复非自动 fix避免在不安全语境下改动代码。修复逻辑在getSuggestion()rules/no-return-array-push.js仅当return直接位于块语句中returnStatement.parent.type BlockStatement且 return 语句与调用语句的注释数量一致避免吞掉注释时才生成建议将return array.push(value);替换为array.push(value); return;通过needsSemicolon()rules/utils/needs-semicolon.js判断是否需要在替换文本前补分号防止 ASI自动分号插入引发语法错误。对应的快照如return array.push(value);的报告与建议输出见 test/snapshots/no-return-array-push.js.md。配置与启用方式规则在配置中的完整声明位于 规则 meta 定义meta: { type: problem, docs: { description: Disallow using the return value of Array#push() and Array#unshift()., recommended: true, }, hasSuggestions: true, languages: [js/js], },✅recommended配置默认启用——推荐配置面向“几乎总是错误”的写法这正是本条规则的定位☑️unopinionated配置默认禁用——该配置只保留无争议的核心规则建议修复支持编辑器建议ESLint suggestions 机制在编辑器中可直接一键应用语言支持languages: [js/js]即针对 JavaScript含 JSX代码生效。在 ESLint 配置中可按需显式控制{ rules: { unicorn/no-return-array-push: error } }或在使用类型信息的项目中保持默认recommended启用即可。规则的导出注册见 rules/index.js。小结与最佳实践记住语言事实push/unshift返回新长度而非添加的值或数组本身追加元素后需要退出函数时先调用方法再单独return确实需要长度时用.length显式读取需要丢弃返回值尤其是返回Promise的自定义 API时用void前缀明确表达意图回调中的简写箭头如source.forEach(item target.push(item))也应改写通常可直接用展开语法target.push(...source)替代流式 API、路由跳转router.push、自定义push/unshift方法不会被误报如有类型信息规则还会更精确地跳过已知非数组接收者编辑器建议修复可一键将return array.push(value);拆分为array.push(value); return;且通过 ASI 安全检查保证改写安全。通过结合 规则源码、完整测试矩阵 与 快照报告你可以完全掌握这条规则的边界行为并放心地在启用unicorn/recommended的项目中依赖它捕获这一类隐蔽的返回值误用。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考