From b75106fc96124a1bc4a8190362eea47ebcc8dc12 Mon Sep 17 00:00:00 2001 From: Victorien Elvinger Date: Mon, 19 Dec 2022 21:53:25 +0100 Subject: [PATCH] feat(rome_js_analyze): `noInnerDeclarations` --- .../src/categories.rs | 1 + .../rome_js_analyze/src/analyzers/nursery.rs | 3 +- .../nursery/no_inner_declarations.rs | 154 +++++ .../src/control_flow/visitor.rs | 3 +- .../noInnerDeclarations/invalid-module.js | 5 + .../invalid-module.js.snap | 52 ++ .../nursery/noInnerDeclarations/invalid.jsonc | 29 + .../noInnerDeclarations/invalid.jsonc.snap | 534 ++++++++++++++++++ .../noInnerDeclarations/valid-module.js | 9 + .../noInnerDeclarations/valid-module.js.snap | 20 + .../nursery/noInnerDeclarations/valid.jsonc | 27 + .../noInnerDeclarations/valid.jsonc.snap | 131 +++++ .../src/configuration/linter/rules.rs | 25 +- editors/vscode/configuration_schema.json | 11 + npm/backend-jsonrpc/src/workspace.ts | 5 + npm/rome/configuration_schema.json | 11 + website/src/pages/lint/rules/index.mdx | 6 + .../pages/lint/rules/noInnerDeclarations.md | 142 +++++ 18 files changed, 1156 insertions(+), 12 deletions(-) create mode 100644 crates/rome_js_analyze/src/analyzers/nursery/no_inner_declarations.rs create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid-module.js create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid-module.js.snap create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid.jsonc create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid.jsonc.snap create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid-module.js create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid-module.js.snap create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid.jsonc create mode 100644 crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid.jsonc.snap create mode 100644 website/src/pages/lint/rules/noInnerDeclarations.md diff --git a/crates/rome_diagnostics_categories/src/categories.rs b/crates/rome_diagnostics_categories/src/categories.rs index 2ea8b3e4cbe..82a33327251 100644 --- a/crates/rome_diagnostics_categories/src/categories.rs +++ b/crates/rome_diagnostics_categories/src/categories.rs @@ -58,6 +58,7 @@ define_dategories! { "lint/nursery/noEmptyInterface": "https://docs.rome.tools/lint/rules/noEmptyInterface", "lint/nursery/noExtraNonNullAssertion":"https://docs.rome.tools/lint/rules/noExtraNonNullAssertion", "lint/nursery/noHeaderScope": "https://docs.rome.tools/lint/rules/noHeaderScope", + "lint/nursery/noInnerDeclarations": "https://docs.rome.tools/lint/rules/noInnerDeclarations", "lint/nursery/noInvalidConstructorSuper": "https://docs.rome.tools/lint/rules/noInvalidConstructorSuper", "lint/nursery/noNonNullAssertion": "https://docs.rome.tools/lint/rules/noNonNullAssertion", "lint/nursery/noPrecisionLoss": "https://docs.rome.tools/lint/rules/noPrecisionLoss", diff --git a/crates/rome_js_analyze/src/analyzers/nursery.rs b/crates/rome_js_analyze/src/analyzers/nursery.rs index 4573049d2b0..904d649ecd8 100644 --- a/crates/rome_js_analyze/src/analyzers/nursery.rs +++ b/crates/rome_js_analyze/src/analyzers/nursery.rs @@ -14,6 +14,7 @@ mod no_empty_interface; mod no_extra_non_null_assertion; mod no_extra_semicolons; mod no_header_scope; +mod no_inner_declarations; mod no_invalid_constructor_super; mod no_non_null_assertion; mod no_precision_loss; @@ -34,4 +35,4 @@ mod use_exponentiation_operator; mod use_is_nan; mod use_media_caption; mod use_numeric_literals; -declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_assign_in_expressions :: NoAssignInExpressions , self :: no_banned_types :: NoBannedTypes , self :: no_comma_operator :: NoCommaOperator , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_case :: NoDuplicateCase , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_extra_semicolons :: NoExtraSemicolons , self :: no_header_scope :: NoHeaderScope , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_precision_loss :: NoPrecisionLoss , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_self_compare :: NoSelfCompare , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unreachable_super :: NoUnreachableSuper , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_useless_switch_case :: NoUselessSwitchCase , self :: no_void_type_return :: NoVoidTypeReturn , self :: no_with :: NoWith , self :: use_default_parameter_last :: UseDefaultParameterLast , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_enum_initializers :: UseEnumInitializers , self :: use_exponentiation_operator :: UseExponentiationOperator , self :: use_is_nan :: UseIsNan , self :: use_media_caption :: UseMediaCaption , self :: use_numeric_literals :: UseNumericLiterals ,] } } +declare_group! { pub (crate) Nursery { name : "nursery" , rules : [self :: no_access_key :: NoAccessKey , self :: no_assign_in_expressions :: NoAssignInExpressions , self :: no_banned_types :: NoBannedTypes , self :: no_comma_operator :: NoCommaOperator , self :: no_const_enum :: NoConstEnum , self :: no_constructor_return :: NoConstructorReturn , self :: no_distracting_elements :: NoDistractingElements , self :: no_duplicate_case :: NoDuplicateCase , self :: no_duplicate_object_keys :: NoDuplicateObjectKeys , self :: no_empty_interface :: NoEmptyInterface , self :: no_extra_non_null_assertion :: NoExtraNonNullAssertion , self :: no_extra_semicolons :: NoExtraSemicolons , self :: no_header_scope :: NoHeaderScope , self :: no_inner_declarations :: NoInnerDeclarations , self :: no_invalid_constructor_super :: NoInvalidConstructorSuper , self :: no_non_null_assertion :: NoNonNullAssertion , self :: no_precision_loss :: NoPrecisionLoss , self :: no_redundant_alt :: NoRedundantAlt , self :: no_redundant_use_strict :: NoRedundantUseStrict , self :: no_self_compare :: NoSelfCompare , self :: no_setter_return :: NoSetterReturn , self :: no_string_case_mismatch :: NoStringCaseMismatch , self :: no_unreachable_super :: NoUnreachableSuper , self :: no_unsafe_finally :: NoUnsafeFinally , self :: no_useless_switch_case :: NoUselessSwitchCase , self :: no_void_type_return :: NoVoidTypeReturn , self :: no_with :: NoWith , self :: use_default_parameter_last :: UseDefaultParameterLast , self :: use_default_switch_clause_last :: UseDefaultSwitchClauseLast , self :: use_enum_initializers :: UseEnumInitializers , self :: use_exponentiation_operator :: UseExponentiationOperator , self :: use_is_nan :: UseIsNan , self :: use_media_caption :: UseMediaCaption , self :: use_numeric_literals :: UseNumericLiterals ,] } } diff --git a/crates/rome_js_analyze/src/analyzers/nursery/no_inner_declarations.rs b/crates/rome_js_analyze/src/analyzers/nursery/no_inner_declarations.rs new file mode 100644 index 00000000000..7ebbac3b7be --- /dev/null +++ b/crates/rome_js_analyze/src/analyzers/nursery/no_inner_declarations.rs @@ -0,0 +1,154 @@ +use rome_analyze::{context::RuleContext, declare_rule, Ast, Rule, RuleDiagnostic}; +use rome_console::markup; +use rome_js_syntax::{ + AnyJsDeclaration, JsExport, JsFunctionBody, JsModuleItemList, JsScript, JsStatementList, + JsStaticInitializationBlockClassMember, +}; +use rome_rowan::AstNode; + +use crate::control_flow::AnyJsControlFlowRoot; + +declare_rule! { + /// Disallow `function` and `var` declarations in nested blocks. + /// + /// A `function` and a `var` are 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. + /// Note that `const` and `let` declarations are block-scoped, and therefore + /// they are not affected by this rule. + /// + /// Moreover, prior to ES2015 a function declaration is only allowed in + /// the nearest root, though parsers sometimes erroneously accept them elsewhere. + /// This only applies to function declarations; named or anonymous function + /// expressions can occur anywhere an expression is permitted. + /// + /// Source: https://eslint.org/docs/rules/no-inner-declarations + /// + /// ## Examples + /// + /// ### Invalid + /// + /// ```js,expect_diagnostic + /// if (test) { + /// function f() {} + /// } + /// ``` + /// + /// ```js,expect_diagnostic + /// if (test) { + /// var x = 1; + /// } + /// ``` + /// + /// ```js,expect_diagnostic + /// function f() { + /// if (test) { + /// function g() {} + /// } + /// } + /// ``` + /// + /// ```js,expect_diagnostic + /// function f() { + /// if (test) { + /// var x = 1; + /// } + /// } + /// ``` + /// + /// ### Valid + /// + /// ```js + /// function f() { } + /// ``` + /// + /// ```js + /// function f() { + /// function g() {} + /// } + /// ``` + /// + /// ```js + /// function f() { + /// var x = 1; + /// } + /// ``` + /// + /// ```js + /// function f() { + /// if (test) { + /// const g = function() {}; + /// } + /// } + /// ``` + /// + pub(crate) NoInnerDeclarations { + version: "next", + name: "noInnerDeclarations", + recommended: true, + } +} + +impl Rule for NoInnerDeclarations { + type Query = Ast; + type State = (); + type Signals = Option; + type Options = (); + + fn run(ctx: &RuleContext) -> Self::Signals { + let decl = ctx.query(); + let parent = match decl { + AnyJsDeclaration::JsFunctionDeclaration(x) => x.syntax().parent(), + AnyJsDeclaration::JsVariableDeclaration(x) => { + if x.is_var() { + // ignore parent (JsVariableStatement or JsVariableDeclarationClause) + x.syntax().parent()?.parent() + } else { + None + } + } + _ => None, + }?; + if JsExport::can_cast(parent.kind()) || JsModuleItemList::can_cast(parent.kind()) { + return None; + } + if let Some(stmt_list) = JsStatementList::cast(parent) { + let parent_kind = stmt_list.syntax().parent()?.kind(); + if JsFunctionBody::can_cast(parent_kind) + || JsScript::can_cast(parent_kind) + || JsStaticInitializationBlockClassMember::can_cast(parent_kind) + { + return None; + } + } + Some(()) + } + + fn diagnostic(ctx: &RuleContext, _: &Self::State) -> Option { + let decl = ctx.query(); + let decl_type = match decl { + AnyJsDeclaration::JsFunctionDeclaration(_) => "function", + _ => "var", + }; + let nearest_root = decl + .syntax() + .ancestors() + .skip(1) + .find_map(AnyJsControlFlowRoot::cast)?; + let nearest_root_type = match nearest_root { + AnyJsControlFlowRoot::JsModule(_) => "module", + AnyJsControlFlowRoot::JsScript(_) => "script", + AnyJsControlFlowRoot::JsStaticInitializationBlockClassMember(_) => "static block", + _ => "enclosing function", + }; + Some(RuleDiagnostic::new( + rule_category!(), + decl.range(), + markup! { + "This "{decl_type}" should be declared at the root of the "{nearest_root_type}"." + }, + ).note(markup! { + "The "{decl_type}" is accessible in the whole body of the "{nearest_root_type}".\nTo avoid confusion, it should be declared at the root of the "{nearest_root_type}"." + })) + } +} diff --git a/crates/rome_js_analyze/src/control_flow/visitor.rs b/crates/rome_js_analyze/src/control_flow/visitor.rs index e47c8935e2a..1ca5164c291 100644 --- a/crates/rome_js_analyze/src/control_flow/visitor.rs +++ b/crates/rome_js_analyze/src/control_flow/visitor.rs @@ -4,7 +4,7 @@ use rome_analyze::{merge_node_visitors, Visitor, VisitorContext}; use rome_js_syntax::{ AnyJsFunction, JsConstructorClassMember, JsGetterClassMember, JsGetterObjectMember, JsLanguage, JsMethodClassMember, JsMethodObjectMember, JsModule, JsScript, JsSetterClassMember, - JsSetterObjectMember, + JsSetterObjectMember, JsStaticInitializationBlockClassMember, }; use rome_rowan::{declare_node_union, AstNode, SyntaxError, SyntaxResult}; @@ -168,6 +168,7 @@ declare_node_union! { | JsMethodClassMember | JsGetterClassMember | JsSetterClassMember + | JsStaticInitializationBlockClassMember } impl rome_analyze::NodeVisitor for FunctionVisitor { diff --git a/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid-module.js b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid-module.js new file mode 100644 index 00000000000..27e44c6c128 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid-module.js @@ -0,0 +1,5 @@ +if (foo) { + var a; + function foo() {} +} +export {}; diff --git a/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid-module.js.snap b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid-module.js.snap new file mode 100644 index 00000000000..340066634c0 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid-module.js.snap @@ -0,0 +1,52 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +assertion_line: 92 +expression: invalid-module.js +--- +# Input +```js +if (foo) { + var a; + function foo() {} +} +export {}; + +``` + +# Diagnostics +``` +invalid-module.js:2:2 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the module. + + 1 │ if (foo) { + > 2 │ var a; + │ ^^^^^ + 3 │ function foo() {} + 4 │ } + + i The var is accessible in the whole body of the module. + To avoid confusion, it should be declared at the root of the module. + + +``` + +``` +invalid-module.js:3:2 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This function should be declared at the root of the module. + + 1 │ if (foo) { + 2 │ var a; + > 3 │ function foo() {} + │ ^^^^^^^^^^^^^^^^^ + 4 │ } + 5 │ export {}; + + i The function is accessible in the whole body of the module. + To avoid confusion, it should be declared at the root of the module. + + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid.jsonc b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid.jsonc new file mode 100644 index 00000000000..2cb2470397d --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid.jsonc @@ -0,0 +1,29 @@ +[ + "if (test) { var foo; }", + "function doSomething() { while (test) { var foo; } }", + + "if (test) { function doSomething() { } }", + "if (foo) var a; ", + "if (foo) /* some comments */ var a;", + "if (foo){ function f(){ if(bar){ var a; } } }", + "if (foo) function f(){ if(bar) var a; }", + "if (foo) { var fn = function(){} }", + "if (foo) function f(){}", + + "function bar() { if (foo) function f(){}; }", + "function bar() { if (foo) var a; }", + "if (foo){ var a; }", + "function doSomething() { do { function somethingElse() { } } while (test); }", + "(function() { if (test) { function doSomething() { } } }());", + "while (test) { var foo; }", + "function doSomething() { if (test) { var foo = 42; } }", + "(function() { if (test) { var foo; } }());", + "const doSomething = () => { if (test) { var foo = 42; } }", + "class C { constructor() { if(test) { var foo; } } }", + "class C { get x() { if(test) { var foo; } } }", + "class C { set x() { if(test) { var foo; } } }", + "class C { method() { if(test) { var foo; } } }", + "class C { static { if (test) { function foo() {} } } }", + "class C { static { if (test) { var foo; } } }", + "class C { static { if (test) { if (anotherTest) { var foo; } } } }" +] diff --git a/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid.jsonc.snap b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid.jsonc.snap new file mode 100644 index 00000000000..52ae6827fd9 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/invalid.jsonc.snap @@ -0,0 +1,534 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +assertion_line: 92 +expression: invalid.jsonc +--- +# Input +```js +if (test) { var foo; } +``` + +# Diagnostics +``` +invalid.jsonc:1:13 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the script. + + > 1 │ if (test) { var foo; } + │ ^^^^^^^ + + i The var is accessible in the whole body of the script. + To avoid confusion, it should be declared at the root of the script. + + +``` + +# Input +```js +function doSomething() { while (test) { var foo; } } +``` + +# Diagnostics +``` +invalid.jsonc:1:41 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the enclosing function. + + > 1 │ function doSomething() { while (test) { var foo; } } + │ ^^^^^^^ + + i 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. + + +``` + +# Input +```js +if (test) { function doSomething() { } } +``` + +# Diagnostics +``` +invalid.jsonc:1:13 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This function should be declared at the root of the script. + + > 1 │ if (test) { function doSomething() { } } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + + i The function is accessible in the whole body of the script. + To avoid confusion, it should be declared at the root of the script. + + +``` + +# Input +```js +if (foo) var a; +``` + +# Diagnostics +``` +invalid.jsonc:1:10 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the script. + + > 1 │ if (foo) var a;· + │ ^^^^^ + + i The var is accessible in the whole body of the script. + To avoid confusion, it should be declared at the root of the script. + + +``` + +# Input +```js +if (foo) /* some comments */ var a; +``` + +# Diagnostics +``` +invalid.jsonc:1:30 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the script. + + > 1 │ if (foo) /* some comments */ var a; + │ ^^^^^ + + i The var is accessible in the whole body of the script. + To avoid confusion, it should be declared at the root of the script. + + +``` + +# Input +```js +if (foo){ function f(){ if(bar){ var a; } } } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This function should be declared at the root of the script. + + > 1 │ if (foo){ function f(){ if(bar){ var a; } } } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + i The function is accessible in the whole body of the script. + To avoid confusion, it should be declared at the root of the script. + + +``` + +``` +invalid.jsonc:1:34 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the enclosing function. + + > 1 │ if (foo){ function f(){ if(bar){ var a; } } } + │ ^^^^^ + + i 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. + + +``` + +# Input +```js +if (foo) function f(){ if(bar) var a; } +``` + +# Diagnostics +``` +invalid.jsonc:1:10 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This function should be declared at the root of the script. + + > 1 │ if (foo) function f(){ if(bar) var a; } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + i The function is accessible in the whole body of the script. + To avoid confusion, it should be declared at the root of the script. + + +``` + +``` +invalid.jsonc:1:32 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the enclosing function. + + > 1 │ if (foo) function f(){ if(bar) var a; } + │ ^^^^^ + + i 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. + + +``` + +# Input +```js +if (foo) { var fn = function(){} } +``` + +# Diagnostics +``` +invalid.jsonc:1:12 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the script. + + > 1 │ if (foo) { var fn = function(){} } + │ ^^^^^^^^^^^^^^^^^^^^^ + + i The var is accessible in the whole body of the script. + To avoid confusion, it should be declared at the root of the script. + + +``` + +# Input +```js +if (foo) function f(){} +``` + +# Diagnostics +``` +invalid.jsonc:1:10 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This function should be declared at the root of the script. + + > 1 │ if (foo) function f(){} + │ ^^^^^^^^^^^^^^ + + i The function is accessible in the whole body of the script. + To avoid confusion, it should be declared at the root of the script. + + +``` + +# Input +```js +function bar() { if (foo) function f(){}; } +``` + +# Diagnostics +``` +invalid.jsonc:1:27 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This function should be declared at the root of the enclosing function. + + > 1 │ function bar() { if (foo) function f(){}; } + │ ^^^^^^^^^^^^^^ + + i 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. + + +``` + +# Input +```js +function bar() { if (foo) var a; } +``` + +# Diagnostics +``` +invalid.jsonc:1:27 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the enclosing function. + + > 1 │ function bar() { if (foo) var a; } + │ ^^^^^ + + i 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. + + +``` + +# Input +```js +if (foo){ var a; } +``` + +# Diagnostics +``` +invalid.jsonc:1:11 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the script. + + > 1 │ if (foo){ var a; } + │ ^^^^^ + + i The var is accessible in the whole body of the script. + To avoid confusion, it should be declared at the root of the script. + + +``` + +# Input +```js +function doSomething() { do { function somethingElse() { } } while (test); } +``` + +# Diagnostics +``` +invalid.jsonc:1:31 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This function should be declared at the root of the enclosing function. + + > 1 │ function doSomething() { do { function somethingElse() { } } while (test); } + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + i 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. + + +``` + +# Input +```js +(function() { if (test) { function doSomething() { } } }()); +``` + +# Diagnostics +``` +invalid.jsonc:1:27 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This function should be declared at the root of the enclosing function. + + > 1 │ (function() { if (test) { function doSomething() { } } }()); + │ ^^^^^^^^^^^^^^^^^^^^^^^^^^ + + i 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. + + +``` + +# Input +```js +while (test) { var foo; } +``` + +# Diagnostics +``` +invalid.jsonc:1:16 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the script. + + > 1 │ while (test) { var foo; } + │ ^^^^^^^ + + i The var is accessible in the whole body of the script. + To avoid confusion, it should be declared at the root of the script. + + +``` + +# Input +```js +function doSomething() { if (test) { var foo = 42; } } +``` + +# Diagnostics +``` +invalid.jsonc:1:38 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the enclosing function. + + > 1 │ function doSomething() { if (test) { var foo = 42; } } + │ ^^^^^^^^^^^^ + + i 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. + + +``` + +# Input +```js +(function() { if (test) { var foo; } }()); +``` + +# Diagnostics +``` +invalid.jsonc:1:27 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the enclosing function. + + > 1 │ (function() { if (test) { var foo; } }()); + │ ^^^^^^^ + + i 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. + + +``` + +# Input +```js +const doSomething = () => { if (test) { var foo = 42; } } +``` + +# Diagnostics +``` +invalid.jsonc:1:41 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the enclosing function. + + > 1 │ const doSomething = () => { if (test) { var foo = 42; } } + │ ^^^^^^^^^^^^ + + i 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. + + +``` + +# Input +```js +class C { constructor() { if(test) { var foo; } } } +``` + +# Diagnostics +``` +invalid.jsonc:1:38 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the enclosing function. + + > 1 │ class C { constructor() { if(test) { var foo; } } } + │ ^^^^^^^ + + i 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. + + +``` + +# Input +```js +class C { get x() { if(test) { var foo; } } } +``` + +# Diagnostics +``` +invalid.jsonc:1:32 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the enclosing function. + + > 1 │ class C { get x() { if(test) { var foo; } } } + │ ^^^^^^^ + + i 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. + + +``` + +# Input +```js +class C { set x() { if(test) { var foo; } } } +``` + +# Diagnostics +``` +invalid.jsonc:1:32 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the enclosing function. + + > 1 │ class C { set x() { if(test) { var foo; } } } + │ ^^^^^^^ + + i 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. + + +``` + +# Input +```js +class C { method() { if(test) { var foo; } } } +``` + +# Diagnostics +``` +invalid.jsonc:1:33 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the enclosing function. + + > 1 │ class C { method() { if(test) { var foo; } } } + │ ^^^^^^^ + + i 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. + + +``` + +# Input +```js +class C { static { if (test) { function foo() {} } } } +``` + +# Diagnostics +``` +invalid.jsonc:1:32 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This function should be declared at the root of the static block. + + > 1 │ class C { static { if (test) { function foo() {} } } } + │ ^^^^^^^^^^^^^^^^^ + + i The function is accessible in the whole body of the static block. + To avoid confusion, it should be declared at the root of the static block. + + +``` + +# Input +```js +class C { static { if (test) { var foo; } } } +``` + +# Diagnostics +``` +invalid.jsonc:1:32 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the static block. + + > 1 │ class C { static { if (test) { var foo; } } } + │ ^^^^^^^ + + i The var is accessible in the whole body of the static block. + To avoid confusion, it should be declared at the root of the static block. + + +``` + +# Input +```js +class C { static { if (test) { if (anotherTest) { var foo; } } } } +``` + +# Diagnostics +``` +invalid.jsonc:1:51 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + + ! This var should be declared at the root of the static block. + + > 1 │ class C { static { if (test) { if (anotherTest) { var foo; } } } } + │ ^^^^^^^ + + i The var is accessible in the whole body of the static block. + To avoid confusion, it should be declared at the root of the static block. + + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid-module.js b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid-module.js new file mode 100644 index 00000000000..56e8946ad09 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid-module.js @@ -0,0 +1,9 @@ +export var x = 1; + +var y = 2; + +function foo() {} + +export function bar() {} + +export default function baz() {} diff --git a/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid-module.js.snap b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid-module.js.snap new file mode 100644 index 00000000000..9637ddc4eea --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid-module.js.snap @@ -0,0 +1,20 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +assertion_line: 92 +expression: valid-module.js +--- +# Input +```js +export var x = 1; + +var y = 2; + +function foo() {} + +export function bar() {} + +export default function baz() {} + +``` + + diff --git a/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid.jsonc b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid.jsonc new file mode 100644 index 00000000000..af67189a8d7 --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid.jsonc @@ -0,0 +1,27 @@ +[ + "function doSomething() { }", + "function doSomething() { function somethingElse() { } }", + "(function() { function doSomething() { } }());", + "if (test) { const fn = function() { }; }", + "if (test) { const fn = function expr() { }; }", + "function decl() { var fn = function expr() { }; }", + "function decl(arg) { var fn; if (arg) { fn = function() { }; } }", + "function decl(arg) { var fn; if (arg) { fn = function expr() { }; } }", + "var x = {doSomething() {function doSomethingElse() {}}}", + "function decl(arg) { var fn; if (arg) { fn = function expr() { }; } }", + "if (test) { let x = 1; }", + "if (test) { const x = 1; }", + "var foo;", + "var foo = 42;", + "function doSomething() { var foo; }", + "(function() { var foo; }());", + "foo(() => { function bar() { } });", + "var fn = () => {var foo;}", + "var x = {doSomething() {var foo;}}", + "exports.foo = () => {}", + "exports.foo = function(){}", + "module.exports = function foo(){}", + "class C { method() { function foo() {} } }", + "class C { method() { var x; } }", + "class C { static { var x; } }" +] diff --git a/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid.jsonc.snap b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid.jsonc.snap new file mode 100644 index 00000000000..23e4a40e04c --- /dev/null +++ b/crates/rome_js_analyze/tests/specs/nursery/noInnerDeclarations/valid.jsonc.snap @@ -0,0 +1,131 @@ +--- +source: crates/rome_js_analyze/tests/spec_tests.rs +assertion_line: 92 +expression: valid.jsonc +--- +# Input +```js +function doSomething() { } +``` + +# Input +```js +function doSomething() { function somethingElse() { } } +``` + +# Input +```js +(function() { function doSomething() { } }()); +``` + +# Input +```js +if (test) { const fn = function() { }; } +``` + +# Input +```js +if (test) { const fn = function expr() { }; } +``` + +# Input +```js +function decl() { var fn = function expr() { }; } +``` + +# Input +```js +function decl(arg) { var fn; if (arg) { fn = function() { }; } } +``` + +# Input +```js +function decl(arg) { var fn; if (arg) { fn = function expr() { }; } } +``` + +# Input +```js +var x = {doSomething() {function doSomethingElse() {}}} +``` + +# Input +```js +function decl(arg) { var fn; if (arg) { fn = function expr() { }; } } +``` + +# Input +```js +if (test) { let x = 1; } +``` + +# Input +```js +if (test) { const x = 1; } +``` + +# Input +```js +var foo; +``` + +# Input +```js +var foo = 42; +``` + +# Input +```js +function doSomething() { var foo; } +``` + +# Input +```js +(function() { var foo; }()); +``` + +# Input +```js +foo(() => { function bar() { } }); +``` + +# Input +```js +var fn = () => {var foo;} +``` + +# Input +```js +var x = {doSomething() {var foo;}} +``` + +# Input +```js +exports.foo = () => {} +``` + +# Input +```js +exports.foo = function(){} +``` + +# Input +```js +module.exports = function foo(){} +``` + +# Input +```js +class C { method() { function foo() {} } } +``` + +# Input +```js +class C { method() { var x; } } +``` + +# Input +```js +class C { static { var x; } } +``` + + diff --git a/crates/rome_service/src/configuration/linter/rules.rs b/crates/rome_service/src/configuration/linter/rules.rs index 44a929d89ac..658d1dc5adb 100644 --- a/crates/rome_service/src/configuration/linter/rules.rs +++ b/crates/rome_service/src/configuration/linter/rules.rs @@ -759,6 +759,8 @@ struct NurserySchema { no_extra_semicolons: Option, #[doc = "Check that the scope attribute is only used on th elements."] no_header_scope: Option, + #[doc = "Disallow function and var declarations in nested blocks."] + no_inner_declarations: Option, #[doc = "Prevents the incorrect use of super() inside classes. It also checks whether a call super() is missing from classes that extends other constructors."] no_invalid_constructor_super: Option, #[doc = "Disallow non-null assertions using the ! postfix operator."] @@ -826,7 +828,7 @@ struct NurserySchema { } impl Nursery { const CATEGORY_NAME: &'static str = "nursery"; - pub(crate) const CATEGORY_RULES: [&'static str; 46] = [ + pub(crate) const CATEGORY_RULES: [&'static str; 47] = [ "noAccessKey", "noAssignInExpressions", "noBannedTypes", @@ -841,6 +843,7 @@ impl Nursery { "noExtraNonNullAssertion", "noExtraSemicolons", "noHeaderScope", + "noInnerDeclarations", "noInvalidConstructorSuper", "noNonNullAssertion", "noNoninteractiveElementToInteractiveRole", @@ -874,7 +877,7 @@ impl Nursery { "useValidAriaProps", "useValidLang", ]; - const RECOMMENDED_RULES: [&'static str; 37] = [ + const RECOMMENDED_RULES: [&'static str; 38] = [ "noAssignInExpressions", "noBannedTypes", "noClassAssign", @@ -888,6 +891,7 @@ impl Nursery { "noExtraNonNullAssertion", "noExtraSemicolons", "noHeaderScope", + "noInnerDeclarations", "noInvalidConstructorSuper", "noNoninteractiveElementToInteractiveRole", "noRedundantAlt", @@ -913,7 +917,7 @@ impl Nursery { "useValidAriaProps", "useValidLang", ]; - const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 37] = [ + const RECOMMENDED_RULES_AS_FILTERS: [RuleFilter<'static>; 38] = [ RuleFilter::Rule("nursery", Self::CATEGORY_RULES[1]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[2]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[3]), @@ -928,9 +932,9 @@ impl Nursery { RuleFilter::Rule("nursery", Self::CATEGORY_RULES[12]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[13]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[14]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[16]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[18]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[21]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[15]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[17]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[19]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[22]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[23]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[24]), @@ -939,18 +943,19 @@ impl Nursery { RuleFilter::Rule("nursery", Self::CATEGORY_RULES[27]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[28]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[29]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[31]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[33]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[30]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[32]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[34]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[35]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[36]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[37]), - RuleFilter::Rule("nursery", Self::CATEGORY_RULES[40]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[38]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[41]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[42]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[43]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[44]), RuleFilter::Rule("nursery", Self::CATEGORY_RULES[45]), + RuleFilter::Rule("nursery", Self::CATEGORY_RULES[46]), ]; pub(crate) fn is_recommended(&self) -> bool { !matches!(self.recommended, Some(false)) } pub(crate) fn get_enabled_rules(&self) -> IndexSet { @@ -977,7 +982,7 @@ impl Nursery { pub(crate) fn is_recommended_rule(rule_name: &str) -> bool { Self::RECOMMENDED_RULES.contains(&rule_name) } - pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 37] { + pub(crate) fn recommended_rules_as_filters() -> [RuleFilter<'static>; 38] { Self::RECOMMENDED_RULES_AS_FILTERS } } diff --git a/editors/vscode/configuration_schema.json b/editors/vscode/configuration_schema.json index a06ae3c2278..9a10c83da37 100644 --- a/editors/vscode/configuration_schema.json +++ b/editors/vscode/configuration_schema.json @@ -742,6 +742,17 @@ } ] }, + "noInnerDeclarations": { + "description": "Disallow function and var declarations in nested blocks.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "noInvalidConstructorSuper": { "description": "Prevents the incorrect use of super() inside classes. It also checks whether a call super() is missing from classes that extends other constructors.", "anyOf": [ diff --git a/npm/backend-jsonrpc/src/workspace.ts b/npm/backend-jsonrpc/src/workspace.ts index 743c47bfdc4..78600afe433 100644 --- a/npm/backend-jsonrpc/src/workspace.ts +++ b/npm/backend-jsonrpc/src/workspace.ts @@ -345,6 +345,10 @@ export interface Nursery { * Check that the scope attribute is only used on th elements. */ noHeaderScope?: RuleConfiguration; + /** + * Disallow function and var declarations in nested blocks. + */ + noInnerDeclarations?: RuleConfiguration; /** * Prevents the incorrect use of super() inside classes. It also checks whether a call super() is missing from classes that extends other constructors. */ @@ -762,6 +766,7 @@ export type Category = | "lint/nursery/noEmptyInterface" | "lint/nursery/noExtraNonNullAssertion" | "lint/nursery/noHeaderScope" + | "lint/nursery/noInnerDeclarations" | "lint/nursery/noInvalidConstructorSuper" | "lint/nursery/noNonNullAssertion" | "lint/nursery/noPrecisionLoss" diff --git a/npm/rome/configuration_schema.json b/npm/rome/configuration_schema.json index a06ae3c2278..9a10c83da37 100644 --- a/npm/rome/configuration_schema.json +++ b/npm/rome/configuration_schema.json @@ -742,6 +742,17 @@ } ] }, + "noInnerDeclarations": { + "description": "Disallow function and var declarations in nested blocks.", + "anyOf": [ + { + "$ref": "#/definitions/RuleConfiguration" + }, + { + "type": "null" + } + ] + }, "noInvalidConstructorSuper": { "description": "Prevents the incorrect use of super() inside classes. It also checks whether a call super() is missing from classes that extends other constructors.", "anyOf": [ diff --git a/website/src/pages/lint/rules/index.mdx b/website/src/pages/lint/rules/index.mdx index ca627211659..fb220a7b1db 100644 --- a/website/src/pages/lint/rules/index.mdx +++ b/website/src/pages/lint/rules/index.mdx @@ -569,6 +569,12 @@ While not technically an error, extra semicolons can cause confusion when readin Check that the scope attribute is only used on th elements.
+

+ noInnerDeclarations +

+Disallow function and var declarations in nested blocks. +
+

noInvalidConstructorSuper

diff --git a/website/src/pages/lint/rules/noInnerDeclarations.md b/website/src/pages/lint/rules/noInnerDeclarations.md new file mode 100644 index 00000000000..7aee4756e92 --- /dev/null +++ b/website/src/pages/lint/rules/noInnerDeclarations.md @@ -0,0 +1,142 @@ +--- +title: Lint Rule noInnerDeclarations +parent: lint/rules/index +--- + +# noInnerDeclarations (since vnext) + +Disallow `function` and `var` declarations in nested blocks. + +A `function` and a `var` are 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. +Note that `const` and `let` declarations are block-scoped, and therefore +they are not affected by this rule. + +Moreover, prior to ES2015 a function declaration is only allowed in +the nearest root, though parsers sometimes erroneously accept them elsewhere. +This only applies to function declarations; named or anonymous function +expressions can occur anywhere an expression is permitted. + +Source: https://eslint.org/docs/rules/no-inner-declarations + +## Examples + +### Invalid + +```jsx +if (test) { + function f() {} +} +``` + +
nursery/noInnerDeclarations.js:2:5 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   This function should be declared at the root of the module.
+  
+    1 │ if (test) {
+  > 2 │     function f() {}
+       ^^^^^^^^^^^^^^^
+    3 │ }
+    4 │ 
+  
+   The function is accessible in the whole body of the module.
+    To avoid confusion, it should be declared at the root of the module.
+  
+
+ +```jsx +if (test) { + var x = 1; +} +``` + +
nursery/noInnerDeclarations.js:2:5 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   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.
+  
+
+ +```jsx +function f() { + if (test) { + function g() {} + } +} +``` + +
nursery/noInnerDeclarations.js:3:9 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   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.
+  
+
+ +```jsx +function f() { + if (test) { + var x = 1; + } +} +``` + +
nursery/noInnerDeclarations.js:3:9 lint/nursery/noInnerDeclarations ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+   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.
+  
+
+ +### Valid + +```jsx +function f() { } +``` + +```jsx +function f() { + function g() {} +} +``` + +```jsx +function f() { + var x = 1; +} +``` + +```jsx +function f() { + if (test) { + const g = function() {}; + } +} +``` +