eslint-plugin-unicorn `prefer-temporal-conversion` 规则深度解析:从 AVA 快照看 Temporal 转换优化的检测与修复行为 📅 发布时间:2026/9/19 3:08:28 👁 浏览次数: eslint-plugin-unicornprefer-temporal-conversion规则深度解析从 AVA 快照看 Temporal 转换优化的检测与修复行为【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn本文以 test/snapshots/prefer-temporal-conversion.js.md 这份 AVA 快照报告为主体结合 docs/rules/prefer-temporal-conversion.md 官方文档与 rules/prefer-temporal-conversion.js 源码实现全面剖析 eslint-plugin-unicorn 中prefer-temporal-conversion规则的检测模式、自动修复策略、TypeScript 类型感知能力与边界行为。读完本文你将能准确理解该规则在什么场景下报告、什么场景下直接修复、什么场景下仅给出编辑器建议以及如何在项目中启用并验证它。规则定位为什么需要直接转换方法prefer-temporal-conversion是 eslint-plugin-unicorn 提供的一条代码风格与正确性规则规则描述为Prefer direct Temporal conversion methods优先使用 Temporal 直接转换方法。它已被列入recommended与unopinionated两套预设配置见 readme.md 第 417 行的规则清单并同时支持--fix自动修复与编辑器建议。Temporal 转换方法如ZonedDateTime.prototype.toPlainDate()可以在 Temporal 各类型之间直接转换。与之相对通过Temporal.PlainDate.from(source)、source.toString()序列化后再次解析、或者手工用source.year、source.month、source.day重建一个对象都属于间接转换间接转换引入了大量样板代码序列化与反序列化路径可能丢失信息例如亚毫秒精度submillisecond precision和日历calendar信息。该规则的目的就是把这类可以一步到位的间接转换统一替换为直接转换方法同时保留语义等价性。快照文档是什么AVA 测试快照的结构解读test/snapshots/prefer-temporal-conversion.js.md是 AVA 测试框架为test/prefer-temporal-conversion.js生成的快照报告实际二进制快照保存于同目录下的prefer-temporal-conversion.js.snap。它逐条记录了每个 invalid 测试用例的输入代码Input报错位置与消息Error / Message消息格式为Prefer \.toPlainDate() when converting to Temporal.PlainDate.自动修复输出Output或编辑器建议Suggestion。这份快照文档共 1525 行覆盖了三组test.snapshot()测试块JS 基础用例、类型感知用例、TypeScript 断言用例是观察规则检测什么、修复成什么最直接、最权威的一手证据。规则覆盖的转换矩阵根据官方文档与源码中的conversions映射表rules/prefer-temporal-conversion.js#L44-L65规则支持的转换关系如下源类型目标类型推荐方法Temporal.ZonedDateTimeTemporal.Instant.toInstant()Temporal.ZonedDateTimeTemporal.PlainDateTime.toPlainDateTime()Temporal.ZonedDateTime或Temporal.PlainDateTimeTemporal.PlainDate.toPlainDate()Temporal.ZonedDateTime或Temporal.PlainDateTimeTemporal.PlainTime.toPlainTime()对应到源码就是 4 条转换定义Instant仅接受ZonedDateTime源且不涉及字段重建PlainDateTime仅接受ZonedDateTime源、字段为日期字段与时间字段的并集PlainDate与PlainTime均接受两种源字段分别为[year, month, day]与[hour, minute, second, millisecond, microsecond, nanosecond]。快照揭示的六大检测模式1. 直接单参数转换Temporal.X.from(source)快照 invalid(1)、invalid(9)、invalid(14) 等展示了最基础的模式const source Temporal.ZonedDateTime.from(2024-01-02T03:04:05.12345678900:00[UTC]); Temporal.Instant.from(source); // ❌ - source.toInstant() Temporal.PlainDate.from(source); // ❌ - source.toPlainDate() Temporal.PlainTime.from(source); // ❌ - source.toPlainTime() Temporal.PlainDateTime.from(source);// ❌ - source.toPlainDateTime()这类精确转换exact conversions语义完全等价因此全部走自动修复Output 字段给出修复结果。2. 序列化往返转换Temporal.X.from(source.toString() / source.toJSON())快照 invalid(2)、(3)、(10)、(11) 等覆盖了通过toString()或toJSON()转成字符串再from的往返模式。源码中getConversionMatch对这两种零参方法做了显式识别rules/prefer-temporal-conversion.js#L131-L135Temporal.PlainDate.from(source.toString()); // ❌ - source.toPlainDate() Temporal.PlainDate.from(source.toJSON()); // ❌ - source.toPlainDate()注意当目标是Instant时序列化往返会降级为编辑器建议而不是自动修复见下文修复与建议的边界因为ZonedDateTime序列化时会把历史时区偏移取整到分钟可能丢失秒级精度。3. Instant 的 epoch 字段重建fromEpochNanoseconds / fromEpochMilliseconds快照 invalid(35)-(44)、(47)-(49) 覆盖了从epochNanoseconds或epochMilliseconds重建Instant的写法new Temporal.Instant(source.epochNanoseconds); // ❌ - source.toInstant() Temporal.Instant.fromEpochNanoseconds(source.epochNanoseconds); // ❌ - source.toInstant() Temporal.Instant.fromEpochMilliseconds(source.epochMilliseconds); // ❌ - 仅建议 source.toInstant() Temporal.Instant.fromEpochNanoseconds(Temporal.Now.zonedDateTimeISO().epochNanoseconds); // ❌ - Temporal.Now.zonedDateTimeISO().toInstant()毫秒版本之所以不能自动修复是因为它丢弃了亚毫秒精度源码canAutofix: !isMilliseconds见 rules/prefer-temporal-conversion.js#L145-L150。快照 invalid(52) 还验证了参数里带/* retain */注释时规则只报告、不提供任何修复或建议。4. 构造器参数重建new Temporal.PlainDate(source.year, source.month, source.day)快照 invalid(4)、(8)、(12)、(17)、(21) 等展示了从源对象逐字段复制给目标构造器的模式new Temporal.PlainDate(source.year, source.month, source.day); // ❌ - source.toPlainDate() new Temporal.PlainTime(source.hour, source.minute, source.second, source.millisecond, source.microsecond, source.nanosecond); // ❌ - source.toPlainTime() new Temporal.PlainDateTime(source.year, source.month, source.day, source.hour, ...); // ❌ - source.toPlainDateTime()时间重建必须包含全部六个时间字段直到nanosecond缺少任何一个字段快照如 valid 用例中的new Temporal.PlainTime(source.hour, source.minute)都不会被报告。源码中getFieldEntries对NewExpression按字段数组下标逐一映射rules/prefer-temporal-conversion.js#L67-L87。5. 属性包property bag重建Temporal.PlainDate.from({year, month, day})快照 invalid(5)-(7)、(13)、(18)-(20) 等覆盖了对象字面量属性包的场景这也是模式最丰富的一类Temporal.PlainDate.from({year: source.year, month: source.month, day: source.day}); // ❌ - source.toPlainDate() Temporal.PlainDate.from({year: source.year, monthCode: source.monthCode, day: source.day, calendar: source.calendarId}); // ❌ - source.toPlainDate() Temporal.PlainTime.from({hour: source.hour, minute: source.minute, ..., nanosecond: source.nanosecond}); // ❌ - source.toPlainTime()关键规则源码 rules/prefer-temporal-conversion.js#L89-L125 的getFieldReconstruction日期属性包中的月份既可以是month也可以是monthCodemonthCode会按month归一化匹配允许显式携带calendar: source.calendarId快照 invalid(6)、(7)属性包不允许出现计算属性名、方法、getter、多余字段或来自不同接收者的字段快照 valid 用例中{year: source.year, month: source.month, day: other.day}、{...source}、{[year]: ...}等均不报告若属性值表达式有副作用如(log(), source).year中的序列表达式接收者也不报告——见快照 invalid(49)、(50) 与源码中hasSideEffect(receiver, ...)检查。6. 变量别名与复杂表达式接收者快照 invalid(47)-(50) 验证了规则对源对象的识别能力常量绑定const alias source; alias.epochNanoseconds、条件表达式(condition ? source : other)、序列表达式(log(), source)都能被追踪并正确替换const alias source; Temporal.Instant.fromEpochNanoseconds(alias.epochNanoseconds); // ❌ - alias.toInstant() Temporal.Instant.fromEpochNanoseconds((condition ? source : other).epochNanoseconds); // ❌ - (condition ? source : other).toInstant()自动修复与编辑器建议的边界官方文档Fixes and suggestions一节与快照共同确认了这条核心分界线语义完全等价的转换自动修复可能改变结果的转换仅给编辑器建议。模式修复方式原因直接单参数from(source)自动修复精确转换基于纳秒的 Instant 重建自动修复无损序列化为 plain 类型PlainDate/PlainTime/PlainDateTime自动修复无损完整时间字段重建自动修复无损显式保留源日历的日期属性包自动修复保留日历基于毫秒的 Instant 重建仅建议丢弃亚毫秒精度序列化为 Instant仅建议ZonedDateTime序列化将历史时区偏移取整到分钟可能丢秒不含日历的日期属性包仅建议默认回退到 ISO 8601 日历日期构造器带数值参数仅建议数值参数被解释为 ISO 字段即使提供日历也一样快照中的具体证据invalid(1) 等自动修复用例输出在Output字段invalid(4) 等仅建议用例输出在Suggestion 1/1字段建议文案为Replace with .toPlainDate().。此外快照 invalid(52)-(55) 证明只要匹配到的转换代码中包含注释无论注释在参数、属性还是调用中规则只报告错误消息不提供 fix 也不提供 suggestion——这是为了防止修复吞掉开发者注释。当--fix替换后需要时源码还会自动补分号或括号快照 invalid(64) 展示了多行代码场景下在语句开头插入;的修复结果源码见 rules/prefer-temporal-conversion.js#L198-L210 的fix函数。源码实现原理速览规则的实现入口是 rules/prefer-temporal-conversion.js 中的create函数#L156-L223它同时监听CallExpression与NewExpression核心流程为识别目标构造器仅接受Temporal.X成员表达式new Temporal.PlainDate(...)、Temporal.PlainDate.from(...)、Temporal.Instant.fromEpochMilliseconds(...)等且排除可选调用、计算属性名、多余参数与 Spread 参数匹配转换模式getConversionMatch依次处理序列化往返toString/toJSON、直接对象转换、字段重建构造器/属性包、Instant 的 epoch 字段验证源类型conversions中通过createTypeCheckers生成类型检查器静态识别new Temporal.X(...)、Temporal.X.from(...)、Temporal.Now.zonedDateTimeISO()/plainDateTimeISO()以及显式 TypeScript 类型标注#L44-L65生成问题报告根据canAutofix决定挂载fix自动修复还是suggest编辑器建议。规则元信息#L228-L244声明了type: suggestion、fixable: code、hasSuggestions: true、schema: []无配置项并标记recommended: unopinionated。TypeScript 支持类型断言、satisfies 与非空断言快照后半部分第 3 组用例专门验证了 TypeScript 下的行为unwrapTypeScriptExpression会剥离包裹转换输入的各类 TS 表达式rules/prefer-temporal-conversion.js#L9// invalid(60)-(63)接收者上的断言 (source as Temporal.ZonedDateTime).epochNanoseconds // ❌ - (source as Temporal.ZonedDateTime).toInstant() (source satisfies Temporal.ZonedDateTime).epochNanoseconds // ❌ - (source satisfies Temporal.ZonedDateTime).toInstant() source!.epochNanoseconds // ❌ - (source!).toInstant() (Temporal.ZonedDateTimesource).epochNanoseconds // ❌ - (Temporal.ZonedDateTimesource).toInstant() // invalid(1)-(4)属性包整体断言 Temporal.PlainDate.from(({...} as Temporal.PlainDateLike)); // ❌ - source.toPlainDate() Temporal.PlainDate.from(({...} satisfies Temporal.PlainDateLike)); // ❌ - source.toPlainDate() Temporal.PlainDate.from((Temporal.PlainDateLike{...})); // ❌ - source.toPlainDate() Temporal.PlainDate.from(({...})!); // ❌ - source.toPlainDate() // invalid(6)-(8)字段级断言、字符串断言 Temporal.PlainDate.from({year: source.year as number, month: (source.month satisfies number), day: source.day!, calendar: source.calendarId as string}); // ❌ - source.toPlainDate() Temporal.PlainDate.from(source.toString() as string); // ❌ - source.toPlainDate() Temporal.PlainDate.from(source.toJSON() satisfies string); // ❌ - source.toPlainDate()对于注释位于断言内部的情况invalid(5)as /* keep */ Temporal.PlainDateLike同样遵守含注释只报告不修复的规则。类型感知模式利用 TypeScript 类型信息快照中的typeAware测试块file.tstypescriptEslintParser验证了规则在启用类型信息时的行为。当无法从语法上识别源类型时规则会借助 TypeScript checker 的getFullyQualifiedName判断符号是否解析为Temporal.ZonedDateTime/Temporal.PlainDateTime源码 #L54-L62declare namespace Temporal { class ZonedDateTime { epochNanoseconds: bigint; year: number; month: number; day: number; calendarId: string; } } declare function getSource(): Temporal.ZonedDateTime; new Temporal.Instant(getSource().epochNanoseconds); // ❌ - getSource().toInstant() Temporal.Instant.from(getSource()); // ❌ - getSource().toInstant() // 通过成员访问的属性包也能识别 Temporal.PlainDate.from({year: holder.source.year, month: holder.source.month, day: holder.source.day, calendar: holder.source.calendarId}); // ❌ - holder.source.toPlainDate()同时快照确认了类型感知的边界当返回类型是联合类型且包含非 Temporal 成员如Temporal.ZonedDateTime | undefined、Temporal.ZonedDateTime | string、类型为unknown/any/Temporal.PlainDate/裸PlainDateTime时规则不报告Temporal.ZonedDateTime | Temporal.PlainDateTime联合类型仅在目标是PlainDate/PlainTime时报告因为两者都有对应的toPlainDate/toPlainTime方法目标是Instant/PlainDateTime时不报告快照 invalid(9)、(10) 与 valid 用例对比可见。明确不检查的边界官方文档与快照 valid 用例共同确认了以下场景不会被报告部分字段或修改过的字段{year, month}缺day、source.day 1多余属性、重复属性、计算属性名、getter 方法混合接收者不同字段来自不同对象展开运算{...source}、可选链source?.toString()、可选调用source.toString?.()字符串操作结果source.toString().slice(0, 10)解析/序列化选项Temporal.PlainDate.from(source.toString({calendarName: never}))、需要额外参数的转换同类型克隆Temporal.PlainDateTime.from(source)源与目标同为 PlainDateTimeDate互操作、任意方法链推断、未知接收者与普通数据对象Temporal.ZonedDateTime.from(value, options, extra)这类参数数量不合规的源构造。规则也不追踪重命名导入仅当本地绑定仍叫Temporal时支持 polyfill 导入快照 invalid(51) 验证了import {Temporal} from js-temporal/polyfill场景。在项目中使用与验证安装与启用npm install --save-dev eslint eslint-plugin-unicorn该规则已默认包含在recommended配置中见 readme.md#L600-L629 的预设配置示例也可以单独启用import unicorn from eslint-plugin-unicorn; import {defineConfig} from eslint/config; export default defineConfig([ { files: [**/*.js], plugins: {unicorn}, extends: [unicorn/recommended], rules: { unicorn/prefer-temporal-conversion: error, }, }, ]);由于规则无配置项schema: []开箱即用无需任何选项。自动修复与编辑器建议运行npx eslint . --fix可自动修复所有精确转换类问题对于会改变结果的转换毫秒重建、Instant 序列化往返、缺省日历的属性包等需要借助编辑器建议逐一手动应用或按官方文档建议先显式添加取整操作如source.epochMilliseconds场景下显式round再交给规则处理。运行快照测试在仓库根目录执行测试命令即可重新生成/校验本文解析的快照文档npx ava test/prefer-temporal-conversion.js测试用例定义位于 test/prefer-temporal-conversion.js其注释Lock down the distinction between exact conversions and restoring discarded information直接点明了自动修复与建议分界的测试意图快照输出保存在 test/snapshots/prefer-temporal-conversion.js.md 与prefer-temporal-conversion.js.snap中。小结prefer-temporal-conversion规则通过识别六类间接转换模式将 Temporal 类型间的转换收敛为原生toInstant()/toPlainDate()/toPlainDateTime()/toPlainTime()方法在消除样板代码的同时避免序列化往返与字段重建带来的精度和日历信息丢失。其精确转换自动修复、信息有损转换仅给建议、含注释只报告的三级行为设计配合对 TypeScript 断言、类型感知、变量别名与复杂表达式的支持使其既激进又安全。快照文档 test/snapshots/prefer-temporal-conversion.js.md 作为行为契约完整记录了每个检测分支的输入、报错与修复结果是理解和使用该规则最可靠的参考手册。【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考