Repository: eslint/eslint
Pull request #12107 (2019-08-17 04:48:32)

Fix: no-duplicate-case false positives on Object.prototype keys
<!--
    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 no-duplicate-case: "error"*/

switch (foo) {
  case toString:
    break;
}
```

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgbm8tZHVwbGljYXRlLWNhc2U6IFwiZXJyb3JcIiovXG5cbnN3aXRjaCAoZm9vKSB7XG4gIGNhc2UgdG9TdHJpbmc6XG4gICAgYnJlYWs7XG59Iiwib3B0aW9ucyI6eyJwYXJzZXJPcHRpb25zIjp7ImVjbWFWZXJzaW9uIjo1LCJzb3VyY2VUeXBlIjoic2NyaXB0IiwiZWNtYUZlYXR1cmVzIjp7fX0sInJ1bGVzIjp7ImNvbnN0cnVjdG9yLXN1cGVyIjoyLCJmb3ItZGlyZWN0aW9uIjoyLCJnZXR0ZXItcmV0dXJuIjoyLCJuby1hc3luYy1wcm9taXNlLWV4ZWN1dG9yIjoyLCJuby1jYXNlLWRlY2xhcmF0aW9ucyI6Miwibm8tY2xhc3MtYXNzaWduIjoyLCJuby1jb21wYXJlLW5lZy16ZXJvIjoyLCJuby1jb25kLWFzc2lnbiI6Miwibm8tY29uc3QtYXNzaWduIjoyLCJuby1jb25zdGFudC1jb25kaXRpb24iOjIsIm5vLWNvbnRyb2wtcmVnZXgiOjIsIm5vLWRlYnVnZ2VyIjoyLCJuby1kZWxldGUtdmFyIjoyLCJuby1kdXBlLWFyZ3MiOjIsIm5vLWR1cGUtY2xhc3MtbWVtYmVycyI6Miwibm8tZHVwZS1rZXlzIjoyLCJuby1kdXBsaWNhdGUtY2FzZSI6Miwibm8tZW1wdHkiOjIsIm5vLWVtcHR5LWNoYXJhY3Rlci1jbGFzcyI6Miwibm8tZW1wdHktcGF0dGVybiI6Miwibm8tZXgtYXNzaWduIjoyLCJuby1leHRyYS1ib29sZWFuLWNhc3QiOjIsIm5vLWV4dHJhLXNlbWkiOjIsIm5vLWZhbGx0aHJvdWdoIjoyLCJuby1mdW5jLWFzc2lnbiI6Miwibm8tZ2xvYmFsLWFzc2lnbiI6Miwibm8taW5uZXItZGVjbGFyYXRpb25zIjoyLCJuby1pbnZhbGlkLXJlZ2V4cCI6Miwibm8taXJyZWd1bGFyLXdoaXRlc3BhY2UiOjIsIm5vLW1pc2xlYWRpbmctY2hhcmFjdGVyLWNsYXNzIjoyLCJuby1taXhlZC1zcGFjZXMtYW5kLXRhYnMiOjIsIm5vLW5ldy1zeW1ib2wiOjIsIm5vLW9iai1jYWxscyI6Miwibm8tb2N0YWwiOjIsIm5vLXByb3RvdHlwZS1idWlsdGlucyI6Miwibm8tcmVkZWNsYXJlIjoyLCJuby1yZWdleC1zcGFjZXMiOjIsIm5vLXNlbGYtYXNzaWduIjoyLCJuby1zaGFkb3ctcmVzdHJpY3RlZC1uYW1lcyI6Miwibm8tc3BhcnNlLWFycmF5cyI6Miwibm8tdGhpcy1iZWZvcmUtc3VwZXIiOjIsIm5vLXVuZXhwZWN0ZWQtbXVsdGlsaW5lIjoyLCJuby11bnJlYWNoYWJsZSI6Miwibm8tdW5zYWZlLWZpbmFsbHkiOjIsIm5vLXVuc2FmZS1uZWdhdGlvbiI6Miwibm8tdW51c2VkLWxhYmVscyI6Miwibm8tdW51c2VkLXZhcnMiOjIsIm5vLXVzZWxlc3MtY2F0Y2giOjIsIm5vLXVzZWxlc3MtZXNjYXBlIjoyLCJuby13aXRoIjoyLCJyZXF1aXJlLWF0b21pYy11cGRhdGVzIjoyLCJyZXF1aXJlLXlpZWxkIjoyLCJ1c2UtaXNuYW4iOjIsInZhbGlkLXR5cGVvZiI6Mn0sImVudiI6e319fQ==)

**What did you expect to happen?**

No warnings.

**What actually happened? Please include the actual, raw output from ESLint.**

```
  4:3  error  Duplicate case label  no-duplicate-case
```
<!--
    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)**

Use `Set` instead of `{}`.

**Is there anything you'd like reviewers to focus on?**

I've added a couple of test cases, only `"switch(a) { case toString: break; }"` was failing before.



Patch:
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 c15f32e66..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) {
@@ -90,7 +96,7 @@ module.exports = {
                     node,
                     messageId: "missingThis",
                     data: {
-                        name: node.parent.key.name
+                        name: astUtils.getFunctionNameWithKind(node)
                     }
                 });
             }
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-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/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/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 89af434fd..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,7 @@ 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'" } }
             ]
         },
         {
@@ -103,7 +103,22 @@ ruleTester.run("class-methods-use-this", rule, {
             options: [{ exceptMethods: ["foo"] }],
             parserOptions: { ecmaVersion: 6 },
             errors: [
-                { type: "FunctionExpression", line: 1, column: 16, messageId: "missingThis", data: { name: "foo" } }
+                { 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/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-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/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/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) {}",