深入理解 Rome 的 noShadowRestrictedNames 规则:禁止遮蔽受限全局名称
【免费下载链接】toolsUnified developer tools for JavaScript, TypeScript, and the web项目地址: https://gitcode.com/gh_mirrors/to/tools
导读
noShadowRestrictedNames是 Rome(统一 JavaScript/TypeScript 开发者工具链)中一条由官方推荐(recommended)的 lint 规则,从 v0.9.0 起随项目发布。它用于禁止开发者用NaN、Set、JSON、Array、Object、undefined等内置受限名称去声明变量、函数、参数或 catch 子句绑定,从而避免遮蔽(shadowing)全局名称导致的命名混乱与难以排查的 bug。读完本文,你将完整掌握该规则的触发场景、诊断信息格式、底层实现原理、内置受限名称清单,以及如何在rome.json配置中开启、关闭或调整它的告警级别。
规则概览:这是什么规则
noShadowRestrictedNames归类于 Rome lint 的suspicious(可疑代码)分组,规则标识为suspicious/noShadowRestrictedNames。官方文档对它的定义只有一句话:
Disallow identifiers from shadowing restricted names.(禁止标识符遮蔽受限名称。)
所谓"遮蔽",指的是在某个作用域内声明了一个与全局(built-in)名称同名的绑定,导致在该作用域内引用该名称时,解析到的是局部绑定而不是全局对象。例如在函数内声明function JSON() {},函数体内的JSON就不再指向全局JSON对象。这类写法极易造成混淆——正如规则诊断信息中的提示语所说:"It's easy to confuse the origin of variables when they're named after a known global."(当变量以某个已知全局命名时,很容易混淆变量的来源。)
该规则标注为recommended(推荐),意味着在 Rome 默认的推荐配置下它会被自动启用,无需额外手动开启。规则的推荐属性可以从源码中的声明直接确认:no_shadow_restricted_names.rs 中declare_rule!宏显式设置了recommended: true,并在同一位置声明了version: "0.9.0"与规则名noShadowRestrictedNames。
何时触发:文档中的 Invalid 示例
官方文档给出了 5 个会触发该规则的典型反例,全部来自规则源码中的文档注释(no_shadow_restricted_names.rs),并经快照测试逐一验证。下面逐一说明:
用受限名称声明函数
function NaN() {}诊断输出(位置1:10指向函数名):
suspicious/noShadowRestrictedNames.js:1:10 lint/suspicious/noShadowRestrictedNames ━━━━━━━━━━━ ✖ Do not shadow the global "NaN" property. > 1 │ function NaN() {} │ ^^^ ℹ Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.用受限名称声明变量
let Set;诊断位置1:5指向标识符Set。
在 catch 子句中遮蔽
try { } catch(Object) {}诊断位置1:15指向 catch 参数Object。注意catch (Object)中的Object是一个绑定,它同样会遮蔽全局Object。
用受限名称声明构造函数
function Array() {}在函数参数中遮蔽
function test(JSON) {console.log(JSON)}诊断位置1:15指向参数JSON。这条示例尤其值得注意:即便函数体内确实"使用"了这个参数,规则仍然会告警,因为遮蔽全局名称本身即被认为是反模式。
诊断信息统一格式
所有触发场景的诊断输出结构一致,包含三个组成部分:
- 错误级别:
✖前缀,表示该诊断属于 error 级别(当规则以默认 error 级别运行时); - 主消息:
Do not shadow the global "<名称>" property.,其中的<名称>是实际被遮蔽的受限名称; - 提示(note):
Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global.,建议开发者重命名该变量。
诊断位置的精确性从快照可以看出:function NaN() {}定位到1:10,let Set;定位到1:5,catch(Object)定位到1:15,均精准落在标识符绑定 token 上,而非整行或整个声明。这种基于精确 token 范围的定位能力,源于规则的查询类型为Ast<JsIdentifierBinding>(标识符绑定节点),诊断范围使用binding.syntax().text_trimmed_range()取得(见 no_shadow_restricted_names.rs)。
内置受限名称清单:BUILTIN 常量
规则判断"是否为受限名称"的依据是rome_js_analyze中 globals 模块定义的BUILTIN常量:一个长度为 66 的字符串数组(runtime.rs)。完整清单如下:
| 分类 | 名称 |
|---|---|
| 全局对象/构造器 | Array、ArrayBuffer、Boolean、DataView、Date、Error、Function、JSON、Map、Math、Number、Object、Promise、Proxy、RegExp、Set、String、Symbol、WeakMap、WeakRef、WeakSet、BigInt、BigInt64Array、BigUint64Array、Float32Array、Float64Array、Int8Array、Int16Array、Int32Array、Uint8Array、Uint8ClampedArray、Uint16Array、Uint32Array、SharedArrayBuffer、Atomics、FinalizationRegistry、Reflect、AggregateError、EvalError、RangeError、ReferenceError、SyntaxError、TypeError、URIError |
| 特殊值 | NaN、Infinity、undefined、globalThis |
| 全局函数 | decodeURI、decodeURIComponent、encodeURI、encodeURIComponent、escape、unescape、eval、isFinite、isNaN、parseFloat、parseInt |
| 对象原型方法 | constructor、hasOwnProperty、isPrototypeOf、propertyIsEnumerable、toLocaleString、toString、valueOf |
几个值得注意的细节:
- 该清单不仅包含标准库构造器,还包含
undefined、NaN、Infinity这类全局属性,以及eval、parseInt等全局函数; - 原型链上的通用方法(
toString、valueOf、constructor等)也被纳入,因此function toString() {}这类声明同样会被拦截; - 判断是大小写敏感的精确字符串匹配:
run函数中执行BUILTIN.contains(&name)(no_shadow_restricted_names.rs),所以let set;(小写)不会被该规则拦截,而let Set;会。从命名上看,该常量描述的是"内置(built-in)全局名称"全集,而同一文件中的ES_5、ES_2015、ES_2017、ES_2020、ES_2021等常量则按 ECMAScript 版本细化了各时代的全局集合,BUILTIN可以理解为各版本全局名称的汇总集合(从源码结构看,BUILTIN覆盖了从 ES5 到较新提案的绝大多数全局绑定)。
测试验证:invalid.jsonc 与快照
规则的行为由测试套件固化。测试输入文件位于 crates/rome_js_analyze/tests/specs/suspicious/noShadowRestrictedNames/invalid.jsonc,内容为 7 个待检测的代码片段:
[ "function NaN() {}", "function undefined() {}", "function Infinity() {}", //function arguments() {} //function eval() {} "function Array() {}", "function test(JSON) { console.log(JSON); }", "let Set;", "try {} catch (Object) {}" ]对应的快照文件 invalid.jsonc.snap 记录了每个输入产生的完整诊断。通过对比测试输入与快照输出,可以得到两个额外的实证结论:
undefined、Infinity同样会被拦截:function undefined() {}与function Infinity() {}分别产生Do not shadow the global "undefined" property.与Do not shadow the global "Infinity" property.的诊断,说明undefined、Infinity与NaN一样位于受限清单中(与前述BUILTIN数组内容吻合);arguments与eval被注释排除在测试之外(//function arguments() {}、//function eval() {}):eval虽在BUILTIN清单中,但这两个用例被刻意注释,说明测试作者对部分边界场景做了有意的取舍,实际拦截行为以源码BUILTIN清单为准。
规则实现原理:从 AST 绑定到诊断
该规则的完整实现位于 no_shadow_restricted_names.rs,整个执行流程只有两个核心阶段:
阶段一:匹配标识符绑定(run)
type Query = Ast<JsIdentifierBinding>; type State = State; type Signals = Option<Self::State>; type Options = (); fn run(ctx: &RuleContext<Self>) -> Option<Self::State> { let binding = ctx.query(); let name = binding.name_token().ok()?; let name = name.text_trimmed(); if BUILTIN.contains(&name) { Some(State { shadowed_name: name.to_string() }) } else { None } }要点:
- 查询类型为
Ast<JsIdentifierBinding>,即规则只对"标识符绑定"节点生效。JsIdentifierBinding是 Rome JS 语法树中代表变量声明、函数名、参数、catch 绑定等"绑定"位置的节点类型(定义于 rome_js_syntax crate 的生成代码中),这决定了规则天然覆盖let/const/var声明、函数声明名、函数参数、catch 子句参数等所有绑定场景; - 取到绑定节点后,通过
name_token()获取名称 token,用text_trimmed()去掉可能存在的空白后得到实际标识符文本; - 与
BUILTIN做contains精确匹配;命中则构造State { shadowed_name }作为信号传出,未命中返回None表示不报告; type Options = ()表明该规则不接受任何配置选项,只能整体开/关或调整级别。
阶段二:生成诊断(diagnostic)
fn diagnostic(ctx: &RuleContext<Self>, state: &Self::State) -> Option<RuleDiagnostic> { let binding = ctx.query(); let diag = RuleDiagnostic::new( rule_category!(), binding.syntax().text_trimmed_range(), markup! { "Do not shadow the global \"" {state.shadowed_name} "\" property." }, ) .note( markup! {"Consider renaming this variable. It's easy to confuse the origin of variables when they're named after a known global."}, ); Some(diag) }要点:
- 诊断范围直接取自绑定节点语法文本的精确范围(
text_trimmed_range()),这就是前面看到的诊断能精确定位到1:10、1:5这类列号的原因; - 主消息中的受限名称来自
run阶段捕获的state.shadowed_name,保证"报什么名字"与"检测到什么名字"完全一致; - 通过
.note()附加重命名建议,即文档中展示的ℹ提示行。
调用链与注册
从规则声明到实际生效的完整链路为:
- 规则在 suspicious.rs 中通过
declare_rule!宏注册; - 规则的诊断分类为
lint/suspicious/noShadowRestrictedNames,在 categories.rs 中登记; - 运行时由
rome_analyze框架的 visitor 遍历 AST,对每个JsIdentifierBinding节点调用规则的run/diagnostic方法产出诊断。
此外,规则文档生成是源码驱动的:官网文档页面(即本仓库中的 website/src/pages/lint/rules/noShadowRestrictedNames.md)中的示例与诊断输出,与declare_rule!注释中的expect_diagnostic示例一一对应,属于通过 xtask 等代码生成工具从源码注释同步出来的文档,因此文档与实现始终一致。
如何在项目中配置该规则
由于该规则是 recommended 规则,Rome 默认配置会启用它。若需自定义,可在项目根目录的rome.json中针对suspicious分组或单条规则进行配置:
{ "linter": { "enabled": true, "rules": { "recommended": true, "suspicious": { "noShadowRestrictedNames": "off" } } } }配置语义说明:
"recommended": true表示启用所有推荐规则,此时noShadowRestrictedNames默认以error级别生效;- 若要降级为警告而不完全关闭,可将值改为
"warn";完全关闭则用"off"; - 规则的配置项在源码中对应 rules.rs 里的
pub no_shadow_restricted_names: Option<RuleConfiguration>字段,并通过"noShadowRestrictedNames"字符串在反序列化时映射(见 rules.rs 的匹配分支),因此rome.json中使用的键名是驼峰式的noShadowRestrictedNames; - 由于该规则
Options类型为(),它不支持类似 ESLint 的options数组(如自定义受限名称列表),只能控制开关与告警级别。
也可通过 CLI 参数在命令行临时调整,例如:
rome check --linter-rules-suspicious-no-shadow-restricted-names=off src/(注:具体 CLI 长选项由 rules.rs 中的bpaf(long(...))属性生成,实际可用选项名以当前版本rome --help输出为准。)
相关使用指引
- 需要禁用某条规则或添加
// rome-ignore抑制注释时,参见 linter 文档中的 Disable a rule 一节; - 需要了解规则的全局配置、级别语义与 recommended 机制时,参见 linter 文档中的 Rule options 一节;
- 所有规则的完整索引见 rules 索引页。
与其他全局相关规则的协同
在rome_js_analyze的 globals 模块(crates/rome_js_analyze/src/globals/runtime.rs)中,除了BUILTIN之外,还定义了按 ECMAScript 版本划分的ES_5、ES_2015、ES_2017、ES_2020、ES_2021等全局集合。这些集合与BUILTIN共同构成了 Rome 对"全局名称"的认知基础,被其他规则复用:
- 与
noShadowRestrictedNames(禁止遮蔽内置名称)不同,部分其他规则关注的是"使用未声明变量"或"对只读全局赋值"等相邻问题,它们同样依赖这套全局名称清单做判定; - 因此,
BUILTIN清单的增删会同时影响多条规则的判定结果,属于全局共享的基础数据。
从源码结构可以推断,BUILTIN是各 ES 版本全局集合的上位汇总:它既包含 ES5 时代的Array、Object、JSON、Math等经典全局,也包含BigInt、Atomics、FinalizationRegistry、WeakRef、AggregateError等较新标准中的全局,甚至纳入了escape/unescape这类历史遗留全局,覆盖面完整。
总结
| 维度 | 结论 |
|---|---|
| 规则全名 | suspicious/noShadowRestrictedNames |
| 引入版本 | v0.9.0 |
| 是否推荐 | 是(recommended,默认 error 级别启用) |
| 检测对象 | 所有JsIdentifierBinding(变量声明、函数名、参数、catch 绑定) |
| 判定依据 | 标识符文本精确匹配BUILTIN常量(66 个内置全局名称) |
| 支持选项 | 无(仅可开/关/调整级别) |
| 典型消息 | Do not shadow the global "<名称>" property. |
noShadowRestrictedNames是一条小而精的规则:实现只有约 80 行 Rust 代码,却通过精确的 AST 绑定查询与共享的全局名称清单,有效拦截了一类极易引发命名混淆的反模式。结合源码(no_shadow_restricted_names.rs、runtime.rs)、测试(invalid.jsonc)与配置(rules.rs),你可以完整追踪它的行为边界,并在实际项目中正确使用或调整它。
【免费下载链接】toolsUnified developer tools for JavaScript, TypeScript, and the web项目地址: https://gitcode.com/gh_mirrors/to/tools
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考