| /** |
| * @fileoverview Rule to enforce declarations in program or function body root. |
| * @author Brandon Mills |
| */ |
| |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Requirements |
| //------------------------------------------------------------------------------ |
| |
| const astUtils = require("./utils/ast-utils"); |
| |
| //------------------------------------------------------------------------------ |
| // Rule Definition |
| //------------------------------------------------------------------------------ |
| |
| 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"; |
| } |
| |
| /** @type {import('../types').Rule.RuleModule} */ |
| module.exports = { |
| meta: { |
| type: "problem", |
| |
| defaultOptions: ["functions", { blockScopedFunctions: "allow" }], |
| |
| docs: { |
| description: |
| "Disallow variable or `function` declarations in nested blocks", |
| recommended: false, |
| url: "https://eslint.org/docs/latest/rules/no-inner-declarations", |
| }, |
| |
| schema: [ |
| { |
| enum: ["functions", "both"], |
| }, |
| { |
| type: "object", |
| properties: { |
| blockScopedFunctions: { |
| enum: ["allow", "disallow"], |
| }, |
| }, |
| additionalProperties: false, |
| }, |
| ], |
| |
| messages: { |
| moveDeclToRoot: "Move {{type}} declaration to {{body}} root.", |
| }, |
| }, |
| |
| create(context) { |
| const both = context.options[0] === "both"; |
| const { blockScopedFunctions } = context.options[1]; |
| |
| const sourceCode = context.sourceCode; |
| const ecmaVersion = context.languageOptions.ecmaVersion; |
| |
| /** |
| * Ensure that a given node is at a program or function body's root. |
| * @param {ASTNode} node Declaration node to check. |
| * @returns {void} |
| */ |
| function check(node) { |
| const parent = node.parent; |
| |
| if ( |
| parent.type === "BlockStatement" && |
| validBlockStatementParent.has(parent.parent.type) |
| ) { |
| return; |
| } |
| |
| if (validParent.has(parent.type)) { |
| return; |
| } |
| |
| context.report({ |
| node, |
| messageId: "moveDeclToRoot", |
| data: { |
| type: |
| node.type === "FunctionDeclaration" |
| ? "function" |
| : "variable", |
| body: getAllowedBodyDescription(node), |
| }, |
| }); |
| } |
| |
| return { |
| FunctionDeclaration(node) { |
| const isInStrictCode = sourceCode.getScope(node).upper.isStrict; |
| |
| if ( |
| blockScopedFunctions === "allow" && |
| ecmaVersion >= 2015 && |
| isInStrictCode |
| ) { |
| return; |
| } |
| |
| check(node); |
| }, |
| VariableDeclaration(node) { |
| if (both && node.kind === "var") { |
| check(node); |
| } |
| }, |
| }; |
| }, |
| }; |