eslint-plugin-unicorn 的 prefer-iterator-concat 规则:从快照测试看 `Iterator.concat(…)` 的自动修复与安全边界
eslint-plugin-unicorn 的 prefer-iterator-concat 规则从快照测试看Iterator.concat(…)的自动修复与安全边界【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicornprefer-iterator-concat是 eslint-plugin-unicorn 中一条用于消除临时展开数组的规则当代码在接收可迭代对象的位置写下[...foo, ...bar]时它建议改用Iterator.concat(foo, bar)从而避免为了拼接多个可迭代对象而物化一个临时数组。本篇以仓库内的 测试快照报告 为主体结合 规则源码、测试用例 与 官方文档逐条解读 29 个 invalid 快照背后的修复行为、建议行为与刻意忽略的安全边界帮助你理解该规则的完整触发面以及在--fix与编辑器建议之间如何取舍。规则要解决什么问题Iterator.concat()是 ECMAScript 提供的迭代器静态方法它把多个可迭代对象懒拼接成一个迭代器而不是像数组展开[...foo, ...bar]那样先创建一个临时数组、再整体物化。对于new Set(...)、Array.from(...)、Promise.all(...)这类本就接受可迭代对象的 API直接传入Iterator.concat(foo, bar)可以省掉中间数组的开销。规则的核心判断逻辑rules/prefer-iterator-concat.js要求被报告的数组全部由展开元素构成且至少有两个元素const isSpreadArray node node.type ArrayExpression node.elements.length 2 node.elements.every(element element?.type SpreadElement);也就是说[...foo, ...bar]会被报告而[first, ...rest]、[...foo]这类混合数组或单展开数组会被刻意忽略——这正是快照中 valid 用例new Set([foo, ...bar])、new Set([...foo])出现的原因。快照报告本身AVA 的test.snapshot产物关联文档 test/snapshots/prefer-iterator-concat.js.md 是 AVA 测试框架为test.snapshot生成的可读报告对应的机器可读快照保存在 test/snapshots/prefer-iterator-concat.js.snap。它的头部明确写道The actual snapshot is saved inprefer-iterator-concat.js.snap. Generated by [AVA].每个invalid(N)条目包含三部分信息Input被测的源码片段Message报告的错误消息与错误位置以^标记的列范围Output 或 Suggestion--fix的自动修复结果或编辑器可手动应用的建议。这份快照报告的价值在于它把规则声称能修什么、只能建议什么、完全不碰什么以逐字对比的方式固定下来任何对规则行为的改动都会造成快照 diff从而在 CI 中被发现。因此它可以视为该规则行为规格的最严格版本。会触发的场景全梳理快照 29 个 invalid 用例1. 接受可迭代对象的构造函数自动修复快照 invalid(1)–invalid(7) 覆盖了Set、Map、WeakSet、WeakMap以及Int8Array、Uint8Array、Float64Array等 TypedArray输入输出new Set([...foo, ...bar])new Set(Iterator.concat(foo, bar))new Map([...foo, ...bar])new Map(Iterator.concat(foo, bar))new WeakSet([...foo, ...bar])new WeakSet(Iterator.concat(foo, bar))new WeakMap([...foo, ...bar])new WeakMap(Iterator.concat(foo, bar))new Int8Array([...foo, ...bar])new Int8Array(Iterator.concat(foo, bar))这类场景在源码中由isIterableAcceptingNewExpression判定rules/prefer-iterator-concat.jsTypedArray 的完整名单来自 rules/shared/typed-array.js共 11 种Int8Array、Uint8Array、Uint8ClampedArray、Int16Array、Uint16Array、Int32Array、Uint32Array、Float16Array、Float32Array、Float64Array、BigInt64Array、BigUint64Array。这些位置本身接受可迭代对象替换为Iterator.concat(...)后语义完全等价因此直接生成 fixer--fix即可自动改写。2..from(...)静态方法与Object.fromEntries部分为建议快照 invalid(8)–invalid(16) 覆盖Array.from、Uint8Array.from与Object.fromEntriesArray.from([...foo, ...bar])→Array.from(Iterator.concat(foo, bar))自动修复Array.from([...foo, ...bar], mapFunction)→Array.from(Iterator.concat(foo, bar), mapFunction)建议Suggestion 1/1Uint8Array.from([...foo, ...bar], mapFunction)→ 同样为建议Object.fromEntries([...foo, ...bar])→Object.fromEntries(Iterator.concat(foo, bar))自动修复。源码中isIterableAcceptingCall把Array.from、Object.fromEntries与 Promise 方法统一纳入候选rules/prefer-iterator-concat.js但isFromCallWithMapper单独识别了带映射函数的.from(...)rules/prefer-iterator-concat.js。快照 invalid(9) 还验证了一个细节Array.from([...foo, ...bar], )第二个参数为空属于无 mapper 场景可自动修复而一旦出现mapFunction就降级为建议。为什么带 mapper 只能给建议官方文档的解释是把数组字面量替换为Iterator.concat(...)会改变 mapper 函数的执行时机——数组展开是先全部物化再逐元素映射而迭代器是边拉取边映射当且仅当存在 mapper 时这一时序差异才可能被观测到因此禁用自动修复。3. Promise 组合方法仅建议快照 invalid(17)–invalid(20) 覆盖Promise.all、Promise.allSettled、Promise.any、Promise.racePromise.all([...foo, ...bar]) // → Promise.all(Iterator.concat(foo, bar))Suggestion 1/1源码中promiseMethods显式列出了这 4 个方法rules/prefer-iterator-concat.js并要求恰好 1 个参数、不可选调用。快照 invalid(20) 的Promise.race、invalid(18) 的Promise.allSettled均以 Suggestion 形式呈现。为什么只给建议因为替换会改变错误的时序语义数组字面量在创建阶段的同步 throw替换为迭代器后可能变成异步 rejectionIterator.concat的惰性求值发生在 Promise 内部消费时。为避免改变程序行为规则只提供编辑器可手动应用的建议。4.for...of、for await...of与yield*仅建议快照 invalid(21)–invalid(23)for (const value of [...foo, ...bar]); // → for (const value of Iterator.concat(foo, bar));Suggestion async () { for await (const value of [...foo, ...bar]); } // → for await (const value of Iterator.concat(foo, bar));Suggestion function * foo() { yield * [...bar, ...baz]; } // → yield * Iterator.concat(bar, baz);Suggestion源码里isInIterableAcceptingParent对ForOfStatementparent.right node与delegate的YieldExpression做了显式识别rules/prefer-iterator-concat.js而isSuggestionOnlyParent把这两类连同 Promise 方法、带 mapper 的.from(...)一并标记为仅建议rules/prefer-iterator-concat.js。原因是for...of与yield*逐个消费迭代器替换后后面的可迭代对象被消费的时机可能改变快照 invalid(13) 的Array.from([...foo, ...throwsOnIteration], mapFunction)正是这类时序敏感用例的典型代表——展开时throwsOnIteration会立即被迭代而Iterator.concat下它的迭代会被推迟。5. 括号、格式与注释的特殊用例快照 invalid(24)–invalid(29) 验证了边界处理new Set([...(foo ? bar : baz), ...(qux)])→new Set(Iterator.concat((foo ? bar : baz), (qux)))每个展开参数用getParenthesizedText保持原有括号rules/prefer-iterator-concat.jsnew Set((([...foo, ...bar])))→new Set(((Iterator.concat(foo, bar))))只替换最内层的数组表达式外层括号原样保留多行格式化数组每行一个展开元素也能正确折叠为单行Iterator.concat(...)含注释的数组只报告、不修复invalid(27)–invalid(29) 中new Set([/* comment */ ...foo, ...bar])只给出 Message没有任何 Output 或 Suggestion。原因在getFix里rules/prefer-iterator-concat.js只要getCommentsInside(arrayExpression)非空fixer 就直接返回undefined防止修复时吞掉注释。不会被触发的场景源码层面的安全边界快照仅记录 invalid 用例而 test/prefer-iterator-concat.js 中的 valid 用例揭示了规则的不干预清单对应的源码条件集中在 rules/prefer-iterator-concat.js混合数组new Set([foo, ...bar])、new Set([, ...foo, ...bar])——不符合isSpreadArray非可迭代接收位const values [...foo, ...bar]、return [...foo, ...bar]、call(...[...foo, ...bar])——不符合isInIterableAcceptingParent调用形态不匹配foo([...bar, ...baz])、Array.of(...)、Array.from?.(...)、Array?.from(...)、Arrayfrom、Object.fromEntries([...foo, ...bar], extra)、Promise.all([...foo, ...bar], extra)——不符合方法名、参数个数或可选调用约束构造函数不匹配new Foo([...foo, ...bar])、new foo.Set(...)、new Map([...foo, ...bar], extra)、new Uint8Array([...foo, ...bar], byteOffset)——要求恰 1 个参数已知 Set 并集场景new Set([...iterator.toArray(), ...other])和const a new Set(); const b new Set(); new Set([...a, ...b])——这两类分别由hasToArraySpreadElement与isKnownSetUnionCase排除rules/prefer-iterator-concat.js官方文档明确说明这是留给prefer-set-methods规则处理的prefer-set-methods可把new Set([...a, ...b])改写为a.union(b)。修复与建议的判定流程一次走完的ArrayExpression访问把规则源码 rules/prefer-iterator-concat.js 的create逻辑串起来一次完整判定如下命中ArrayExpression节点后先过滤必须是全展开数组、必须处于可迭代接收位、不含toArray()展开、不是已知 Set 并集尝试生成 fixer含注释则放弃按父节点类型分流for...of、yield*、Promise 方法、带 mapper 的.from(...)→ 无论是否有 fixer都返回suggest只有无 fixer 时才退化为纯报告其余场景构造函数、Object.fromEntries、无 mapper 的.from(...)→ 有 fixer 则附带fix否则仅报告。规则元数据rules/prefer-iterator-concat.js声明了type: suggestion、fixable: code、hasSuggestions: true两条消息 ID 分别为prefer-iterator-concatUseIterator.concat(…)instead of creating a temporary array from spreads.与prefer-iterator-concat/suggestionUseIterator.concat(…).——前者在快照的 Message 字段出现后者在 Suggestion 字段出现。该规则默认不在recommended与unopinionated配置中启用需要在 ESLint 配置中手动打开docs/rules/prefer-iterator-concat.md。如何在本地复现这些快照仓库使用 AVA 做测试。若要复现本文引用的全部 29 个 invalid 场景在项目根目录执行npx ava test/prefer-iterator-concat.js无参数运行校验当前代码行为与 test/snapshots/prefer-iterator-concat.js.snap 中固化的一致新增用例后加-u更新快照npx ava test/prefer-iterator-concat.js -u注意更新快照会把新的输出永久固化需要人工核对 diff 后再提交。运行后可在 test/snapshots/prefer-iterator-concat.js.md 中逐条核对 Input / Message / Output / Suggestion。这份快照报告就是该规则行为最完整、最可验证的活文档。小结通过快照报告可以总结出prefer-iterator-concat的三大行为原则自动修复的边界仅限语义完全等价的场景——接收可迭代对象的构造函数、Object.fromEntries、无 mapper 的.from(...)只给建议的场景可能改变执行时序mapper、for...of、yield*或错误语义Promise 方法的位置宁可让开发者手动确认完全不碰的场景混合数组、单展开、非接收位、已知 Set 并集以及任何含注释的数组。这种能修的尽量修、有风险只建议、有歧义不干预的分级策略正是该规则在自动修复安全性与代码改进能力之间保持平衡的体现也是阅读 规则源码 与 测试用例 时最值得借鉴的设计思路。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考