Repository: eslint/eslint (js)

Issue:
Bug: False positives with no-constant-condition
### Environment

Online Playground

### What parser are you using?

Default (Espree)

### What did you do?

```js
if(`${[...a]}`) {
  //
};

if(`${[{toString: () => a}]}`) {
  //
};


if("" + {toString: () => a}) {
  //
}

if("" + [{toString: () => a}]) {
  //
};

if(+[{toString: () => a}]) {
  //
};



```

[Playground Link](https://eslint.org/demo#eyJ0ZXh0IjoiXG5pZihgJHtbLi4uYV19YCkge1xuICAvL1xufTtcblxuaWYoYCR7W3t0b1N0cmluZzogKCkgPT4gYX1dfWApIHtcbiAgLy9cbn07XG5cblxuaWYoXCJcIiArIHt0b1N0cmluZzogKCkgPT4gYX0pIHtcbiAgLy9cbn1cblxuaWYoXCJcIiArIFt7dG9TdHJpbmc6ICgpID0+IGF9XSkge1xuICAvL1xufTtcblxuaWYoK1t7dG9TdHJpbmc6ICgpID0+IGF9XSkge1xuICAvL1xufTtcblxuXG4iLCJvcHRpb25zIjp7InBhcnNlck9wdGlvbnMiOnsiZWNtYVZlcnNpb24iOiJsYXRlc3QiLCJzb3VyY2VUeXBlIjoic2NyaXB0IiwiZWNtYUZlYXR1cmVzIjp7fX0sInJ1bGVzIjp7Im5vLWNvbnN0YW50LWNvbmRpdGlvbiI6Mn0sImVudiI6e319fQ==)

### What did you expect to happen?

No errors

### What actually happened?

Each condition triggers `no-constant-condition`

### Participation

- [ ] I am willing to submit a pull request for this issue.

### Additional comments

I was looking into the implementation of the `isConstant` function in `no-constant-condition` as part of #15296. Specifically I was trying to understand the meaning of the case where the `inBooleanPosition` flag is set to false.

I realized that the reason I was having such a hard time understanding it is that it's not really sound. For example, we call `isConstant` with `inBooleanPosition = false` in cases where values are going to get coerced into numbers or strings and we want to know if the resulting string/number will have constant truthiness. However, the checks are not sufficient to actually validate that.

I think that currently isConstant with inBooleanPosition set to false is supposed to check if a value has constant truthiness, even when that value is being first coerced to some other type (string/number/...). I'm not sure that's a function which is feasible to write correctly. Instead, I suspect we will want individual functions that handle the string and number cases, or something along those lines.

Rather than trying to solve this by adding more special cases to `isConstant` (which is already rather hard to grock), I wonder if we could break it up into a collection of simpler helper functions which check for more explicit cases.

Pull request description:
fix: Prevent false positives with no-constant-condition
Fixes #15467 by defining `isConstant` with `inBooleanPosition` set to `false` as meaning:

> For both string and number, if coerced to that type, the value will be constant.

<!--
    Thank you for contributing!

    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

#### Prerequisites checklist

- [x] I have read the [contributing guidelines](https://github.com/eslint/eslint/blob/HEAD/CONTRIBUTING.md).

#### What is the purpose of this pull request? (put an "X" next to an item)

<!--
    The following template is intentionally not a markdown checkbox list for the reasons
    explained in https://github.com/eslint/eslint/pull/12848#issuecomment-580302888
-->

[x] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/bug-report.md))
[x] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/HEAD/templates/rule-change-proposal.md))

<!--
    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.)
-->

<!--
    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)

Make `no-constant-condition` more correct. See #15467 for more information.

#### Is there anything you'd like reviewers to focus on?

Would ESLint treat this as a breaking change? It will remove some false positives, but also -- in a few rare cases -- introduce a few false negatives.

<!-- markdownlint-disable-file MD004 -->


Patch:
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 5d9aa9536..939496ded 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1,9 +1,9 @@
 name: CI
 on:
   push:
-    branches: [master, main]
+    branches: [main]
   pull_request:
-    branches: [master, main]
+    branches: [main]
 
 jobs:
   verify_files:
diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml
index ef362a4b6..60f7922fd 100644
--- a/.github/workflows/codeql-analysis.yml
+++ b/.github/workflows/codeql-analysis.yml
@@ -13,10 +13,10 @@ name: "CodeQL"
 
 on:
   push:
-    branches: [master, main]
+    branches: [main]
   pull_request:
     # The branches below must be a subset of the branches above
-    branches: [master, main]
+    branches: [main]
   schedule:
     - cron: '28 17 * * 5'
 
diff --git a/docs/rules/no-restricted-imports.md b/docs/rules/no-restricted-imports.md
index d8e06101b..414485fdf 100644
--- a/docs/rules/no-restricted-imports.md
+++ b/docs/rules/no-restricted-imports.md
@@ -150,7 +150,11 @@ import DisallowedObject from "foo";
     message: "Please import 'DisallowedObject' from '/bar/baz/' instead."
 }]}]*/
 
+import { DisallowedObject } from "foo";
+
 import { DisallowedObject as AllowedObject } from "foo";
+
+import { "DisallowedObject" as AllowedObject } from "foo";
 ```
 
 ```js
diff --git a/docs/rules/no-useless-rename.md b/docs/rules/no-useless-rename.md
index fba312332..1a827288c 100644
--- a/docs/rules/no-useless-rename.md
+++ b/docs/rules/no-useless-rename.md
@@ -52,8 +52,11 @@ Examples of **incorrect** code for this rule by default:
 /*eslint no-useless-rename: "error"*/
 
 import { foo as foo } from "bar";
+import { "foo" as foo } from "bar";
 export { foo as foo };
+export { foo as "foo" };
 export { foo as foo } from "bar";
+export { "foo" as "foo" } from "bar";
 let { foo: foo } = bar;
 let { 'foo': foo } = bar;
 function foo({ bar: bar }) {}
@@ -68,10 +71,13 @@ Examples of **correct** code for this rule by default:
 import * as foo from "foo";
 import { foo } from "bar";
 import { foo as bar } from "baz";
+import { "foo" as bar } from "baz";
 
 export { foo };
 export { foo as bar };
+export { foo as "bar" };
 export { foo as bar } from "foo";
+export { "foo" as "bar" } from "foo";
 
 let { foo } = bar;
 let { foo: bar } = baz;
diff --git a/lib/rules/no-constant-condition.js b/lib/rules/no-constant-condition.js
index 0bcb31931..8adc9bca4 100644
--- a/lib/rules/no-constant-condition.js
+++ b/lib/rules/no-constant-condition.js
@@ -124,7 +124,8 @@ module.exports = {
          * Checks if a node has a constant truthiness value.
          * @param {ASTNode} node The AST node to check.
          * @param {boolean} inBooleanPosition `false` if checking branch of a condition.
-         *  `true` in all other cases
+         *  `true` in all other cases. When `false`, checks if -- for both string and
+         *  number -- if coerced to that type, the value will be constant.
          * @returns {Bool} true when node's truthiness is constant
          * @private
          */
@@ -138,15 +139,31 @@ module.exports = {
                 case "Literal":
                 case "ArrowFunctionExpression":
                 case "FunctionExpression":
-                case "ObjectExpression":
+                    return true;
                 case "ClassExpression":
+                case "ObjectExpression":
+
+                    /**
+                     * In theory objects like:
+                     *
+                     * `{toString: () => a}`
+                     * `{valueOf: () => a}`
+                     *
+                     * Or a classes like:
+                     *
+                     * `class { static toString() { return a } }`
+                     * `class { static valueOf() { return a } }`
+                     *
+                     * Are not constant verifiably when `inBooleanPosition` is
+                     * false, but it's an edge case we've opted not to handle.
+                     */
                     return true;
                 case "TemplateLiteral":
                     return (inBooleanPosition && node.quasis.some(quasi => quasi.value.cooked.length)) ||
-                        node.expressions.every(exp => isConstant(exp, inBooleanPosition));
+                        node.expressions.every(exp => isConstant(exp, false));
 
                 case "ArrayExpression": {
-                    if (node.parent.type === "BinaryExpression" && node.parent.operator === "+") {
+                    if (!inBooleanPosition) {
                         return node.elements.every(element => isConstant(element, false));
                     }
                     return true;
@@ -196,6 +213,8 @@ module.exports = {
 
                 case "SequenceExpression":
                     return isConstant(node.expressions[node.expressions.length - 1], inBooleanPosition);
+                case "SpreadElement":
+                    return isConstant(node.argument, inBooleanPosition);
 
                 // no default
             }
diff --git a/lib/rules/no-restricted-imports.js b/lib/rules/no-restricted-imports.js
index 6813037f1..455cc336c 100644
--- a/lib/rules/no-restricted-imports.js
+++ b/lib/rules/no-restricted-imports.js
@@ -4,6 +4,12 @@
  */
 "use strict";
 
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const astUtils = require("./utils/ast-utils");
+
 //------------------------------------------------------------------------------
 // Rule Definition
 //------------------------------------------------------------------------------
@@ -269,12 +275,12 @@ module.exports = {
                     } else if (specifier.type === "ImportNamespaceSpecifier") {
                         name = "*";
                     } else if (specifier.imported) {
-                        name = specifier.imported.name;
+                        name = astUtils.getModuleExportName(specifier.imported);
                     } else if (specifier.local) {
-                        name = specifier.local.name;
+                        name = astUtils.getModuleExportName(specifier.local);
                     }
 
-                    if (name) {
+                    if (typeof name === "string") {
                         if (importNames.has(name)) {
                             importNames.get(name).push(specifierData);
                         } else {
diff --git a/lib/rules/no-useless-rename.js b/lib/rules/no-useless-rename.js
index 616ec2a43..2489f57bc 100644
--- a/lib/rules/no-useless-rename.js
+++ b/lib/rules/no-useless-rename.js
@@ -132,8 +132,10 @@ module.exports = {
                 return;
             }
 
-            if (node.imported.name === node.local.name &&
-                    node.imported.range[0] !== node.local.range[0]) {
+            if (
+                node.imported.range[0] !== node.local.range[0] &&
+                astUtils.getModuleExportName(node.imported) === node.local.name
+            ) {
                 reportError(node, node.imported, "Import");
             }
         }
@@ -148,8 +150,10 @@ module.exports = {
                 return;
             }
 
-            if (node.local.name === node.exported.name &&
-                    node.local.range[0] !== node.exported.range[0]) {
+            if (
+                node.local.range[0] !== node.exported.range[0] &&
+                astUtils.getModuleExportName(node.local) === astUtils.getModuleExportName(node.exported)
+            ) {
                 reportError(node, node.local, "Export");
             }
 


Test patch:
diff --git a/tests/lib/rules/no-constant-condition.js b/tests/lib/rules/no-constant-condition.js
index 0b5f3c5a1..9cca27b90 100644
--- a/tests/lib/rules/no-constant-condition.js
+++ b/tests/lib/rules/no-constant-condition.js
@@ -175,7 +175,17 @@ ruleTester.run("no-constant-condition", rule, {
         "function* foo() {while (true) {function* foo() {yield;}yield;}}",
         "function* foo() { for (let x = yield; x < 10; x++) {yield;}yield;}",
         "function* foo() { for (let x = yield; ; x++) { yield; }}",
-        "if (new Number(x) + 1 === 2) {}"
+        "if (new Number(x) + 1 === 2) {}",
+
+        // #15467
+        "if([a]==[b]) {}",
+        "if (+[...a]) {}",
+        "if (+[...[...a]]) {}",
+        "if (`${[...a]}`) {}",
+        "if (`${[a]}`) {}",
+        "if (+[a]) {}",
+        "if (0 - [a]) {}",
+        "if (1 * [a]) {}"
     ],
     invalid: [
         { code: "for(;true;);", errors: [{ messageId: "unexpected", type: "Literal" }] },
@@ -262,8 +272,6 @@ ruleTester.run("no-constant-condition", rule, {
 
         // #5228 , typeof conditions
         { code: "if(typeof x){}", errors: [{ messageId: "unexpected", type: "UnaryExpression" }] },
-        { code: "if(`${typeof x}`){}", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
-        { code: "if(`${''}${typeof x}`){}", errors: [{ messageId: "unexpected", type: "TemplateLiteral" }] },
         { code: "if(typeof 'abc' === 'string'){}", errors: [{ messageId: "unexpected", type: "BinaryExpression" }] },
         { code: "if(a = typeof b){}", errors: [{ messageId: "unexpected", type: "AssignmentExpression" }] },
         { code: "if(a, typeof b){}", errors: [{ messageId: "unexpected", type: "SequenceExpression" }] },
@@ -358,18 +366,6 @@ ruleTester.run("no-constant-condition", rule, {
             code: "if(''+[]) {}",
             errors: [{ messageId: "unexpected", type: "BinaryExpression" }]
         },
-        {
-            code: "if([a]==[a]) {}",
-            errors: [{ messageId: "unexpected", type: "BinaryExpression" }]
-        },
-        {
-            code: "if([a] - '') {}",
-            errors: [{ messageId: "unexpected", type: "BinaryExpression" }]
-        },
-        {
-            code: "if(+[a]) {}",
-            errors: [{ messageId: "unexpected", type: "UnaryExpression" }]
-        },
         {
             code: "if(+1) {}",
             errors: [{ messageId: "unexpected", type: "UnaryExpression" }]
@@ -397,7 +393,9 @@ ruleTester.run("no-constant-condition", rule, {
         // Boxed primitives are always truthy
         { code: "if(new Boolean(foo)) {}", errors: [{ messageId: "unexpected" }] },
         { code: "if(new String(foo)) {}", errors: [{ messageId: "unexpected" }] },
-        { code: "if(new Number(foo)) {}", errors: [{ messageId: "unexpected" }] }
+        { code: "if(new Number(foo)) {}", errors: [{ messageId: "unexpected" }] },
 
+        // Spreading a constant array
+        { code: "if(`${[...['a']]}`) {}", errors: [{ messageId: "unexpected" }] }
     ]
 });
diff --git a/tests/lib/rules/no-restricted-imports.js b/tests/lib/rules/no-restricted-imports.js
index c86e67ecf..3cf7eb371 100644
--- a/tests/lib/rules/no-restricted-imports.js
+++ b/tests/lib/rules/no-restricted-imports.js
@@ -16,7 +16,7 @@ const rule = require("../../../lib/rules/no-restricted-imports"),
 // Tests
 //------------------------------------------------------------------------------
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2020, sourceType: "module" } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2022, sourceType: "module" } });
 
 ruleTester.run("no-restricted-imports", rule, {
     valid: [
@@ -92,6 +92,34 @@ ruleTester.run("no-restricted-imports", rule, {
                 }]
             }]
         },
+        {
+            code: "import { 'AllowedObject' as bar } from \"foo\";",
+            options: [{
+                paths: [{
+                    name: "foo",
+                    importNames: ["DisallowedObject"],
+                    message: "Please import 'DisallowedObject' from /bar/ instead."
+                }]
+            }]
+        },
+        {
+            code: "import { ' ' as bar } from \"foo\";",
+            options: [{
+                paths: [{
+                    name: "foo",
+                    importNames: [""]
+                }]
+            }]
+        },
+        {
+            code: "import { '' as bar } from \"foo\";",
+            options: [{
+                paths: [{
+                    name: "foo",
+                    importNames: [" "]
+                }]
+            }]
+        },
         {
             code: "import { DisallowedObject } from \"foo\";",
             options: [{
@@ -112,6 +140,16 @@ ruleTester.run("no-restricted-imports", rule, {
                 }]
             }]
         },
+        {
+            code: "import { 'AllowedObject' as DisallowedObject } from \"foo\";",
+            options: [{
+                paths: [{
+                    name: "foo",
+                    importNames: ["DisallowedObject"],
+                    message: "Please import 'DisallowedObject' from /bar/ instead."
+                }]
+            }]
+        },
         {
             code: "import { AllowedObject, AllowedObjectTwo } from \"foo\";",
             options: [{
@@ -196,6 +234,24 @@ ruleTester.run("no-restricted-imports", rule, {
                 name: "bar",
                 importNames: ["DisallowedObject"]
             }]
+        },
+        {
+            code: "export { 'AllowedObject' } from \"foo\";",
+            options: [{
+                paths: [{
+                    name: "foo",
+                    importNames: ["DisallowedObject"]
+                }]
+            }]
+        },
+        {
+            code: "export { 'AllowedObject' as DisallowedObject } from \"foo\";",
+            options: [{
+                paths: [{
+                    name: "foo",
+                    importNames: ["DisallowedObject"]
+                }]
+            }]
         }
     ],
     invalid: [{
@@ -344,6 +400,70 @@ ruleTester.run("no-restricted-imports", rule, {
             column: 9,
             endColumn: 17
         }]
+    }, {
+        code: "export {'foo' as b} from \"fs\";",
+        options: [{
+            paths: [{
+                name: "fs",
+                importNames: ["foo"],
+                message: "Don't import 'foo'."
+            }]
+        }],
+        errors: [{
+            message: "'foo' import from 'fs' is restricted. Don't import 'foo'.",
+            type: "ExportNamedDeclaration",
+            line: 1,
+            column: 9,
+            endColumn: 19
+        }]
+    }, {
+        code: "export {'foo'} from \"fs\";",
+        options: [{
+            paths: [{
+                name: "fs",
+                importNames: ["foo"],
+                message: "Don't import 'foo'."
+            }]
+        }],
+        errors: [{
+            message: "'foo' import from 'fs' is restricted. Don't import 'foo'.",
+            type: "ExportNamedDeclaration",
+            line: 1,
+            column: 9,
+            endColumn: 14
+        }]
+    }, {
+        code: "export {'👍'} from \"fs\";",
+        options: [{
+            paths: [{
+                name: "fs",
+                importNames: ["👍"],
+                message: "Don't import '👍'."
+            }]
+        }],
+        errors: [{
+            message: "'👍' import from 'fs' is restricted. Don't import '👍'.",
+            type: "ExportNamedDeclaration",
+            line: 1,
+            column: 9,
+            endColumn: 13
+        }]
+    }, {
+        code: "export {''} from \"fs\";",
+        options: [{
+            paths: [{
+                name: "fs",
+                importNames: [""],
+                message: "Don't import ''."
+            }]
+        }],
+        errors: [{
+            message: "'' import from 'fs' is restricted. Don't import ''.",
+            type: "ExportNamedDeclaration",
+            line: 1,
+            column: 9,
+            endColumn: 11
+        }]
     }, {
         code: "export * as ns from \"fs\";",
         options: [{
@@ -505,6 +625,55 @@ ruleTester.run("no-restricted-imports", rule, {
             endColumn: 43
         }]
     },
+    {
+        code: "import { 'DisallowedObject' as AllowedObject } from \"foo\";",
+        options: [{
+            paths: [{
+                name: "foo",
+                importNames: ["DisallowedObject"],
+                message: "Please import 'DisallowedObject' from /bar/ instead."
+            }]
+        }],
+        errors: [{
+            message: "'DisallowedObject' import from 'foo' is restricted. Please import 'DisallowedObject' from /bar/ instead.",
+            type: "ImportDeclaration",
+            line: 1,
+            column: 10,
+            endColumn: 45
+        }]
+    },
+    {
+        code: "import { '👍' as bar } from \"foo\";",
+        options: [{
+            paths: [{
+                name: "foo",
+                importNames: ["👍"]
+            }]
+        }],
+        errors: [{
+            message: "'👍' import from 'foo' is restricted.",
+            type: "ImportDeclaration",
+            line: 1,
+            column: 10,
+            endColumn: 21
+        }]
+    },
+    {
+        code: "import { '' as bar } from \"foo\";",
+        options: [{
+            paths: [{
+                name: "foo",
+                importNames: [""]
+            }]
+        }],
+        errors: [{
+            message: "'' import from 'foo' is restricted.",
+            type: "ImportDeclaration",
+            line: 1,
+            column: 10,
+            endColumn: 19
+        }]
+    },
     {
         code: "import { AllowedObject, DisallowedObject } from \"foo\";",
         options: [{
diff --git a/tests/lib/rules/no-useless-rename.js b/tests/lib/rules/no-useless-rename.js
index 66077f2b4..262cf2cc5 100644
--- a/tests/lib/rules/no-useless-rename.js
+++ b/tests/lib/rules/no-useless-rename.js
@@ -47,11 +47,43 @@ ruleTester.run("no-useless-rename", rule, {
         "import {foo} from 'foo';",
         "import {foo as bar} from 'foo';",
         "import {foo as bar, baz as qux} from 'foo';",
+        {
+            code: "import {'foo' as bar} from 'baz';",
+            parserOptions: { ecmaVersion: 2022 }
+        },
         "export {foo} from 'foo';",
         "var foo = 0;export {foo as bar};",
         "var foo = 0; var baz = 0; export {foo as bar, baz as qux};",
         "export {foo as bar} from 'foo';",
         "export {foo as bar, baz as qux} from 'foo';",
+        {
+            code: "var foo = 0; export {foo as 'bar'};",
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "export {foo as 'bar'} from 'baz';",
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "export {'foo' as bar} from 'baz';",
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "export {'foo' as 'bar'} from 'baz';",
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "export {'' as ' '} from 'baz';",
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "export {' ' as ''} from 'baz';",
+            parserOptions: { ecmaVersion: 2022 }
+        },
+        {
+            code: "export {'foo'} from 'bar';",
+            parserOptions: { ecmaVersion: 2022 }
+        },
         {
             code: "const {...stuff} = myObject;",
             parserOptions: { ecmaVersion: 2018 }
@@ -380,6 +412,12 @@ ruleTester.run("no-useless-rename", rule, {
             output: "import {foo} from 'foo';",
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Import", name: "foo" } }]
         },
+        {
+            code: "import {'foo' as foo} from 'foo';",
+            output: "import {foo} from 'foo';",
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Import", name: "foo" } }]
+        },
         {
             code: "import {\\u0061 as a} from 'foo';",
             output: "import {a} from 'foo';",
@@ -418,6 +456,42 @@ ruleTester.run("no-useless-rename", rule, {
             output: "var foo = 0; export {foo};",
             errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "foo" } }]
         },
+        {
+            code: "var foo = 0; export {foo as 'foo'};",
+            output: "var foo = 0; export {foo};",
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "foo" } }]
+        },
+        {
+            code: "export {foo as 'foo'} from 'bar';",
+            output: "export {foo} from 'bar';",
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "foo" } }]
+        },
+        {
+            code: "export {'foo' as foo} from 'bar';",
+            output: "export {'foo'} from 'bar';",
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "foo" } }]
+        },
+        {
+            code: "export {'foo' as 'foo'} from 'bar';",
+            output: "export {'foo'} from 'bar';",
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "foo" } }]
+        },
+        {
+            code: "export {' 👍 ' as ' 👍 '} from 'bar';",
+            output: "export {' 👍 '} from 'bar';",
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: " 👍 " } }]
+        },
+        {
+            code: "export {'' as ''} from 'bar';",
+            output: "export {''} from 'bar';",
+            parserOptions: { ecmaVersion: 2022 },
+            errors: [{ messageId: "unnecessarilyRenamed", data: { type: "Export", name: "" } }]
+        },
         {
             code: "var a = 0; export {a as \\u0061};",
             output: "var a = 0; export {a};",