diff --git a/docs/rules/no-inner-declarations.md b/docs/rules/no-inner-declarations.md index 23fd9253b10..0592084bce1 100644 --- a/docs/rules/no-inner-declarations.md +++ b/docs/rules/no-inner-declarations.md @@ -81,6 +81,8 @@ function doSomethingElse() { function doAnotherThing() { } } } + +if (foo) function f(){} ``` Examples of **correct** code for this rule with the default `"functions"` option: @@ -102,6 +104,8 @@ var fn; if (test) { fn = function fnExpression() { }; } + +if (foo) var a; ``` ### both @@ -120,6 +124,11 @@ function doAnotherThing() { var bar = 81; } } + + +if (foo) var a; + +if (foo) function f(){} ``` Examples of **correct** code for this rule with the `"both"` option: diff --git a/lib/rules/no-inner-declarations.js b/lib/rules/no-inner-declarations.js index e1c29e0a3b4..0768bc61149 100644 --- a/lib/rules/no-inner-declarations.js +++ b/lib/rules/no-inner-declarations.js @@ -5,10 +5,19 @@ "use strict"; +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +const astUtils = require("./utils/ast-utils"); + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ +const validParent = new Set(["Program", "ExportNamedDeclaration", "ExportDefaultDeclaration"]); +const validBlockStatementParent = new Set(["FunctionDeclaration", "FunctionExpression", "ArrowFunctionExpression"]); + module.exports = { meta: { type: "problem", @@ -33,54 +42,37 @@ module.exports = { create(context) { - /** - * Find the nearest Program or Function ancestor node. - * @returns {Object} Ancestor's type and distance from node. - */ - function nearestBody() { - const ancestors = context.getAncestors(); - let ancestor = ancestors.pop(), - generation = 1; - - while (ancestor && ["Program", "FunctionDeclaration", - "FunctionExpression", "ArrowFunctionExpression" - ].indexOf(ancestor.type) < 0) { - generation += 1; - ancestor = ancestors.pop(); - } - - return { - - // Type of containing ancestor - type: ancestor.type, - - // Separation between ancestor and node - distance: generation - }; - } - /** * 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 body = nearestBody(), - valid = ((body.type === "Program" && body.distance === 1) || - body.distance === 2); - - if (!valid) { - context.report({ - node, - messageId: "moveDeclToRoot", - data: { - type: (node.type === "FunctionDeclaration" ? "function" : "variable"), - body: (body.type === "Program" ? "program" : "function body") - } - }); + const parent = node.parent; + + if ( + parent.type === "BlockStatement" && validBlockStatementParent.has(parent.parent.type) + ) { + return; + } + + if (validParent.has(parent.type)) { + return; } + + const upperFunction = astUtils.getUpperFunction(parent); + + context.report({ + node, + messageId: "moveDeclToRoot", + data: { + type: (node.type === "FunctionDeclaration" ? "function" : "variable"), + body: (upperFunction === null ? "program" : "function body") + } + }); } + return { FunctionDeclaration: check, diff --git a/tests/lib/rules/no-inner-declarations.js b/tests/lib/rules/no-inner-declarations.js index d255824619e..a9f70fd3f5e 100644 --- a/tests/lib/rules/no-inner-declarations.js +++ b/tests/lib/rules/no-inner-declarations.js @@ -45,74 +45,233 @@ ruleTester.run("no-inner-declarations", rule, { code: "var x = {doSomething() {var foo;}}", options: ["both"], parserOptions: { ecmaVersion: 6 } + }, + { + code: "export var foo;", + options: ["both"], + parserOptions: { sourceType: "module", ecmaVersion: 6 } + }, + { + code: "export function bar() {}", + options: ["both"], + parserOptions: { sourceType: "module", ecmaVersion: 6 } + }, + { + code: "export default function baz() {}", + options: ["both"], + parserOptions: { sourceType: "module", ecmaVersion: 6 } + }, + { + code: "exports.foo = () => {}", + options: ["both"], + parserOptions: { ecmaVersion: 6 } + }, + { + code: "exports.foo = function(){}", + options: ["both"] + }, + { + code: "module.exports = function foo(){}", + options: ["both"] } ], // Examples of code that should trigger the rule - invalid: [{ - code: "if (test) { function doSomething() { } }", - options: ["both"], - errors: [{ - messageId: "moveDeclToRoot", - data: { - type: "function", - body: "program" - }, - type: "FunctionDeclaration" - }] - }, { - code: "function doSomething() { do { function somethingElse() { } } while (test); }", - errors: [{ - messageId: "moveDeclToRoot", - data: { - type: "function", - body: "function body" - }, - type: "FunctionDeclaration" - }] - }, { - code: "(function() { if (test) { function doSomething() { } } }());", - errors: [{ - messageId: "moveDeclToRoot", - data: { - type: "function", - body: "function body" - }, - type: "FunctionDeclaration" - }] - }, { - code: "while (test) { var foo; }", - options: ["both"], - errors: [{ - messageId: "moveDeclToRoot", - data: { - type: "variable", - body: "program" - }, - type: "VariableDeclaration" - }] - }, { - code: "function doSomething() { if (test) { var foo = 42; } }", - options: ["both"], - errors: [{ - messageId: "moveDeclToRoot", - data: { - type: "variable", - body: "function body" - }, - type: "VariableDeclaration" - }] - }, { - code: "(function() { if (test) { var foo; } }());", - options: ["both"], - errors: [{ - messageId: "moveDeclToRoot", - data: { - type: "variable", - body: "function body" - }, - type: "VariableDeclaration" - }] - }] + invalid: [ + { + code: "if (test) { function doSomething() { } }", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "function", + body: "program" + }, + type: "FunctionDeclaration" + }] + }, { + code: "if (foo) var a; ", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "program" + }, + type: "VariableDeclaration" + }] + }, { + code: "if (foo) /* some comments */ var a; ", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "program" + }, + type: "VariableDeclaration" + }] + }, { + code: "if (foo){ function f(){ if(bar){ var a; } } }", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "function", + body: "program" + }, + type: "FunctionDeclaration" + }, { + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "function body" + }, + type: "VariableDeclaration" + }] + }, { + code: "if (foo) function f(){ if(bar) var a; } ", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "function", + body: "program" + }, + type: "FunctionDeclaration" + }, { + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "function body" + }, + type: "VariableDeclaration" + }] + }, { + code: "if (foo) { var fn = function(){} } ", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "program" + }, + type: "VariableDeclaration" + }] + }, + { + code: "if (foo) function f(){} ", + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "function", + body: "program" + }, + type: "FunctionDeclaration" + }] + }, + { + code: "function bar() { if (foo) function f(){}; }", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "function", + body: "function body" + }, + type: "FunctionDeclaration" + }] + }, + { + code: "function bar() { if (foo) var a; }", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "function body" + }, + type: "VariableDeclaration" + }] + }, + { + code: "if (foo){ var a; }", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "program" + }, + type: "VariableDeclaration" + }] + }, { + code: "function doSomething() { do { function somethingElse() { } } while (test); }", + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "function", + body: "function body" + }, + type: "FunctionDeclaration" + }] + }, { + code: "(function() { if (test) { function doSomething() { } } }());", + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "function", + body: "function body" + }, + type: "FunctionDeclaration" + }] + }, { + code: "while (test) { var foo; }", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "program" + }, + type: "VariableDeclaration" + }] + }, { + code: "function doSomething() { if (test) { var foo = 42; } }", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "function body" + }, + type: "VariableDeclaration" + }] + }, { + code: "(function() { if (test) { var foo; } }());", + options: ["both"], + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "function body" + }, + type: "VariableDeclaration" + }] + }, { + code: "const doSomething = () => { if (test) { var foo = 42; } }", + options: ["both"], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + messageId: "moveDeclToRoot", + data: { + type: "variable", + body: "function body" + }, + type: "VariableDeclaration" + }] + } + + ] });