Repository: eslint/eslint (JavaScript)

Issue:
no-inner-declarations inconsistency
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).

    This template is for bug reports. If you are here for another reason, please see below:

    1. To propose a new rule: https://eslint.org/docs/developer-guide/contributing/new-rules
    2. To request a rule change: https://eslint.org/docs/developer-guide/contributing/rule-changes
    3. To request a change that is not a bug fix, rule change, or new rule: https://eslint.org/docs/developer-guide/contributing/changes
    4. If you have any questions, please stop by our chatroom: https://gitter.im/eslint/eslint

    Note that leaving sections blank will make it difficult for us to troubleshoot and we may have to close the issue.
-->

**Tell us about your environment**

* **ESLint Version:**  6.3.0
* **Node Version:** 10.16.0
* **npm Version:** 6.9.0

**What parser (default, Babel-ESLint, etc.) are you using?**

default

**Please show your full configuration:**

<details>
<summary>Configuration</summary>

<!-- Paste your configuration below: -->
```js
module.exports = {
  parserOptions: {
    ecmaVersion: 2015,
  }
};
```

</details>

**What did you do? Please include the actual source code causing the issue, as well as the command that you used to run ESLint.**

<!-- Paste the source code below: -->

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgbm8taW5uZXItZGVjbGFyYXRpb25zOiBbXCJlcnJvclwiLCBcImJvdGhcIl0qL1xuXG5pZiAoZm9vKVxuICB2YXIgYTtcblxuaWYgKGZvbylcbiAgZnVuY3Rpb24gZigpe31cblxuZnVuY3Rpb24gYmFyKCkge1xuICBpZiAoZm9vKVxuICAgIHZhciBhO1xuXG4gIGlmIChmb28pXG4gICAgZnVuY3Rpb24gZigpe31cbn1cbiIsIm9wdGlvbnMiOnsicGFyc2VyT3B0aW9ucyI6eyJlY21hVmVyc2lvbiI6Niwic291cmNlVHlwZSI6InNjcmlwdCIsImVjbWFGZWF0dXJlcyI6e319LCJydWxlcyI6eyJjb25zdHJ1Y3Rvci1zdXBlciI6MiwiZm9yLWRpcmVjdGlvbiI6MiwiZ2V0dGVyLXJldHVybiI6Miwibm8tYXN5bmMtcHJvbWlzZS1leGVjdXRvciI6Miwibm8tY2FzZS1kZWNsYXJhdGlvbnMiOjIsIm5vLWNsYXNzLWFzc2lnbiI6Miwibm8tY29tcGFyZS1uZWctemVybyI6Miwibm8tY29uZC1hc3NpZ24iOjIsIm5vLWNvbnN0LWFzc2lnbiI6Miwibm8tY29uc3RhbnQtY29uZGl0aW9uIjoyLCJuby1jb250cm9sLXJlZ2V4IjoyLCJuby1kZWJ1Z2dlciI6Miwibm8tZGVsZXRlLXZhciI6Miwibm8tZHVwZS1hcmdzIjoyLCJuby1kdXBlLWNsYXNzLW1lbWJlcnMiOjIsIm5vLWR1cGUta2V5cyI6Miwibm8tZHVwbGljYXRlLWNhc2UiOjIsIm5vLWVtcHR5IjoyLCJuby1lbXB0eS1jaGFyYWN0ZXItY2xhc3MiOjIsIm5vLWVtcHR5LXBhdHRlcm4iOjIsIm5vLWV4LWFzc2lnbiI6Miwibm8tZXh0cmEtYm9vbGVhbi1jYXN0IjoyLCJuby1leHRyYS1zZW1pIjoyLCJuby1mYWxsdGhyb3VnaCI6Miwibm8tZnVuYy1hc3NpZ24iOjIsIm5vLWdsb2JhbC1hc3NpZ24iOjIsIm5vLWlubmVyLWRlY2xhcmF0aW9ucyI6Miwibm8taW52YWxpZC1yZWdleHAiOjIsIm5vLWlycmVndWxhci13aGl0ZXNwYWNlIjoyLCJuby1taXNsZWFkaW5nLWNoYXJhY3Rlci1jbGFzcyI6Miwibm8tbWl4ZWQtc3BhY2VzLWFuZC10YWJzIjoyLCJuby1uZXctc3ltYm9sIjoyLCJuby1vYmotY2FsbHMiOjIsIm5vLW9jdGFsIjoyLCJuby1wcm90b3R5cGUtYnVpbHRpbnMiOjIsIm5vLXJlZ2V4LXNwYWNlcyI6Miwibm8tc2VsZi1hc3NpZ24iOjIsIm5vLXNoYWRvdy1yZXN0cmljdGVkLW5hbWVzIjoyLCJuby1zcGFyc2UtYXJyYXlzIjoyLCJuby10aGlzLWJlZm9yZS1zdXBlciI6Miwibm8tdW5leHBlY3RlZC1tdWx0aWxpbmUiOjIsIm5vLXVucmVhY2hhYmxlIjoyLCJuby11bnNhZmUtZmluYWxseSI6Miwibm8tdW5zYWZlLW5lZ2F0aW9uIjoyLCJuby11bnVzZWQtbGFiZWxzIjoyLCJuby11c2VsZXNzLWNhdGNoIjoyLCJuby11c2VsZXNzLWVzY2FwZSI6Miwibm8td2l0aCI6MiwicmVxdWlyZS1hdG9taWMtdXBkYXRlcyI6MiwicmVxdWlyZS15aWVsZCI6MiwidXNlLWlzbmFuIjoyLCJ2YWxpZC10eXBlb2YiOjJ9LCJlbnYiOnt9fX0=)

```js
/*eslint no-inner-declarations: ["error", "both"]*/

if (foo)
  var a;

if (foo)
  function f(){}

function bar() {
  if (foo)
    var a;

  if (foo)
    function f(){}
}
```

<!-- Paste the command you used to run ESLint: -->
```bash
eslint index.js
```

**What did you expect to happen?**

To be consistent, either 4 errors or no errors?

> This rule requires that function declarations and, optionally, variable declarations be in the **root** of a program or the **body** of a function.

Are these declarations in the root/body? Also, the documentation is later more specific:

> "functions" (default) disallows function declarations in nested **blocks**

> "both" disallows function and var declarations in nested **blocks**

**What actually happened? Please include the actual, raw output from ESLint.**

2 errors:

```
11:5  error  Move variable declaration to function body root  no-inner-declarations
14:5  error  Move function declaration to function body root  no-inner-declarations
```

```js
/*eslint no-inner-declarations: ["error", "both"]*/

if (foo)
  var a; // no error

if (foo)
  function f(){} // no error

function bar() {
  if (foo)
    var a; // error

  if (foo)
    function f(){} // error
}
```

**Are you willing to submit a pull request to fix this bug?**

Yes, if this is confirmed to be a bug.

Perhaps the rule should allow only these places for `function` and `var` declaration nodes:

* `Program > node`
* `ExportNamedDeclaration > node`
* `ExportDefaultDeclaration > node` (function only)
* `Function > BlockStatement > node`

meaning that there should be 4 errors in the above example, but I'm not sure is this the intention of this rule.


Discussion:
I would be :+1: for having 4 errors in your example.

Unfortunately, it looks like there wasn't enough interest from the team
or community to implement this change. While we wish we'd be able to
accommodate everyone's requests, we do need to prioritize. We've found
that issues failing to reach accepted status after 21 days tend to
never be accepted, and as such, we [close those issues](https://eslint.org/docs/maintainer-guide/issues#when-to-close-an-issue).
This doesn't mean the idea isn't interesting or useful, just that it's
not something the team can commit to.

Thanks for contributing to ESLint and we appreciate your understanding.

[//]: # (auto-close)

This looks like a bug to me - reopening so we can evaluate.

Unfortunately, it looks like there wasn't enough interest from the team
or community to implement this change. While we wish we'd be able to
accommodate everyone's requests, we do need to prioritize. We've found
that accepted issues failing to be implemented after 90 days tend to
never be implemented, and as such, we [close those issues](https://eslint.org/docs/maintainer-guide/issues#when-to-close-an-issue).
This doesn't mean the idea isn't interesting or useful, just that it's
not something the team can commit to.

Thanks for contributing to ESLint and we appreciate your understanding.

[//]: # (auto-close)

@mdjermanovic when wrapping the if statement blocks with parens `{ }` , all are showing errors


```js
/*eslint no-inner-declarations: ["error", "both"]*/

if (foo){
  var a; // error
}

if (foo){
  function f(){}  // error
}

function bar() {
  if (foo)
    var a;  // error

  if (foo){
    function f(){}  // error
  }
}

```

[**demo**](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgbm8taW5uZXItZGVjbGFyYXRpb25zOiBbXCJlcnJvclwiLCBcImJvdGhcIl0qL1xuXG5pZiAoZm9vKXtcbiAgdmFyIGE7XG59XG5cbmlmIChmb28pe1xuICBmdW5jdGlvbiBmKCl7fVxufVxuXG5mdW5jdGlvbiBiYXIoKSB7XG4gIGlmIChmb28pXG4gICAgdmFyIGE7XG5cbiAgaWYgKGZvbyl7XG4gICAgZnVuY3Rpb24gZigpe31cbiAgfVxufVxuIiwib3B0aW9ucyI6eyJwYXJzZXJPcHRpb25zIjp7ImVjbWFWZXJzaW9uIjo2LCJzb3VyY2VUeXBlIjoic2NyaXB0IiwiZWNtYUZlYXR1cmVzIjp7fX0sInJ1bGVzIjp7ImNvbnN0cnVjdG9yLXN1cGVyIjoyLCJmb3ItZGlyZWN0aW9uIjoyLCJnZXR0ZXItcmV0dXJuIjoyLCJuby1hc3luYy1wcm9taXNlLWV4ZWN1dG9yIjoyLCJuby1jYXNlLWRlY2xhcmF0aW9ucyI6Miwibm8tY2xhc3MtYXNzaWduIjoyLCJuby1jb21wYXJlLW5lZy16ZXJvIjoyLCJuby1jb25kLWFzc2lnbiI6Miwibm8tY29uc3QtYXNzaWduIjoyLCJuby1jb25zdGFudC1jb25kaXRpb24iOjIsIm5vLWNvbnRyb2wtcmVnZXgiOjIsIm5vLWRlYnVnZ2VyIjoyLCJuby1kZWxldGUtdmFyIjoyLCJuby1kdXBlLWFyZ3MiOjIsIm5vLWR1cGUtY2xhc3MtbWVtYmVycyI6Miwibm8tZHVwZS1rZXlzIjoyLCJuby1kdXBsaWNhdGUtY2FzZSI6Miwibm8tZW1wdHkiOjIsIm5vLWVtcHR5LWNoYXJhY3Rlci1jbGFzcyI6Miwibm8tZW1wdHktcGF0dGVybiI6Miwibm8tZXgtYXNzaWduIjoyLCJuby1leHRyYS1ib29sZWFuLWNhc3QiOjIsIm5vLWV4dHJhLXNlbWkiOjIsIm5vLWZhbGx0aHJvdWdoIjoyLCJuby1mdW5jLWFzc2lnbiI6Miwibm8tZ2xvYmFsLWFzc2lnbiI6Miwibm8taW5uZXItZGVjbGFyYXRpb25zIjoyLCJuby1pbnZhbGlkLXJlZ2V4cCI6Miwibm8taXJyZWd1bGFyLXdoaXRlc3BhY2UiOjIsIm5vLW1pc2xlYWRpbmctY2hhcmFjdGVyLWNsYXNzIjoyLCJuby1taXhlZC1zcGFjZXMtYW5kLXRhYnMiOjIsIm5vLW5ldy1zeW1ib2wiOjIsIm5vLW9iai1jYWxscyI6Miwibm8tb2N0YWwiOjIsIm5vLXByb3RvdHlwZS1idWlsdGlucyI6Miwibm8tcmVnZXgtc3BhY2VzIjoyLCJuby1zZWxmLWFzc2lnbiI6Miwibm8tc2hhZG93LXJlc3RyaWN0ZWQtbmFtZXMiOjIsIm5vLXNwYXJzZS1hcnJheXMiOjIsIm5vLXRoaXMtYmVmb3JlLXN1cGVyIjoyLCJuby11bmV4cGVjdGVkLW11bHRpbGluZSI6Miwibm8tdW5yZWFjaGFibGUiOjIsIm5vLXVuc2FmZS1maW5hbGx5IjoyLCJuby11bnNhZmUtbmVnYXRpb24iOjIsIm5vLXVudXNlZC1sYWJlbHMiOjIsIm5vLXVzZWxlc3MtY2F0Y2giOjIsIm5vLXVzZWxlc3MtZXNjYXBlIjoyLCJuby13aXRoIjoyLCJyZXF1aXJlLWF0b21pYy11cGRhdGVzIjoyLCJyZXF1aXJlLXlpZWxkIjoyLCJ1c2UtaXNuYW4iOjIsInZhbGlkLXR5cGVvZiI6Mn0sImVudiI6e319fQ==)
~`BlockStatement >  VariableDeclaration` should be check as well~


@mdjermanovic I have a fix working for this! mind if I submit the change?

here is the fix https://github.com/anikethsaha/eslint/commit/83f83c5efe11e5aeb5ad486c9e221a704e339b84
@anikethsaha Sure, thanks!

It isn't entirely clear what should be done, but it looks like a good idea to continue the discussion on a PR.
@mdjermanovic @anikethsaha Could one of you sum up what needs to be discussed here so that the rest of the team can easily provide input?
The question is should we change the logic to report all declarations except those that have either:

* `Program` parent
* `ExportNamedDeclaration`  parent
* `ExportDefaultDeclaration` parent
* `BlockStatement` parent and `FunctionDeclaration` grandparent
* `BlockStatement` parent and `FunctionExpression` grandparent
* `BlockStatement` parent and `ArrowFunctionExpression` grandparent

In particular, cases like these would be new warnings:

```js
if (foo)
  var a; 

if (foo)
  function f(){}

while (foo)
  var a; 
```
Understood. I think there should be no errors in the original example since the documentation specifically refers to blocks. This feels especially true with lexically scoped variable declarations, since the actual scope would change.
I believe there are no lexically scoped variable declarations in those cases.

`if (foo) function f(){}` is allowed only by Annex B ([B.3.4](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-functiondeclarations-in-ifstatement-statement-clauses))

In strict mode, it's a syntax error.

In non-strict mode, it's treated as if it was `if (foo) { function f(){} }`, which by [B.3.3](https://www.ecma-international.org/ecma-262/10.0/index.html#sec-block-level-function-declarations-web-legacy-compatibility-semantics) isn't lexically scoped in non-strict mode.

```js
/* not strict */

f; // doesn't throw ReferenceError

console.log(typeof f); // undefined, isn't intialized yet

if (true)
    function f() { return 33; }

console.log(f()); // 33


g; // doesn't throw ReferenceError

console.log(typeof g); // undefined, isn't intialized yet

if (true) {
    function g() { return 55; }
}

console.log(g()); // 55
```

If this rule shouldn't report `if (foo) function f(){}` by default, then it would be useful to have an option or another rule for that, since it's a legacy Annex B feature with a confusing behavior.

> I believe there are no lexically scoped variable declarations in those cases.

Ah, you're right. Thanks for explaining this. I agree with you that this should have 4 errors. :+1:
Given three team members (@platinumazure, @mdjermanovic, and myself) believe this should have 4 errors, I think we can confidently move forward here. Removing the "needs bikeshedding" label.



Patch:
diff --git a/docs/rules/no-inner-declarations.md b/docs/rules/no-inner-declarations.md
index 23fd9253b105..0592084bce16 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 e1c29e0a3b4f..0768bc61149c 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,


Test patch:
diff --git a/tests/lib/rules/no-inner-declarations.js b/tests/lib/rules/no-inner-declarations.js
index d255824619ec..a9f70fd3f5ef 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"
+            }]
+        }
+
+    ]
 });