Repository: eslint/eslint (js)

Issue:
constructor-super throws when it encounters nested loops
<!--
This template is for bug reports. If you are reporting a bug, please continue on. If you are here for another reason, please see below:

1. To propose a new rule: http://eslint.org/docs/developer-guide/contributing/new-rules
2. To request a change: http://eslint.org/docs/developer-guide/contributing/changes
3. 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:** master
* **Node Version:** 7.7.2
* **npm Version:** 4.1.2

**What parser (default, Babel-ESLint, etc.) are you using?**

default

**Please show your full configuration:**

```yml
rules:
    constructor-super: error
parserOptions:
    ecmaVersion: 2015
```

**What did you do? Please include the actual source code causing the issue.**

<!-- Paste the source code below: -->
```js
class Foo extends Bar {
    constructor() {
        for (a in b) for (c in d);
    }
}
```

**What did you expect to happen?**

I expected ESLint to not crash.

**What actually happened? Please include the actual, raw output from ESLint.**

ESLint crashed.

```
TypeError: Cannot read property 'calledInSomePaths' of undefined
    at isCalledInSomePath (/path/to/eslint/lib/rules/constructor-super.js:132:63)
    at Array.some (native)
    at CodePath.funcInfo.codePath.traverseSegments.segment (/path/to/eslint/lib/rules/constructor-super.js:270:63)
    at CodePath.traverseSegments (/path/to/eslint/lib/code-path-analysis/code-path.js:208:30)
    at EventEmitter.onCodePathSegmentLoop (/path/to/eslint/lib/rules/constructor-super.js:263:35)
    at emitThree (events.js:116:13)
    at EventEmitter.emit (events.js:197:7)
    at CodePathAnalyzer.onLooped (/path/to/eslint/lib/code-path-analysis/code-path-analyzer.js:646:26)
    at makeLooped (/path/to/eslint/lib/code-path-analysis/code-path-state.js:191:15)
    at CodePathState.popLoopContext (/path/to/eslint/lib/code-path-analysis/code-path-state.js:1024:17)
```

It looks like when `CodePath#traverseSegments` is called, there is a segment where one of `segment.prevSegments` never triggered `onCodePathSegmentStart`. I'm not familiar with code path analysis, so I don't know whether this is a rule bug or a code path analysis bug in core.

@mysticatea, do you know where the issue might be coming from?

Pull request description:
Fix: broken code path of direct nested loops (fixes #8248)
**What is the purpose of this pull request? (put an "X" next to item)**

[X] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))

Fixes #8248.

**What changes did you make? (Give an overview)**

This PR fixes the broken code path about the direct nest of same kind loops.
The fix is below. Other diffs are for tests.

```diff
@@ -512,13 +512,8 @@ function processCodePathToExit(analyzer, node) {
             break;
     }
 
-    /*
-     * Skip updating the current segment to avoid creating useless segments if
-     * the node type is the same as the parent node type.
-     */
-    if (!dontForward && (!node.parent || node.type !== node.parent.type)) {
-
-        // Emits onCodePathSegmentStart events if updated.
+    // Emits onCodePathSegmentStart events if updated.
+    if (!dontForward) {
         forwardCurrentToHead(analyzer, node);
     }
     debug.dumpState(node, state, true);
```

I had been trying to optimize the code path, but it has been breaking.
I just removed the broken optimizing code.

As a result, the code path about the direct nest of same kind loops is recovered.
For example:

```js
while (a)
    while (b)
        foo()
```

<details><summary>Before: outer loop's looping path is lacking.</summary>

![image](https://cloud.githubusercontent.com/assets/1937871/24038814/68fd7878-0b46-11e7-8871-a928a635e838.png)

</details>

<details><summary>After: OK.</summary>

![image](https://cloud.githubusercontent.com/assets/1937871/24038773/387f610c-0b46-11e7-91ce-bdb93f17ab62.png)

</details>

----

**Is there anything you'd like reviewers to focus on?**

http://www.webgraphviz.com/ is useful to confirm test's code path with DOT diagrams.


Patch:
diff --git a/docs/rules/no-restricted-syntax.md b/docs/rules/no-restricted-syntax.md
index 2e94e5d66..ffe763c8b 100644
--- a/docs/rules/no-restricted-syntax.md
+++ b/docs/rules/no-restricted-syntax.md
@@ -58,4 +58,3 @@ If you don't want to restrict your code from using any JavaScript features or sy
 * [no-console](no-console.md)
 * [no-debugger](no-debugger.md)
 * [no-restricted-properties](no-restricted-properties.md)
-* [no-restricted-syntax](no-restricted-syntax.md)
diff --git a/docs/rules/no-unused-expressions.md b/docs/rules/no-unused-expressions.md
index 3b4a065e8..480915ee2 100644
--- a/docs/rules/no-unused-expressions.md
+++ b/docs/rules/no-unused-expressions.md
@@ -29,7 +29,8 @@ Sequence expressions (those using a comma, such as `a = 1, b = 2`) are always co
 This rule, in its default state, does not require any arguments. If you would like to enable one or more of the following you may pass an object with the options set as follows:
 
 * `allowShortCircuit` set to `true` will allow you to use short circuit evaluations in your expressions (Default: `false`).
-* `allowTernary` set to `true` will enable you use ternary operators in your expressions similarly to short circuit evaluations (Default: `false`).
+* `allowTernary` set to `true` will enable you to use ternary operators in your expressions similarly to short circuit evaluations (Default: `false`).
+* `allowTaggedTemplates` set to `true` will enable you to use tagged template literals in your expressions (Default: `false`).
 
 These options allow unused expressions *only if all* of the code paths either directly change the state (for example, assignment statement) or could have *side effects* (for example, function call).
 
@@ -56,6 +57,8 @@ a() && function namedFunctionInExpressionContext () {f();}
 
 (function anIncompleteIIFE () {});
 
+injectGlobal`body{ color: red; }`
+
 ```
 
 Note that one or more string expression statements (with or without semi-colons) will only be considered as unused if they are not in the beginning of a script, module, or function (alone and uninterrupted by other statements). Otherwise, they will be treated as part of a "directive prologue", a section potentially usable by JavaScript engines. This includes "strict mode" directives.
@@ -140,3 +143,21 @@ Examples of **correct** code for the `{ "allowShortCircuit": true, "allowTernary
 
 a ? b() || (c = d) : e()
 ```
+
+### allowTaggedTemplates
+
+Examples of **incorrect** code for the `{ "allowTaggedTemplates": true }` option:
+
+```js
+/*eslint no-unused-expressions: ["error", { "allowTaggedTemplates": true }]*/
+
+`some untagged template string`;
+```
+
+Examples of **correct** code for the `{ "allowTaggedTemplates": true }` option:
+
+```js
+/*eslint no-unused-expressions: ["error", { "allowTaggedTemplates": true }]*/
+
+tag`some tagged template string`;
+```
diff --git a/lib/code-path-analysis/code-path-analyzer.js b/lib/code-path-analysis/code-path-analyzer.js
index cb8b1e1bf..539b5e18b 100644
--- a/lib/code-path-analysis/code-path-analyzer.js
+++ b/lib/code-path-analysis/code-path-analyzer.js
@@ -512,13 +512,8 @@ function processCodePathToExit(analyzer, node) {
             break;
     }
 
-    /*
-     * Skip updating the current segment to avoid creating useless segments if
-     * the node type is the same as the parent node type.
-     */
-    if (!dontForward && (!node.parent || node.type !== node.parent.type)) {
-
-        // Emits onCodePathSegmentStart events if updated.
+    // Emits onCodePathSegmentStart events if updated.
+    if (!dontForward) {
         forwardCurrentToHead(analyzer, node);
     }
     debug.dumpState(node, state, true);
diff --git a/lib/code-path-analysis/debug-helpers.js b/lib/code-path-analysis/debug-helpers.js
index 622bd6081..9af985ce8 100644
--- a/lib/code-path-analysis/debug-helpers.js
+++ b/lib/code-path-analysis/debug-helpers.js
@@ -107,22 +107,23 @@ module.exports = {
                 text += "style=\"rounded,dashed,filled\",fillcolor=\"#FF9800\",label=\"<<unreachable>>\\n";
             }
 
-            if (segment.internal.nodes.length > 0) {
-                text += segment.internal.nodes.map(node => {
-                    switch (node.type) {
-                        case "Identifier": return `${node.type} (${node.name})`;
-                        case "Literal": return `${node.type} (${node.value})`;
-                        default: return node.type;
-                    }
-                }).join("\\n");
-            } else if (segment.internal.exitNodes.length > 0) {
-                text += segment.internal.exitNodes.map(node => {
-                    switch (node.type) {
-                        case "Identifier": return `${node.type}:exit (${node.name})`;
-                        case "Literal": return `${node.type}:exit (${node.value})`;
-                        default: return `${node.type}:exit`;
-                    }
-                }).join("\\n");
+            if (segment.internal.nodes.length > 0 || segment.internal.exitNodes.length > 0) {
+                text += [].concat(
+                    segment.internal.nodes.map(node => {
+                        switch (node.type) {
+                            case "Identifier": return `${node.type} (${node.name})`;
+                            case "Literal": return `${node.type} (${node.value})`;
+                            default: return node.type;
+                        }
+                    }),
+                    segment.internal.exitNodes.map(node => {
+                        switch (node.type) {
+                            case "Identifier": return `${node.type}:exit (${node.name})`;
+                            case "Literal": return `${node.type}:exit (${node.value})`;
+                            default: return `${node.type}:exit`;
+                        }
+                    })
+                ).join("\\n");
             } else {
                 text += "????";
             }
diff --git a/lib/ignored-paths.js b/lib/ignored-paths.js
index bace73db6..cfca7fa4f 100644
--- a/lib/ignored-paths.js
+++ b/lib/ignored-paths.js
@@ -201,6 +201,12 @@ class IgnoredPaths {
 
         const ig = ignore().add(DEFAULT_IGNORE_DIRS);
 
+        if (this.options.dotfiles !== true) {
+
+            // Ignore hidden folders.  (This cannot be ".*", or else it's not possible to unignore hidden files)
+            ig.add([".*/*", "!../"]);
+        }
+
         if (this.options.ignore) {
             ig.add(this.ig.custom);
         }
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index 35fdfbfa6..8be8a5a49 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -67,6 +67,7 @@ module.exports = {
         const NESTED_BINARY = ALL_NODES && context.options[1] && context.options[1].nestedBinaryExpressions === false;
         const EXCEPT_RETURN_ASSIGN = ALL_NODES && context.options[1] && context.options[1].returnAssign === false;
         const IGNORE_JSX = ALL_NODES && context.options[1] && context.options[1].ignoreJSX;
+        const PRECEDENCE_OF_ASSIGNMENT_EXPR = precedence({ type: "AssignmentExpression" });
         const PRECEDENCE_OF_UPDATE_EXPR = precedence({ type: "UpdateExpression" });
 
         /**
@@ -394,12 +395,12 @@ module.exports = {
                 report(node.callee);
             }
             if (node.arguments.length === 1) {
-                if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({ type: "AssignmentExpression" })) {
+                if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                     report(node.arguments[0]);
                 }
             } else {
                 [].forEach.call(node.arguments, arg => {
-                    if (hasExcessParens(arg) && precedence(arg) >= precedence({ type: "AssignmentExpression" })) {
+                    if (hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                         report(arg);
                     }
                 });
@@ -456,7 +457,11 @@ module.exports = {
          * @returns {void}
          */
         function checkSpreadOperator(node) {
-            if (node.argument && hasExcessParens(node.argument)) {
+            const hasExtraParens = precedence(node.argument) >= PRECEDENCE_OF_ASSIGNMENT_EXPR
+                ? hasExcessParens(node.argument)
+                : hasDoubleExcessParens(node.argument);
+
+            if (hasExtraParens) {
                 report(node.argument);
             }
         }
@@ -464,7 +469,7 @@ module.exports = {
         return {
             ArrayExpression(node) {
                 [].forEach.call(node.elements, e => {
-                    if (e && hasExcessParens(e) && precedence(e) >= precedence({ type: "AssignmentExpression" })) {
+                    if (e && hasExcessParens(e) && precedence(e) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                         report(e);
                     }
                 });
@@ -476,7 +481,7 @@ module.exports = {
                 }
 
                 if (node.body.type !== "BlockStatement") {
-                    if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= precedence({ type: "AssignmentExpression" })) {
+                    if (sourceCode.getFirstToken(node.body).value !== "{" && hasExcessParens(node.body) && precedence(node.body) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                         report(node.body);
                         return;
                     }
@@ -510,11 +515,11 @@ module.exports = {
                     report(node.test);
                 }
 
-                if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({ type: "AssignmentExpression" })) {
+                if (hasExcessParens(node.consequent) && precedence(node.consequent) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                     report(node.consequent);
                 }
 
-                if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({ type: "AssignmentExpression" })) {
+                if (hasExcessParens(node.alternate) && precedence(node.alternate) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                     report(node.alternate);
                 }
             },
@@ -615,7 +620,7 @@ module.exports = {
                 [].forEach.call(node.properties, e => {
                     const v = e.value;
 
-                    if (v && hasExcessParens(v) && precedence(v) >= precedence({ type: "AssignmentExpression" })) {
+                    if (v && hasExcessParens(v) && precedence(v) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
                         report(v);
                     }
                 });
@@ -671,7 +676,7 @@ module.exports = {
 
             VariableDeclarator(node) {
                 if (node.init && hasExcessParens(node.init) &&
-                        precedence(node.init) >= precedence({ type: "AssignmentExpression" }) &&
+                        precedence(node.init) >= PRECEDENCE_OF_ASSIGNMENT_EXPR &&
 
                         // RegExp literal is allowed to have parens (#1589)
                         !(node.init.type === "Literal" && node.init.regex)) {
diff --git a/lib/rules/no-unused-expressions.js b/lib/rules/no-unused-expressions.js
index 548e02f46..b4e1074d5 100644
--- a/lib/rules/no-unused-expressions.js
+++ b/lib/rules/no-unused-expressions.js
@@ -25,6 +25,9 @@ module.exports = {
                     },
                     allowTernary: {
                         type: "boolean"
+                    },
+                    allowTaggedTemplates: {
+                        type: "boolean"
                     }
                 },
                 additionalProperties: false
@@ -35,7 +38,8 @@ module.exports = {
     create(context) {
         const config = context.options[0] || {},
             allowShortCircuit = config.allowShortCircuit || false,
-            allowTernary = config.allowTernary || false;
+            allowTernary = config.allowTernary || false,
+            allowTaggedTemplates = config.allowTaggedTemplates || false;
 
         /**
          * @param {ASTNode} node - any node
@@ -95,12 +99,17 @@ module.exports = {
                     return isValidExpression(node.consequent) && isValidExpression(node.alternate);
                 }
             }
+
             if (allowShortCircuit) {
                 if (node.type === "LogicalExpression") {
                     return isValidExpression(node.right);
                 }
             }
 
+            if (allowTaggedTemplates && node.type === "TaggedTemplateExpression") {
+                return true;
+            }
+
             return /^(?:Assignment|Call|New|Update|Yield|Await)Expression$/.test(node.type) ||
                 (node.type === "UnaryExpression" && ["delete", "void"].indexOf(node.operator) >= 0);
         }


Test patch:
diff --git a/tests/fixtures/code-path-analysis/for--direct-nest.js b/tests/fixtures/code-path-analysis/for--direct-nest.js
new file mode 100644
index 000000000..ee7d4781d
--- /dev/null
+++ b/tests/fixtures/code-path-analysis/for--direct-nest.js
@@ -0,0 +1,30 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_5;
+s1_2->s1_9;
+s1_5->s1_8->s1_4->s1_2;
+s1_9->final;
+*/
+for (var i = 0; i < 10; ++i)
+    for (var j = 0; j < 10; ++j)
+        foo
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program\nForStatement\nVariableDeclaration\nVariableDeclarator\nIdentifier (i)\nLiteral (0)\nIdentifier:exit (i)\nLiteral:exit (0)\nVariableDeclarator:exit\nVariableDeclaration:exit"];
+    s1_2[label="BinaryExpression\nIdentifier (i)\nLiteral (10)\nIdentifier:exit (i)\nLiteral:exit (10)\nBinaryExpression:exit"];
+    s1_3[label="ForStatement\nVariableDeclaration\nVariableDeclarator\nIdentifier (j)\nLiteral (0)\nIdentifier:exit (j)\nLiteral:exit (0)\nVariableDeclarator:exit\nVariableDeclaration:exit"];
+    s1_5[label="BinaryExpression\nIdentifier (j)\nLiteral (10)\nIdentifier:exit (j)\nLiteral:exit (10)\nBinaryExpression:exit"];
+    s1_6[label="ExpressionStatement\nIdentifier (foo)\nIdentifier:exit (foo)\nExpressionStatement:exit"];
+    s1_7[label="UpdateExpression\nIdentifier (j)\nIdentifier:exit (j)\nUpdateExpression:exit"];
+    s1_9[label="ForStatement:exit\nProgram:exit"];
+    s1_8[label="ForStatement:exit"];
+    s1_4[label="UpdateExpression\nIdentifier (i)\nIdentifier:exit (i)\nUpdateExpression:exit"];
+    initial->s1_1->s1_2->s1_3->s1_5->s1_6->s1_7->s1_5;
+    s1_2->s1_9;
+    s1_5->s1_8->s1_4->s1_2;
+    s1_9->final;
+}
+*/
diff --git a/tests/fixtures/code-path-analysis/for-in--direct-nest.js b/tests/fixtures/code-path-analysis/for-in--direct-nest.js
new file mode 100644
index 000000000..de5df3183
--- /dev/null
+++ b/tests/fixtures/code-path-analysis/for-in--direct-nest.js
@@ -0,0 +1,30 @@
+/*expected
+initial->s1_1->s1_3->s1_2->s1_4->s1_6->s1_5->s1_7->s1_5;
+s1_3->s1_9;
+s1_6->s1_8->s1_2;
+s1_7->s1_8->s1_9->final;
+*/
+for (var a in {}) 
+    for (var b in {})
+        foo;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program\nForInStatement"];
+    s1_3[label="ObjectExpression\nObjectExpression:exit"];
+    s1_2[label="VariableDeclaration\nVariableDeclarator\nIdentifier (a)\nIdentifier:exit (a)\nVariableDeclarator:exit\nVariableDeclaration:exit"];
+    s1_4[label="ForInStatement"];
+    s1_6[label="ObjectExpression\nObjectExpression:exit"];
+    s1_5[label="VariableDeclaration\nVariableDeclarator\nIdentifier (b)\nIdentifier:exit (b)\nVariableDeclarator:exit\nVariableDeclaration:exit"];
+    s1_7[label="ExpressionStatement\nIdentifier (foo)\nIdentifier:exit (foo)\nExpressionStatement:exit"];
+    s1_9[label="ForInStatement:exit\nProgram:exit"];
+    s1_8[label="ForInStatement:exit"];
+    initial->s1_1->s1_3->s1_2->s1_4->s1_6->s1_5->s1_7->s1_5;
+    s1_3->s1_9;
+    s1_6->s1_8->s1_2;
+    s1_7->s1_8->s1_9->final;
+}
+*/
diff --git a/tests/fixtures/code-path-analysis/for-of--direct-nest.js b/tests/fixtures/code-path-analysis/for-of--direct-nest.js
new file mode 100644
index 000000000..1224bb0d1
--- /dev/null
+++ b/tests/fixtures/code-path-analysis/for-of--direct-nest.js
@@ -0,0 +1,30 @@
+/*expected
+initial->s1_1->s1_3->s1_2->s1_4->s1_6->s1_5->s1_7->s1_5;
+s1_3->s1_9;
+s1_6->s1_8->s1_2;
+s1_7->s1_8->s1_9->final;
+*/
+for (var a of []) 
+    for (var b of [])
+        foo;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program\nForOfStatement"];
+    s1_3[label="ArrayExpression\nArrayExpression:exit"];
+    s1_2[label="VariableDeclaration\nVariableDeclarator\nIdentifier (a)\nIdentifier:exit (a)\nVariableDeclarator:exit\nVariableDeclaration:exit"];
+    s1_4[label="ForOfStatement"];
+    s1_6[label="ArrayExpression\nArrayExpression:exit"];
+    s1_5[label="VariableDeclaration\nVariableDeclarator\nIdentifier (b)\nIdentifier:exit (b)\nVariableDeclarator:exit\nVariableDeclaration:exit"];
+    s1_7[label="ExpressionStatement\nIdentifier (foo)\nIdentifier:exit (foo)\nExpressionStatement:exit"];
+    s1_9[label="ForOfStatement:exit\nProgram:exit"];
+    s1_8[label="ForOfStatement:exit"];
+    initial->s1_1->s1_3->s1_2->s1_4->s1_6->s1_5->s1_7->s1_5;
+    s1_3->s1_9;
+    s1_6->s1_8->s1_2;
+    s1_7->s1_8->s1_9->final;
+}
+*/
diff --git a/tests/fixtures/code-path-analysis/if-3.js b/tests/fixtures/code-path-analysis/if-3.js
index e1fe10d1a..33a225e8c 100644
--- a/tests/fixtures/code-path-analysis/if-3.js
+++ b/tests/fixtures/code-path-analysis/if-3.js
@@ -1,7 +1,7 @@
 /*expected
 initial->s1_1->s1_2->s1_9;
-s1_1->s1_3->s1_4->s1_5->s1_6->s1_9;
-s1_3->s1_7->s1_9;
+s1_1->s1_3->s1_4->s1_5->s1_6->s1_8->s1_9;
+s1_3->s1_7->s1_8;
 s1_4->s1_6;
 s1_9->final;
 */
@@ -20,17 +20,18 @@ digraph {
     node[shape=box,style="rounded,filled",fillcolor=white];
     initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
     final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
-    s1_1[label="Program\nIfStatement\nIdentifier (a)"];
-    s1_2[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)"];
+    s1_1[label="Program\nIfStatement\nIdentifier (a)\nIdentifier:exit (a)"];
+    s1_2[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
     s1_9[label="IfStatement:exit\nProgram:exit"];
-    s1_3[label="IfStatement\nIdentifier (b)"];
-    s1_4[label="BlockStatement\nIfStatement\nIdentifier (c)"];
-    s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)"];
+    s1_3[label="IfStatement\nIdentifier (b)\nIdentifier:exit (b)"];
+    s1_4[label="BlockStatement\nIfStatement\nIdentifier (c)\nIdentifier:exit (c)"];
+    s1_5[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
     s1_6[label="IfStatement:exit\nBlockStatement:exit"];
-    s1_7[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (hoge)"];
+    s1_8[label="IfStatement:exit"];
+    s1_7[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (hoge)\nIdentifier:exit (hoge)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
     initial->s1_1->s1_2->s1_9;
-    s1_1->s1_3->s1_4->s1_5->s1_6->s1_9;
-    s1_3->s1_7->s1_9;
+    s1_1->s1_3->s1_4->s1_5->s1_6->s1_8->s1_9;
+    s1_3->s1_7->s1_8;
     s1_4->s1_6;
     s1_9->final;
 }
diff --git a/tests/fixtures/code-path-analysis/if-6.js b/tests/fixtures/code-path-analysis/if-6.js
new file mode 100644
index 000000000..09572a150
--- /dev/null
+++ b/tests/fixtures/code-path-analysis/if-6.js
@@ -0,0 +1,37 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_6->s1_8->s1_9;
+s1_1->s1_9;
+s1_2->s1_7->s1_8;
+s1_3->s1_5->s1_6;
+s1_9->final;
+*/
+if (a)
+    if (b)
+        if (c)
+            foo;
+        else
+            bar;
+    else
+        baz;
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program\nIfStatement\nIdentifier (a)\nIdentifier:exit (a)"];
+    s1_2[label="IfStatement\nIdentifier (b)\nIdentifier:exit (b)"];
+    s1_3[label="IfStatement\nIdentifier (c)\nIdentifier:exit (c)"];
+    s1_4[label="ExpressionStatement\nIdentifier (foo)\nIdentifier:exit (foo)\nExpressionStatement:exit"];
+    s1_6[label="IfStatement:exit"];
+    s1_8[label="IfStatement:exit"];
+    s1_9[label="IfStatement:exit\nProgram:exit"];
+    s1_7[label="ExpressionStatement\nIdentifier (baz)\nIdentifier:exit (baz)\nExpressionStatement:exit"];
+    s1_5[label="ExpressionStatement\nIdentifier (bar)\nIdentifier:exit (bar)\nExpressionStatement:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_6->s1_8->s1_9;
+    s1_1->s1_9;
+    s1_2->s1_7->s1_8;
+    s1_3->s1_5->s1_6;
+    s1_9->final;
+}
+*/
diff --git a/tests/fixtures/code-path-analysis/logical--if-and-3.js b/tests/fixtures/code-path-analysis/logical--if-and-3.js
index 22900f383..a63b5fb71 100644
--- a/tests/fixtures/code-path-analysis/logical--if-and-3.js
+++ b/tests/fixtures/code-path-analysis/logical--if-and-3.js
@@ -1,9 +1,10 @@
 /*expected
 initial->s1_1->s1_2->s1_3->s1_9;
-s1_1->s1_4->s1_5->s1_6->s1_9;
-s1_2->s1_4->s1_7->s1_9;
+s1_1->s1_4->s1_5->s1_6->s1_8->s1_9;
+s1_2->s1_4->s1_7->s1_8;
 s1_5->s1_7;
 s1_9->final;
+
 */
 if (a && b) {
     foo();
@@ -18,17 +19,18 @@ digraph {
     node[shape=box,style="rounded,filled",fillcolor=white];
     initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
     final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
-    s1_1[label="Program\nIfStatement\nLogicalExpression\nIdentifier (a)"];
-    s1_2[label="Identifier (b)"];
-    s1_3[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)"];
+    s1_1[label="Program\nIfStatement\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"];
+    s1_2[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"];
+    s1_3[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
     s1_9[label="IfStatement:exit\nProgram:exit"];
-    s1_4[label="IfStatement\nLogicalExpression\nIdentifier (c)"];
-    s1_5[label="Identifier (d)"];
-    s1_6[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)"];
-    s1_7[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (qiz)"];
+    s1_4[label="IfStatement\nLogicalExpression\nIdentifier (c)\nIdentifier:exit (c)"];
+    s1_5[label="Identifier (d)\nIdentifier:exit (d)\nLogicalExpression:exit"];
+    s1_6[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_8[label="IfStatement:exit"];
+    s1_7[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (qiz)\nIdentifier:exit (qiz)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
     initial->s1_1->s1_2->s1_3->s1_9;
-    s1_1->s1_4->s1_5->s1_6->s1_9;
-    s1_2->s1_4->s1_7->s1_9;
+    s1_1->s1_4->s1_5->s1_6->s1_8->s1_9;
+    s1_2->s1_4->s1_7->s1_8;
     s1_5->s1_7;
     s1_9->final;
 }
diff --git a/tests/fixtures/code-path-analysis/logical--if-or-3.js b/tests/fixtures/code-path-analysis/logical--if-or-3.js
index bf2d7d432..23dd57f66 100644
--- a/tests/fixtures/code-path-analysis/logical--if-or-3.js
+++ b/tests/fixtures/code-path-analysis/logical--if-or-3.js
@@ -1,9 +1,10 @@
 /*expected
 initial->s1_1->s1_2->s1_3->s1_9;
 s1_1->s1_3;
-s1_2->s1_4->s1_5->s1_6->s1_9;
+s1_2->s1_4->s1_5->s1_6->s1_8->s1_9;
 s1_4->s1_6;
-s1_5->s1_7->s1_9->final;
+s1_5->s1_7->s1_8;
+s1_9->final;
 */
 if (a || b) {
     foo();
@@ -18,18 +19,20 @@ digraph {
     node[shape=box,style="rounded,filled",fillcolor=white];
     initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
     final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
-    s1_1[label="Program\nIfStatement\nLogicalExpression\nIdentifier (a)"];
-    s1_2[label="Identifier (b)"];
-    s1_3[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)"];
+    s1_1[label="Program\nIfStatement\nLogicalExpression\nIdentifier (a)\nIdentifier:exit (a)"];
+    s1_2[label="Identifier (b)\nIdentifier:exit (b)\nLogicalExpression:exit"];
+    s1_3[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (foo)\nIdentifier:exit (foo)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
     s1_9[label="IfStatement:exit\nProgram:exit"];
-    s1_4[label="IfStatement\nLogicalExpression\nIdentifier (c)"];
-    s1_5[label="Identifier (d)"];
-    s1_6[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)"];
-    s1_7[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (qiz)"];
+    s1_4[label="IfStatement\nLogicalExpression\nIdentifier (c)\nIdentifier:exit (c)"];
+    s1_5[label="Identifier (d)\nIdentifier:exit (d)\nLogicalExpression:exit"];
+    s1_6[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (bar)\nIdentifier:exit (bar)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
+    s1_8[label="IfStatement:exit"];
+    s1_7[label="BlockStatement\nExpressionStatement\nCallExpression\nIdentifier (qiz)\nIdentifier:exit (qiz)\nCallExpression:exit\nExpressionStatement:exit\nBlockStatement:exit"];
     initial->s1_1->s1_2->s1_3->s1_9;
     s1_1->s1_3;
-    s1_2->s1_4->s1_5->s1_6->s1_9;
+    s1_2->s1_4->s1_5->s1_6->s1_8->s1_9;
     s1_4->s1_6;
-    s1_5->s1_7->s1_9->final;
+    s1_5->s1_7->s1_8;
+    s1_9->final;
 }
 */
diff --git a/tests/fixtures/code-path-analysis/while--direct-nest.js b/tests/fixtures/code-path-analysis/while--direct-nest.js
new file mode 100644
index 000000000..52bfc53ef
--- /dev/null
+++ b/tests/fixtures/code-path-analysis/while--direct-nest.js
@@ -0,0 +1,28 @@
+/*expected
+initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_4;
+s1_2->s1_7;
+s1_4->s1_6->s1_2;
+s1_7->final;
+*/
+while (a)
+    while (b)
+        foo
+
+/*DOT
+digraph {
+    node[shape=box,style="rounded,filled",fillcolor=white];
+    initial[label="",shape=circle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    final[label="",shape=doublecircle,style=filled,fillcolor=black,width=0.25,height=0.25];
+    s1_1[label="Program\nWhileStatement"];
+    s1_2[label="Identifier (a)\nIdentifier:exit (a)"];
+    s1_3[label="WhileStatement"];
+    s1_4[label="Identifier (b)\nIdentifier:exit (b)"];
+    s1_5[label="ExpressionStatement\nIdentifier (foo)\nIdentifier:exit (foo)\nExpressionStatement:exit"];
+    s1_7[label="WhileStatement:exit\nProgram:exit"];
+    s1_6[label="WhileStatement:exit"];
+    initial->s1_1->s1_2->s1_3->s1_4->s1_5->s1_4;
+    s1_2->s1_7;
+    s1_4->s1_6->s1_2;
+    s1_7->final;
+} 
+*/
diff --git a/tests/fixtures/ignored-paths/.eslintignoreWithUnignoredDefaults b/tests/fixtures/ignored-paths/.eslintignoreWithUnignoredDefaults
index 07f3a42ba..ca93372b2 100644
--- a/tests/fixtures/ignored-paths/.eslintignoreWithUnignoredDefaults
+++ b/tests/fixtures/ignored-paths/.eslintignoreWithUnignoredDefaults
@@ -1,2 +1,3 @@
 !/node_modules/package
 !/bower_components/package
+!.hidden/package
diff --git a/tests/lib/config/config-file.js b/tests/lib/config/config-file.js
index 5b6521909..c6ecc09ad 100644
--- a/tests/lib/config/config-file.js
+++ b/tests/lib/config/config-file.js
@@ -123,17 +123,15 @@ function createStubModuleResolver(mapping) {
      * @constructor
      * @private
      */
-    function StubModuleResolver() {}
+    return class StubModuleResolver {
+        resolve(name) { // eslint-disable-line class-methods-use-this
+            if (mapping.hasOwnProperty(name)) {
+                return mapping[name];
+            }
 
-    StubModuleResolver.prototype.resolve = function(name) {
-        if (mapping.hasOwnProperty(name)) {
-            return mapping[name];
+            throw new Error(`Cannot find module '${name}'`);
         }
-
-        throw new Error(`Cannot find module '${name}'`);
     };
-
-    return StubModuleResolver;
 }
 
 //------------------------------------------------------------------------------
diff --git a/tests/lib/ignored-paths.js b/tests/lib/ignored-paths.js
index 381589883..643a11ada 100644
--- a/tests/lib/ignored-paths.js
+++ b/tests/lib/ignored-paths.js
@@ -550,9 +550,10 @@ describe("IgnoredPaths", () => {
             assert.isTrue(shouldIgnore(resolve("bower_components/a")));
             assert.isTrue(shouldIgnore(resolve("bower_components/a/b")));
             assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/a")));
         });
 
-        it("should ignore default folders there is an ignore file without unignored defaults", () => {
+        it("should ignore default folders when there is an ignore file without unignored defaults", () => {
             const cwd = getFixturePath();
             const ignoredPaths = new IgnoredPaths({ ignore: true, ignorePath: getFixturePath(".eslintignore"), cwd });
 
@@ -564,6 +565,7 @@ describe("IgnoredPaths", () => {
             assert.isTrue(shouldIgnore(resolve("bower_components/a")));
             assert.isTrue(shouldIgnore(resolve("bower_components/a/b")));
             assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/a")));
         });
 
         it("should not ignore things which are re-included in ignore file", () => {
@@ -578,8 +580,10 @@ describe("IgnoredPaths", () => {
             assert.isTrue(shouldIgnore(resolve("bower_components/a")));
             assert.isTrue(shouldIgnore(resolve("bower_components/a/b")));
             assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/a")));
             assert.isFalse(shouldIgnore(resolve("node_modules/package")));
             assert.isFalse(shouldIgnore(resolve("bower_components/package")));
+            assert.isFalse(shouldIgnore(resolve(".hidden/package")));
         });
 
         it("should ignore files which we try to re-include in ignore file when ignore option is disabled", () => {
@@ -594,8 +598,10 @@ describe("IgnoredPaths", () => {
             assert.isTrue(shouldIgnore(resolve("bower_components/a")));
             assert.isTrue(shouldIgnore(resolve("bower_components/a/b")));
             assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/a")));
             assert.isTrue(shouldIgnore(resolve("node_modules/package")));
             assert.isTrue(shouldIgnore(resolve("bower_components/package")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/package")));
         });
 
         it("should not ignore dirs which are re-included by ignorePattern", () => {
@@ -610,9 +616,21 @@ describe("IgnoredPaths", () => {
             assert.isTrue(shouldIgnore(resolve("bower_components/a")));
             assert.isTrue(shouldIgnore(resolve("bower_components/a/b")));
             assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isTrue(shouldIgnore(resolve(".hidden/a")));
             assert.isFalse(shouldIgnore(resolve("node_modules/package")));
             assert.isTrue(shouldIgnore(resolve("bower_components/package")));
         });
+
+        it("should not ignore hidden dirs when dotfiles is enabled", () => {
+            const cwd = getFixturePath("no-ignore-file");
+            const ignoredPaths = new IgnoredPaths({ ignore: true, cwd, dotfiles: true });
+
+            const shouldIgnore = ignoredPaths.getIgnoredFoldersGlobChecker();
+            const resolve = createResolve(cwd);
+
+            assert.isFalse(shouldIgnore(resolve(".hidden")));
+            assert.isFalse(shouldIgnore(resolve(".hidden/a")));
+        });
     });
 
 });
diff --git a/tests/lib/rules/constructor-super.js b/tests/lib/rules/constructor-super.js
index 00e8b5e23..98fca60db 100644
--- a/tests/lib/rules/constructor-super.js
+++ b/tests/lib/rules/constructor-super.js
@@ -208,6 +208,16 @@ ruleTester.run("constructor-super", rule, {
         {
             code: "class A extends B { constructor() { return; super(); } }",
             errors: [{ message: "Expected to call 'super()'.", type: "MethodDefinition" }]
+        },
+
+        // https://github.com/eslint/eslint/issues/8248
+        {
+            code: `class Foo extends Bar {
+                constructor() {
+                    for (a in b) for (c in d);
+                }
+            }`,
+            errors: [{ message: "Expected to call 'super()'.", type: "MethodDefinition" }]
         }
     ]
 });
diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js
index b1e945e5e..349c14994 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -380,6 +380,21 @@ ruleTester.run("no-extra-parens", rule, {
                 ecmaFeatures: { experimentalObjectRestSpread: true }
             }
         },
+        {
+            code: "let a = [ ...(b, c) ]",
+            parserOptions: { ecmaVersion: 2015 }
+        },
+        {
+            code: "let a = { ...(b, c) }",
+            parserOptions: {
+                ecmaVersion: 2015,
+                ecmaFeatures: { experimentalObjectRestSpread: true }
+            }
+        },
+        {
+            code: "var [x = (1, foo)] = bar",
+            parserOptions: { ecmaVersion: 2015 }
+        },
         {
             code: "class A extends B {}",
             parserOptions: { ecmaVersion: 2015 }
@@ -891,6 +906,25 @@ ruleTester.run("no-extra-parens", rule, {
                 }
             }
         ),
+        invalid(
+            "let a = [...((b, c))]",
+            "let a = [...(b, c)]",
+            "SequenceExpression",
+            1,
+            { parserOptions: { ecmaVersion: 2015 } }
+        ),
+        invalid(
+            "let a = {...((b, c))}",
+            "let a = {...(b, c)}",
+            "SequenceExpression",
+            1,
+            {
+                parserOptions: {
+                    ecmaVersion: 2015,
+                    ecmaFeatures: { experimentalObjectRestSpread: true }
+                }
+            }
+        ),
         invalid(
             "class A extends (B) {}",
             "class A extends B {}",
diff --git a/tests/lib/rules/no-unused-expressions.js b/tests/lib/rules/no-unused-expressions.js
index 57eb39ed5..520705179 100644
--- a/tests/lib/rules/no-unused-expressions.js
+++ b/tests/lib/rules/no-unused-expressions.js
@@ -60,6 +60,16 @@ ruleTester.run("no-unused-expressions", rule, {
             code: "async function foo() { foo ? await bar : await baz; }",
             options: [{ allowTernary: true }],
             parserOptions: { ecmaVersion: 8 }
+        },
+        {
+            code: "tag`tagged template literal`",
+            options: [{ allowTaggedTemplates: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "shouldNotBeAffectedByAllowTemplateTagsOption()",
+            options: [{ allowTaggedTemplates: true }],
+            parserOptions: { ecmaVersion: 6 }
         }
     ],
     invalid: [
@@ -72,6 +82,16 @@ ruleTester.run("no-unused-expressions", rule, {
         { code: "a() || false", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "a || (b = c)", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "a ? b() || (c = d) : e", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
+        {
+            code: "`untagged template literal`",
+            errors: ["Expected an assignment or function call and instead saw an expression."],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "tag`tagged template literal`",
+            errors: ["Expected an assignment or function call and instead saw an expression."],
+            parserOptions: { ecmaVersion: 6 }
+        },
         { code: "a && b()", options: [{ allowTernary: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "a ? b() : c()", options: [{ allowShortCircuit: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "a || b", options: [{ allowShortCircuit: true }], errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
@@ -85,6 +105,24 @@ ruleTester.run("no-unused-expressions", rule, {
         { code: "function foo() {\"directive one\"; f(); \"directive two\"; }", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "if (0) { \"not a directive\"; f(); }", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
         { code: "function foo() { var foo = true; \"use strict\"; }", errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
-        { code: "var foo = () => { var foo = true; \"use strict\"; }", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] }
+        { code: "var foo = () => { var foo = true; \"use strict\"; }", parserOptions: { ecmaVersion: 6 }, errors: [{ message: "Expected an assignment or function call and instead saw an expression.", type: "ExpressionStatement" }] },
+        {
+            code: "`untagged template literal`",
+            errors: ["Expected an assignment or function call and instead saw an expression."],
+            options: [{ allowTaggedTemplates: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "`untagged template literal`",
+            errors: ["Expected an assignment or function call and instead saw an expression."],
+            options: [{ allowTaggedTemplates: false }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: "tag`tagged template literal`",
+            errors: ["Expected an assignment or function call and instead saw an expression."],
+            options: [{ allowTaggedTemplates: false }],
+            parserOptions: { ecmaVersion: 6 }
+        }
     ]
 });