eslint-plugin-unicorn 规则实战:prefer-number-properties 统一 Number 静态方法与属性
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
本篇技术指南围绕 eslint-plugin-unicorn 的unicorn/prefer-number-properties规则展开,讲解它如何将全局数字函数(parseInt、parseFloat、isNaN、isFinite)与可选开启的全局常量(NaN、Infinity、-Infinity)统一收敛到Number构造函数的静态成员上。读完本文,你将掌握该规则的全部检查维度、checkNaN与checkInfinity两个选项的配置方法、自动修复与手动建议(suggestion)的边界条件,以及它与prefer-global-number-constants、prefer-number-coercion等相邻规则之间的协作与冲突关系。
规则背景:为什么用Number静态成员替代全局数字函数
ECMAScript 2015(ES6)出于一致性的考虑,将原本散落在全局作用域的数字处理函数迁移到了Number构造函数上,并在此基础上做了小幅改进。prefer-number-properties规则正是为了贯彻这一语言演进方向,限制代码中对全局数字 API 的使用,引导开发者统一书写为Number.*静态成员形式。
从 规则实现 可以看到,规则内部把需要追踪的全局对象划分成了两组:
const globalObjects = { // Safe to replace with `Number` properties parseInt: true, parseFloat: true, NaN: true, Infinity: true, // Unsafe to replace with `Number` properties isNaN: false, isFinite: false, };parseInt、parseFloat、NaN、Infinity:标记为 "safe"(安全替换),因为Number.parseInt、Number.parseFloat、Number.NaN、Number.POSITIVE_INFINITY/Number.NEGATIVE_INFINITY与对应全局形式在语义上完全一致,可以放心自动修复。isNaN、isFinite:标记为 "unsafe"(不安全替换),因为全局isNaN/isFinite与Number.isNaN/Number.isFinite存在微妙的语义差异(见下文),不能无条件自动改写。
该规则被收录于recommended与unopinionated两个配置中,注册位置见 rules/index.js。
检查清单:五个被约束的全局成员
规则共约束 5 个全局成员的用法:
| 全局形式 | 推荐写法 | 可自动修复? |
|---|---|---|
parseInt(带非十进制 radix) | Number.parseInt() | ✅ 是(fixable) |
parseFloat | Number.parseFloat() | ✅ 是(fixable) |
isNaN | Number.isNaN() | ⚠️ 仅当参数可确定为 number 时自动修复,否则为建议 |
isFinite | Number.isFinite() | ⚠️ 仅当参数可确定为 number 时自动修复,否则为建议 |
NaN(开启checkNaN后) | Number.NaN | ✅ 是(fixable) |
Infinity(开启checkInfinity后) | Number.POSITIVE_INFINITY | ✅ 是(fixable) |
-Infinity(开启checkInfinity后) | Number.NEGATIVE_INFINITY | ✅ 是(fixable) |
isNaN/isFinite的语义差异决定了修复方式
全局isNaN(value)与isFinite(value)在执行判断前会先把参数强制转换为 number,而Number.isNaN(value)与Number.isFinite(value)不做任何类型转换。例如isNaN('foo')返回true(字符串被转成NaN),而Number.isNaN('foo')返回false(字符串不是 number 类型,直接判为非 NaN)。
因此源码在 is-number.js 的辅助下实现了isCallWithNumberArgument判定(见 prefer-number-properties.js):只有当调用参数被证明一定是 number 时(数字字面量、Number()调用、数学运算结果、带number类型注解的 TypeScript 标识符、as number/satisfies number断言等),重写才是安全的,规则才会给出自动修复;否则只提供 editor suggestion 供手动确认。这一点在测试中有非常详细的对照(见 test/prefer-number-properties.js):isNaN(10)、isNaN(foo - 1)、isNaN(foo.length)可自动修复,而isNaN(foo)、isNaN(foo + bar)、isNaN(...foo)仅给建议。
示例:基本用法与错误修正
parseInt→Number.parseInt
// ❌ const foo = parseInt('10', 2); // ✅ const foo = Number.parseInt('10', 2);parseFloat→Number.parseFloat
// ❌ const foo = parseFloat('10.5'); // ✅ const foo = Number.parseFloat('10.5');isNaN→Number.isNaN
// ❌ const foo = isNaN(10); // ✅ const foo = Number.isNaN(10);isFinite→Number.isFinite
// ❌ const foo = isFinite(10); // ✅ const foo = Number.isFinite(10);边界情况:规则刻意放行的写法
规则不是无脑报错,以下写法均视为合法:
1. 无 radix 或 base-10 的parseInt不受此规则约束
// ✅ 正确写法 const foo = Number.parseInt('10', 2);// ✅ 无 radix / base-10 调用由 prefer-number-coercion 处理 const foo = parseInt('10', 10);实现中的isBase10OrNoRadixParseIntCall(见 prefer-number-properties.js)会跳过parseInt(value)(无第二个参数)以及 radix 静态求值等于10的调用;radix 为0时运行时也按十进制处理,但规则仍会报告并建议改写(测试见 test/prefer-number-properties.js)。
原因在于:base-10 的parseInt()与parseFloat()推荐使用Number()直接转换,这由另一个规则 prefer-number-coercion 负责(语义略有不同——parseFloat('50px')提取数字前缀得到50,而Number('50px')解析整个字符串得到NaN)。如果你希望强制要求显式 radix,可搭配 ESLint 内置的radix规则使用。
2. 从Number解构出来的标识符不再报告
// ✅ const {parseInt} = Number; const foo = parseInt('10', 2);这里局部变量parseInt已经遮蔽(shadow)了全局标识符,全局引用追踪器不会命中。
3. 数值零检测中的Infinity在未开启checkInfinity时不报告
// ✅ const isPositiveZero = value => value === 0 && 1 / value === Infinity;// ✅ const isNegativeZero = value => value === 0 && 1 / value === -Infinity;4. 其他放行场景
从测试用例(test/prefer-number-properties.js)可以归纳出规则还会放行:
- 标识符被遮蔽:局部变量、函数参数、解构
const {parseInt} = Number、类方法等任何形式的 shadowing; - 纯写入而非读取:
global.isFinite = Number.isFinite;、解构赋值目标等左侧写入位置; - 删除操作:
delete global.isFinite;; - TypeScript 枚举成员:如
export enum NumberSymbol { Decimal, NaN }中的NaN成员,以及declare var NaN: number;等声明文件场景(见 test/prefer-number-properties.js)。
配置选项:checkInfinity 与 checkNaN
选项类型为object,均默认关闭(默认值定义见 prefer-number-properties.js):
| 选项 | 类型 | 默认值 | 作用 |
|---|---|---|---|
checkInfinity | boolean | false | 是否检查全局Infinity与-Infinity |
checkNaN | boolean | false | 是否检查全局NaN |
checkInfinity
开启后,规则会约束Infinity与-Infinity的用法:
/* eslint unicorn/prefer-number-properties: ["error", {"checkInfinity": true}] */ // ❌ const foo = Infinity; // ✅ const foo = Number.POSITIVE_INFINITY;// ❌ const foo = -Infinity; // ✅ const foo = Number.NEGATIVE_INFINITY;实现中通过isNegative判断Infinity是否被一元负号包裹(见 prefer-number-properties.js),从而决定改写为POSITIVE_INFINITY还是NEGATIVE_INFINITY;对-Infinity的修复会整段替换外层一元表达式,并调用fixSpaceAroundKeyword处理关键字周围空白(见 prefer-number-properties.js),例如return-Infinity这类紧凑写法也能被安全修复。同时存在一个边界守卫:delete -Infinity(即delete -Infinity这种对-Infinity的删除表达式)不会被误报(isDeletedNegativeInfinity,见 prefer-number-properties.js)。
checkNaN
开启后,规则会约束NaN的用法:
/* eslint unicorn/prefer-number-properties: ["error", {"checkNaN": true}] */ // ❌ const foo = NaN; // ✅ const foo = Number.NaN;从测试快照(test/prefer-number-properties.js)可以看到,开启后规则覆盖的场景相当全面:bar[NaN]、{NaN}、{NaN: NaN}、{foo = NaN}默认值、NaN.toString()、class Foo3 {[NaN] = 1}等均会被改写为Number.NaN。
[!CAUTION]
checkNaN与checkInfinity强制的是与 prefer-global-number-constants相反的方向,而后者默认已在recommended与unopinionated配置中开启。如果你更偏好Number.NaN、Number.POSITIVE_INFINITY、Number.NEGATIVE_INFINITY这种常量形式,请禁用prefer-global-number-constants,避免两条规则对同一处代码发出冲突报告。
相邻规则协作:prefer-global-number-constants 与 prefer-number-coercion
这三个规则共同构成了数字 API 风格管理的完整矩阵,建议一起理解:
- prefer-global-number-constants:与
prefer-number-properties的checkNaN/checkInfinity选项方向相反,偏好更短、更易读的全局形式NaN、Infinity、-Infinity,而不是Number.NaN、Number.POSITIVE_INFINITY、Number.NEGATIVE_INFINITY。当替换后的全局标识符未被遮蔽时,前两者可自动修复;Number.NEGATIVE_INFINITY因涉及语句开头与链式表达式的解析变化,只报告不自动修复。 - prefer-number-coercion:负责
parseFloat()与 base-10parseInt()→Number()的转换建议(如Math.trunc(Number(value))),与prefer-number-properties对非十进制parseInt的处理形成互补,两者覆盖的 radix 场景不重叠。
实现原理:GlobalReferenceTracker 的全局引用追踪
该规则没有遍历 AST 手动检查,而是复用了项目内的GlobalReferenceTracker工具(rules/utils/global-reference-tracker.js)。它基于@eslint-community/eslint-utils的ReferenceTracker构建,在Program节点退出时(context.onExit)扫描全局作用域中的所有引用,自动完成:
- 构建追踪映射:把待检查的全局对象名(
parseInt、parseFloat、NaN、Infinity以及条件启用的isNaN/isFinite)转换成 trace map; - 过滤:通过
filter回调跳过左侧写入(isLeftHandSide)、delete -Infinity、无 radix/base-10 的parseInt等场景; - 处理:调用
getPropertyProblem生成诊断信息——安全场景直接挂fix(可--fix自动修复),不安全场景挂suggest(editor suggestion 手动应用)。
两条消息模板定义在 prefer-number-properties.js:
error:Prefer \Number.{{property}}` over `{{description}}`.`suggestion:Replace \{{description}}` with `Number.{{property}}`.`
正因为使用了全局引用追踪而非简单的标识符匹配,规则天然具备遮蔽(shadowing)感知能力:只要某个局部作用域声明了自己的parseInt、NaN等同名标识符,规则就不会误伤该局部用法。
实测验证:如何在自己的项目里启用并观察效果
- 安装:确保项目已安装
eslint-plugin-unicorn并在 ESLint 配置中启用该插件。 - 使用推荐配置:
recommended/unopinionated配置已默认开启本规则(此时仅约束四个函数形式,NaN与Infinity不检查)。 - 按需开启常量检查:在规则配置中传入
{"checkInfinity": true}和/或{"checkNaN": true};若同时使用recommended配置,需同步禁用 prefer-global-number-constants 以免冲突。 - 执行修复:运行
npx eslint --fix应用自动修复;无法自动修复的场景(如参数类型不确定的isNaN/isFinite)会在编辑器中以 suggestion 形式出现,手动应用即可。 - 回归验证:项目的测试套件在 test/prefer-number-properties.js 中对全部修复与建议场景(含 TypeScript parser、shadowing、
Infinity正负号、边界语法)做了快照覆盖,可作为行为基准参考。
【免费下载链接】eslint-plugin-unicornMore than 300 powerful ESLint rules项目地址: https://gitcode.com/GitHub_Trending/es/eslint-plugin-unicorn
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考