Repository: eslint/eslint
Pull request #12085 (2019-08-10 00:51:45)

Fix: prefer-template autofix produces syntax error with octal escapes
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[X] Bug fix

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->
**Tell us about your environment**

* **ESLint Version:** 6.1.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.**

```js
/*eslint prefer-template: "error"*/

"\0 \1" + foo
```

```js
/*eslint prefer-template: "error"*/

"\08" + foo
```

**What did you expect to happen?**

Not a syntax error in autofix.

**What actually happened? Please include the actual, raw output from ESLint.**

```js
/*eslint prefer-template: "error"*/

`\0 \1${  foo}`
```

`Parsing error: Octal literal in template string`

```js
/*eslint prefer-template: "error"*/

`\08${  foo}`
```

`Parsing error: Octal literal in template string`
<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (https://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**

Made a new regex in `astUtils`, which fixes two bugs:

* Octal escapes after a valid `\0` were missed (e.g. `"\0 \1"`). This bug was probably inherited from `no-octal-escape` (fixed there in #12079)
* Whether or not `"\08"` and `"\09"` are octal escapes (#12080) these two de facto produce parsing errors in template literals.

**Is there anything you'd like reviewers to focus on?**

* Regex and its performance.

I'll also fix the `quotes` rule in another PR when this gets merged.


Patch:
diff --git a/README.md b/README.md
index 02c875ec6..061eafefc 100644
--- a/README.md
+++ b/README.md
@@ -59,8 +59,6 @@ After that, you can run ESLint on any file or directory like this:
 $ ./node_modules/.bin/eslint yourfile.js
 ```
 
-It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, any plugins or shareable configs that you use must be installed locally in either case.
-
 ## <a name="configuration"></a>Configuration
 
 After running `eslint --init`, you'll have a `.eslintrc` file in your directory. In it, you'll see some rules configured like this:
diff --git a/docs/developer-guide/scope-manager-interface.md b/docs/developer-guide/scope-manager-interface.md
index 08986caa2..3ee5b23d3 100644
--- a/docs/developer-guide/scope-manager-interface.md
+++ b/docs/developer-guide/scope-manager-interface.md
@@ -28,7 +28,7 @@ This document was written based on the implementation of [eslint-scope](https://
     * `node` (`ASTNode`) ... An AST node to get their scope.
     * `inner` (`boolean`) ... If the node has multiple scope, this returns the outermost scope normally. If `inner` is `true` then this returns the innermost scope. Default is `false`.
 * **Return type:** `Scope | null`
-* **Description:** Get the scope of a given AST node. The gotten scope's `block` property is the node. This method never returns `function-expression-name` scope and `TDZ` scope. If the node does not have their scope, this returns `null`.
+* **Description:** Get the scope of a given AST node. The gotten scope's `block` property is the node. This method never returns `function-expression-name` scope. If the node does not have their scope, this returns `null`.
 
 #### getDeclaredVariables(node)
 
@@ -77,7 +77,7 @@ Those members are defined but not used in ESLint.
 #### type
 
 * **Type:** `string`
-* **Description:** The type of this scope. This is one of `"block"`, `"catch"`, `"class"`, `"for"`, `"function"`, `"function-expression-name"`, `"global"`, `"module"`, `"switch"`, `"with"`, `"TDZ"`
+* **Description:** The type of this scope. This is one of `"block"`, `"catch"`, `"class"`, `"for"`, `"function"`, `"function-expression-name"`, `"global"`, `"module"`, `"switch"`, `"with"`
 
 #### isStrict
 
@@ -334,7 +334,7 @@ Those members are defined but not used in ESLint.
 #### type
 
 * **Type:** `string`
-* **Description:** The type of this definition. One of `"CatchClause"`, `"ClassName"`, `"FunctionName"`, `"ImplicitGlobalVariable"`, `"ImportBinding"`, `"Parameter"`, `"TDZ"`, and `"Variable"`.
+* **Description:** The type of this definition. One of `"CatchClause"`, `"ClassName"`, `"FunctionName"`, `"ImplicitGlobalVariable"`, `"ImportBinding"`, `"Parameter"`, and `"Variable"`.
 
 #### name
 
@@ -354,7 +354,6 @@ Those members are defined but not used in ESLint.
 | `"ImplicitGlobalVariable"` | `Program`
 | `"ImportBinding"`          | `ImportSpecifier`, `ImportDefaultSpecifier`, or `ImportNamespaceSpecifier`
 | `"Parameter"`              | `FunctionDeclaration`, `FunctionExpression`, or `ArrowFunctionExpression`
-| `"TDZ"`                    | ?
 | `"Variable"`               | `VariableDeclarator`
 
 #### parent
@@ -370,7 +369,6 @@ Those members are defined but not used in ESLint.
 | `"ImplicitGlobalVariable"` | `null`
 | `"ImportBinding"`          | `ImportDeclaration`
 | `"Parameter"`              | `null`
-| `"TDZ"`                    | `null`
 | `"Variable"`               | `VariableDeclaration`
 
 ### Deprecated members
diff --git a/docs/rules/no-mixed-operators.md b/docs/rules/no-mixed-operators.md
index 3c0bb2fab..3206d18fb 100644
--- a/docs/rules/no-mixed-operators.md
+++ b/docs/rules/no-mixed-operators.md
@@ -5,6 +5,8 @@ This rule warns when different operators are used consecutively without parenthe
 
 ```js
 var foo = a && b || c || d;    /*BAD: Unexpected mix of '&&' and '||'.*/
+var foo = a && b ? c : d;      /*BAD: Unexpected mix of '&&' and '?:'.*/
+var foo = (a && b) ? c : d;    /*GOOD*/
 var foo = (a && b) || c || d;  /*GOOD*/
 var foo = a && (b || c || d);  /*GOOD*/
 ```
@@ -23,10 +25,21 @@ will generate
 1:18  Unexpected mix of '&&' and '||'. (no-mixed-operators)
 ```
 
+```js
+var foo = a && b ? c : d;
+```
+
+will generate
+
+```sh
+1:13  Unexpected mix of '&&' and '?:'. (no-mixed-operators)
+1:18  Unexpected mix of '&&' and '?:'. (no-mixed-operators)
+```
+
 
 ## Rule Details
 
-This rule checks `BinaryExpression` and `LogicalExpression`.
+This rule checks `BinaryExpression`, `LogicalExpression` and `ConditionalExpression`.
 
 This rule may conflict with [no-extra-parens](no-extra-parens.md) rule.
 If you use both this and [no-extra-parens](no-extra-parens.md) rule together, you need to use the `nestedBinaryExpressions` option of [no-extra-parens](no-extra-parens.md) rule.
@@ -75,7 +88,8 @@ var foo = (a + b) * c;
 
 This rule has 2 options.
 
-* `groups` (`string[][]`) - specifies operator groups to be checked. The `groups` option is a list of groups, and a group is a list of binary operators. Default operator groups are defined as arithmetic, bitwise, comparison, logical, and relational operators.
+* `groups` (`string[][]`) - specifies operator groups to be checked. The `groups` option is a list of groups, and a group is a list of binary operators. Default operator groups are defined as arithmetic, bitwise, comparison, logical, and relational operators. Note: Ternary operator(?:) can be part of any group and by default is allowed to be mixed with other operators.
+
 * `allowSamePrecedence` (`boolean`) - specifies whether to allow mixed operators if they are of equal precedence. Default is `true`.
 
 ### groups
@@ -87,6 +101,7 @@ The following operators can be used in `groups` option:
 * Comparison Operators: `"=="`, `"!="`, `"==="`, `"!=="`, `">"`, `">="`, `"<"`, `"<="`
 * Logical Operators: `"&&"`, `"||"`
 * Relational Operators: `"in"`, `"instanceof"`
+* Ternary Operator: `?:`
 
 Now, consider the following group configuration: `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}`.
 There are 2 groups specified in this configuration: bitwise operators and logical operators.
@@ -102,6 +117,12 @@ var foo = a && b < 0 || c > 0 || d + 1 === 0;
 var foo = a & b | c;
 ```
 
+```js
+/*eslint no-mixed-operators: ["error", {"groups": [["&&", "||", "?:"]]}]*/
+
+var foo = a || b ? c : d;
+```
+
 Examples of **correct** code for this rule with `{"groups": [["&", "|", "^", "~", "<<", ">>", ">>>"], ["&&", "||"]]}` option:
 
 ```js
@@ -118,6 +139,13 @@ var foo = a + (b * c);
 var foo = (a + b) * c;
 ```
 
+```js
+/*eslint no-mixed-operators: ["error", {"groups": [["&&", "||", "?:"]]}]*/
+
+var foo = (a || b) ? c : d;
+var foo = a || (b ? c : d);
+```
+
 ### allowSamePrecedence
 
 Examples of **correct** code for this rule with `{"allowSamePrecedence": true}` option:
diff --git a/docs/user-guide/getting-started.md b/docs/user-guide/getting-started.md
index 45633f618..1bebf7d69 100644
--- a/docs/user-guide/getting-started.md
+++ b/docs/user-guide/getting-started.md
@@ -33,7 +33,7 @@ After that, you can run ESLint on any file or directory like this:
 $ ./node_modules/.bin/eslint yourfile.js
 ```
 
-It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, any plugins or shareable configs that you use must be installed locally in either case.
+It is also possible to install ESLint globally rather than locally (using `npm install eslint --global`). However, this is not recommended, and any plugins or shareable configs that you use must be installed locally in either case.
 
 ## Configuration
 
diff --git a/lib/init/npm-utils.js b/lib/init/npm-utils.js
index 26e78406f..3a680aae9 100644
--- a/lib/init/npm-utils.js
+++ b/lib/init/npm-utils.js
@@ -135,7 +135,7 @@ function check(packages, opt) {
  * Check whether node modules are included in the dependencies of a project's
  * package.json.
  *
- * Convienience wrapper around check().
+ * Convenience wrapper around check().
  *
  * @param   {string[]} packages  Array of node modules to check.
  * @param   {string}   rootDir   The directory contianing a package.json
@@ -150,7 +150,7 @@ function checkDeps(packages, rootDir) {
  * Check whether node modules are included in the devDependencies of a project's
  * package.json.
  *
- * Convienience wrapper around check().
+ * Convenience wrapper around check().
  *
  * @param   {string[]} packages  Array of node modules to check.
  * @returns {Object}             An object whose keys are the module names
diff --git a/lib/rules/class-methods-use-this.js b/lib/rules/class-methods-use-this.js
index 0eb1da87f..4bf17090a 100644
--- a/lib/rules/class-methods-use-this.js
+++ b/lib/rules/class-methods-use-this.js
@@ -5,6 +5,12 @@
 
 "use strict";
 
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -34,7 +40,7 @@ module.exports = {
         }],
 
         messages: {
-            missingThis: "Expected 'this' to be used by class method '{{name}}'."
+            missingThis: "Expected 'this' to be used by class {{name}}."
         }
     },
     create(context) {
@@ -70,7 +76,8 @@ module.exports = {
          * @private
          */
         function isIncludedInstanceMethod(node) {
-            return isInstanceMethod(node) && !exceptMethods.has(node.key.name);
+            return isInstanceMethod(node) &&
+                (node.computed || !exceptMethods.has(node.key.name));
         }
 
         /**
@@ -89,7 +96,7 @@ module.exports = {
                     node,
                     messageId: "missingThis",
                     data: {
-                        name: node.parent.key.name
+                        name: astUtils.getFunctionNameWithKind(node)
                     }
                 });
             }
diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js
index 61184ddcd..2e8fff8b9 100644
--- a/lib/rules/dot-notation.js
+++ b/lib/rules/dot-notation.js
@@ -9,13 +9,16 @@
 //------------------------------------------------------------------------------
 
 const astUtils = require("./utils/ast-utils");
+const keywords = require("./utils/keywords");
 
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
 
 const validIdentifier = /^[a-zA-Z_$][a-zA-Z0-9_$]*$/u;
-const keywords = require("./utils/keywords");
+
+// `null` literal must be handled separately.
+const literalTypesToCheck = new Set(["string", "boolean"]);
 
 module.exports = {
     meta: {
@@ -115,7 +118,8 @@ module.exports = {
             MemberExpression(node) {
                 if (
                     node.computed &&
-                    node.property.type === "Literal"
+                    node.property.type === "Literal" &&
+                    (literalTypesToCheck.has(typeof node.property.value) || astUtils.isNullLiteral(node.property))
                 ) {
                     checkComputedProperty(node, node.property.value);
                 }
diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js
index d0751b4a2..1b7f69cfa 100644
--- a/lib/rules/no-dupe-keys.js
+++ b/lib/rules/no-dupe-keys.js
@@ -120,7 +120,7 @@ module.exports = {
                 }
 
                 // Skip if the name is not static.
-                if (!name) {
+                if (name === null) {
                     return;
                 }
 
diff --git a/lib/rules/no-duplicate-case.js b/lib/rules/no-duplicate-case.js
index 93c8548f9..c8a0fa9da 100644
--- a/lib/rules/no-duplicate-case.js
+++ b/lib/rules/no-duplicate-case.js
@@ -33,17 +33,19 @@ module.exports = {
 
         return {
             SwitchStatement(node) {
-                const mapping = {};
+                const previousKeys = new Set();
 
-                node.cases.forEach(switchCase => {
-                    const key = sourceCode.getText(switchCase.test);
+                for (const switchCase of node.cases) {
+                    if (switchCase.test) {
+                        const key = sourceCode.getText(switchCase.test);
 
-                    if (mapping[key]) {
-                        context.report({ node: switchCase, messageId: "unexpected" });
-                    } else {
-                        mapping[key] = switchCase;
+                        if (previousKeys.has(key)) {
+                            context.report({ node: switchCase, messageId: "unexpected" });
+                        } else {
+                            previousKeys.add(key);
+                        }
                     }
-                });
+                }
             }
         };
     }
diff --git a/lib/rules/no-extra-bind.js b/lib/rules/no-extra-bind.js
index 5380cf217..cc0b1f843 100644
--- a/lib/rules/no-extra-bind.js
+++ b/lib/rules/no-extra-bind.js
@@ -98,6 +98,7 @@ module.exports = {
                 grandparent.type === "CallExpression" &&
                 grandparent.callee === parent &&
                 grandparent.arguments.length === 1 &&
+                grandparent.arguments[0].type !== "SpreadElement" &&
                 parent.type === "MemberExpression" &&
                 parent.object === node &&
                 astUtils.getStaticPropertyName(parent) === "bind"
diff --git a/lib/rules/no-extra-boolean-cast.js b/lib/rules/no-extra-boolean-cast.js
index 8dd526477..9ae9b5be6 100644
--- a/lib/rules/no-extra-boolean-cast.js
+++ b/lib/rules/no-extra-boolean-cast.js
@@ -50,8 +50,8 @@ module.exports = {
         /**
          * Check if a node is in a context where its value would be coerced to a boolean at runtime.
          *
-         * @param {Object} node The node
-         * @param {Object} parent Its parent
+         * @param {ASTNode} node The node
+         * @param {ASTNode} parent Its parent
          * @returns {boolean} If it is in a boolean context
          */
         function isInBooleanContext(node, parent) {
@@ -65,6 +65,15 @@ module.exports = {
             );
         }
 
+        /**
+         * Check if a node has comments inside.
+         *
+         * @param {ASTNode} node The node to check.
+         * @returns {boolean} `true` if it has comments inside.
+         */
+        function hasCommentsInside(node) {
+            return Boolean(sourceCode.getCommentsInside(node).length);
+        }
 
         return {
             UnaryExpression(node) {
@@ -89,7 +98,12 @@ module.exports = {
                     context.report({
                         node,
                         messageId: "unexpectedNegation",
-                        fix: fixer => fixer.replaceText(parent, sourceCode.getText(node.argument))
+                        fix: fixer => {
+                            if (hasCommentsInside(parent)) {
+                                return null;
+                            }
+                            return fixer.replaceText(parent, sourceCode.getText(node.argument));
+                        }
                     });
                 }
             },
@@ -106,10 +120,35 @@ module.exports = {
                         messageId: "unexpectedCall",
                         fix: fixer => {
                             if (!node.arguments.length) {
-                                return fixer.replaceText(parent, "true");
+                                if (parent.type === "UnaryExpression" && parent.operator === "!") {
+
+                                    // !Boolean() -> true
+
+                                    if (hasCommentsInside(parent)) {
+                                        return null;
+                                    }
+
+                                    const replacement = "true";
+                                    let prefix = "";
+                                    const tokenBefore = sourceCode.getTokenBefore(parent);
+
+                                    if (tokenBefore && tokenBefore.range[1] === parent.range[0] &&
+                                            !astUtils.canTokensBeAdjacent(tokenBefore, replacement)) {
+                                        prefix = " ";
+                                    }
+
+                                    return fixer.replaceText(parent, prefix + replacement);
+                                }
+
+                                // Boolean() -> false
+                                if (hasCommentsInside(node)) {
+                                    return null;
+                                }
+                                return fixer.replaceText(node, "false");
                             }
 
-                            if (node.arguments.length > 1 || node.arguments[0].type === "SpreadElement") {
+                            if (node.arguments.length > 1 || node.arguments[0].type === "SpreadElement" ||
+                                    hasCommentsInside(node)) {
                                 return null;
                             }
 
diff --git a/lib/rules/no-extra-parens.js b/lib/rules/no-extra-parens.js
index c9278f2d0..5e41d7468 100644
--- a/lib/rules/no-extra-parens.js
+++ b/lib/rules/no-extra-parens.js
@@ -980,7 +980,21 @@ module.exports = {
 
             SpreadElement: checkSpreadOperator,
             SpreadProperty: checkSpreadOperator,
-            ExperimentalSpreadProperty: checkSpreadOperator
+            ExperimentalSpreadProperty: checkSpreadOperator,
+
+            TemplateLiteral(node) {
+                node.expressions
+                    .filter(e => e && hasExcessParens(e))
+                    .forEach(report);
+            },
+
+            AssignmentPattern(node) {
+                const { right } = node;
+
+                if (right && hasExcessParens(right) && precedence(right) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
+                    report(right);
+                }
+            }
         };
 
     }
diff --git a/lib/rules/no-mixed-operators.js b/lib/rules/no-mixed-operators.js
index 21e1d95c6..8d1c7a6af 100644
--- a/lib/rules/no-mixed-operators.js
+++ b/lib/rules/no-mixed-operators.js
@@ -20,12 +20,14 @@ const BITWISE_OPERATORS = ["&", "|", "^", "~", "<<", ">>", ">>>"];
 const COMPARISON_OPERATORS = ["==", "!=", "===", "!==", ">", ">=", "<", "<="];
 const LOGICAL_OPERATORS = ["&&", "||"];
 const RELATIONAL_OPERATORS = ["in", "instanceof"];
+const TERNARY_OPERATOR = ["?:"];
 const ALL_OPERATORS = [].concat(
     ARITHMETIC_OPERATORS,
     BITWISE_OPERATORS,
     COMPARISON_OPERATORS,
     LOGICAL_OPERATORS,
-    RELATIONAL_OPERATORS
+    RELATIONAL_OPERATORS,
+    TERNARY_OPERATOR
 );
 const DEFAULT_GROUPS = [
     ARITHMETIC_OPERATORS,
@@ -34,7 +36,7 @@ const DEFAULT_GROUPS = [
     LOGICAL_OPERATORS,
     RELATIONAL_OPERATORS
 ];
-const TARGET_NODE_TYPE = /^(?:Binary|Logical)Expression$/u;
+const TARGET_NODE_TYPE = /^(?:Binary|Logical|Conditional)Expression$/u;
 
 /**
  * Normalizes options.
@@ -65,6 +67,18 @@ function includesBothInAGroup(groups, left, right) {
     return groups.some(group => group.indexOf(left) !== -1 && group.indexOf(right) !== -1);
 }
 
+/**
+ * Checks whether the given node is a conditional expression and returns the test node else the left node.
+ *
+ * @param {ASTNode} node - A node which can be a BinaryExpression or a LogicalExpression node.
+ * This parent node can be BinaryExpression, LogicalExpression
+ *      , or a ConditionalExpression node
+ * @returns {ASTNode} node the appropriate node(left or test).
+ */
+function getChildNode(node) {
+    return node.type === "ConditionalExpression" ? node.test : node.left;
+}
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -121,7 +135,7 @@ module.exports = {
             const b = node.parent;
 
             return (
-                !includesBothInAGroup(options.groups, a.operator, b.operator) ||
+                !includesBothInAGroup(options.groups, a.operator, b.type === "ConditionalExpression" ? "?:" : b.operator) ||
                 (
                     options.allowSamePrecedence &&
                     astUtils.getPrecedence(a) === astUtils.getPrecedence(b)
@@ -139,12 +153,25 @@ module.exports = {
          * @returns {boolean} `true` if the node was mixed.
          */
         function isMixedWithParent(node) {
+
             return (
                 node.operator !== node.parent.operator &&
                 !astUtils.isParenthesised(sourceCode, node)
             );
         }
 
+        /**
+         * Checks whether the operator of a given node is mixed with a
+         * conditional expression.
+         *
+         * @param {ASTNode} node - A node to check. This is a conditional
+         *      expression node
+         * @returns {boolean} `true` if the node was mixed.
+         */
+        function isMixedWithConditionalParent(node) {
+            return !astUtils.isParenthesised(sourceCode, node) && !astUtils.isParenthesised(sourceCode, node.test);
+        }
+
         /**
          * Gets the operator token of a given node.
          *
@@ -153,7 +180,7 @@ module.exports = {
          * @returns {Token} The operator token of the node.
          */
         function getOperatorToken(node) {
-            return sourceCode.getTokenAfter(node.left, astUtils.isNotClosingParenToken);
+            return sourceCode.getTokenAfter(getChildNode(node), astUtils.isNotClosingParenToken);
         }
 
         /**
@@ -167,13 +194,13 @@ module.exports = {
          */
         function reportBothOperators(node) {
             const parent = node.parent;
-            const left = (parent.left === node) ? node : parent;
-            const right = (parent.left !== node) ? node : parent;
+            const left = (getChildNode(parent) === node) ? node : parent;
+            const right = (getChildNode(parent) !== node) ? node : parent;
             const message =
                 "Unexpected mix of '{{leftOperator}}' and '{{rightOperator}}'.";
             const data = {
-                leftOperator: left.operator,
-                rightOperator: right.operator
+                leftOperator: left.operator || "?:",
+                rightOperator: right.operator || "?:"
             };
 
             context.report({
@@ -198,17 +225,25 @@ module.exports = {
          * @returns {void}
          */
         function check(node) {
-            if (TARGET_NODE_TYPE.test(node.parent.type) &&
-                isMixedWithParent(node) &&
-                !shouldIgnore(node)
-            ) {
-                reportBothOperators(node);
+            if (TARGET_NODE_TYPE.test(node.parent.type)) {
+                if (node.parent.type === "ConditionalExpression" && !shouldIgnore(node) && isMixedWithConditionalParent(node.parent)) {
+                    reportBothOperators(node);
+                } else {
+                    if (TARGET_NODE_TYPE.test(node.parent.type) &&
+                        isMixedWithParent(node) &&
+                        !shouldIgnore(node)
+                    ) {
+                        reportBothOperators(node);
+                    }
+                }
             }
+
         }
 
         return {
             BinaryExpression: check,
             LogicalExpression: check
+
         };
     }
 };
diff --git a/lib/rules/no-restricted-syntax.js b/lib/rules/no-restricted-syntax.js
index 74eea1478..41aa9fa39 100644
--- a/lib/rules/no-restricted-syntax.js
+++ b/lib/rules/no-restricted-syntax.js
@@ -21,7 +21,7 @@ module.exports = {
 
         schema: {
             type: "array",
-            items: [{
+            items: {
                 oneOf: [
                     {
                         type: "string"
@@ -36,7 +36,7 @@ module.exports = {
                         additionalProperties: false
                     }
                 ]
-            }],
+            },
             uniqueItems: true,
             minItems: 0
         }
diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js
index 48df3aa3c..8094de57c 100644
--- a/lib/rules/no-unused-vars.js
+++ b/lib/rules/no-unused-vars.js
@@ -507,7 +507,7 @@ module.exports = {
             const childScopes = scope.childScopes;
             let i, l;
 
-            if (scope.type !== "TDZ" && (scope.type !== "global" || config.vars === "all")) {
+            if (scope.type !== "global" || config.vars === "all") {
                 for (i = 0, l = variables.length; i < l; ++i) {
                     const variable = variables[i];
 
diff --git a/lib/rules/prefer-template.js b/lib/rules/prefer-template.js
index f73ac34f8..a2507d452 100644
--- a/lib/rules/prefer-template.js
+++ b/lib/rules/prefer-template.js
@@ -52,16 +52,7 @@ function isOctalEscapeSequence(node) {
         return false;
     }
 
-    const match = node.raw.match(/^([^\\]|\\[^0-7])*\\([0-7]{1,3})/u);
-
-    if (match) {
-
-        // \0 is actually not considered an octal
-        if (match[2] !== "0" || typeof match[3] !== "undefined") {
-            return true;
-        }
-    }
-    return false;
+    return astUtils.hasOctalEscapeSequence(node.raw);
 }
 
 /**
diff --git a/lib/rules/sort-keys.js b/lib/rules/sort-keys.js
index beda42f1e..c314d4a63 100644
--- a/lib/rules/sort-keys.js
+++ b/lib/rules/sort-keys.js
@@ -29,7 +29,13 @@ const astUtils = require("./utils/ast-utils"),
  * @private
  */
 function getPropertyName(node) {
-    return astUtils.getStaticPropertyName(node) || node.key.name || null;
+    const staticName = astUtils.getStaticPropertyName(node);
+
+    if (staticName !== null) {
+        return staticName;
+    }
+
+    return node.key.name || null;
 }
 
 /**
@@ -151,9 +157,11 @@ module.exports = {
                 const numKeys = stack.numKeys;
                 const thisName = getPropertyName(node);
 
-                stack.prevName = thisName || prevName;
+                if (thisName !== null) {
+                    stack.prevName = thisName;
+                }
 
-                if (!prevName || !thisName || numKeys < minKeys) {
+                if (prevName === null || thisName === null || numKeys < minKeys) {
                     return;
                 }
 
diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js
index 78ae7bc01..7c17fc2ec 100644
--- a/lib/rules/utils/ast-utils.js
+++ b/lib/rules/utils/ast-utils.js
@@ -38,6 +38,7 @@ const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
 const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]);
 
 const DECIMAL_INTEGER_PATTERN = /^(0|[1-9]\d*)$/u;
+const OCTAL_ESCAPE_PATTERN = /^(?:[^\\]|\\[^0-7]|\\0(?![0-9]))*\\(?:[1-7]|0[0-9])/u;
 
 /**
  * Checks reference if is non initializer and writable.
@@ -1101,7 +1102,7 @@ module.exports = {
         } else {
             const name = module.exports.getStaticPropertyName(parent);
 
-            if (name) {
+            if (name !== null) {
                 tokens.push(`'${name}'`);
             }
         }
@@ -1373,5 +1374,20 @@ module.exports = {
             "/*".length +
             (match ? match.index + 1 : 0)
         );
+    },
+
+    /**
+     * Determines whether the given raw string contains an octal escape sequence.
+     *
+     * "\1", "\2" ... "\7"
+     * "\00", "\01" ... "\09"
+     *
+     * "\0", when not followed by a digit, is not an octal escape sequence.
+     *
+     * @param {string} rawString A string in its raw representation.
+     * @returns {boolean} `true` if the string contains at least one octal escape sequence.
+     */
+    hasOctalEscapeSequence(rawString) {
+        return OCTAL_ESCAPE_PATTERN.test(rawString);
     }
 };
diff --git a/lib/rules/yoda.js b/lib/rules/yoda.js
index 43783c193..89c4a8afd 100644
--- a/lib/rules/yoda.js
+++ b/lib/rules/yoda.js
@@ -119,7 +119,7 @@ function same(a, b) {
             const nameA = astUtils.getStaticPropertyName(a);
 
             // x.y = x["y"]
-            if (nameA) {
+            if (nameA !== null) {
                 return (
                     same(a.object, b.object) &&
                     nameA === astUtils.getStaticPropertyName(b)


Test patch:
diff --git a/tests/lib/rules/class-methods-use-this.js b/tests/lib/rules/class-methods-use-this.js
index 852124245..5238b9d68 100644
--- a/tests/lib/rules/class-methods-use-this.js
+++ b/tests/lib/rules/class-methods-use-this.js
@@ -37,49 +37,49 @@ ruleTester.run("class-methods-use-this", rule, {
             code: "class A { foo() {} }",
             parserOptions: { ecmaVersion: 6 },
             errors: [
-                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "foo" } }
+                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
             ]
         },
         {
             code: "class A { foo() {/**this**/} }",
             parserOptions: { ecmaVersion: 6 },
             errors: [
-                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "foo" } }
+                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
             ]
         },
         {
             code: "class A { foo() {var a = function () {this};} }",
             parserOptions: { ecmaVersion: 6 },
             errors: [
-                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "foo" } }
+                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
             ]
         },
         {
             code: "class A { foo() {var a = function () {var b = function(){this}};} }",
             parserOptions: { ecmaVersion: 6 },
             errors: [
-                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "foo" } }
+                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
             ]
         },
         {
             code: "class A { foo() {window.this} }",
             parserOptions: { ecmaVersion: 6 },
             errors: [
-                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "foo" } }
+                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
             ]
         },
         {
             code: "class A { foo() {that.this = 'this';} }",
             parserOptions: { ecmaVersion: 6 },
             errors: [
-                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "foo" } }
+                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
             ]
         },
         {
             code: "class A { foo() { () => undefined; } }",
             parserOptions: { ecmaVersion: 6 },
             errors: [
-                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "foo" } }
+                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
             ]
         },
         {
@@ -87,7 +87,7 @@ ruleTester.run("class-methods-use-this", rule, {
             options: [{ exceptMethods: ["bar"] }],
             parserOptions: { ecmaVersion: 6 },
             errors: [
-                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "foo" } }
+                { type: "FunctionExpression", line: 1, column: 14, messageId: "missingThis", data: { name: "method 'foo'" } }
             ]
         },
         {
@@ -95,7 +95,30 @@ ruleTester.run("class-methods-use-this", rule, {
             options: [{ exceptMethods: ["foo"] }],
             parserOptions: { ecmaVersion: 6 },
             errors: [
-                { type: "FunctionExpression", line: 1, column: 34, messageId: "missingThis", data: { name: "hasOwnProperty" } }
+                { type: "FunctionExpression", line: 1, column: 34, messageId: "missingThis", data: { name: "method 'hasOwnProperty'" } }
+            ]
+        },
+        {
+            code: "class A { [foo]() {} }",
+            options: [{ exceptMethods: ["foo"] }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { type: "FunctionExpression", line: 1, column: 16, messageId: "missingThis", data: { name: "method" } }
+            ]
+        },
+        {
+            code: "class A { foo(){} 'bar'(){} 123(){} [`baz`](){} [a](){} [f(a)](){} get quux(){} set[a](b){} *quuux(){} }",
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { message: "Expected 'this' to be used by class method 'foo'.", type: "FunctionExpression", column: 14 },
+                { message: "Expected 'this' to be used by class method 'bar'.", type: "FunctionExpression", column: 24 },
+                { message: "Expected 'this' to be used by class method '123'.", type: "FunctionExpression", column: 32 },
+                { message: "Expected 'this' to be used by class method 'baz'.", type: "FunctionExpression", column: 44 },
+                { message: "Expected 'this' to be used by class method.", type: "FunctionExpression", column: 52 },
+                { message: "Expected 'this' to be used by class method.", type: "FunctionExpression", column: 63 },
+                { message: "Expected 'this' to be used by class getter 'quux'.", type: "FunctionExpression", column: 76 },
+                { message: "Expected 'this' to be used by class setter.", type: "FunctionExpression", column: 87 },
+                { message: "Expected 'this' to be used by class generator method 'quuux'.", type: "FunctionExpression", column: 99 }
             ]
         }
     ]
diff --git a/tests/lib/rules/dot-notation.js b/tests/lib/rules/dot-notation.js
index 490a0956c..5532ea358 100644
--- a/tests/lib/rules/dot-notation.js
+++ b/tests/lib/rules/dot-notation.js
@@ -57,7 +57,8 @@ ruleTester.run("dot-notation", rule, {
         "a.null;",
         "a[undefined];",
         "a[void 0];",
-        "a[b()];"
+        "a[b()];",
+        { code: "a[/(?<zero>0)/];", parserOptions: { ecmaVersion: 2018 } }
     ],
     invalid: [
         {
@@ -82,6 +83,16 @@ ruleTester.run("dot-notation", rule, {
             output: "a.null;",
             errors: [{ messageId: "useDot", data: { key: "null" } }]
         },
+        {
+            code: "a[true];",
+            output: "a.true;",
+            errors: [{ messageId: "useDot", data: { key: "true" } }]
+        },
+        {
+            code: "a[false];",
+            output: "a.false;",
+            errors: [{ messageId: "useDot", data: { key: "false" } }]
+        },
         {
             code: "a['b'];",
             output: "a.b;",
diff --git a/tests/lib/rules/no-dupe-keys.js b/tests/lib/rules/no-dupe-keys.js
index 518f6c36c..a0a1e7a8c 100644
--- a/tests/lib/rules/no-dupe-keys.js
+++ b/tests/lib/rules/no-dupe-keys.js
@@ -22,6 +22,11 @@ ruleTester.run("no-dupe-keys", rule, {
     valid: [
         "var foo = { __proto__: 1, two: 2};",
         "var x = { foo: 1, bar: 2 };",
+        "var x = { '': 1, bar: 2 };",
+        "var x = { '': 1, ' ': 2 };",
+        { code: "var x = { '': 1, [null]: 2 };", parserOptions: { ecmaVersion: 6 } },
+        { code: "var x = { '': 1, [a]: 2 };", parserOptions: { ecmaVersion: 6 } },
+        { code: "var x = { [a]: 1, [a]: 2 };", parserOptions: { ecmaVersion: 6 } },
         "+{ get a() { }, set a(b) { } };",
         { code: "var x = { a: b, [a]: b };", parserOptions: { ecmaVersion: 6 } },
         { code: "var x = { a: b, ...c }", parserOptions: { ecmaVersion: 2018 } },
@@ -32,6 +37,8 @@ ruleTester.run("no-dupe-keys", rule, {
     invalid: [
         { code: "var x = { a: b, ['a']: b };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
         { code: "var x = { y: 1, y: 2 };", errors: [{ messageId: "unexpected", data: { name: "y" }, type: "ObjectExpression" }] },
+        { code: "var x = { '': 1, '': 2 };", errors: [{ messageId: "unexpected", data: { name: "" }, type: "ObjectExpression" }] },
+        { code: "var x = { '': 1, [``]: 2 };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "" }, type: "ObjectExpression" }] },
         { code: "var foo = { 0x1: 1, 1: 2};", errors: [{ messageId: "unexpected", data: { name: "1" }, type: "ObjectExpression" }] },
         { code: "var x = { \"z\": 1, z: 2 };", errors: [{ messageId: "unexpected", data: { name: "z" }, type: "ObjectExpression" }] },
         { code: "var foo = {\n  bar: 1,\n  bar: 1,\n}", errors: [{ messageId: "unexpected", data: { name: "bar" }, line: 3, column: 3 }] },
diff --git a/tests/lib/rules/no-duplicate-case.js b/tests/lib/rules/no-duplicate-case.js
index 21f6267a0..a6e8e0ff3 100644
--- a/tests/lib/rules/no-duplicate-case.js
+++ b/tests/lib/rules/no-duplicate-case.js
@@ -29,71 +29,105 @@ ruleTester.run("no-duplicate-case", rule, {
         "var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(a + 1).p1: break; case f(a + 2).p1: break; default: break;}",
         "var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(a == 1 ? 2 : 3).p1: break; case f(a === 1 ? 2 : 3).p1: break; default: break;}",
         "var a = 1, f1 = function() { return { p1: 1 } }, f2 = function() { return { p1: 2 } }; switch (a) {case f1().p1: break; case f2().p1: break; default: break;}",
-        "var a = [1,2]; switch(a.toString()){case ([1,2]).toString():break; case ([1]).toString():break; default:break;}"
+        "var a = [1,2]; switch(a.toString()){case ([1,2]).toString():break; case ([1]).toString():break; default:break;}",
+        "switch(a) { case a: break; } switch(a) { case a: break; }",
+        "switch(a) { case toString: break; }"
     ],
     invalid: [
         {
             code: "var a = 1; switch (a) {case 1: break; case 1: break; case 2: break; default: break;}",
             errors: [{
                 messageId: "unexpected",
-                type: "SwitchCase"
+                type: "SwitchCase",
+                column: 39
             }]
         },
         {
             code: "var a = '1'; switch (a) {case '1': break; case '1': break; case '2': break; default: break;}",
             errors: [{
                 messageId: "unexpected",
-                type: "SwitchCase"
+                type: "SwitchCase",
+                column: 43
             }]
         },
         {
             code: "var a = 1, one = 1; switch (a) {case one: break; case one: break; case 2: break; default: break;}",
             errors: [{
                 messageId: "unexpected",
-                type: "SwitchCase"
+                type: "SwitchCase",
+                column: 50
             }]
         },
         {
             code: "var a = 1, p = {p: {p1: 1, p2: 1}}; switch (a) {case p.p.p1: break; case p.p.p1: break; default: break;}",
             errors: [{
                 messageId: "unexpected",
-                type: "SwitchCase"
+                type: "SwitchCase",
+                column: 69
             }]
         },
         {
             code: "var a = 1, f = function(b) { return b ? { p1: 1 } : { p1: 2 }; }; switch (a) {case f(true).p1: break; case f(true).p1: break; default: break;}",
             errors: [{
                 messageId: "unexpected",
-                type: "SwitchCase"
+                type: "SwitchCase",
+                column: 103
             }]
         },
         {
             code: "var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(a + 1).p1: break; case f(a + 1).p1: break; default: break;}",
             errors: [{
                 messageId: "unexpected",
-                type: "SwitchCase"
+                type: "SwitchCase",
+                column: 87
             }]
         },
         {
             code: "var a = 1, f = function(s) { return { p1: s } }; switch (a) {case f(a === 1 ? 2 : 3).p1: break; case f(a === 1 ? 2 : 3).p1: break; default: break;}",
             errors: [{
                 messageId: "unexpected",
-                type: "SwitchCase"
+                type: "SwitchCase",
+                column: 97
             }]
         },
         {
             code: "var a = 1, f1 = function() { return { p1: 1 } }; switch (a) {case f1().p1: break; case f1().p1: break; default: break;}",
             errors: [{
                 messageId: "unexpected",
-                type: "SwitchCase"
+                type: "SwitchCase",
+                column: 83
             }]
         },
         {
             code: "var a = [1, 2]; switch(a.toString()){case ([1, 2]).toString():break; case ([1, 2]).toString():break; default:break;}",
             errors: [{
                 messageId: "unexpected",
-                type: "SwitchCase"
+                type: "SwitchCase",
+                column: 70
             }]
+        },
+        {
+            code: "switch (a) { case a: case a: }",
+            errors: [{
+                messageId: "unexpected",
+                type: "SwitchCase",
+                column: 22
+            }]
+        },
+        {
+            code: "switch (a) { case a: break; case b: break; case a: break; case c: break; case a: break; }",
+            errors: [
+                {
+                    messageId: "unexpected",
+                    type: "SwitchCase",
+                    column: 44
+                },
+                {
+                    messageId: "unexpected",
+                    type: "SwitchCase",
+                    column: 74
+                }
+            ]
         }
     ]
 });
diff --git a/tests/lib/rules/no-extra-bind.js b/tests/lib/rules/no-extra-bind.js
index e9c72bca6..cc2b164e8 100644
--- a/tests/lib/rules/no-extra-bind.js
+++ b/tests/lib/rules/no-extra-bind.js
@@ -21,6 +21,7 @@ const errors = [{ messageId: "unexpected", type: "CallExpression" }];
 ruleTester.run("no-extra-bind", rule, {
     valid: [
         "var a = function(b) { return b }.bind(c, d)",
+        { code: "var a = function(b) { return b }.bind(...c)", parserOptions: { ecmaVersion: 6 } },
         "var a = function() { this.b }()",
         "var a = function() { this.b }.foo()",
         "var a = f.bind(a)",
diff --git a/tests/lib/rules/no-extra-boolean-cast.js b/tests/lib/rules/no-extra-boolean-cast.js
index 4d2890e84..129329ff0 100644
--- a/tests/lib/rules/no-extra-boolean-cast.js
+++ b/tests/lib/rules/no-extra-boolean-cast.js
@@ -227,6 +227,314 @@ ruleTester.run("no-extra-boolean-cast", rule, {
                 messageId: "unexpectedCall",
                 type: "CallExpression"
             }]
+        },
+        {
+            code: "if (!Boolean()) { foo() }",
+            output: "if (true) { foo() }",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "while (!Boolean()) { foo() }",
+            output: "while (true) { foo() }",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "var foo = Boolean() ? bar() : baz()",
+            output: "var foo = false ? bar() : baz()",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "if (Boolean()) { foo() }",
+            output: "if (false) { foo() }",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "while (Boolean()) { foo() }",
+            output: "while (false) { foo() }",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+
+        // Adjacent tokens tests
+        {
+            code: "void!Boolean()",
+            output: "void true",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "void! Boolean()",
+            output: "void true",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "typeof!Boolean()",
+            output: "typeof true",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "(!Boolean())",
+            output: "(true)",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "+!Boolean()",
+            output: "+true",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "void !Boolean()",
+            output: "void true",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "void(!Boolean())",
+            output: "void(true)",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "void/**/!Boolean()",
+            output: "void/**/true",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+
+        // Comments tests
+        {
+            code: "!/**/!!foo",
+            output: "!/**/foo",
+            errors: [{
+                messageId: "unexpectedNegation",
+                type: "UnaryExpression"
+            }]
+        },
+        {
+            code: "!!/**/!foo",
+            output: null,
+            errors: [{
+                messageId: "unexpectedNegation",
+                type: "UnaryExpression"
+            }]
+        },
+        {
+            code: "!!!/**/foo",
+            output: null,
+            errors: [{
+                messageId: "unexpectedNegation",
+                type: "UnaryExpression"
+            }]
+        },
+        {
+            code: "!!!foo/**/",
+            output: "!foo/**/",
+            errors: [{
+                messageId: "unexpectedNegation",
+                type: "UnaryExpression"
+            }]
+        },
+        {
+            code: "if(!/**/!foo);",
+            output: null,
+            errors: [{
+                messageId: "unexpectedNegation",
+                type: "UnaryExpression"
+            }]
+        },
+        {
+            code: "(!!/**/foo ? 1 : 2)",
+            output: null,
+            errors: [{
+                messageId: "unexpectedNegation",
+                type: "UnaryExpression"
+            }]
+        },
+        {
+            code: "!/**/Boolean(foo)",
+            output: "!/**/foo",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "!Boolean/**/(foo)",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "!Boolean(/**/foo)",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "!Boolean(foo/**/)",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "!Boolean(foo)/**/",
+            output: "!foo/**/",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "if(Boolean/**/(foo));",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "(Boolean(foo/**/) ? 1 : 2)",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "/**/!Boolean()",
+            output: "/**/true",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "!/**/Boolean()",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "!Boolean/**/()",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "!Boolean(/**/)",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "!Boolean()/**/",
+            output: "true/**/",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "if(!/**/Boolean());",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "(!Boolean(/**/) ? 1 : 2)",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "if(/**/Boolean());",
+            output: "if(/**/false);",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "if(Boolean/**/());",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "if(Boolean(/**/));",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "if(Boolean()/**/);",
+            output: "if(false/**/);",
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
+        },
+        {
+            code: "(Boolean/**/() ? 1 : 2)",
+            output: null,
+            errors: [{
+                messageId: "unexpectedCall",
+                type: "CallExpression"
+            }]
         }
     ]
 });
diff --git a/tests/lib/rules/no-extra-parens.js b/tests/lib/rules/no-extra-parens.js
index 6c6eeb2a1..86b5ae4d6 100644
--- a/tests/lib/rules/no-extra-parens.js
+++ b/tests/lib/rules/no-extra-parens.js
@@ -1197,6 +1197,12 @@ ruleTester.run("no-extra-parens", rule, {
             "Identifier",
             1
         ),
+        invalid("let s = `${(v)}`", "let s = `${v}`", "Identifier"),
+        invalid("let s = `${(a, b)}`", "let s = `${a, b}`", "SequenceExpression"),
+        invalid("function foo(a = (b)) {}", "function foo(a = b) {}", "Identifier"),
+        invalid("const bar = (a = (b)) => a", "const bar = (a = b) => a", "Identifier"),
+        invalid("const [a = (b)] = []", "const [a = b] = []", "Identifier"),
+        invalid("const {a = (b)} = {}", "const {a = b} = {}", "Identifier"),
 
         // https://github.com/eslint/eslint/issues/11706 (also in valid[])
         {
diff --git a/tests/lib/rules/no-mixed-operators.js b/tests/lib/rules/no-mixed-operators.js
index d740b8562..4e822181f 100644
--- a/tests/lib/rules/no-mixed-operators.js
+++ b/tests/lib/rules/no-mixed-operators.js
@@ -47,7 +47,18 @@ ruleTester.run("no-mixed-operators", rule, {
         {
             code: "a * b / c",
             options: [{ allowSamePrecedence: true }]
-        }
+        },
+        {
+            code: "(a || b) ? c : d",
+            options: [{ groups: [["&&", "||", "?:"]] }]
+        },
+        {
+            code: "a || (b ? c : d)",
+            options: [{ groups: [["&&", "||", "?:"]] }]
+        },
+        "a || (b ? c : d)",
+        "(a || b) ? c : d",
+        "a || b ? c : d"
     ],
     invalid: [
         {
@@ -110,6 +121,38 @@ ruleTester.run("no-mixed-operators", rule, {
                 { column: 3, message: "Unexpected mix of '*' and '/'." },
                 { column: 7, message: "Unexpected mix of '*' and '/'." }
             ]
+        },
+        {
+            code: "a || b ? c : d",
+            options: [{ groups: [["&&", "||", "?:"]] }],
+            errors: [
+                { column: 3, message: "Unexpected mix of '||' and '?:'." },
+                { column: 8, message: "Unexpected mix of '||' and '?:'." }
+            ]
+        },
+        {
+            code: "a && b ? 1 : 2",
+            options: [{ groups: [["&&", "||", "?:"]] }],
+            errors: [
+                { column: 3, message: "Unexpected mix of '&&' and '?:'." },
+                { column: 8, message: "Unexpected mix of '&&' and '?:'." }
+            ]
+        },
+        {
+            code: "x ? a && b : 0",
+            options: [{ groups: [["&&", "||", "?:"]] }],
+            errors: [
+                { column: 3, message: "Unexpected mix of '?:' and '&&'." },
+                { column: 7, message: "Unexpected mix of '?:' and '&&'." }
+            ]
+        },
+        {
+            code: "x ? 0 : a && b",
+            options: [{ groups: [["&&", "||", "?:"]] }],
+            errors: [
+                { column: 3, message: "Unexpected mix of '?:' and '&&'." },
+                { column: 11, message: "Unexpected mix of '?:' and '&&'." }
+            ]
         }
     ]
 });
diff --git a/tests/lib/rules/prefer-template.js b/tests/lib/rules/prefer-template.js
index 9e987a813..439b261cc 100644
--- a/tests/lib/rules/prefer-template.js
+++ b/tests/lib/rules/prefer-template.js
@@ -199,6 +199,16 @@ ruleTester.run("prefer-template", rule, {
             output: null,
             errors
         },
+        {
+            code: "foo + '\\0\\1'",
+            output: null,
+            errors
+        },
+        {
+            code: "foo + '\\08'",
+            output: null,
+            errors
+        },
         {
             code: "foo + '\\\\033'",
             output: "`${foo  }\\\\033`",
diff --git a/tests/lib/rules/require-await.js b/tests/lib/rules/require-await.js
index c63a1ef5e..7b43f3266 100644
--- a/tests/lib/rules/require-await.js
+++ b/tests/lib/rules/require-await.js
@@ -72,6 +72,10 @@ ruleTester.run("require-await", rule, {
             code: "(class { async foo() { doSomething() } })",
             errors: ["Async method 'foo' has no 'await' expression."]
         },
+        {
+            code: "(class { async ''() { doSomething() } })",
+            errors: ["Async method '' has no 'await' expression."]
+        },
         {
             code: "async function foo() { async () => { await doSomething() } }",
             errors: ["Async function 'foo' has no 'await' expression."]
diff --git a/tests/lib/rules/sort-keys.js b/tests/lib/rules/sort-keys.js
index de2eeef1e..eb26c5250 100644
--- a/tests/lib/rules/sort-keys.js
+++ b/tests/lib/rules/sort-keys.js
@@ -22,6 +22,10 @@ ruleTester.run("sort-keys", rule, {
     valid: [
 
         // default (asc)
+        { code: "var obj = {'':1, [``]:2}", options: [], parserOptions: { ecmaVersion: 6 } },
+        { code: "var obj = {[``]:1, '':2}", options: [], parserOptions: { ecmaVersion: 6 } },
+        { code: "var obj = {'':1, a:2}", options: [] },
+        { code: "var obj = {[``]:1, a:2}", options: [], parserOptions: { ecmaVersion: 6 } },
         { code: "var obj = {_:2, a:1, b:3} // default", options: [] },
         { code: "var obj = {a:1, b:3, c:2}", options: [] },
         { code: "var obj = {a:2, b:3, b_:1}", options: [] },
@@ -32,6 +36,8 @@ ruleTester.run("sort-keys", rule, {
 
         // ignore non-simple computed properties.
         { code: "var obj = {a:1, b:3, [a + b]: -1, c:2}", options: [], parserOptions: { ecmaVersion: 6 } },
+        { code: "var obj = {'':1, [f()]:2, a:3}", options: [], parserOptions: { ecmaVersion: 6 } },
+        { code: "var obj = {a:1, [b++]:2, '':3}", options: ["desc"], parserOptions: { ecmaVersion: 6 } },
 
         // ignore properties separated by spread properties
         { code: "var obj = {a:1, ...z, b:1}", options: [], parserOptions: { ecmaVersion: 2018 } },
@@ -39,6 +45,8 @@ ruleTester.run("sort-keys", rule, {
         { code: "var obj = {...a, b:1, ...c, d:1}", options: [], parserOptions: { ecmaVersion: 2018 } },
         { code: "var obj = {...a, b:1, ...d, ...c, e:2, z:5}", options: [], parserOptions: { ecmaVersion: 2018 } },
         { code: "var obj = {b:1, ...c, ...d, e:2}", options: [], parserOptions: { ecmaVersion: 2018 } },
+        { code: "var obj = {a:1, ...z, '':2}", options: [], parserOptions: { ecmaVersion: 2018 } },
+        { code: "var obj = {'':1, ...z, 'a':2}", options: ["desc"], parserOptions: { ecmaVersion: 2018 } },
 
         // not ignore properties not separated by spread properties
         { code: "var obj = {...z, a:1, b:1}", options: [], parserOptions: { ecmaVersion: 2018 } },
@@ -159,6 +167,15 @@ ruleTester.run("sort-keys", rule, {
     invalid: [
 
         // default (asc)
+        {
+            code: "var obj = {a:1, '':2} // default",
+            errors: ["Expected object keys to be in ascending order. '' should be before 'a'."]
+        },
+        {
+            code: "var obj = {a:1, [``]:2} // default",
+            parserOptions: { ecmaVersion: 6 },
+            errors: ["Expected object keys to be in ascending order. '' should be before 'a'."]
+        },
         {
             code: "var obj = {a:1, _:2, b:3} // default",
             errors: ["Expected object keys to be in ascending order. '_' should be before 'a'."]
@@ -234,6 +251,31 @@ ruleTester.run("sort-keys", rule, {
             parserOptions: { ecmaVersion: 2018 },
             errors: ["Expected object keys to be in descending order. 'b' should be before 'a'."]
         },
+        {
+            code: "var obj = {...z, '':1, a:2}",
+            options: ["desc"],
+            parserOptions: { ecmaVersion: 2018 },
+            errors: ["Expected object keys to be in descending order. 'a' should be before ''."]
+        },
+
+        // ignore non-simple computed properties, but their position shouldn't affect other comparisons.
+        {
+            code: "var obj = {a:1, [b+c]:2, '':3}",
+            parserOptions: { ecmaVersion: 6 },
+            errors: ["Expected object keys to be in ascending order. '' should be before 'a'."]
+        },
+        {
+            code: "var obj = {'':1, [b+c]:2, a:3}",
+            options: ["desc"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: ["Expected object keys to be in descending order. 'a' should be before ''."]
+        },
+        {
+            code: "var obj = {b:1, [f()]:2, '':3, a:4}",
+            options: ["desc"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: ["Expected object keys to be in descending order. 'a' should be before ''."]
+        },
 
         // not ignore simple computed properties.
         {
@@ -478,6 +520,21 @@ ruleTester.run("sort-keys", rule, {
         },
 
         // desc
+        {
+            code: "var obj = {'':1, a:'2'} // desc",
+            options: ["desc"],
+            errors: [
+                "Expected object keys to be in descending order. 'a' should be before ''."
+            ]
+        },
+        {
+            code: "var obj = {[``]:1, a:'2'} // desc",
+            options: ["desc"],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                "Expected object keys to be in descending order. 'a' should be before ''."
+            ]
+        },
         {
             code: "var obj = {a:1, _:2, b:3} // desc",
             options: ["desc"],
diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js
index 678b7f274..314deeadf 100644
--- a/tests/lib/rules/utils/ast-utils.js
+++ b/tests/lib/rules/utils/ast-utils.js
@@ -671,7 +671,9 @@ describe("ast-utils", () => {
             "async () => {}": "async arrow function",
             "({ foo: function foo() {} })": "method 'foo'",
             "({ foo: function() {} })": "method 'foo'",
+            "({ '': function() {} })": "method ''",
             "({ ['foo']: function() {} })": "method 'foo'",
+            "({ ['']: function() {} })": "method ''",
             "({ [foo]: function() {} })": "method",
             "({ foo() {} })": "method 'foo'",
             "({ foo: function* foo() {} })": "generator method 'foo'",
@@ -1248,4 +1250,82 @@ describe("ast-utils", () => {
             assert.strictEqual(astUtils.equalTokens(ast.body[0], ast.body[1], sourceCode), false);
         });
     });
+
+    describe("hasOctalEscapeSequence", () => {
+
+        /* eslint-disable quote-props */
+        const expectedResults = {
+            "\\1": true,
+            "\\2": true,
+            "\\7": true,
+            "\\00": true,
+            "\\01": true,
+            "\\02": true,
+            "\\07": true,
+            "\\08": true,
+            "\\09": true,
+            "\\10": true,
+            "\\12": true,
+            " \\1": true,
+            "\\1 ": true,
+            "a\\1": true,
+            "\\1a": true,
+            "a\\1a": true,
+            " \\01": true,
+            "\\01 ": true,
+            "a\\01": true,
+            "\\01a": true,
+            "a\\01a": true,
+            "a\\08a": true,
+            "\\0\\1": true,
+            "\\0\\01": true,
+            "\\0\\08": true,
+            "\\n\\1": true,
+            "\\n\\01": true,
+            "\\n\\08": true,
+            "\\\\\\1": true,
+            "\\\\\\01": true,
+            "\\\\\\08": true,
+
+            "\\0": false,
+            "\\8": false,
+            "\\9": false,
+            " \\0": false,
+            "\\0 ": false,
+            "a\\0": false,
+            "\\0a": false,
+            "a\\8a": false,
+            "\\0\\8": false,
+            "\\8\\0": false,
+            "\\80": false,
+            "\\81": false,
+            "\\\\": false,
+            "\\\\0": false,
+            "\\\\01": false,
+            "\\\\08": false,
+            "\\\\1": false,
+            "\\\\12": false,
+            "\\\\\\0": false,
+            "\\\\\\8": false,
+            "\\0\\\\": false,
+            "0": false,
+            "1": false,
+            "8": false,
+            "01": false,
+            "08": false,
+            "80": false,
+            "12": false,
+            "\\a": false,
+            "\\n": false
+        };
+        /* eslint-enable quote-props */
+
+        Object.keys(expectedResults).forEach(key => {
+            it(`should return ${expectedResults[key]} for ${key}`, () => {
+                const ast = espree.parse(`"${key}"`);
+
+                assert.strictEqual(astUtils.hasOctalEscapeSequence(ast.body[0].expression.raw), expectedResults[key]);
+            });
+        });
+    });
 });
diff --git a/tests/lib/rules/yoda.js b/tests/lib/rules/yoda.js
index 89979ff58..d2c183477 100644
--- a/tests/lib/rules/yoda.js
+++ b/tests/lib/rules/yoda.js
@@ -50,6 +50,14 @@ ruleTester.run("yoda", rule, {
         }, {
             code: "if ('blue' < x.y && x.y < 'green') {}",
             options: ["never", { exceptRange: true }]
+        }, {
+            code: "if (0 < x[``] && x[``] < 100) {}",
+            options: ["never", { exceptRange: true }],
+            parserOptions: { ecmaVersion: 2015 }
+        }, {
+            code: "if (0 < x[''] && x[``] < 100) {}",
+            options: ["never", { exceptRange: true }],
+            parserOptions: { ecmaVersion: 2015 }
         }, {
             code: "if (0 <= x['y'] && x['y'] <= 100) {}",
             options: ["never", { exceptRange: true }]
@@ -332,6 +340,66 @@ ruleTester.run("yoda", rule, {
                 }
             ]
         },
+        {
+            code: "if (0 <= a[''] && a.b < 1) {}",
+            output: "if (a[''] >= 0 && a.b < 1) {}",
+            options: ["never", { exceptRange: true }],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: "<=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "if (0 <= a[''] && a[' '] < 1) {}",
+            output: "if (a[''] >= 0 && a[' '] < 1) {}",
+            options: ["never", { exceptRange: true }],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: "<=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "if (0 <= a[''] && a[null] < 1) {}",
+            output: "if (a[''] >= 0 && a[null] < 1) {}",
+            options: ["never", { exceptRange: true }],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: "<=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "if (0 <= a[''] && a[b] < 1) {}",
+            output: "if (a[''] >= 0 && a[b] < 1) {}",
+            options: ["never", { exceptRange: true }],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: "<=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
+        {
+            code: "if (0 <= a[''] && a[b()] < 1) {}",
+            output: "if (a[''] >= 0 && a[b()] < 1) {}",
+            options: ["never", { exceptRange: true }],
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: "<=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
         {
             code: "if (0 <= a[b()] && a[b()] < 1) {}",
             output: "if (a[b()] >= 0 && a[b()] < 1) {}",