资讯动态

Rome 的 noInnerDeclarations 规则详解:禁止 `function` 与 `var` 声明逃逸出所属块(v12.0.0 起为推荐规则)

发布时间:2026/9/20 12:03:03 来源:尧图企业网站定制
Rome 的 noInnerDeclarations 规则详解禁止function与var声明逃逸出所属块v12.0.0 起为推荐规则【免费下载链接】toolsUnified developer tools for JavaScript, TypeScript, and the web项目地址: https://gitcode.com/gh_mirrors/to/tools本篇技术指南深入讲解 Rome统一 JavaScript、TypeScript 与 Web 开发工具链内置 lint 规则noInnerDeclarations它为何存在、如何判定、在 ES module 等场景下的例外以及如何配置与关闭。读完你将掌握该规则背后的 JS 声明提升语义并能依据本文在真实项目中正确使用、验证或按需禁用此规则同时结合 Rome 源码理解其判定机制。规则概述noInnerDeclarations自v12.0.0起被 Rome 引入并被标记为recommended推荐属于correctness正确性规则组。其官方语义为Disallowfunctionandvardeclarations that are accessible outside their block.即禁止那些作用域会逃逸出所在块的function与var声明。在 lint 报告中该规则的类别为lint/correctness/noInnerDeclarations。从 crates/rome_service/src/configuration/linter/rules.rs 的源码可以看到noInnerDeclarations同时出现在Correctness::GROUP_RULES共 26 条与Correctness::RECOMMENDED_RULES共 24 条两份清单中这意味着它默认随 Rome 的推荐规则集启用无需任何额外配置即可生效。为什么需要这条规则var与function的声明提升语义规则文档给出了核心动机Avaris accessible in the whole body of the nearest root (function, module, script, static block). To avoid confusion, they should be declared to the nearest root.也就是说var声明的变量在整个最近根作用域函数体、模块、脚本或类的静态初始化块内都可访问——这正是 JavaScript 的**变量提升hoisting**行为。因此把var写在一个嵌套的if/while/for块里并不会把它限制在块内反而会误导读者以为它是块级作用域从而造成困惑甚至 bugif (test) { var x 1; // 实际在整个函数/模块/脚本中都可访问 }对function声明同理在 ES2015 之前函数声明仅允许出现在最近根作用域中尽管部分解析器会错误地在其他地方接受它。ES2015 之后在 ES module 内部函数声明总是块级作用域的因此模块场景下函数声明可以安全地出现在嵌套块中。规则还明确了两类不受影响的声明const和let声明天然是块级作用域的不会被本规则拦截位于ES module中的嵌套块内function声明是允许的。无效示例与诊断信息以下代码会被noInnerDeclarations判定为违规四个示例覆盖了脚本/模块中的块内声明与函数体内嵌套块中的声明两种典型场景。示例一脚本的if块中声明函数if (test) { function f() {} }诊断输出位于correctness/noInnerDeclarations.js:2:5✖ This function should be declared at the root of the script. 1 │ if (test) { 2 │ function f() {} │ ^^^^^^^^^^^^^ 3 │ } 4 │ ℹ The function is accessible in the whole body of the script. To avoid confusion, it should be declared at the root of the script.示例二模块的if块中声明varif (test) { var x 1; }诊断输出correctness/noInnerDeclarations.js:2:5✖ This var should be declared at the root of the module. 1 │ if (test) { 2 │ var x 1; │ ^^^^^^^^^ 3 │ } 4 │ ℹ The var is accessible in the whole body of the module. To avoid confusion, it should be declared at the root of the module.注意由于文件被识别为模块例如包含import/export或由解析器判定为 ESM这里诊断中强调的是module根而示例一诊断的是script根。区分完全取决于文件的实际解析类型。示例三函数体内嵌套块中声明函数function f() { if (test) { function g() {} } }诊断输出correctness/noInnerDeclarations.js:3:9✖ This function should be declared at the root of the enclosing function. 1 │ function f() { 2 │ if (test) { 3 │ function g() {} │ ^^^^^^^^^^^^^ 4 │ } 5 │ } ℹ The function is accessible in the whole body of the enclosing function. To avoid confusion, it should be declared at the root of the enclosing function.示例四函数体内嵌套块中声明varfunction f() { if (test) { var x 1; } }诊断输出correctness/noInnerDeclarations.js:3:9✖ This var should be declared at the root of the enclosing function. 1 │ function f() { 2 │ if (test) { 3 │ var x 1; │ ^^^^^^^^^ 4 │ } 5 │ } ℹ The var is accessible in the whole body of the enclosing function. To avoid confusion, it should be declared at the root of the enclosing function.综合四个示例可以看到诊断信息会动态区分三类根作用域并给出对应建议script、module与enclosing function此外静态初始化块也会被识别详见下文源码分析。有效示例什么情况不会触发以下是noInnerDeclarations判定为合法的代码模式。在模块中函数声明是块级作用域的因此允许// inside a module, function declarations are block-scoped and thus allowed. if (test) { function f() {} } export {}声明在函数/脚本根部的函数function f() { }嵌套函数声明在函数体内函数体本身即最近根作用域function f() { function g() {} }var声明在函数体内未嵌套在额外块中function f() { var x 1; }块内使用const绑定函数表达式const/let块级作用域不受影响function f() { if (test) { const g function() {}; } }注意最后一个示例即便在普通脚本非模块中const/let绑定的函数表达式也是严格块级作用域的因此本规则只针对声明式function与var对函数表达式function() {}/() {}一律放行。源码实现剖析规则如何判定该规则的实现位于 crates/rome_js_analyze/src/analyzers/correctness/no_inner_declarations.rs。declare_rule!宏中声明了规则元数据version: 12.0.0、name: noInnerDeclarations、recommended: true与文档页完全一致。查询目标与前置放行规则通过type Query AstAnyJsDeclaration对所有声明节点发起查询并在run方法中按声明种类分三路处理见no_inner_declarations.rs第 103-159 行TsDeclareFunctionDeclaration若文件是模块ctx.source_type::JsFileSource().is_module()直接返回NoneESM 隐式严格模式下函数声明是块级作用域的同时跳过TsDeclareStatement取父节点的父节点继续判定。JsFunctionDeclaration若文件是模块直接放行否则取父节点继续判定。JsVariableDeclaration先通过x.is_var()确认是var声明——const/let直接返回None印证了文档中const/let 不受影响的说明随后沿语法树向上遍历跳过JS_VARIABLE_STATEMENT、JS_VARIABLE_DECLARATION_CLAUSE、TS_DECLARE_STATEMENT等包装节点找到真正的语句父节点。根作用域白名单拿到声明所在语句后代码检查其父节点种类第 140-157 行若父节点是JS_EXPORT、TS_EXPORT_DECLARE_CLAUSE或JS_MODULE_ITEM_LIST说明声明直接处于导出或模块根层面放行若父节点可转换为JsStatementList且该语句列表的父节点是JS_FUNCTION_BODY、JS_SCRIPT或JS_STATIC_INITIALIZATION_BLOCK_CLASS_MEMBER之一说明声明就位于函数体/脚本/静态初始化块的根部放行其余情形声明嵌在if、while、do、for等控制流块的语句列表内一律报告违规。从实现可以确认规则的根作用域边界恰与文档所述一致function、module经由模块白名单、script、static block四种。诊断信息如何定位最近根在diagnostic方法中第 161-187 行规则通过decl.syntax().ancestors().skip(1).find_map(AnyJsControlFlowRoot::cast)沿祖先链向上查找最近的控制流根节点再依据其类型生成诊断文案中的根名称JsModule→module、JsScript→script、JsStaticInitializationBlockClassMember→static block其余函数体等归为enclosing function。这解释了示例诊断中declared at the root of the script / module / enclosing function文案的精确来源——它们不是硬编码而是运行时根据语法结构动态计算的。配置、推荐规则与关闭方式在 rome.json 中配置noInnerDeclarations属于correctness组配置键为noInnerDeclarations。由于它是 recommended 规则默认即开启如需显式声明或调整级别可参考以下配置结构{ linter: { enabled: true, rules: { correctness: { noInnerDeclarations: warn } } } }on、off、warn三种取值对应规则的开、关与警告级别。规则本身不提供额外选项type Options ()见源码第 101 行因此无需也不支持细粒度参数配置。按行或按文件禁用与其他 Rome 规则一致可通过注释临时豁免。通用做法是在违规声明所在行前添加规则抑制注释// rome-ignore lint/correctness/noInnerDeclarations: 这里确实需要块内声明 if (test) { function f() {} }关于完整的禁用语法行内禁用、文件级禁用与规则选项体系可参考网站文档 linter 索引页 中的Disable a rule与Rule options章节。验证配置正确性Rome 的配置解析会对规则名做校验若在rome.json中误写规则名会触发配置诊断。相关快照可见 incorrect_rule_name.snap它验证了错误规则名被拒绝的 CLI 行为规则配置的序列化/反序列化逻辑位于 crates/rome_service/src/configuration/linter/rules.rs 与 crates/rome_service/src/configuration/parse/json/rules.rsVS Code 扩展的配置提示则来自 editors/vscode/configuration_schema.json。测试用例佐证规则的规范测试位于 crates/rome_js_analyze/tests/specs/correctness/noInnerDeclarations覆盖了文档与源码之外的大量边界情形非常适合作为你理解或复现规则的素材invalid.jsonc25 个无效用例涵盖if/while/do/for控制流、单语句无花括号if (foo) var a;、带注释的声明、IIFE 内块、箭头函数与 class 的构造器/getter/setter/方法内块以及静态初始化块中嵌套块内的function/varvalid.jsonc25 个有效用例包括块内const fn function(){}/let、函数体根部的var/函数声明、对象方法体内声明、class 方法体与静态块根部的声明以及module.exports function foo(){}等 CJS 导出模式valid-module.jsESM 下根部export var/export function/export default function均合法valid-moduke.tsTypeScriptdeclare声明含export declare被放行印证源码中跳过TsDeclareStatement的逻辑valid-block-scoped-func-decl.js通过export {}将文件标记为 ESM 后块内function f() {}合法——这是模块内函数声明块级作用域最直观的用例invalid-module.cjs.cjs文件CommonJS非 ESM中块内var a;与function foo() {}均违规。从这些用例可以得出可复现的结论同一段块内函数声明代码在 ESM 文件.js/.mjs/含export中合法在脚本或.cjs文件中违规而块内var无论何种文件类型都违规模块内var依然函数/模块级提升模块白名单只豁免函数声明见源码第 107-109、115-118 行。总结noInnerDeclarations是 Romecorrectness组中一条开箱即用的推荐规则它的存在价值在于消除声明写在块内、作用域却穿透到根这一 JavaScript 历史遗留陷阱检查对象var声明与声明式function含 TSdeclare function的兼容处理不检查const/let与函数表达式例外情形ES module 内的块级function声明、声明位于函数体/脚本/静态初始化块/模块根部的场景诊断能力根据语法树动态计算最近根作用域script / module / static block / enclosing function并给出精确的修复建议工程接入作为 recommended 规则默认启用也可在rome.json的linter.rules.correctness下显式调整或关闭支持注释级豁免配置校验有 CLI 快照测试保障。相关链接规则文档原文noInnerDeclarations.md规则实现源码no_inner_declarations.rs规则组与推荐清单rules.rs规范测试用例noInnerDeclarations 测试目录禁用规则与规则选项说明linter 索引页【免费下载链接】toolsUnified developer tools for JavaScript, TypeScript, and the web项目地址: https://gitcode.com/gh_mirrors/to/tools创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价