Repository: eslint/eslint (JavaScript)

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)
```


Discussion:
Thanks for the issue, @rubennorte!

I think just like the last issue about supporting unknown operators, we need to treat this as a bug.

I've left a comment on your PR. Let me know there if you have any questions. Thanks!



Patch:
diff --git a/lib/code-path-analysis/code-path-analyzer.js b/lib/code-path-analysis/code-path-analyzer.js
index 80b73faa3cb1..f4a7fee7fcd8 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/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 000000000000..248480710b7f
--- /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 251057021af4..e06f3cd9a5ab 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");