Repository: swc-project/swc (rust)

Issue:
When `let` => `var`, a scope error can cause the variable to be incorrectly overwritten
### Describe the bug

`var` in block statement will overwrite the variable which has a same name,
defined in identifier label. Like the sample code below. But `let` won't.
So when convert code like that, please change the variable name to make them
separated.

### Input code

```typescript
let a = 10;
for (let b = 0; b < a; b++) {
    let c = 0, b = 10, d = 100;
    console.log(b);
}
```


### Config

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


### Playground link

https://play.swc.rs/?version=1.2.118&code=H4sIAAAAAAAAA8tJLVFIVLBVMDSw5uVKyy9S0MgBiiQBRQysgZSNQiKQ0tbWVKjm5VIAApBsMkhWB6zIEEingGmQfpCC5Py84vycVL2c%2FHSNJE2gYC0vFwApdniYZQAAAA%3D%3D&config=H4sIAAAAAAAAA0WNOw4CMQxE7%2BI6LRQ5AQ2HsIJ3FZSfbK%2BUKNq7k0CAdmbemw5PcWA7hJyFwG4YhAxQVeKE4UahEAtY5WPEBVmI51xaUqxggVxEceyLghmuuhSnAUXeSedELqOLPvmtTdblWJhEfm8R0x6%2B5%2BdAY34cM%2BigrdBbcYXz71icl%2FsafsgXAm4Ch80AAAA%3D

### Expected behavior

```
var a = 10;
for(var b = 0; b < a; b++){
    var c = 0, b1 = 10, d = 100;
    console.log(b1);
}
```

### Version

1.2.118

### Additional context

_No response_

Pull request:
test(es/transforms/compat): update test cases for block_scoping
<!-- Thanks for submitting a pull request! Please provide enough information so that others can review your pull request. -->

**Description:**

This PR attempts to fix #2998 by assigning a new ident if same one found in loop nodes. To avoid re-renaming loop node's init, PR introduces new flag to let visit_mut_var_decl indicate if it's in loop's body or not and only reassign new ident in loop body and there are existing ident.

**Related issue (if exists):**

- closes #2998


Patch:
diff --git a/crates/swc_ecma_transforms_compat/src/es2015/block_scoping.rs b/crates/swc_ecma_transforms_compat/src/es2015/block_scoping.rs
index ceca67a98a..f34c449791 100644
--- a/crates/swc_ecma_transforms_compat/src/es2015/block_scoping.rs
+++ b/crates/swc_ecma_transforms_compat/src/es2015/block_scoping.rs
@@ -30,6 +30,7 @@ pub fn block_scoping() -> impl Fold {
         scope: Default::default(),
         vars: vec![],
         var_decl_kind: VarDeclKind::Var,
+        in_loop_body_scope: false,
     })
 }
 
@@ -54,6 +55,7 @@ struct BlockScoping {
     scope: ScopeStack,
     vars: Vec<VarDeclarator>,
     var_decl_kind: VarDeclKind,
+    in_loop_body_scope: bool,
 }
 
 impl BlockScoping {
@@ -71,7 +73,9 @@ impl BlockScoping {
         };
         self.scope.push(kind);
 
+        self.in_loop_body_scope = true;
         node.visit_mut_with(self);
+        self.in_loop_body_scope = false;
 
         if remove {
             self.scope.truncate(len);
@@ -557,6 +561,23 @@ impl VisitMut for BlockScoping {
         self.var_decl_kind = old;
 
         var.kind = VarDeclKind::Var;
+
+        if !self.in_loop_body_scope {
+            return;
+        }
+
+        // If loop body contains same ident to loop node's ident, rename it to avoid
+        // variable hoisting overwrites inner declaration.
+        for decl in var.decls.iter_mut() {
+            if let Pat::Ident(name) = &mut decl.name {
+                if let Some(ScopeKind::ForLetLoop { args, .. }) = self.scope.last() {
+                    let id = &(*name).id.to_id();
+                    if args.contains(id) {
+                        (*name).id = private_ident!((*name).id.take().sym);
+                    }
+                }
+            }
+        }
     }
 
     fn visit_mut_var_declarator(&mut self, var: &mut VarDeclarator) {


Test patch:
diff --git a/crates/swc_ecma_transforms_compat/tests/es2015_block_scoping.rs b/crates/swc_ecma_transforms_compat/tests/es2015_block_scoping.rs
index 35ec94fe30..84fc19b317 100644
--- a/crates/swc_ecma_transforms_compat/tests/es2015_block_scoping.rs
+++ b/crates/swc_ecma_transforms_compat/tests/es2015_block_scoping.rs
@@ -956,3 +956,51 @@ test!(
     for(var key in keys)_loop(key);
     "
 );
+
+test!(
+    ::swc_ecma_parser::Syntax::default(),
+    |_| block_scoping(),
+    issue_2998_1,
+    "
+    let a = 5;
+for (let b = 0; b < a; b++) {
+    let c = 0, b = 10, d = 100;
+    console.log(b);
+}
+    ",
+    "
+    var a = 5;
+for(var b = 0; b < a; b++){
+    var c = 0, b1 = 10, d = 100;
+    console.log(b);
+}
+    "
+);
+
+test!(
+    ::swc_ecma_parser::Syntax::default(),
+    |_| block_scoping(),
+    issue_2998_2,
+    "
+    for (var a; ;) { }
+    for (var a = ['a', 'b']; ;) { }
+    ",
+    "
+    for (var a; ;) { }
+    for (var a = ['a', 'b']; ;) { }
+    "
+);
+
+test_exec!(
+    ::swc_ecma_parser::Syntax::default(),
+    |_| block_scoping(),
+    issue_2998_3,
+    "let a = 5;
+const expected = [];
+for (let b = 0; b < a; b++) {
+    let c = 0, b = 10, d = 100;
+    expected.push(b);
+}
+expect(expected).toEqual([0,1,2,3,4]);
+"
+);