Repository: swc-project/swc (rust)

Issue:
lose this inside function declare
### Describe the bug

declare a function inside a for...of loop, the ```this``` inside function declare will be changed to ```_this```

### Input code

```typescript
class C{
    mount(){
        let overrideFucNames = ['addChild', 'addChildAt','removeChild','removeChildAt'];
        let cardWidth = 1275;
        for(let v of overrideFucNames)  
        {
            //when you put this inside the for...of loop, the 'this' inside function declare will lose
            let overrideTarget = this.$cardsBox;
            let origiFuc = overrideTarget[v];
            overrideTarget[v] = function(){
                if(this.width > cardWidth){
                    this.scaleX = this.scaleY =  (cardWidth / this.width).toFixed(2);   
                }
                origiFuc.apply(this, arguments);
            };
        }
        
    }
}
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "target": "es5",
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": false
}
```


### Playground link

https://play.swc.rs/?version=1.2.206&code=H4sIAAAAAAAAA21RTWvDMAy9D%2FYfdBgkgZCwwtghdLAVetxpsI3Sg4mVxuDEwXb6weh%2Fn%2Bw1i9NU%2BOAnPT19lZIZA6uf%2Bzsga1Tf2ji5IGcSLag9ai04rvvynTVoYAmbiHG%2BqoXkUQr%2F%2F1cbpZHGhhIusRBRdFtMlUum%2BafgtibJx8XzUxCulI4dZQ%2BqmnWQAIzMoFtneX6osYWT6qHrLdhaGBCtoWz6o9PNsow0pVJd6l2R40QDqerb0grVAsdSMo1wEFIS2%2BC0TriZD6Z3BJe%2BWvbgxjJv6ljcyNBiJ2gM4k6TN%2FvtFX0Wp5yhucmNBhNV7Osf%2FEZfxu3eIjvzbFMyiV9D7x59E4J4PE4Oo26SWbUWR%2BTxIikgvMNg57lrmDpjXSdPvssUaKy%2Bwdaa5Grwc4ADsb8fOej9AkM8RE23AgAA&config=H4sIAAAAAAAAA0WMSw7DIBBD7zJrtu0id%2BghRnQSUfHTmEpFiLsXGqrsLPv5NXrB0tYos0J0JtRY%2BEMbiQ0Mqy4XMgMb1c4e0g0V1kPKRHAbm08JslZDwUW312myKWQV4Jo4Hv5P9iEK6fmeRaNSs%2FyEd%2BqXY%2F0cHgs8n18oc7pftgAAAA%3D%3D

### Expected behavior

the ```this``` inside function declare remain unchanged

### Actual behavior

the ```this``` inside function declare changed to _this

### Version

1.2.206

### Additional context

_No response_

Pull request:
fix(es/compat): this in function with block scoped vars
<!-- 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:**

One could see that [babel](https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-block-scoping/src/index.ts#L768) uses `unwrapFunctionEnvironment` here too.

<!-- 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):**
closes #5048

Patch:
diff --git a/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs b/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs
index 665c9f0cfa..55d9be66c0 100644
--- a/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2015/block_scoping/mod.rs
@@ -10,8 +10,8 @@ use swc_common::{
 use swc_ecma_ast::*;
 use swc_ecma_transforms_base::helper;
 use swc_ecma_utils::{
-    contains_arguments, contains_this_expr, find_pat_ids, prepend_stmt, private_ident, quote_ident,
-    quote_str, undefined, var::VarCollector, ExprFactory, StmtLike,
+    find_pat_ids, function::FnEnvHoister, prepend_stmt, private_ident, quote_ident, quote_str,
+    undefined, var::VarCollector, ExprFactory, StmtLike,
 };
 use swc_ecma_visit::{
     as_folder, noop_visit_mut_type, noop_visit_type, visit_mut_obj_and_computed, Fold, Visit,
@@ -151,31 +151,10 @@ impl BlockScoping {
             if used.is_empty() {
                 return;
             }
-            let this = if contains_this_expr(body_stmt) {
-                let ident = private_ident!("_this");
-                self.vars.push(VarDeclarator {
-                    span: DUMMY_SP,
-                    name: ident.clone().into(),
-                    init: Some(Box::new(Expr::This(ThisExpr { span: DUMMY_SP }))),
-                    definite: false,
-                });
-                Some(ident)
-            } else {
-                None
-            };
 
-            let arguments = if contains_arguments(body_stmt) {
-                let ident = private_ident!("_arguments");
-                self.vars.push(VarDeclarator {
-                    span: DUMMY_SP,
-                    name: ident.clone().into(),
-                    init: Some(Box::new(Expr::Ident(quote_ident!("arguments")))),
-                    definite: false,
-                });
-                Some(ident)
-            } else {
-                None
-            };
+            let mut env_hoister = FnEnvHoister::default();
+            body_stmt.visit_mut_with(&mut env_hoister);
+            self.vars.extend(env_hoister.to_decl());
 
             let mut flow_helper = FlowHelper {
                 all: &args,
@@ -198,13 +177,11 @@ impl BlockScoping {
                 },
             };
 
-            if !flow_helper.mutated.is_empty() || this.is_some() || arguments.is_some() {
+            if !flow_helper.mutated.is_empty() {
                 let no_modification = flow_helper.mutated.is_empty();
                 let mut v = MutationHandler {
                     map: &mut flow_helper.mutated,
                     in_function: false,
-                    this,
-                    arguments,
                 };
 
                 // Modifies identifiers, and add reassignments to break / continue / return
@@ -911,8 +888,6 @@ impl VisitMut for FlowHelper<'_> {
 struct MutationHandler<'a> {
     map: &'a mut AHashMap<Id, SyntaxContext>,
     in_function: bool,
-    this: Option<Ident>,
-    arguments: Option<Ident>,
 }
 
 impl MutationHandler<'_> {
@@ -958,36 +933,13 @@ impl VisitMut for MutationHandler<'_> {
         self.in_function = old;
     }
 
-    fn visit_mut_expr(&mut self, n: &mut Expr) {
-        n.visit_mut_children_with(self);
-
-        match n {
-            Expr::This(this) => {
-                if let Some(remapped) = &self.this {
-                    *n = Expr::Ident(Ident::new(
-                        remapped.sym.clone(),
-                        this.span.with_ctxt(remapped.span.ctxt),
-                    ))
-                }
-            }
-            Expr::Ident(id) if id.sym == js_word!("arguments") => {
-                if let Some(arguments) = &self.arguments {
-                    *n = Expr::Ident(arguments.clone())
-                }
-            }
-            _ => {}
-        }
-    }
-
     fn visit_mut_function(&mut self, n: &mut Function) {
         let old = self.in_function;
-        let arguments = self.arguments.take();
         self.in_function = true;
 
         n.visit_mut_children_with(self);
 
         self.in_function = old;
-        self.arguments = arguments;
     }
 
     fn visit_mut_ident(&mut self, n: &mut Ident) {


Test patch:
diff --git a/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/1/input.js b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/1/input.js
new file mode 100644
index 0000000000..7e32c12685
--- /dev/null
+++ b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/1/input.js
@@ -0,0 +1,24 @@
+class C {
+    mount() {
+        let overrideFucNames = [
+            "addChild",
+            "addChildAt",
+            "removeChild",
+            "removeChildAt",
+        ];
+        let cardWidth = 1275;
+        for (let v of overrideFucNames) {
+            //when you put this inside the for...of loop, the 'this' inside function declare will lose
+            let overrideTarget = this.$cardsBox;
+            let origiFuc = overrideTarget[v];
+            overrideTarget[v] = function () {
+                if (this.width > cardWidth) {
+                    this.scaleX = this.scaleY = (
+                        cardWidth / this.width
+                    ).toFixed(2);
+                }
+                origiFuc.apply(this, arguments);
+            };
+        }
+    }
+}
diff --git a/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/1/output.js b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/1/output.js
new file mode 100644
index 0000000000..34b33ca583
--- /dev/null
+++ b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/1/output.js
@@ -0,0 +1,23 @@
+class C {
+    mount() {
+        var _this = this, _loop = function(v) {
+            //when you put this inside the for...of loop, the 'this' inside function declare will lose
+            var overrideTarget = _this.$cardsBox;
+            var origiFuc = overrideTarget[v];
+            overrideTarget[v] = function() {
+                if (this.width > cardWidth) {
+                    this.scaleX = this.scaleY = (cardWidth / this.width).toFixed(2);
+                }
+                origiFuc.apply(this, arguments);
+            };
+        };
+        var overrideFucNames = [
+            "addChild",
+            "addChildAt",
+            "removeChild",
+            "removeChildAt", 
+        ];
+        var cardWidth = 1275;
+        for (var v of overrideFucNames)_loop(v);
+    }
+}
diff --git a/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/2/input.js b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/2/input.js
new file mode 100644
index 0000000000..b4e0e4d23d
--- /dev/null
+++ b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/2/input.js
@@ -0,0 +1,13 @@
+class C extends D {
+    mount() {
+        let overrideFucNames = [
+            "addChild",
+            "addChildAt",
+            "removeChild",
+            "removeChildAt",
+        ];
+        for (let v of overrideFucNames) {
+            setTimeout(() => super.mount(v));
+        }
+    }
+}
diff --git a/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/2/output.js b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/2/output.js
new file mode 100644
index 0000000000..37172f6201
--- /dev/null
+++ b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-5048/2/output.js
@@ -0,0 +1,14 @@
+class C extends D {
+    mount() {
+        var _this = this, _superprop_get_mount = ()=>super.mount, _loop = function(v) {
+            setTimeout(()=>_superprop_get_mount().call(_this, v));
+        };
+        var overrideFucNames = [
+            "addChild",
+            "addChildAt",
+            "removeChild",
+            "removeChildAt", 
+        ];
+        for (var v of overrideFucNames)_loop(v);
+    }
+}