Repository: swc-project/swc (rust)

Issue:
??= operator in a method retains the lvalue from being GCed in the module global variable
### Describe the bug

When compiling TS to es2020 target, ??= operator "temporarily" saves the target object in a global module variable. The problem is that it is not "temporary": it retains this object in memory forever, it will never be garbage collected.

I stepped on it accidentally, while learning some anomalies in Node memory dump using Chrome memory snapshot profiler.

### Input code

```typescript
function method() {
  const obj = {};
  obj.prop ??= "x".repeat(10000000);
}

method();
// the large string should be eventually GCed along with the large string, but it will never happen in SWC+es2020
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "typescript",
      "decorators": true
    },
    "target": "es2020"
  },
  "module": {
    "type": "commonjs"
  }
}
```


### Playground link

https://play.swc.rs/?version=1.3.39&code=H4sIAAAAAAAAA2WMvQ6CMBSF9z7FiRNEA8hKCAODD%2BDgXOBKa2rbtBd%2FQnh3mxgnz3RyvnznutiRtbO4Eys3ZTlWAYzORoYbbmixbk1aUi98cB5d12L32hWBPEnOjtU3eSM2IX4njShLsCIYGWZC5KDtjKjcYiYMBHqQ5UUa88appwnSuMSfmtWfdcCwMDQnagxsMgOU9J4stMX50u8p1lVdfQAVjZi5yQAAAA%3D%3D&config=H4sIAAAAAAAAAxWMQQrAIAwE%2F7JnD8WjvxENpaU2kkSoiH%2Bv3padnR24NSEM1ChKspP21%2BKHAOuVNMlVDQ6ZEks0FkUwaTQdLMpJtoak%2FvAHVlU4t4f2y5YXSlwKv7dizh8GxEdwawAAAA%3D%3D

### Expected behavior

The compiled code should have looked like:

```
"use strict";
function method() {
    var _obj;
    const obj = {};
    (_obj = obj).prop ?? (_obj.prop = "x".repeat(10000000));
}
method();
```

I.e. the temporary _obj should've been created WITHIN the function, not in the global scope.

### Actual behavior

Notice that _obj is created at top level. Which means that the large string allocated (the one created during the very last call to method()) will never be garbage collected, even after `out` passed to method() loses all other references.

```
"use strict";
var _obj; // <-- WRONG
function method() {
    const obj = {};
    (_obj = obj).prop ?? (_obj.prop = "x".repeat(10000000));
}
method();
```

### Version

1.3.39

### Additional context

Obviously, ES2021 target fixes this, because ??= operator is implemented natively there.

It applies not only to functions, but to class methods too (and I suspect, to closures as well): https://play.swc.rs/?version=1.3.39&code=H4sIAAAAAAAAAy2MTQ7CIBSE95xi0hXdEN22aVh4AM9A8VnbVCBAoknD3X1gZzd%2F3%2BoyxaexhPu84RBAiD7oASnH1S2jKELY3aSE255a%2F6b88g%2Fp522on76FAHtVr9B6QvftVKRAJsvr5a9%2B5FlpOO9ShmXcBEefCpbccqBO9lHY%2FwBM7y%2B5mgAAAA%3D%3D&config=H4sIAAAAAAAAAxWMQQrAIAwE%2F7JnD8WjvxENpaU2kkSoiH%2Bv3padnR24NSEM1ChKspP21%2BKHAOuVNMlVDQ6ZEks0FkUwaTQdLMpJtoak%2FvAHVlU4t4f2y5YXSlwKv7dizh8GxEdwawAAAA%3D%3D

Pull request:
fix(es/compat): Insert the variable declaration nearest to the available statements
<!-- 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:**

<!-- Explain the **motivation** for making this change. What existing problem does the pull request solve? -->

**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):**
- fix #7064 

Patch:
diff --git a/crates/swc_ecma_transforms_compat/src/es2021/logical_assignments.rs b/crates/swc_ecma_transforms_compat/src/es2021/logical_assignments.rs
index f60091df81..bdcc8d3860 100644
--- a/crates/swc_ecma_transforms_compat/src/es2021/logical_assignments.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2021/logical_assignments.rs
@@ -174,12 +174,12 @@ impl VisitMut for Operators {
         }
     }
 
-    fn visit_mut_module(&mut self, n: &mut Module) {
+    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
         n.visit_mut_children_with(self);
 
         if !self.vars.is_empty() {
             prepend_stmt(
-                &mut n.body,
+                n,
                 VarDecl {
                     span: DUMMY_SP,
                     kind: VarDeclKind::Var,
@@ -191,12 +191,12 @@ impl VisitMut for Operators {
         }
     }
 
-    fn visit_mut_script(&mut self, n: &mut Script) {
+    fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
         n.visit_mut_children_with(self);
 
         if !self.vars.is_empty() {
             prepend_stmt(
-                &mut n.body,
+                n,
                 VarDecl {
                     span: DUMMY_SP,
                     kind: VarDeclKind::Var,


Test patch:
diff --git a/crates/swc/tests/fixture/issues-7xxx/7064/case1/input/.swcrc b/crates/swc/tests/fixture/issues-7xxx/7064/case1/input/.swcrc
new file mode 100644
index 0000000000..08bf530be8
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7064/case1/input/.swcrc
@@ -0,0 +1,12 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "typescript",
+            "decorators": true
+        },
+        "target": "es2020"
+    },
+    "module": {
+        "type": "commonjs"
+    }
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-7xxx/7064/case1/input/index.js b/crates/swc/tests/fixture/issues-7xxx/7064/case1/input/index.js
new file mode 100644
index 0000000000..20ce09d97c
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7064/case1/input/index.js
@@ -0,0 +1,7 @@
+function method() {
+    const obj = {};
+    obj.prop ??= "x".repeat(10000000);
+  }
+  
+  method();
+  // the large string should be eventually GCed along with the large string, but it will never happen in SWC+es2020
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-7xxx/7064/case1/output/index.js b/crates/swc/tests/fixture/issues-7xxx/7064/case1/output/index.js
new file mode 100644
index 0000000000..e887b45fcd
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7064/case1/output/index.js
@@ -0,0 +1,7 @@
+"use strict";
+function method() {
+    var _obj;
+    const obj = {};
+    (_obj = obj).prop ?? (_obj.prop = "x".repeat(10000000));
+}
+method(); // the large string should be eventually GCed along with the large string, but it will never happen in SWC+es2020