diff --git a/docs/rules/no-inner-declarations.md b/docs/rules/no-inner-declarations.md index 9bdfaa7c400..7c64e9dc19e 100644 --- a/docs/rules/no-inner-declarations.md +++ b/docs/rules/no-inner-declarations.md @@ -56,7 +56,7 @@ function doSomething() { ## Rule Details -This rule requires that function declarations and, optionally, variable declarations be in the root of a program or the body of a function. +This rule requires that function declarations and, optionally, variable declarations be in the root of a program, or in the root of the body of a function, or in the root of the body of a class static block. ## Options @@ -83,6 +83,14 @@ function doSomethingElse() { } if (foo) function f(){} + +class C { + static { + if (test) { + function doSomething() { } + } + } +} ``` Examples of **correct** code for this rule with the default `"functions"` option: @@ -96,6 +104,12 @@ function doSomethingElse() { function doAnotherThing() { } } +class C { + static { + function doSomething() { } + } +} + if (test) { asyncCall(id, function (err, data) { }); } @@ -125,17 +139,23 @@ function doAnotherThing() { } } - if (foo) var a; if (foo) function f(){} + +class C { + static { + if (test) { + var something; + } + } +} ``` Examples of **correct** code for this rule with the `"both"` option: ```js /*eslint no-inner-declarations: ["error", "both"]*/ -/*eslint-env es6*/ var bar = 42; @@ -146,6 +166,12 @@ if (test) { function doAnotherThing() { var baz = 81; } + +class C { + static { + var something; + } +} ``` ## When Not To Use It diff --git a/lib/rules/no-inner-declarations.js b/lib/rules/no-inner-declarations.js index 9bbe24d7c82..49b5114a209 100644 --- a/lib/rules/no-inner-declarations.js +++ b/lib/rules/no-inner-declarations.js @@ -15,9 +15,33 @@ const astUtils = require("./utils/ast-utils"); // Rule Definition //------------------------------------------------------------------------------ -const validParent = new Set(["Program", "ExportNamedDeclaration", "ExportDefaultDeclaration"]); +const validParent = new Set(["Program", "StaticBlock", "ExportNamedDeclaration", "ExportDefaultDeclaration"]); const validBlockStatementParent = new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]); +/** + * Finds the nearest enclosing context where this rule allows declarations and returns its description. + * @param {ASTNode} node Node to search from. + * @returns {string} Description. One of "program", "function body", "class static block body". + */ +function getAllowedBodyDescription(node) { + let { parent } = node; + + while (parent) { + + if (parent.type === "StaticBlock") { + return "class static block body"; + } + + if (astUtils.isFunction(parent)) { + return "function body"; + } + + ({ parent } = parent); + } + + return "program"; +} + module.exports = { meta: { type: "problem", @@ -59,14 +83,12 @@ module.exports = { return; } - const upperFunction = astUtils.getUpperFunction(parent); - context.report({ node, messageId: "moveDeclToRoot", data: { type: (node.type === "FunctionDeclaration" ? "function" : "variable"), - body: (upperFunction === null ? "program" : "function body") + body: getAllowedBodyDescription(node) } }); } diff --git a/tests/lib/rules/no-inner-declarations.js b/tests/lib/rules/no-inner-declarations.js index a9f70fd3f5e..5fcb2f3c866 100644 --- a/tests/lib/rules/no-inner-declarations.js +++ b/tests/lib/rules/no-inner-declarations.js @@ -73,8 +73,27 @@ ruleTester.run("no-inner-declarations", rule, { { code: "module.exports = function foo(){}", options: ["both"] + }, + { + code: "class C { method() { function foo() {} } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { method() { var x; } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { function foo() {} } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 } + }, + { + code: "class C { static { var x; } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 } } - ], // Examples of code that should trigger the rule @@ -271,7 +290,54 @@ ruleTester.run("no-inner-declarations", rule, { }, type: "VariableDeclaration" }] + }, { + code: "class C { method() { if(test) { var foo; } } }", + options: ["both"], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "function body" + }, + type: "VariableDeclaration" + }] + }, { + code: "class C { static { if (test) { function foo() {} } } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "function", + body: "class static block body" + }, + type: "FunctionDeclaration" + }] + }, { + code: "class C { static { if (test) { var foo; } } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "class static block body" + }, + type: "VariableDeclaration" + }] + }, { + code: "class C { static { if (test) { if (anotherTest) { var foo; } } } }", + options: ["both"], + parserOptions: { ecmaVersion: 2022 }, + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "class static block body" + }, + type: "VariableDeclaration" + }] } - ] });