eslint-plugin-unicorn 的 no-array-callback-reference 规则:杜绝把函数引用直接传给迭代器方法

eslint-plugin-unicorn 的 no-array-callback-reference 规则:杜绝把函数引用直接传给迭代器方法 eslint-plugin-unicorn 的 no-array-callback-reference 规则杜绝把函数引用直接传给迭代器方法【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn本篇文章深入讲解 eslint-plugin-unicorn 中的unicorn/no-array-callback-reference规则它为什么存在、覆盖哪些迭代器方法、如何处理 TypeScript 类型谓词与接收者类型以及如何通过ignore选项与编辑器建议editor suggestions在真实项目中落地。读完本文你将掌握该规则的完整行为边界、典型误用模式与源码级实现原理能够直接在自己的 ESLint 配置中启用并灵活调优。为什么不能把函数引用直接传给迭代器方法JavaScript 的数组迭代器方法map、forEach、filter、reduce等在调用回调函数时会额外传入不止一个参数。以Array.prototype.map为例其回调签名是(element, index, array)也就是说迭代器实际会向你的回调传入元素、索引、整个数组三个参数。当你把只接受一个参数的函数引用直接传给迭代器方法时多余的参数会被静默忽略看起来一切正常可一旦该函数在未来被修改成接受第二个参数行为就会悄然改变产生难以排查的 bug。文档中的经典示例一个unicorn模块的演进假设你有一个导出unicorn函数的模块const unicorn x x 1; export default unicorn;你在业务代码里这样使用它import unicorn from unicorn; [1, 2, 3].map(unicorn); // [2, 3, 4]随后unicorn模块发布了一个 minor 版本新增了第二个参数const unicorn (x, y) x (y ? y : 1); export default unicorn;由于map会把元素索引作为第二个参数传入你的代码结果立刻变成了[2, 3, 5]很可能直接导致线上故障——而你甚至没有改动任何业务代码。这正是本规则要防患于未然的核心场景让函数只以你预期的方式、以预期数量的参数被调用import unicorn from unicorn; [1, 2, 3].map(x unicorn(x)); // [2, 3, 4]显式的内联包装函数把我要传什么参数写死在了调用点升级第三方依赖时行为不再漂移。规则的适用范围与行为边界本规则规则源码的官方描述是Prevent passing a function reference directly to iterator methods其meta.type为problem默认在 ✅recommended配置中启用在 ☑️unopinionated配置中禁用。启用状态与配置说明见 核心规则替换/推荐配置 及 flat 配置基础 相关的推荐配置体系。以下几点是该规则精确定义的行为边界理解它们能避免误报与困惑1. 同样适用于 TypeScript这条规则在 TypeScript 下同样生效前提是函数接受的参数类型与迭代器方法传入的参数类型一致。如果回调显式声明了与迭代器参数不兼容的类型签名规则不会误报。2. 有意报告本地声明的回调即使回调是在当前文件中用const声明、箭头函数或函数表达式定义的只要它被以函数引用的形式直接传入迭代器方法规则依然会报告。官方文档明确指出当你希望回调参数显式化时就应该使用内联包装函数。测试中也有对应用例例如 test/no-array-callback-reference.js 中const callback value value; array.map(callback);仍会被判定为错误。3. 类型谓词回调放行对于.every()、.filter()、.find()、.findLast()四个方法类型谓词type predicate回调被允许直接传递。原因在于TypeScript 的Array.prototype.filter等方法拥有类型谓词重载可以对数组进行类型收窄narrowing若强行用包装函数包裹收窄能力会丢失。源码中通过methodsWithTypePredicateOverloads集合rules/no-array-callback-reference.js记录这四个方法再由isTypePredicateCallback判定回调是否是类型谓词。isTypePredicateCallback的判断基于语法层面保持轻量、无需类型感知覆盖四种定义形态rules/no-array-callback-reference.js函数声明返回类型注解为TSTypePredicate如value is string即放行导入绑定从其他模块导入的回调保守地一律放行因为无法在不做类型感知分析的情况下确认其返回类型函数参数参数的类型注解是(value) value is string形态的函数类型时放行变量定义箭头函数/函数表达式直接带谓词返回类型或变量注解本身是谓词函数类型时放行。测试用例test/no-array-callback-reference.js覆盖了上述四种形态以及三元表达式中的谓词回调。作为对照map中的谓词回调不会被放行因为map没有谓词重载foo.map(isString)依然报错。4. 接收者receiver类型感知规则会对迭代器方法调用所在的接收者对象做类型判断已知既不是数组也不是类型化数组的接收者会被忽略例如Set、Map或本地声明的、恰好有同名方法的类本地声明的类型仅凭注解即可识别无需类型信息而类型来自其他模块的接收者则需要开启 typescript-eslint 的类型感知 lint未知类型的接收者仍会被报告——如果你在非数组 API恰好与数组方法同名上使用该 API就需要ignore选项或行内禁用注释来豁免。该逻辑由工具函数isKnownNonIndexedCollection支撑rules/utils/is-array.js它会将Map、ReadonlyMap、WeakMap、Set、ReadonlySet、WeakSet、CanvasRenderingContext2D等视为已知非索引集合rules/utils/is-array.js。规则在真正报告前调用isKnownNonIndexedCollectionrules/no-array-callback-reference.js并且把这一昂贵的类型解析放在最后执行——先通过语法快速排除内联回调再做接收者类型解析。接收者的类型判定配置receiverTypeOptionsrules/no-array-callback-reference.js包含几个关键语义checkClassHeritage/checkClassSyntax解析继承关系与类语法——继承自Array的类仍是合法接收者而本地声明、遮蔽内置名称的类则不是treatMixedUnionAsTarget联合类型中一旦有成员不是数组就跳过调用可能落在该成员上其同名方法的回调签名是另一套allowNullishInMixedUnion先剔除空值成员——空值接收者在回调被调用前就会抛错回调参数问题无关紧要。测试中的验证用例包括new Set()、new Map()、class Foo {} const collection new Foo(); collection.map(callback);均为合法不报告以及 TS 下的declare const collection: string[] | Setstring;同样被跳过。覆盖的迭代器方法规则通过iteratorMethods映射表rules/no-array-callback-reference.js管理所有受检方法。每个方法都配置了建议修复时使用的参数名列表、最小参数数量、是否返回undefined、以及各自的忽略名单。参数模板简单方法 vs reduce 类方法方法建议修复的回调参数模板最小参数数every、filter、find、findLast、findIndex、findLastIndex、flatMap、forEach、map、some(element, index, array)1reduce、reduceRight(accumulator, element, index, array)2源码中reduce/reduceRight显式声明了parameters: [accumulator, element, index, array]与minParameters: 2rules/no-array-callback-reference.js其余方法默认[element, index, array]、minParameters: 1rules/no-array-callback-reference.js。这意味着reduce的修复建议永远不会生成只带一个参数的包装函数因为reduce回调的前两个参数累加器与元素都是必要的。方法级忽略名单规则并非对所有回调引用一刀切而是按方法维护内置忽略名单map忽略String、Number、BigInt、Boolean、Symbol五个原始类型包装函数rules/no-array-callback-reference.js。array.map(Boolean)这类用法是安全的、被官方认可的写法every、filter、find、findLast、findIndex、findLastIndex、some忽略Booleanrules/no-array-callback-reference.jsfilter额外忽略Vue.filter(name, fn)调用Vue 的过滤器 API 与数组filter无关map额外忽略types.map(...)调用——这是为了兼容 mobx-state-tree 的类型工厂 API源码 rules/no-array-callback-reference.js测试见 test/no-array-callback-reference.js。注意Boolean只在合理位置被忽略。测试明确验证了foo.reduce(Boolean, initialValue)与foo.forEach(Boolean)仍然报错test/no-array-callback-reference.js因为对这两个方法而言Boolean作为回调没有等价于数组判真的语义。完整示例正确与错误用法单元素类方法// ❌ const foo array.map(callback); // ✅ const foo array.map(element callback(element));// ✅ 内置布尔转换是安全用法 const foo array.map(Boolean);语句型回调forEachforEach的回调没有返回值因此修复建议会生成语句块形式的包装// ❌ array.forEach(callback); // ✅ array.forEach(element { callback(element); });谓词类方法// ❌ const foo array.every(callback); // ✅ const foo array.every(element callback(element));// ❌ const foo array.filter(callback); // ✅ const foo array.filter(element callback(element));// ✅ const foo array.filter(Boolean);// ❌ const foo array.find(callback); // ✅ const foo array.find(element callback(element));// ❌ const index array.findIndex(callback); // ✅ const index array.findIndex(element callback(element));// ❌ const foo array.some(callback); // ✅ const foo array.some(element callback(element));累加类方法reduce / reduceRight// ❌ const foo array.reduce(callback, 0); // ✅ const foo array.reduce( (accumulator, element) accumulator callback(element), 0 );// ❌ const foo array.reduceRight(callback, []); // ✅ const foo array.reduceRight( (accumulator, element) [ ...accumulator, callback(element) ], [] );flatMap// ❌ const foo array.flatMap(callback); // ✅ const foo array.flatMap(element callback(element));工厂函数返回的回调回调位置出现调用结果即先把函数生产出来时同样报错因为调用点的意图被隐藏了// ❌ array.forEach(someFunction({foo: bar})); // ✅ const callback someFunction({foo: bar}); array.forEach(element { callback(element); });携带 this 实参的调用迭代器方法支持传入第二个参数作为thisArg。此时建议修复会保留该参数并用普通函数而非箭头函数访问this// ❌ array.forEach(callback, thisArgument); // ✅ array.forEach(function (element) { callback(element, this); }, thisArgument);非数组场景不报错下面的写法是合法的——Promise.map来自 Bluebird其回调只接收一个参数不存在多参数漂移问题readFile作为Promise.map的回调天然安全// ✅ function readFile(filename) { return fs.readFile(filename, utf8); } Promise.map(filenames, readFile);修复机制编辑器建议Editor Suggestions本规则不做自动修复而是通过 ESLint 的编辑器建议hasSuggestions: true见 rules/no-array-callback-reference.js提供手动可选的修复方案。每条错误会附带多条递进式建议分别对应不同数量的回调参数。以bar.map(fn)为例测试断言见 test/no-array-callback-reference.js实际消息与建议为错误消息Do not pass functionfndirectly to.map(…).建议 1Replace functionfnwith… fn(element).→bar.map((element) fn(element))建议 2Replace functionfnwith… fn(element, index).→bar.map((element, index) fn(element, index))建议 3Replace functionfnwith… fn(element, index, array).→bar.map((element, index, array) fn(element, index, array))reduce类方法则从两个参数起步例如bar.reduce(fn)会生成(accumulator, element)、(accumulator, element, index)、(accumulator, element, index, array)三个递进建议test/no-array-callback-reference.js。建议的智能化细节从源码与测试可以提炼出建议生成器getProblem/getSuggestionParametersrules/no-array-callback-reference.js的几个贴心行为复数转单数接收者变量名若为复数形式element参数名会被替换为单数。items.map(fn)的建议是items.map((item) fn(item))classes→(element, index, classes)中的classes替换array形参indices→indices。该逻辑依赖工具函数singular名称冲突规避当被引用的函数名与建议参数名冲突时参数名会加下划线。items.map(item)的建议是items.map((element) item(element))参数避开itemfoo.map(element)则变成(element_)items.map(index)变成(item, index_)test/no-array-callback-reference.js成员表达式回调为lib.fn这类成员表达式时使用匿名消息Do not pass function directly to ...建议为… …(element)三元表达式array.map(condition ? toFile : toBuffer)会对两个分支分别报告并分别生成建议未匹配的分支保持不变await/yield参数回调若是yield/await表达式只报告错误、不提供建议rules/no-array-callback-reference.js函数绑定foo.map(function (a) {}.bind(bar))这类CallExpression形式的回调包括bind的结果一律不报告因为其参数数量已被显式固定源码注释见 rules/no-array-callback-reference.js。Optionsignore 配置规则配置项类型为object目前仅有ignore一个选项。ignore类型string[]语义需要忽略的 callee被调用对象。匹配的是迭代器方法所调用到的对象因此Angular会忽略所有Angular.method(…)调用。schema 约束数组元素必须是字符串且不重复uniqueItems: true见 rules/no-array-callback-reference.js默认值为{ignore: []}。默认忽略名单以下 callee始终被忽略无需手动配置源码 rules/no-array-callback-reference.jsPromise、React.Children、Children、lodash、underscore、_、Async、async、this、$、jQuery这些库的迭代器 API 回调语义与原生数组不同例如 Bluebird 的Promise.map、lodash 的_.map、jQuery 的$(...).map直接传函数引用是安全且惯用的写法。测试中Promise.map(fn)、lodash.map(fn)、$(this).find(tooltip)等均为合法用例test/no-array-callback-reference.js。配置示例在 ESLint 配置文件中{ unicorn/no-array-callback-reference: [ error, { ignore: [ Angular, P ] } ] }或在代码顶部使用行内注释配置仅对该文件生效/* eslint unicorn/no-array-callback-reference: [error, {ignore: [Angular]}] */ Angular.forEach(list, fn); // Passes测试还验证了几个ignore的边界行为test/no-array-callback-reference.js支持多段成员表达式myLib.utils.map(list, fn)可用ignore: [myLib.utils]豁免支持链式调用myLib(args).map(fn)可用ignore: [myLib]豁免源码中会同时匹配callee.object及其CallExpression形态默认忽略名单依然生效配置了ignore: [Angular]后Promise.map、lodash.map仍被豁免不匹配的 callee 照常报告配置ignore: [Angular]时Other.forEach(fn)仍会报错。源码级工作流程规则是如何判断的综合 rules/no-array-callback-reference.js 的create入口整个判定流程可以归纳为一条清晰的流水线形状过滤仅处理CallExpression中标准的成员方法调用非可选调用、非常规属性、参数个数 12 个foomap、foomap、new foo.map(fn)、foo.map()、foo.map(fn, a, b)均不进入检查test/no-array-callback-reference.js方法匹配方法名必须命中iteratorMethods映射表callee 豁免检查默认忽略名单、用户ignore名单与方法级shouldIgnoreCallExpression钩子回调展开若首个实参是三元表达式会递归展开所有分支getTernaryConsequentAndALternaterules/no-array-callback-reference.js逐个分支判定回调排除内联箭头函数/函数表达式、CallExpression含bind、命中方法级忽略名单如Boolean、确定不是函数的值数组、对象字面量、模板字符串、一元/二元表达式等见definitelyNotFunctionValueNodeTypesrules/no-array-callback-reference.js以及类型谓词回调都会被排除常量引用还会沿着const定义链向上追溯getConstVariableInitializer接收者类型解析最后一步、代价最高调用isKnownNonIndexedCollection判定接收者是否已知非数组集合是则跳过产出问题为剩余回调生成错误与递进式建议getProblem。值得注意的细节是isAwaitExpressionArgumentrules/no-array-callback-reference.js对于await foo.map(bar)这种将结果await的调用除reduce/reduceRight外的所有方法都会被整体跳过——因为await通常出现在Promise相关的场景如await oidc.Client.find(clientId)这些回调的多参数语义往往是有意的测试中对应 issue #813 的用例test/no-array-callback-reference.js。消息与建议文案速查规则定义了四种消息 IDrules/no-array-callback-reference.js消息 ID文案触发场景error-with-nameDo not pass function{{name}}directly to.{{method}}(…).回调是具名标识符error-without-nameDo not pass function directly to.{{method}}(…).回调是成员表达式等无名单值replace-with-nameReplace function{{name}}with… {{name}}({{parameters}}).具名标识符的建议replace-without-nameReplace function with… …({{parameters}}).无名单值的建议在项目中启用与调优由于该规则默认包含在recommended配置中使用 eslint-plugin-unicorn 推荐配置的项目开箱即用无需额外声明。若你使用 flat config可通过 flat 配置基础 了解推荐的接入方式。自定义接入示例export default [ // ... 其他配置 { rules: { unicorn/no-array-callback-reference: error, // 或者带选项 // unicorn/no-array-callback-reference: [error, {ignore: [Angular]}], }, }, ];调优建议项目里使用了 BluebirdPromise.map、lodash/underscore、jQuery 等库的迭代器 API无需任何配置默认名单已覆盖使用了 Angular、mobx-state-treetypes.map等自带同名方法的 API要么依赖本规则的接收者类型判断与types.map特例要么显式加入ignore非数组 API 恰好与数组方法同名且类型无法被静态识别时规则会照常报告——此时用ignore或行内禁用注释而不是全局关闭规则。测试与质量保障该规则拥有非常完整的测试覆盖test/no-array-callback-reference.js1135 行涵盖全部 12 个迭代器方法的建议输出、可选链foo?.map(fn)、双参数thisArg场景、await/yield边界、三元表达式逐分支报告、成员表达式回调、名称冲突规避element_、index_、ignore选项的多种形态、TypeScript 类型感知场景string[]、readonly string[]、元组、Uint8Array、泛型约束、联合类型、继承Array的类以及类型谓词回调的放行与不放行对照。这些用例是理解规则行为边界最直接的活文档。输出文章【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考