Repository: eslint/eslint
Pull request #12056 (2019-08-03 05:14:42)

Update: Handle locally unsupported regex in computed property 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:** 8.16.0
* **npm Version:** 6.4.1

**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: 2018,
  },
};
```

</details>

**What did you do? Please include the actual source code causing the issue.**

This bug is observable in environments that don't support ES2018 regexp syntax, namely Node 8. 

For the online demo use Firefox (current Firefox version is 68, still doesn't support this syntax), results are different when compared to Chrome.

This bug affects 5 rules:

-----------------------------------------

`no-dupe-keys` false negative:

```js
/*eslint no-dupe-keys: "error"*/
({ "/(?<zero>0)/": 1, [/(?<zero>0)/]: 2 })
```

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgbm8tZHVwZS1rZXlzOiBcImVycm9yXCIqL1xuKHsgXCIvKD88emVybz4wKS9cIjogMSwgWy8oPzx6ZXJvPjApL106IDIgfSlcblxuXG4iLCJvcHRpb25zIjp7InBhcnNlck9wdGlvbnMiOnsiZWNtYVZlcnNpb24iOjksInNvdXJjZVR5cGUiOiJzY3JpcHQiLCJlY21hRmVhdHVyZXMiOnt9fSwicnVsZXMiOnt9LCJlbnYiOnt9fX0=) (open in Firefox)

-----------------------------------------

`no-dupe-keys` false positive:

```js
/*eslint no-dupe-keys: "error"*/
({ null: 1, [/(?<zero>0)/]: 2 })
```

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgbm8tZHVwZS1rZXlzOiBcImVycm9yXCIqL1xuKHsgbnVsbDogMSwgWy8oPzx6ZXJvPjApL106IDIgfSlcblxuXG4iLCJvcHRpb25zIjp7InBhcnNlck9wdGlvbnMiOnsiZWNtYVZlcnNpb24iOjksInNvdXJjZVR5cGUiOiJzY3JpcHQiLCJlY21hRmVhdHVyZXMiOnt9fSwicnVsZXMiOnt9LCJlbnYiOnt9fX0=) (open in Firefox)

-----------------------------------------

`sort-keys` false negative:

```js
/*eslint sort-keys: "error"*/
({ null: 1, [/(?<zero>0)/]: 2 })
```

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgc29ydC1rZXlzOiBcImVycm9yXCIqL1xuKHsgbnVsbDogMSwgWy8oPzx6ZXJvPjApL106IDIgfSlcblxuXG4iLCJvcHRpb25zIjp7InBhcnNlck9wdGlvbnMiOnsiZWNtYVZlcnNpb24iOjksInNvdXJjZVR5cGUiOiJzY3JpcHQiLCJlY21hRmVhdHVyZXMiOnt9fSwicnVsZXMiOnt9LCJlbnYiOnt9fX0=) (open in Firefox)

-----------------------------------------

`sort-keys` false positive:

```js
/*eslint sort-keys: "error"*/
({ [/(?<zero>0)/]: 1, "/(?<zero>0)/": 2 })
```

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgc29ydC1rZXlzOiBcImVycm9yXCIqL1xuKHsgWy8oPzx6ZXJvPjApL106IDEsIFwiLyg/PHplcm8+MCkvXCI6IDIgfSlcblxuXG4iLCJvcHRpb25zIjp7InBhcnNlck9wdGlvbnMiOnsiZWNtYVZlcnNpb24iOjksInNvdXJjZVR5cGUiOiJzY3JpcHQiLCJlY21hRmVhdHVyZXMiOnt9fSwicnVsZXMiOnt9LCJlbnYiOnt9fX0=) (open in Firefox)

-----------------------------------------

`no-self-assign` false negative:

```js
/*eslint no-self-assign: "error"*/
foo["/(?<zero>0)/"] = foo[/(?<zero>0)/]
```

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgbm8tc2VsZi1hc3NpZ246IFwiZXJyb3JcIiovXG5mb29bXCIvKD88emVybz4wKS9cIl0gPSBmb29bLyg/PHplcm8+MCkvXVxuXG5cbiIsIm9wdGlvbnMiOnsicGFyc2VyT3B0aW9ucyI6eyJlY21hVmVyc2lvbiI6OSwic291cmNlVHlwZSI6InNjcmlwdCIsImVjbWFGZWF0dXJlcyI6e319LCJydWxlcyI6e30sImVudiI6e319fQ==) (open in Firefox)

-----------------------------------------

`no-self-assign` false positive:

```js
/*eslint no-self-assign: "error"*/
foo.null = foo[/(?<zero>0)/];
```

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgbm8tc2VsZi1hc3NpZ246IFwiZXJyb3JcIiovXG5mb28ubnVsbCA9IGZvb1svKD88emVybz4wKS9dXG5cblxuIiwib3B0aW9ucyI6eyJwYXJzZXJPcHRpb25zIjp7ImVjbWFWZXJzaW9uIjo5LCJzb3VyY2VUeXBlIjoic2NyaXB0IiwiZWNtYUZlYXR1cmVzIjp7fX0sInJ1bGVzIjp7fSwiZW52Ijp7fX19) (open in Firefox)

-----------------------------------------

`yoda` false negative:

```js
/*eslint yoda: ["error", "never", { "exceptRange": true }]*/
if (1 < foo.null && foo[/(?<zero>0)/] < 10) {}
```

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgeW9kYTogW1wiZXJyb3JcIiwgXCJuZXZlclwiLCB7IFwiZXhjZXB0UmFuZ2VcIjogdHJ1ZSB9XSovXG5pZiAoMSA8IGZvby5udWxsICYmIGZvb1svKD88emVybz4wKS9dIDwgMTApIHt9XG5cblxuIiwib3B0aW9ucyI6eyJwYXJzZXJPcHRpb25zIjp7ImVjbWFWZXJzaW9uIjo5LCJzb3VyY2VUeXBlIjoic2NyaXB0IiwiZWNtYUZlYXR1cmVzIjp7fX0sInJ1bGVzIjp7fSwiZW52Ijp7fX19) (open in Firefox)

-----------------------------------------

`yoda` false positive:

```js
/*eslint yoda: ["error", "never", { "exceptRange": true }]*/
if (1 < foo["/(?<zero>0)/"] && foo[/(?<zero>0)/] < 10) {}
```

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgeW9kYTogW1wiZXJyb3JcIiwgXCJuZXZlclwiLCB7IFwiZXhjZXB0UmFuZ2VcIjogdHJ1ZSB9XSovXG5pZiAoMSA8IGZvb1tcIi8oPzx6ZXJvPjApL1wiXSAmJiBmb29bLyg/PHplcm8+MCkvXSA8IDEwKSB7fVxuXG5cbiIsIm9wdGlvbnMiOnsicGFyc2VyT3B0aW9ucyI6eyJlY21hVmVyc2lvbiI6OSwic291cmNlVHlwZSI6InNjcmlwdCIsImVjbWFGZWF0dXJlcyI6e319LCJydWxlcyI6e30sImVudiI6e319fQ==) (open in Firefox)

-----------------------------------------

`no-restricted-properties` false negative:

```js
/*eslint no-restricted-properties: ["error", { "property": "/(?<zero>0)/" }]*/
foo[/(?<zero>0)/];
```

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgbm8tcmVzdHJpY3RlZC1wcm9wZXJ0aWVzOiBbXCJlcnJvclwiLCB7IFwicHJvcGVydHlcIjogXCIvKD88emVybz4wKS9cIiB9XSovXG5mb29bLyg/PHplcm8+MCkvXTtcblxuXG4iLCJvcHRpb25zIjp7InBhcnNlck9wdGlvbnMiOnsiZWNtYVZlcnNpb24iOjksInNvdXJjZVR5cGUiOiJzY3JpcHQiLCJlY21hRmVhdHVyZXMiOnt9fSwicnVsZXMiOnt9LCJlbnYiOnt9fX0=) (open in Firefox)

-----------------------------------------

`no-restricted-properties` false positive:

```js
/*eslint no-restricted-properties: ["error", { "property": "null" }]*/
foo[/(?<zero>0)/];
```

[Demo link](https://eslint.org/demo#eyJ0ZXh0IjoiLyplc2xpbnQgbm8tcmVzdHJpY3RlZC1wcm9wZXJ0aWVzOiBbXCJlcnJvclwiLCB7IFwicHJvcGVydHlcIjogXCJudWxsXCIgfV0qL1xuZm9vWy8oPzx6ZXJvPjApL107XG5cblxuIiwib3B0aW9ucyI6eyJwYXJzZXJPcHRpb25zIjp7ImVjbWFWZXJzaW9uIjo5LCJzb3VyY2VUeXBlIjoic2NyaXB0IiwiZWNtYUZlYXR1cmVzIjp7fX0sInJ1bGVzIjp7fSwiZW52Ijp7fX19) (open in Firefox)

**What did you expect to happen?**

Warnings for 'false negative' examples, no warnings for 'false positive' examples.

**What actually happened? Please include the actual, raw output from ESLint.**

The opposite.

<!--
    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)**

Fixed `astUtils.getStaticPropertyName`

**Is there anything you'd like reviewers to focus on?**

Part of the code is extracted to new function `getStaticStringValue`, it's easier to test and it might be useful for something else.

Unrelated to this PR:

* The same fix should be added for bigint.
* `accesor-pairs` should use `getStaticPropertyName`, but this rule doesn't work well. It should enforce pairs per key, not per the whole literal. I'm working on this, a PR will be ready very soon.
* Some other rules might also use `getStaticPropertyName` instead of a partial/duplicate logic, I'm working on this.
* Some rules are treating empty string name as null, I'm working on this, will be ready very soon.

kaicataldo: Sorry, to avoid confusion I usually wait to review until after a change is accepted. Code LGTM and I support this change, but we’ll need another 👍🏼 And a champion.
mysticatea: Would you try [getPropertyName](https://mysticatea.github.io/eslint-utils/api/ast-utils.html#getpropertyname) function of `eslint-utils`? I guess that the utility covers more cases.
mdjermanovic: > Sorry, to avoid confusion I usually wait to review until after a change is accepted. Code LGTM and I support this change, but we’ll need another 👍🏼 And a champion.

Sorry, I should be more specific and the title/commit message indeed sounds like this is an enhancement.

This was supposed to be a **bug fix**. The title has 'Update' prefix because the fix can produce more warnings.

The function already supports regexp literals, but it doesn't 'support' RegExpLiteral extension of the Literal interface, i.e. the `regex` property.

The bug is in `return String(prop.value)` which will evaluate to `"null"` string if the ESLint is executed in an environment that doesn't support given regex syntax.
mdjermanovic: > Would you try [getPropertyName](https://mysticatea.github.io/eslint-utils/api/ast-utils.html#getpropertyname) function of `eslint-utils`?

I'll try it on Node 8 with test cases from this PR to be sure, but I think it doesn't use `regex` properties?

```js
Literal(node) {
        //istanbul ignore if : this is implementation-specific behavior.
        if (node.regex != null && node.value == null) {
            // It was a RegExp literal, but Node.js didn't support it.
            return null
        }
        return node
    },
```

instead of `return null` it might be something like
```js
return { value: `/${node.regex.pattern}/${node.regex.flags}` }
```

or maybe there is a specific reason to return `null` in this scenario? I'm asking because it would apply to this PR as well. 

>  I guess that the utility covers more cases.

Looks like a lot more, even without the scope argument, I'm not sure would it be a breaking change?
mdjermanovic: I think I see the reason now, `getStaticValue()` returns the value itself, which is impossible in this scenario. But then there is a problem with `getStringIfConstant()` which doesn't return the string in this scenario, although it would be possible.
mdjermanovic: Changed the title and commit message. I still believe this is a bug fix rather than enhancement :)
kaicataldo: @mysticatea Have your concerns been addressed?
mysticatea: I apologize for I lost track on this. LGTM.
mdjermanovic: @mysticatea no problem, but this will now collide with #12701, which handles this + bigints as well.

Might be easier to close this PR and do everything in #12701?
mysticatea: Thank you. I'm fine with either way... (1) we merge this PR to master then master to #12701, or (2) I cherry-pick this to #12701. Probably those are samely easy.
mdjermanovic: Rebased and fixed conflicts & JSDoc, for the case that this gets merged.

Patch:
diff --git a/lib/rules/utils/ast-utils.js b/lib/rules/utils/ast-utils.js
index 01c6b47b8..b4e8e2980 100644
--- a/lib/rules/utils/ast-utils.js
+++ b/lib/rules/utils/ast-utils.js
@@ -865,6 +865,44 @@ module.exports = {
         return isFunction(node) && module.exports.isEmptyBlock(node.body);
     },
 
+    /**
+     * Returns the result of the string conversion applied to the evaluated value of the given expression node,
+     * if it can be determined statically.
+     *
+     * This function returns a `string` value for all `Literal` nodes and simple `TemplateLiteral` nodes only.
+     * In all other cases, this function returns `null`.
+     * @param {ASTNode} node Expression node.
+     * @returns {string|null} String value if it can be determined. Otherwise, `null`.
+     */
+    getStaticStringValue(node) {
+        switch (node.type) {
+            case "Literal":
+                if (node.value === null) {
+                    if (module.exports.isNullLiteral(node)) {
+                        return String(node.value); // "null"
+                    }
+                    if (node.regex) {
+                        return `/${node.regex.pattern}/${node.regex.flags}`;
+                    }
+
+                    // Otherwise, this is an unknown literal. The function will return null.
+
+                } else {
+                    return String(node.value);
+                }
+                break;
+            case "TemplateLiteral":
+                if (node.expressions.length === 0 && node.quasis.length === 1) {
+                    return node.quasis[0].value.cooked;
+                }
+                break;
+
+            // no default
+        }
+
+        return null;
+    },
+
     /**
      * Gets the property name of a given node.
      * The node can be a MemberExpression, a Property, or a MethodDefinition.
@@ -911,23 +949,12 @@ module.exports = {
             // no default
         }
 
-        switch (prop && prop.type) {
-            case "Literal":
-                return String(prop.value);
-
-            case "TemplateLiteral":
-                if (prop.expressions.length === 0 && prop.quasis.length === 1) {
-                    return prop.quasis[0].value.cooked;
-                }
-                break;
-
-            case "Identifier":
-                if (!node.computed) {
-                    return prop.name;
-                }
-                break;
+        if (prop) {
+            if (prop.type === "Identifier" && !node.computed) {
+                return prop.name;
+            }
 
-            // no default
+            return module.exports.getStaticStringValue(prop);
         }
 
         return null;


Test patch:
diff --git a/tests/lib/rules/no-dupe-keys.js b/tests/lib/rules/no-dupe-keys.js
index a0a1e7a8c..a6062a2a2 100644
--- a/tests/lib/rules/no-dupe-keys.js
+++ b/tests/lib/rules/no-dupe-keys.js
@@ -32,6 +32,7 @@ ruleTester.run("no-dupe-keys", rule, {
         { code: "var x = { a: b, ...c }", parserOptions: { ecmaVersion: 2018 } },
         { code: "var x = { get a() {}, set a (value) {} };", parserOptions: { ecmaVersion: 6 } },
         { code: "var x = { a: 1, b: { a: 2 } };", parserOptions: { ecmaVersion: 6 } },
+        { code: "var x = ({ null: 1, [/(?<zero>0)/]: 2 })", parserOptions: { ecmaVersion: 2018 } },
         { code: "var {a, a} = obj", parserOptions: { ecmaVersion: 6 } }
     ],
     invalid: [
@@ -44,6 +45,7 @@ ruleTester.run("no-dupe-keys", rule, {
         { code: "var foo = {\n  bar: 1,\n  bar: 1,\n}", errors: [{ messageId: "unexpected", data: { name: "bar" }, line: 3, column: 3 }] },
         { code: "var x = { a: 1, get a() {} };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
         { code: "var x = { a: 1, set a(value) {} };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "a" }, type: "ObjectExpression" }] },
-        { code: "var x = { a: 1, b: { a: 2 }, get b() {} };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "b" }, type: "ObjectExpression" }] }
+        { code: "var x = { a: 1, b: { a: 2 }, get b() {} };", parserOptions: { ecmaVersion: 6 }, errors: [{ messageId: "unexpected", data: { name: "b" }, type: "ObjectExpression" }] },
+        { code: "var x = ({ '/(?<zero>0)/': 1, [/(?<zero>0)/]: 2 })", parserOptions: { ecmaVersion: 2018 }, errors: [{ messageId: "unexpected", data: { name: "/(?<zero>0)/" }, type: "ObjectExpression" }] }
     ]
 });
diff --git a/tests/lib/rules/no-restricted-properties.js b/tests/lib/rules/no-restricted-properties.js
index c1be7a12c..9c5c57950 100644
--- a/tests/lib/rules/no-restricted-properties.js
+++ b/tests/lib/rules/no-restricted-properties.js
@@ -95,6 +95,12 @@ ruleTester.run("no-restricted-properties", rule, {
             options: [{
                 object: "foo"
             }]
+        }, {
+            code: "foo[/(?<zero>0)/]",
+            options: [{
+                property: "null"
+            }],
+            parserOptions: { ecmaVersion: 2018 }
         }, {
             code: "let bar = foo;",
             options: [{ object: "foo", property: "bar" }],
@@ -248,6 +254,11 @@ ruleTester.run("no-restricted-properties", rule, {
             code: "foo.bar.baz();",
             options: [{ property: "bar" }],
             errors: [{ message: "'bar' is restricted from being used.", type: "MemberExpression" }]
+        }, {
+            code: "foo[/(?<zero>0)/]",
+            options: [{ property: "/(?<zero>0)/" }],
+            parserOptions: { ecmaVersion: 2018 },
+            errors: [{ message: "'/(?<zero>0)/' is restricted from being used.", type: "MemberExpression" }]
         }, {
             code: "require.call({}, 'foo')",
             options: [{
diff --git a/tests/lib/rules/no-self-assign.js b/tests/lib/rules/no-self-assign.js
index eaa0d02d1..c20c01fcb 100644
--- a/tests/lib/rules/no-self-assign.js
+++ b/tests/lib/rules/no-self-assign.js
@@ -50,6 +50,7 @@ ruleTester.run("no-self-assign", rule, {
         { code: "a[b] = a.b", options: [{ props: true }] },
         { code: "a.b().c = a.b().c", options: [{ props: true }] },
         { code: "b().c = b().c", options: [{ props: true }] },
+        { code: "a.null = a[/(?<zero>0)/]", options: [{ props: true }], parserOptions: { ecmaVersion: 2018 } },
         { code: "a[b + 1] = a[b + 1]", options: [{ props: true }] }, // it ignores non-simple computed properties.
         {
             code: "a.b = a.b",
@@ -133,6 +134,7 @@ ruleTester.run("no-self-assign", rule, {
             code: "this.x = this.x",
             options: [{ props: true }],
             errors: ["'this.x' is assigned to itself."]
-        }
+        },
+        { code: "a['/(?<zero>0)/'] = a[/(?<zero>0)/]", options: [{ props: true }], parserOptions: { ecmaVersion: 2018 }, errors: ["'a[/(?<zero>0)/]' is assigned to itself."] }
     ]
 });
diff --git a/tests/lib/rules/sort-keys.js b/tests/lib/rules/sort-keys.js
index eb26c5250..31ca1c2ac 100644
--- a/tests/lib/rules/sort-keys.js
+++ b/tests/lib/rules/sort-keys.js
@@ -33,6 +33,7 @@ ruleTester.run("sort-keys", rule, {
         { code: "var obj = {$:1, A:3, _:2, a:4}", options: [] },
         { code: "var obj = {1:1, '11':2, 2:4, A:3}", options: [] },
         { code: "var obj = {'#':1, 'Z':2, À:3, è:4}", options: [] },
+        { code: "var obj = { [/(?<zero>0)/]: 1, '/(?<zero>0)/': 2 }", options: [], parserOptions: { ecmaVersion: 2018 } },
 
         // ignore non-simple computed properties.
         { code: "var obj = {a:1, b:3, [a + b]: -1, c:2}", options: [], parserOptions: { ecmaVersion: 6 } },
@@ -204,6 +205,11 @@ ruleTester.run("sort-keys", rule, {
             code: "var obj = {'#':1, À:3, 'Z':2, è:4}",
             errors: ["Expected object keys to be in ascending order. 'Z' should be before 'À'."]
         },
+        {
+            code: "var obj = { null: 1, [/(?<zero>0)/]: 2 }",
+            parserOptions: { ecmaVersion: 2018 },
+            errors: ["Expected object keys to be in ascending order. '/(?<zero>0)/' should be before 'null'."]
+        },
 
         // not ignore properties not separated by spread properties
         {
diff --git a/tests/lib/rules/utils/ast-utils.js b/tests/lib/rules/utils/ast-utils.js
index fb5526ff6..1acc22701 100644
--- a/tests/lib/rules/utils/ast-utils.js
+++ b/tests/lib/rules/utils/ast-utils.js
@@ -403,6 +403,83 @@ describe("ast-utils", () => {
         });
     });
 
+    describe("getStaticStringValue", () => {
+
+        /* eslint-disable quote-props */
+        const expectedResults = {
+
+            // string literals
+            "''": "",
+            "'foo'": "foo",
+
+            // boolean literals
+            "false": "false",
+            "true": "true",
+
+            // null literal
+            "null": "null",
+
+            // number literals
+            "0": "0",
+            "0.": "0",
+            ".0": "0",
+            "1": "1",
+            "1.": "1",
+            ".1": "0.1",
+            "12": "12",
+            ".12": "0.12",
+            "0.12": "0.12",
+            "12.34": "12.34",
+            "12e3": "12000",
+            "12e-3": "0.012",
+            "12.34e5": "1234000",
+            "12.34e-5": "0.0001234",
+            "011": "9",
+            "081": "81",
+            "0b11": "3",
+            "0b011": "3",
+            "0o11": "9",
+            "0o011": "9",
+            "0x11": "17",
+            "0x011": "17",
+
+            // regexp literals
+            "/a/": "/a/",
+            "/a/i": "/a/i",
+            "/[0-9]/": "/[0-9]/",
+            "/(?<zero>0)/": "/(?<zero>0)/",
+            "/(?<zero>0)/s": "/(?<zero>0)/s",
+            "/(?<=a)b/s": "/(?<=a)b/s",
+
+            // simple template literals
+            "``": "",
+            "`foo`": "foo",
+
+            // unsupported
+            "`${''}`": null,
+            "`${0}`": null,
+            "tag``": null,
+            "-0": null,
+            "-1": null,
+            "1 + 2": null,
+            "[]": null,
+            "({})": null,
+            "foo": null,
+            "undefined": null,
+            "this": null,
+            "(function () {})": null
+        };
+        /* eslint-enable quote-props */
+
+        Object.keys(expectedResults).forEach(key => {
+            it(`should return ${expectedResults[key]} for ${key}`, () => {
+                const ast = espree.parse(key, { ecmaVersion: 2018 });
+
+                assert.strictEqual(astUtils.getStaticStringValue(ast.body[0].expression), expectedResults[key]);
+            });
+        });
+    });
+
     describe("getStaticPropertyName", () => {
         it("should return 'b' for `a.b`", () => {
             const ast = espree.parse("a.b");
@@ -509,6 +586,13 @@ describe("ast-utils", () => {
             assert.strictEqual(astUtils.getStaticPropertyName(node), "100");
         });
 
+        it("should return '/(?<zero>0)/' for `[/(?<zero>0)/]: 1`", () => {
+            const ast = espree.parse("({[/(?<zero>0)/]: 1})", { ecmaVersion: 2018 });
+            const node = ast.body[0].expression.properties[0];
+
+            assert.strictEqual(astUtils.getStaticPropertyName(node), "/(?<zero>0)/");
+        });
+
         it("should return null for `[b]: 1`", () => {
             const ast = espree.parse("({[b]: 1})", { ecmaVersion: 6 });
             const node = ast.body[0].expression.properties[0];
diff --git a/tests/lib/rules/yoda.js b/tests/lib/rules/yoda.js
index 7908499d8..414cb5a04 100644
--- a/tests/lib/rules/yoda.js
+++ b/tests/lib/rules/yoda.js
@@ -91,6 +91,10 @@ ruleTester.run("yoda", rule, {
         }, {
             code: "if (0 <= a.b && a[\"b\"] <= 100) {}",
             options: ["never", { exceptRange: true }]
+        }, {
+            code: "if (1 <= a['/(?<zero>0)/'] && a[/(?<zero>0)/] <= 100) {}",
+            options: ["never", { exceptRange: true }],
+            parserOptions: { ecmaVersion: 2018 }
         },
 
         // onlyEquality
@@ -412,6 +416,19 @@ ruleTester.run("yoda", rule, {
                 }
             ]
         },
+        {
+            code: "if (0 <= a.null && a[/(?<zero>0)/] <= 1) {}",
+            output: "if (a.null >= 0 && a[/(?<zero>0)/] <= 1) {}",
+            options: ["never", { exceptRange: true }],
+            parserOptions: { ecmaVersion: 2018 },
+            errors: [
+                {
+                    messageId: "expected",
+                    data: { expectedSide: "right", operator: "<=" },
+                    type: "BinaryExpression"
+                }
+            ]
+        },
         {
             code: "if (3 == a) {}",
             output: "if (a == 3) {}",