Rome 的 useIsNan 规则详解:用 Number.isNaN 替代 NaN 比较,杜绝隐蔽的 JavaScript 逻辑错误

发布时间:2026/9/20 3:43:27
Rome 的 useIsNan 规则详解:用 Number.isNaN 替代 NaN 比较,杜绝隐蔽的 JavaScript 逻辑错误 开发工具CLILint格式化静态分析代码质量构建工具【免费下载链接】toolsUnified developer tools for JavaScript, TypeScript, and the web项目地址https://gitcode.com/gh_mirrors/to/tools点击查看免费下载本文聚焦 Rome该仓库的 JavaScript/TypeScript 统一开发者工具链在correctness组中内置的useIsNanlint 规则它强制开发者在使用NaN时调用isNaN()/Number.isNaN()而非直接进行比较。你将理解NaN的 IEEE 754 语义为何会导致比较结果反直觉掌握该规则对二元比较表达式与switch语句两类模式的完整检测逻辑并通过源码与测试用例use_is_nan.rs、invalid.js验证其边界行为最终能在 rome.json 中按需启用、关闭或调整该规则的严重级别。规则概览Require calls toisNaN()when checking forNaNuseIsNan是 Rome 官方推荐的 lint 规则recommended: true自 v12.0.0 起随 Rome 一同发布归属于correctness正确性规则组完整标识为lint/correctness/useIsNan。它的职责只有一句话检查NaN时必须调用isNaN()函数禁止直接与NaN进行比较。规则说明页面位于 website/src/pages/lint/rules/useIsNan.md同时在 crates/rome_js_analyze/src/semantic_analyzers/correctness/use_is_nan.rs 中有源码级声明declare_rule! { /// Require calls to isNaN() when checking for NaN. pub(crate) UseIsNan { version: 12.0.0, name: useIsNan, recommended: true, } }该规则的语义源头来自 ESLint 的 use-isnan 规则Rome 将其移植并融合了自身的语义分析能力。由于它是 recommended 规则启用 linter 后即默认生效无需额外配置即可拦截代码中的NaN误比较。为什么禁止直接比较NaNIEEE 754 语义剖析要理解这条规则的价值必须回到 JavaScript 的数值语义。NaNNot-a-Number是Number类型中的一个特殊值用于表示 IEEE Standard for Binary Floating-Point ArithmeticIEEE 754双精度 64 位浮点格式下的所有非数值结果——例如0 / 0、parseInt(abc)、Math.sqrt(-1)都会产生NaN。NaN在 JavaScript 中最反直觉的特性是它不与任何值相等包括它自己。这直接导致以下比较结果表达式求值结果NaN NaN或NaN NaNfalseNaN ! NaN或NaN ! NaNtrue也就是说NaN既不大于、不小于、也不等于任何数任何对它的大小/相等比较都是恒假或恒真的因而毫无判断价值。试图用x NaN判断x是否非数值永远得不到true而x ! NaN则恒为true等于没有做任何检查代码中的分支将永远走同一条路径属于典型的隐蔽逻辑错误。因此正确做法是使用函数测试Number.isNaN(value)不进行任何类型转换仅当参数本身就是数值类型的NaN时返回true全局isNaN(value)当参数不是数值时会先将其强制转换为数值再判断语义与Number.isNaN不同。注意Number.isNaN()与全局isNaN()的行为并不等价。全局isNaN(abc)会把abc先转为NaN再返回true而Number.isNaN(abc)直接返回false因为abc不是数值。因此Number.isNaN()是判断一个值是否为NaN更可靠的方式Rome 的诊断消息也统一建议使用Number.isNaN。检测范围与诊断消息从源码结构看useIsNan的查询对象由三类语法节点组成use_is_nan.rsdeclare_node_union! { pub(crate) UseIsNanQuery JsBinaryExpression | JsCaseClause | JsSwitchStatement }对应三种场景规则输出三类诊断消息见Message::as_str场景触发节点诊断消息二元比较表达式JsBinaryExpressionUse the Number.isNaN function to compare with NaN.switch的case测试表达式JsCaseClausecase NaN can never match. Use Number.isNaN before the switch.switch的判断表达式JsSwitchStatementswitch(NaN) can never match a case clause. Use Number.isNaN instead of the switch.1. 二元比较表达式比较运算符 NaN 操作数当二元表达式的运算符是比较运算符、、!、!、、、、且左操作数或右操作数中任意一方命中NaN时触发。规则实现先通过is_comparison_operator()过滤出比较运算再对两侧操作数分别调用has_nan检查if bin_expr.is_comparison_operator() (has_nan(bin_expr.left().ok()?, model) || has_nan(bin_expr.right().ok()?, model)) { return Some(RuleState { message_id: Message::BinaryExpression, range: bin_expr.range(), }); }值得注意的是ESLint 的原始规则默认只拦截/!//!因为恒等运算符判断NaN时直接抛出错误而 Rome 的实现把范围扩大到了全部六类比较运算符——因为对NaN的任何大小比较同样恒为false/true都属于无意义判断。2. switch 语句case 值与判断表达式switch的语义是使用严格相等逐一比较判断表达式与各个case值因此只要其中一方是NaN就必然匹配失败当某个case的测试表达式是NaNcase NaN:时该分支永远不可能命中当switch的判断表达式是NaNswitch(NaN) {...}时任何case都永远无法匹配。这两种情形分别由JsCaseClause与JsSwitchStatement两个分支捕获并给出针对性提示。has_nan 判定逻辑什么是NaN 表达式has_nan函数use_is_nan.rs决定了一个表达式是否被识别为NaN其判定规则相当精细去掉括号先对表达式调用omit_parentheses()因此(NaN)、((NaN))这类括号包裹形式同样会被识别直接标识符若表达式是全局标识符且名字为NaN命中成员表达式若表达式是成员访问且成员名为NaN且其对象是名为Number的全局标识符命中——即支持Number.NaN也支持Number[NaN]与Number?.NaN这类等价写法语义绑定校验最后通过model.binding(reference).is_none()确认该标识符没有被局部变量遮蔽。第 4 点是最关键的设计如果用户在局部作用域里声明了const NaN 5;之类的变量那么x NaN比较的就不再是全局NaN规则会放行。这种全局标识符绑定语义正是 Rome 的语义分析rome_js_semantic提供的SemanticModel相对 ESLint 纯语法匹配的优势所在。同理globalThis.NaN、window.NaN、globalThis.Number.NaN这类写法在浏览器/Node 环境中指向同一个全局NaN测试用例中也被列为无效代码见 invalid.js。无效代码示例命中规则的写法以下代码均会被lint/correctness/useIsNan报告诊断源自规则文档与 invalid.js 测试用例// 直接与 NaN 比较恒为 false/true毫无意义 123 NaN 123 ! NaN NaN abc NaN ! abc 123 NaN; 123 ! NaN; NaN abc; abc NaN; // 与 Number.NaN 比较同样无效 123 Number.NaN Number.NaN abc x Number?.NaN; x Number[NaN]; // switch 中 case 值为 NaN分支永不可达 switch(foo) { case (NaN): break; } switch(foo) { case Number.NaN: break; } // switch 判断表达式为 NaN所有 case 永不可达 switch(NaN) { case foo: break; } switch(NaN) {} // 全局环境中的 NaN 引用同样被识别 123 globalThis.NaN; 123 window.NaN; 123 globalThis.Number.NaN;运行 lint 时诊断输出的格式大致如下摘自 invalid.js.snapinvalid.js:1:1 lint/correctness/useIsNan ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ ! Use the Number.isNaN function to compare with NaN. 1 │ 123 NaN; │ ^^^^^^^^^^而对于switch场景诊断消息会分别提示case NaN can never match. Use Number.isNaN before the switch.或switch(NaN) can never match a case clause. Use Number.isNaN instead of the switch.并精准定位到case测试表达式或判断表达式的文本区间。有效代码示例正确的 NaN 处理方式以下写法不会触发规则摘自规则文档与 valid.js// 使用 Number.isNaN / 全局 isNaN 进行判断 if (Number.isNaN(123) ! true) {} isNaN(NaN) true; Number.isNaN(Number.NaN) true; // NaN 仅作为普通数值参与运算非比较 foo(Number.NaN / 2) foo(NaN 1); var x NaN; var x Number.NaN; // 空的 switch 或 case 值不是 NaN switch(foo) {} switch(NaN) { default: break; } switch(foo(NaN)) {} // 标识符大小写不同或非全局绑定不命中 switch(Nan) {} switch(foo) { case Nan: break } x Number[NaN]; // 成员名是变量 NaN不是字面量成员 NaN从 valid.js 可以看到x Number[NaN]是合法的这里NaN是作为计算属性名的变量其值为NaN规则只匹配成员名为字面量NaN的访问。同理switch(Nan)、case Nan大写N不同因为标识符名字不精确等于NaN也不命中。这些边界用例说明该规则的匹配非常精确不会误伤命名类似的标识符。测试用例invalid/valid 快照如何验证规则Rome 的每条 lint 规则都配有端到端快照测试useIsNan的用例位于 crates/rome_js_analyze/tests/specs/correctness/useIsNan/invalid.js汇总了 63 行应当报错的代码覆盖全部六类比较运算符//!/!////、NaN在左/右操作数的各种位置、Number.NaN/Number[NaN]/Number?.NaN、globalThis.NaN/window.NaN等全局引用以及switch中case NaN/switch(NaN)的十几种排列组合含多个case NaN、case (NaN)括号形式、default分支共存等invalid.js.snap对应的诊断快照精确记录每条诊断的位置与消息文本valid.js52 行合法代码验证isNaN()/Number.isNaN()调用、算术运算中的NaN、变量声明、空switch、大小写变体Nan、字符串NaN等均不误报valid.js.snap确认无诊断输出。这些用例由 spec_tests.rs 驱动执行保证规则行为在后续迭代中保持稳定。从测试规模可以看出useIsNan对比较运算 switch这两大类NaN误用场景的覆盖是相当完备的。在项目配置中管理 useIsNanuseIsNan属于 recommended 规则默认启用且以error级别输出诊断。项目通过 rome.json 中的linter.rules配置来控制它完整配置语法见 linter/index.mdx保持默认启用推荐做法无需任何配置即可生效{ linter: { enabled: true, rules: { correctness: {} } } }关闭规则将correctness.useIsNan设为off{ linter: { enabled: true, rules: { correctness: { useIsNan: off } } } }降低严重级别在重构或 CI 过渡期可设为warn避免阻断构建{ linter: { enabled: true, rules: { correctness: { useIsNan: warn } } } }针对单行忽略当确有业务需要直接比较NaN例如特意利用x ! NaN恒为 true 的行为时可通过 suppression 注释局部放行语法见 linter/index.mdx// rome-ignore lint/correctness/useIsNan: 这里特意依赖恒真比较 const alwaysTrue x ! NaN;规则选项方面useIsNan的Options类型为()use_is_nan.rs即该规则不接受任何额外配置参数行为固定为文档所述配置时只需控制其leveloff/warn/error。小结useIsNan是 Rome 中一条小而关键的正确性规则它把 JavaScript 开发者最容易忽略的NaN恒不等语义IEEE 754 规范使然转化为编译期诊断强制以Number.isNaN()或全局isNaN()替代NaN比较并额外覆盖了switch中case NaN与switch(NaN)两种永不可达分支场景。其实现借助语义模型识别全局绑定、支持Number.NaN/Number[NaN]等变体并规避局部变量遮蔽配以覆盖数十种边界情形的快照测试。对任何 Rome 使用者而言保持这条 recommended 规则开启是避免NaN相关隐性 bug 的最低成本手段。赞分享开发工具CLILint格式化静态分析代码质量构建工具【免费下载链接】toolsUnified developer tools for JavaScript, TypeScript, and the web项目地址https://gitcode.com/gh_mirrors/to/tools点击查看免费下载相关推荐深入解析 Rome 的 noSelfCompare 规则杜绝变量自比较陷阱深入解析 Rome 的 noSelfCompare 规则杜绝变量自比较陷阱 noSelfCompare 是 Rome 内置 lint 规则集中位于 suspi开发工具CLILint格式化静态分析代码质量构建工具Rome 的 noGlobalIsNan 规则用 Number.isNaN 替代全局 isNaN 的完整指南Rome 的 noGlobalIsNan 规则用 Number.isNaN 替代全局 isNaN 的完整指南 noGlobalIsNan 是 Rome本仓库开发工具CLILint格式化静态分析代码质量构建工具Rome 的 useValidForDirection 规则详解检测 for 循环计数器方向错误杜绝死循环Rome 的 useValidForDirection 规则详解检测 for 循环计数器方向错误杜绝死循环 useValidForDirection 是 R开发工具CLILint格式化静态分析代码质量构建工具上一篇DataEase 数据可视化设计系统构建一致的图表风格下一篇163MusicLyrics5分钟搞定全网音乐歌词的终极免费解决方案创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考