Repository: eslint/eslint (js)

Issue:
Error when nesting an unknown logical operator
**Tell us about your environment**

* **ESLint Version:** 5.0.0
* **Node Version:** 9.11.2

**What parser (default, Babel-ESLint, etc.) are you using?** babel-eslint@9.0.0-beta.1

**Please show your full configuration:**

<details>
<summary>Configuration</summary>

<!-- Paste your configuration below: -->
```js
{
  "parser": "babel-eslint",
  "rules": {
    "strict": 0
  }
}
```

</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: -->
```js
foo && bar ?? baz
```

<!-- Paste the command you used to run ESLint: -->
```bash
node_modules/.bin/eslint my-file.js
```

**What did you expect to happen?**

No errors

**What actually happened? Please include the actual, raw output from ESLint.**

```
TypeError: Cannot read property 'trueForkContext' of null
    at CodePathState.popChoiceContext (<some-path>/node_modules/eslint/lib/code-path-analysis/code-path-state.js:403:35)
    at processCodePathToExit (<some-path>/node_modules/eslint/lib/code-path-analysis/code-path-analyzer.js:457:23)
    at CodePathAnalyzer.leaveNode (<some-path>/node_modules/eslint/lib/code-path-analysis/code-path-analyzer.js:653:9)
    at nodeQueue.forEach.traversalInfo (<some-path>/node_modules/eslint/lib/linter.js:863:28)
```

Pull request description:
Fix: do not fail on nested unknown operators
**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[X] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:

**What changes did you make? (Give an overview)**

Fixes #10560, after adding support in #10476.



Patch:
diff --git a/.eslintrc.js b/.eslintrc.js
index e17201ac0..71f801a84 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -25,5 +25,17 @@ module.exports = {
         "eslint-plugin/test-case-shorthand-strings": "error",
         "rulesdir/multiline-comment-style": "error",
         "rulesdir/no-useless-catch": "error"
-    }
+    },
+    overrides: [
+        {
+            files: ["tests/**/*"],
+            env: { mocha: true },
+            rules: {
+                "no-restricted-syntax": ["error", {
+                    selector: "CallExpression[callee.object.name='assert'][callee.property.name='doesNotThrow']",
+                    message: "`assert.doesNotThrow()` should be replaced with a comment next to the code."
+                }]
+            }
+        }
+    ]
 };
diff --git a/docs/rules/no-irregular-whitespace.md b/docs/rules/no-irregular-whitespace.md
index e3f65159a..a1c854d1e 100644
--- a/docs/rules/no-irregular-whitespace.md
+++ b/docs/rules/no-irregular-whitespace.md
@@ -4,6 +4,8 @@ Invalid or irregular whitespace causes issues with ECMAScript 5 parsers and also
 
 Various whitespace characters can be inputted by programmers by mistake for example from copying or keyboard shortcuts. Pressing Alt + Space on macOS adds in a non breaking space character for example.
 
+A simple fix for this problem could be to rewrite the offending line from scratch. This might also be a problem introduced by the text editor: if rewriting the line does not fix it, try using a different editor.
+
 Known issues these spaces cause:
 
 * Zero Width Space
diff --git a/lib/code-path-analysis/code-path-analyzer.js b/lib/code-path-analysis/code-path-analyzer.js
index 80b73faa3..f4a7fee7f 100644
--- a/lib/code-path-analysis/code-path-analyzer.js
+++ b/lib/code-path-analysis/code-path-analyzer.js
@@ -60,7 +60,7 @@ function isForkingByTrueOrFalse(node) {
             return parent.test === node;
 
         case "LogicalExpression":
-            return true;
+            return isHandledLogicalOperator(parent.operator);
 
         default:
             return false;


Test patch:
diff --git a/tests/.eslintrc.yml b/tests/.eslintrc.yml
deleted file mode 100644
index f2531eaa4..000000000
--- a/tests/.eslintrc.yml
+++ /dev/null
@@ -1,8 +0,0 @@
-env:
-  mocha: true
-
-rules:
-  no-restricted-syntax:
-    - error
-    - selector: "CallExpression[callee.object.name='assert'][callee.property.name='doesNotThrow']"
-      message: "`assert.doesNotThrow()` should be replaced with a comment next to the code."
diff --git a/tests/fixtures/parsers/unknown-operators/unknown-logical-operator-nested.js b/tests/fixtures/parsers/unknown-operators/unknown-logical-operator-nested.js
new file mode 100644
index 000000000..248480710
--- /dev/null
+++ b/tests/fixtures/parsers/unknown-operators/unknown-logical-operator-nested.js
@@ -0,0 +1,227 @@
+/**
+ * The output AST has been modified to represent an unknown operator
+ * (replaced ?? with %%)
+ *
+ * Parser: babel-eslint v8.2.3
+ * (with this fix for tokens: https://github.com/babel/babel-eslint/pull/632)
+ * Source code:
+ * foo && bar ?? baz
+ */
+
+exports.parse = () => ({
+    type: "Program",
+    start: 0,
+    end: 17,
+    loc: {
+        start: {
+            line: 1,
+            column: 0
+        },
+        end: {
+            line: 1,
+            column: 17
+        }
+    },
+    range: [0, 17],
+    comments: [],
+    tokens: [
+        {
+            type: "Identifier",
+            value: "foo",
+            start: 0,
+            end: 3,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 0
+                },
+                end: {
+                    line: 1,
+                    column: 3
+                }
+            },
+            range: [0, 3]
+        },
+        {
+            type: "Punctuator",
+            value: "&&",
+            start: 4,
+            end: 6,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 4
+                },
+                end: {
+                    line: 1,
+                    column: 6
+                }
+            },
+            range: [4, 6]
+        },
+        {
+            type: "Identifier",
+            value: "bar",
+            start: 7,
+            end: 10,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 7
+                },
+                end: {
+                    line: 1,
+                    column: 10
+                }
+            },
+            range: [7, 10]
+        },
+        {
+            type: "Punctuator",
+            value: "%%",
+            start: 11,
+            end: 13,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 11
+                },
+                end: {
+                    line: 1,
+                    column: 13
+                }
+            },
+            range: [11, 13]
+        },
+        {
+            type: "Identifier",
+            value: "baz",
+            start: 14,
+            end: 17,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 14
+                },
+                end: {
+                    line: 1,
+                    column: 17
+                }
+            },
+            range: [14, 17]
+        }
+    ],
+    sourceType: "module",
+    body: [
+        {
+            type: "ExpressionStatement",
+            start: 0,
+            end: 17,
+            loc: {
+                start: {
+                    line: 1,
+                    column: 0
+                },
+                end: {
+                    line: 1,
+                    column: 17
+                }
+            },
+            range: [0, 17],
+            expression: {
+                type: "LogicalExpression",
+                start: 0,
+                end: 17,
+                loc: {
+                    start: {
+                        line: 1,
+                        column: 0
+                    },
+                    end: {
+                        line: 1,
+                        column: 17
+                    }
+                },
+                range: [0, 17],
+                left: {
+                    type: "LogicalExpression",
+                    start: 0,
+                    end: 10,
+                    loc: {
+                        start: {
+                            line: 1,
+                            column: 0
+                        },
+                        end: {
+                            line: 1,
+                            column: 10
+                        }
+                    },
+                    range: [0, 10],
+                    left: {
+                        type: "Identifier",
+                        start: 0,
+                        end: 3,
+                        loc: {
+                            start: {
+                                line: 1,
+                                column: 0
+                            },
+                            end: {
+                                line: 1,
+                                column: 3
+                            },
+                            identifierName: "foo"
+                        },
+                        range: [0, 3],
+                        name: "foo",
+                        _babelType: "Identifier"
+                    },
+                    operator: "&&",
+                    right: {
+                        type: "Identifier",
+                        start: 7,
+                        end: 10,
+                        loc: {
+                            start: {
+                                line: 1,
+                                column: 7
+                            },
+                            end: {
+                                line: 1,
+                                column: 10
+                            },
+                            identifierName: "bar"
+                        },
+                        range: [7, 10],
+                        name: "bar",
+                        _babelType: "Identifier"
+                    },
+                    _babelType: "LogicalExpression"
+                },
+                operator: "%%",
+                right: {
+                    type: "Identifier",
+                    start: 14,
+                    end: 17,
+                    loc: {
+                        start: {
+                            line: 1,
+                            column: 14
+                        },
+                        end: {
+                            line: 1,
+                            column: 17
+                        },
+                        identifierName: "baz"
+                    },
+                    range: [14, 17],
+                    name: "baz",
+                    _babelType: "Identifier"
+                },
+                _babelType: "LogicalExpression"
+            },
+            _babelType: "ExpressionStatement"
+        }
+    ]
+});
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index 251057021..e06f3cd9a 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -4480,6 +4480,16 @@ describe("Linter", () => {
                 assert.strictEqual(messages.length, 0);
             });
 
+            it("should not throw or report errors when the custom parser returns nested unrecognized operators (https://github.com/eslint/eslint/issues/10560)", () => {
+                const code = "foo && bar %% baz";
+                const parser = path.join(parserFixtures, "unknown-operators", "unknown-logical-operator-nested.js");
+
+                // This shouldn't throw
+                const messages = linter.verify(code, { parser }, filename, true);
+
+                assert.strictEqual(messages.length, 0);
+            });
+
             it("should strip leading line: prefix from parser error", () => {
                 const parser = path.join(parserFixtures, "line-error.js");
                 const messages = linter.verify(";", { parser }, "filename");
diff --git a/tests/lib/testers/no-test-runners.js b/tests/lib/testers/no-test-runners.js
index a14ac3bab..eac1da3fd 100644
--- a/tests/lib/testers/no-test-runners.js
+++ b/tests/lib/testers/no-test-runners.js
@@ -4,7 +4,6 @@
  */
 "use strict";
 
-/* global describe, it */
 /* eslint-disable no-global-assign*/
 const assert = require("assert");
 const RuleTester = require("../../../lib/testers/rule-tester");
diff --git a/tests/lib/testers/rule-tester.js b/tests/lib/testers/rule-tester.js
index 0a51dc26b..4265ab1b9 100644
--- a/tests/lib/testers/rule-tester.js
+++ b/tests/lib/testers/rule-tester.js
@@ -4,8 +4,6 @@
  */
 "use strict";
 
-/* global describe, it */
-
 //------------------------------------------------------------------------------
 // Requirements
 //------------------------------------------------------------------------------