Repository: swc-project/swc (rust)

Issue:
Mangling redeclares variable in destructuring when `eval()` is present
### Describe the bug

When `eval()` is present, the SWC minifier may redeclare a variable that has already been declared, leading to a syntax error when the JavaScript is parsed by the browser.

This only occurs when `eval()` is present, and it only occurs with `{ mangle: true }`.

### Input code

```typescript
export function func(a, b) {
  const { c: d } = a;
}
eval('');
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es2015",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": {
        "toplevel": false,
        "keep_classnames": false,
        "keep_fnames": false,
        "keep_private_props": false,
        "ie8": false,
        "safari10": false
      }
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true
}
```


### Playground link

https://play.swc.rs/?version=1.3.50&code=H4sIAAAAAAAAA0utKMgvKlFIK81LLsnMzwMzNBJ1FJI0Faq5FBSS8%2FOKSxSqFZKtFFIUahVsFRKtuWq5UssSczTU1TWtAUyFWmI%2BAAAA&config=H4sIAAAAAAAAA32QSw7CMAxE9z1F5TULQAIh7sAZkBWcKiVprDhUVIi7k34C5SM2%2Bcy8iZW5FWUJtSjYl7d0TBfGIBSe96RI10S8JgVIORQVDEdYZLeW3tJohQbpPjoQMVQUh5Ssl6vNlADrvVBOTJozjdHdfKbyjgOJvIM9ik1laYb2ozxbasl%2Bwsk6E%2FFRWRRp0NHXc5nQf10OpsVIaff8CzK0%2B6EKagxmtXxrJ%2FVT5HVoCpw%2FXWYfgtgxja1t4QXlgp5DwMghJ2O4UHF%2FAE8y38fLAQAA

### Expected behavior

The variable should not be redeclared, and there should be no syntax error.

### Actual behavior

The output is:

```js
export function func(c, n) {
    const { c: n  } = c;
}
eval('');
```

Note that the variable `n` is redeclared. If you run this in a browser you will get the error:

    SyntaxError: Identifier 'n' has already been declared

Here is [a repro](https://codepen.io/nolanlawson-the-selector/pen/WNarXmB) you can load in a browser.

You can also verify with ESLint. [Here is a repro in the ESLint Playground](https://eslint.org/play/#eyJ0ZXh0IjoiZXhwb3J0IGZ1bmN0aW9uIGZ1bmMoYywgbikge1xuICAgIGNvbnN0IHsgYzogbiAgfSA9IGM7XG59XG5ldmFsKCcnKTtcbiIsIm9wdGlvbnMiOnsicGFyc2VyT3B0aW9ucyI6eyJlY21hVmVyc2lvbiI6ImxhdGVzdCIsInNvdXJjZVR5cGUiOiJtb2R1bGUiLCJlY21hRmVhdHVyZXMiOnt9fSwicnVsZXMiOnsiY29uc3RydWN0b3Itc3VwZXIiOlsiZXJyb3IiXSwiZm9yLWRpcmVjdGlvbiI6WyJlcnJvciJdLCJnZXR0ZXItcmV0dXJuIjpbImVycm9yIl0sIm5vLWFzeW5jLXByb21pc2UtZXhlY3V0b3IiOlsiZXJyb3IiXSwibm8tY2FzZS1kZWNsYXJhdGlvbnMiOlsiZXJyb3IiXSwibm8tY2xhc3MtYXNzaWduIjpbImVycm9yIl0sIm5vLWNvbXBhcmUtbmVnLXplcm8iOlsiZXJyb3IiXSwibm8tY29uZC1hc3NpZ24iOlsiZXJyb3IiXSwibm8tY29uc3QtYXNzaWduIjpbImVycm9yIl0sIm5vLWNvbnN0YW50LWNvbmRpdGlvbiI6WyJlcnJvciJdLCJuby1jb250cm9sLXJlZ2V4IjpbImVycm9yIl0sIm5vLWRlYnVnZ2VyIjpbImVycm9yIl0sIm5vLWRlbGV0ZS12YXIiOlsiZXJyb3IiXSwibm8tZHVwZS1hcmdzIjpbImVycm9yIl0sIm5vLWR1cGUtY2xhc3MtbWVtYmVycyI6WyJlcnJvciJdLCJuby1kdXBlLWVsc2UtaWYiOlsiZXJyb3IiXSwibm8tZHVwZS1rZXlzIjpbImVycm9yIl0sIm5vLWR1cGxpY2F0ZS1jYXNlIjpbImVycm9yIl0sIm5vLWVtcHR5IjpbImVycm9yIl0sIm5vLWVtcHR5LWNoYXJhY3Rlci1jbGFzcyI6WyJlcnJvciJdLCJuby1lbXB0eS1wYXR0ZXJuIjpbImVycm9yIl0sIm5vLWV4LWFzc2lnbiI6WyJlcnJvciJdLCJuby1leHRyYS1ib29sZWFuLWNhc3QiOlsiZXJyb3IiXSwibm8tZXh0cmEtc2VtaSI6WyJlcnJvciJdLCJuby1mYWxsdGhyb3VnaCI6WyJlcnJvciJdLCJuby1mdW5jLWFzc2lnbiI6WyJlcnJvciJdLCJuby1nbG9iYWwtYXNzaWduIjpbImVycm9yIl0sIm5vLWltcG9ydC1hc3NpZ24iOlsiZXJyb3IiXSwibm8taW5uZXItZGVjbGFyYXRpb25zIjpbImVycm9yIl0sIm5vLWludmFsaWQtcmVnZXhwIjpbImVycm9yIl0sIm5vLWlycmVndWxhci13aGl0ZXNwYWNlIjpbImVycm9yIl0sIm5vLWxvc3Mtb2YtcHJlY2lzaW9uIjpbImVycm9yIl0sIm5vLW1pc2xlYWRpbmctY2hhcmFjdGVyLWNsYXNzIjpbImVycm9yIl0sIm5vLW1peGVkLXNwYWNlcy1hbmQtdGFicyI6WyJlcnJvciJdLCJuby1uZXctc3ltYm9sIjpbImVycm9yIl0sIm5vLW5vbm9jdGFsLWRlY2ltYWwtZXNjYXBlIjpbImVycm9yIl0sIm5vLW9iai1jYWxscyI6WyJlcnJvciJdLCJuby1vY3RhbCI6WyJlcnJvciJdLCJuby1wcm90b3R5cGUtYnVpbHRpbnMiOlsiZXJyb3IiXSwibm8tcmVkZWNsYXJlIjpbImVycm9yIl0sIm5vLXJlZ2V4LXNwYWNlcyI6WyJlcnJvciJdLCJuby1zZWxmLWFzc2lnbiI6WyJlcnJvciJdLCJuby1zZXR0ZXItcmV0dXJuIjpbImVycm9yIl0sIm5vLXNoYWRvdy1yZXN0cmljdGVkLW5hbWVzIjpbImVycm9yIl0sIm5vLXNwYXJzZS1hcnJheXMiOlsiZXJyb3IiXSwibm8tdGhpcy1iZWZvcmUtc3VwZXIiOlsiZXJyb3IiXSwibm8tdW5kZWYiOlsiZXJyb3IiXSwibm8tdW5leHBlY3RlZC1tdWx0aWxpbmUiOlsiZXJyb3IiXSwibm8tdW5yZWFjaGFibGUiOlsiZXJyb3IiXSwibm8tdW5zYWZlLWZpbmFsbHkiOlsiZXJyb3IiXSwibm8tdW5zYWZlLW5lZ2F0aW9uIjpbImVycm9yIl0sIm5vLXVuc2FmZS1vcHRpb25hbC1jaGFpbmluZyI6WyJlcnJvciJdLCJuby11bnVzZWQtbGFiZWxzIjpbImVycm9yIl0sIm5vLXVudXNlZC12YXJzIjpbImVycm9yIl0sIm5vLXVzZWxlc3MtYmFja3JlZmVyZW5jZSI6WyJlcnJvciJdLCJuby11c2VsZXNzLWNhdGNoIjpbImVycm9yIl0sIm5vLXVzZWxlc3MtZXNjYXBlIjpbImVycm9yIl0sIm5vLXdpdGgiOlsiZXJyb3IiXSwicmVxdWlyZS15aWVsZCI6WyJlcnJvciJdLCJ1c2UtaXNuYW4iOlsiZXJyb3IiXSwidmFsaWQtdHlwZW9mIjpbImVycm9yIl19LCJlbnYiOnsiZXM2Ijp0cnVlfX19).

### Version

1.3.50

### Additional context

Possibly related to #7253.

For comparison, [Terser](https://try.terser.org/) does not have this issue:

![Screenshot of Terser playground showing the proper syntax output](https://user-images.githubusercontent.com/283842/231819922-7df1d3f2-8ca5-4cd3-be2c-4d698f7f19a7.png)

Pull request description:
fix(es/renamer): Ensure that param and function body are in same scope
<!-- Note: CI script will automatically rebase your PR so please do not rebase unless required -->

<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**Description:**

The probleam arises in L235 of swc_ecma_transforms_base/src/rename/mod.rs
```rs
unit!(visit_mut_fn_decl, FnDecl, true);
```

which calls `get_map` and evals to
```rs
node.visit_children_with(&mut v);
```
with `FnDecl` and `Analyzer` in L132. However in `Analyzer`, a visit to raw function was not overloaded, so function arguments and function body are considered different scope.

**BREAKING CHANGE:**

<!--
If this PR introduces a breaking change, it must contain a notice for it to be included in the CHANGELOG. Add description or remove entirely if not breaking.

You may need to update `MIGRATION.md` for the breaking changes.
-->

**Related issue (if exists):**
closes #7261 

Patch:
diff --git a/crates/swc_ecma_transforms_base/src/rename/analyzer/mod.rs b/crates/swc_ecma_transforms_base/src/rename/analyzer/mod.rs
index c0b7dba069..4960292ccc 100644
--- a/crates/swc_ecma_transforms_base/src/rename/analyzer/mod.rs
+++ b/crates/swc_ecma_transforms_base/src/rename/analyzer/mod.rs
@@ -235,11 +235,7 @@ impl Visit for Analyzer {
                     self.add_decl(id.to_id(), true);
                 }
 
-                self.with_fn_scope(|v| {
-                    f.function.decorators.visit_with(v);
-                    f.function.params.visit_with(v);
-                    v.visit_fn_body_within_same_scope(&f.function.body);
-                })
+                f.function.visit_with(self)
             }
             DefaultDecl::TsInterfaceDecl(_) => {}
         }
@@ -305,14 +301,19 @@ impl Visit for Analyzer {
                 });
             })
         } else {
-            self.with_fn_scope(|v| {
-                f.function.decorators.visit_with(v);
-                f.function.params.visit_with(v);
-                v.visit_fn_body_within_same_scope(&f.function.body);
-            })
+            f.function.visit_with(self)
         }
     }
 
+    // ensure param and function body always in same scope
+    fn visit_function(&mut self, f: &Function) {
+        self.with_fn_scope(|v| {
+            f.decorators.visit_with(v);
+            f.params.visit_with(v);
+            v.visit_fn_body_within_same_scope(&f.body);
+        })
+    }
+
     fn visit_for_in_stmt(&mut self, n: &ForInStmt) {
         self.with_scope(ScopeKind::Block, |v| {
             n.left.visit_with(v);
@@ -370,11 +371,7 @@ impl Visit for Analyzer {
     fn visit_method_prop(&mut self, f: &MethodProp) {
         f.key.visit_with(self);
 
-        self.with_fn_scope(|v| {
-            f.function.decorators.visit_with(v);
-            f.function.params.visit_with(v);
-            v.visit_fn_body_within_same_scope(&f.function.body);
-        })
+        f.function.visit_with(self)
     }
 
     fn visit_named_export(&mut self, n: &NamedExport) {
diff --git a/crates/swc_ecma_transforms_base/src/rename/mod.rs b/crates/swc_ecma_transforms_base/src/rename/mod.rs
index 25fca6f9f3..250954c487 100644
--- a/crates/swc_ecma_transforms_base/src/rename/mod.rs
+++ b/crates/swc_ecma_transforms_base/src/rename/mod.rs
@@ -113,7 +113,7 @@ where
         &self,
         node: &N,
         skip_one: bool,
-        is_module_or_script: bool,
+        top_level: bool,
         has_eval: bool,
     ) -> AHashMap<Id, JsWord>
     where
@@ -139,7 +139,7 @@ where
 
         let mut map = RenameMap::default();
 
-        let mut unresolved = if !is_module_or_script {
+        let mut unresolved = if !top_level {
             let mut unresolved = self.unresolved.clone();
             unresolved.extend(self.get_unresolved(node, has_eval));
             Cow::Owned(unresolved)


Test patch:
diff --git a/crates/swc/tests/tsc-references/privateNameStaticsAndStaticMethods.1.normal.js b/crates/swc/tests/tsc-references/privateNameStaticsAndStaticMethods.1.normal.js
index c3829c48f2..c0fc3f9926 100644
--- a/crates/swc/tests/tsc-references/privateNameStaticsAndStaticMethods.1.normal.js
+++ b/crates/swc/tests/tsc-references/privateNameStaticsAndStaticMethods.1.normal.js
@@ -1,8 +1,8 @@
 //// [privateNameStaticsAndStaticMethods.ts]
 class A {
     static #foo(a) {}
-    static async #bar(a1) {}
-    static async *#baz(a2) {
+    static async #bar(a) {}
+    static async *#baz(a) {
         return 3;
     }
     static #_quux;
@@ -21,7 +21,7 @@ class A {
     }
 }
 class B extends A {
-    static #foo(a3) {}
+    static #foo(a) {}
     constructor(){
         super();
         B.#foo("str");
diff --git a/crates/swc/tests/tsc-references/privateNamesAndMethods.1.normal.js b/crates/swc/tests/tsc-references/privateNamesAndMethods.1.normal.js
index d03ca01481..41438d1cf9 100644
--- a/crates/swc/tests/tsc-references/privateNamesAndMethods.1.normal.js
+++ b/crates/swc/tests/tsc-references/privateNamesAndMethods.1.normal.js
@@ -1,8 +1,8 @@
 //// [privateNamesAndMethods.ts]
 class A {
     #foo(a) {}
-    async #bar(a1) {}
-    async *#baz(a2) {
+    async #bar(a) {}
+    async *#baz(a) {
         return 3;
     }
     #_quux;
@@ -21,7 +21,7 @@ class A {
     }
 }
 class B extends A {
-    #foo(a3) {}
+    #foo(a) {}
     constructor(){
         super();
         this.#foo("str");
diff --git a/crates/swc/tests/tsc-references/privateNamesAndStaticMethods.1.normal.js b/crates/swc/tests/tsc-references/privateNamesAndStaticMethods.1.normal.js
index 21a04a8ef3..be9e83b723 100644
--- a/crates/swc/tests/tsc-references/privateNamesAndStaticMethods.1.normal.js
+++ b/crates/swc/tests/tsc-references/privateNamesAndStaticMethods.1.normal.js
@@ -1,8 +1,8 @@
 //// [privateNamesAndStaticMethods.ts]
 class A {
     static #foo(a) {}
-    static async #bar(a1) {}
-    static async *#baz(a2) {
+    static async #bar(a) {}
+    static async *#baz(a) {
         return 3;
     }
     static #_quux;
@@ -21,7 +21,7 @@ class A {
     }
 }
 class B extends A {
-    static #foo(a3) {}
+    static #foo(a) {}
     constructor(){
         super();
         B.#foo("str");
diff --git a/crates/swc_ecma_minifier/tests/mangle/issue-7261/input.js b/crates/swc_ecma_minifier/tests/mangle/issue-7261/input.js
new file mode 100644
index 0000000000..467eea0435
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/mangle/issue-7261/input.js
@@ -0,0 +1,5 @@
+export function func(a, b) {
+    const { c: d } = a;
+    console.log(d);
+}
+eval("");
diff --git a/crates/swc_ecma_minifier/tests/mangle/issue-7261/output.js b/crates/swc_ecma_minifier/tests/mangle/issue-7261/output.js
new file mode 100644
index 0000000000..d25ff1999e
--- /dev/null
+++ b/crates/swc_ecma_minifier/tests/mangle/issue-7261/output.js
@@ -0,0 +1,5 @@
+export function func(o, c) {
+    const { c: n  } = o;
+    console.log(n);
+}
+eval("");
diff --git a/crates/swc_ecma_minifier/tests/terser/compress/properties/mangle_private_properties/output.js b/crates/swc_ecma_minifier/tests/terser/compress/properties/mangle_private_properties/output.js
index cf66467b46..302f96dca1 100644
--- a/crates/swc_ecma_minifier/tests/terser/compress/properties/mangle_private_properties/output.js
+++ b/crates/swc_ecma_minifier/tests/terser/compress/properties/mangle_private_properties/output.js
@@ -2,5 +2,5 @@ class s {
     #s = 123;
     #e() {}
     get #t() {}
-    set #t(e) {}
+    set #t(s) {}
 }
diff --git a/crates/swc_ecma_minifier/tests/terser/compress/properties/mangle_private_properties/output.mangleOnly.js b/crates/swc_ecma_minifier/tests/terser/compress/properties/mangle_private_properties/output.mangleOnly.js
index cf66467b46..302f96dca1 100644
--- a/crates/swc_ecma_minifier/tests/terser/compress/properties/mangle_private_properties/output.mangleOnly.js
+++ b/crates/swc_ecma_minifier/tests/terser/compress/properties/mangle_private_properties/output.mangleOnly.js
@@ -2,5 +2,5 @@ class s {
     #s = 123;
     #e() {}
     get #t() {}
-    set #t(e) {}
+    set #t(s) {}
 }