Repository: babel/babel (TypeScript)

Issue:
[Bug]: transform error when use two loops in one function with @babel/plugin-transform-block-scoping@7.20.9
### 💻

- [ ] Would you like to work on a fix?

### How are you using Babel?

Programmatic API (`babel.transform`, `babel.parse`)

### Input code

```
const test = (data) => {
  for (let i = 0; i < data.length; i++) {
    const x = data[i];
    data.find((item) => item === x);
  }
  for (let i = 0; i < data.length; i++) {
    const x = data[i];
    data.find((item) => item === x);
  }
}
```

### Configuration file name

.babelrc

### Configuration

{
  "presets": [
    "@babel/preset-env"
  ]
}

### Current and expected behavior

current output:
```
"use strict";

var test = function test(data) {
  var _loop = function _loop() {
    var x = data[i];
    data.find(function (item) {
      return item === x;
    });
  };
  for (var i = 0; i < data.length; i++) {
    _loop();
  }
  var _loop2 = function _loop2() {
    var x = data[i];
    data.find(function (item) {
      return item === x;
    });
  };
  for (var _i = 0; _i < data.length; _i++) {
    _loop2();
  }
};
```

the second `var x = data[i];` should be `var x = data[_i];`

### Environment

  System:
    OS: macOS 12.6.2
  Binaries:
    Node: 14.19.0 - /usr/local/bin/node
    Yarn: 1.22.17 - /usr/local/bin/yarn
    npm: 6.14.16 - /usr/local/bin/npm
  npmPackages:
    @babel/cli: ^7.17.6 => 7.17.6 
    @babel/core: ^7.20.7 => 7.20.7 
    @babel/node: ^7.16.8 => 7.16.8 
    @babel/plugin-transform-block-scoping: ^7.20.9 => 7.20.9 
    @babel/preset-env: ^7.16.11 => 7.16.11 

### Possible solution

_No response_

### Additional context

_No response_
[Bug]: transform error when use two loops in one function with @babel/plugin-transform-block-scoping@7.20.9
### 💻

- [ ] Would you like to work on a fix?

### How are you using Babel?

Programmatic API (`babel.transform`, `babel.parse`)

### Input code

```
const test = (data) => {
  for (let i = 0; i < data.length; i++) {
    const x = data[i];
    data.find((item) => item === x);
  }
  for (let i = 0; i < data.length; i++) {
    const x = data[i];
    data.find((item) => item === x);
  }
}
```

### Configuration file name

.babelrc

### Configuration

{
  "presets": [
    "@babel/preset-env"
  ]
}

### Current and expected behavior

current output:
```
"use strict";

var test = function test(data) {
  var _loop = function _loop() {
    var x = data[i];
    data.find(function (item) {
      return item === x;
    });
  };
  for (var i = 0; i < data.length; i++) {
    _loop();
  }
  var _loop2 = function _loop2() {
    var x = data[i];
    data.find(function (item) {
      return item === x;
    });
  };
  for (var _i = 0; _i < data.length; _i++) {
    _loop2();
  }
};
```

the second `var x = data[i];` should be `var x = data[_i];`

### Environment

  System:
    OS: macOS 12.6.2
  Binaries:
    Node: 14.19.0 - /usr/local/bin/node
    Yarn: 1.22.17 - /usr/local/bin/yarn
    npm: 6.14.16 - /usr/local/bin/npm
  npmPackages:
    @babel/cli: ^7.17.6 => 7.17.6 
    @babel/core: ^7.20.7 => 7.20.7 
    @babel/node: ^7.16.8 => 7.16.8 
    @babel/plugin-transform-block-scoping: ^7.20.9 => 7.20.9 
    @babel/preset-env: ^7.16.11 => 7.16.11 

### Possible solution

_No response_

### Additional context

_No response_


Hints:
Hey @Erucy! We really appreciate you taking the time to report an issue. The collaborators on this project attempt to help as many people as possible, but we're a limited number of volunteers, so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant [Slack community](https://babeljs.slack.com) that typically always has someone willing to help. You can sign-up [here](https://slack.babeljs.io/) for an invite.
Hey @Erucy! We really appreciate you taking the time to report an issue. The collaborators on this project attempt to help as many people as possible, but we're a limited number of volunteers, so it's possible this won't be addressed swiftly.

If you need any help, or just have general Babel or JavaScript questions, we have a vibrant [Slack community](https://babeljs.slack.com) that typically always has someone willing to help. You can sign-up [here](https://slack.babeljs.io/) for an invite.

Patch:
diff --git a/packages/babel-plugin-transform-block-scoping/src/index.ts b/packages/babel-plugin-transform-block-scoping/src/index.ts
--- a/packages/babel-plugin-transform-block-scoping/src/index.ts
+++ b/packages/babel-plugin-transform-block-scoping/src/index.ts
@@ -71,14 +71,15 @@ export default declare((api, opts: Options) => {
 
           if (headPath && isBlockScoped(headPath.node)) {
             const names = Object.keys(headPath.getBindingIdentifiers());
+            const headScope = headPath.scope;
 
-            for (const name of names) {
+            for (let name of names) {
               if (bodyScope?.hasOwnBinding(name)) continue; // shadowed
 
-              let binding = headPath.scope.getOwnBinding(name);
+              let binding = headScope.getOwnBinding(name);
               if (!binding) {
-                headPath.scope.crawl();
-                binding = headPath.scope.getOwnBinding(name);
+                headScope.crawl();
+                binding = headScope.getOwnBinding(name);
               }
               const { usages, capturedInClosure, hasConstantViolations } =
                 getUsageInBody(binding, path);
@@ -86,7 +87,16 @@ export default declare((api, opts: Options) => {
               if (capturedInClosure) {
                 markNeedsBodyWrap();
                 captured.push(name);
+              } else if (headScope.parent.hasBinding(name)) {
+                // If the binding is not captured, there is no need
+                // of adding it to the closure param. However, rename
+                // it if it shadows an outer binding, because the
+                // closure will be moved to an outer level.
+                const newName = headScope.generateUid(name);
+                headPath.scope.rename(name, newName);
+                name = newName;
               }
+
               if (isForStatement && hasConstantViolations) {
                 updatedBindingsUsages.set(name, usages);
               }
diff --git a/packages/babel-plugin-transform-block-scoping/src/loop.ts b/packages/babel-plugin-transform-block-scoping/src/loop.ts
--- a/packages/babel-plugin-transform-block-scoping/src/loop.ts
+++ b/packages/babel-plugin-transform-block-scoping/src/loop.ts
@@ -177,16 +177,9 @@ export function wrapLoopBody(
   const callArgs = [];
   const closureParams = [];
   const updater = [];
-  for (const name of captured) {
+  for (const [name, updatedUsage] of updatedBindingsUsages) {
     callArgs.push(t.identifier(name));
 
-    const updatedUsage = updatedBindingsUsages.get(name);
-    if (!updatedUsage) {
-      // Not updated, re-use the same name
-      closureParams.push(t.identifier(name));
-      continue;
-    }
-
     const innerName = loopPath.scope.generateUid(name);
     closureParams.push(t.identifier(innerName));
     updater.push(
@@ -194,6 +187,11 @@ export function wrapLoopBody(
     );
     for (const path of updatedUsage) path.replaceWith(t.identifier(innerName));
   }
+  for (const name of captured) {
+    if (updatedBindingsUsages.has(name)) continue; // already injected
+    callArgs.push(t.identifier(name));
+    closureParams.push(t.identifier(name));
+  }
 
   const id = loopPath.scope.generateUid("loop");
   const fn = t.functionExpression(


Test patch:
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/for-variable-update-different-captured/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/for-variable-update-different-captured/exec.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/for-variable-update-different-captured/exec.js
@@ -0,0 +1,9 @@
+let res = [];
+
+for (let i = 1; i < 6; i++) {
+  let y = i;
+  res.push((() => y)());
+  i++;
+}
+
+expect(res).toEqual([1, 3, 5]);
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/for-variable-update-different-captured/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/for-variable-update-different-captured/input.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/for-variable-update-different-captured/input.js
@@ -0,0 +1,5 @@
+for (let i = 4; i < 6; i++) {
+  let y = i;
+  () => y;
+  i++;
+}
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/for-variable-update-different-captured/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/for-variable-update-different-captured/output.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/for-variable-update-different-captured/output.js
@@ -0,0 +1,11 @@
+var _loop = function (_i) {
+  var y = _i;
+  (function () {
+    return y;
+  });
+  _i++;
+  i = _i;
+};
+for (var i = 4; i < 6; i++) {
+  _loop(i);
+}
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-capture/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-capture/exec.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-capture/exec.js
@@ -0,0 +1,8 @@
+let res = [];
+
+let i = 0;
+for (let i = 4; i < 6; i++) {
+  res.push((() => i)());
+}
+
+expect(res).toEqual([4, 5]);
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-capture/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-capture/input.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-capture/input.js
@@ -0,0 +1,4 @@
+let i;
+for (let i = 4; i < 6; i++) {
+  () => i;
+}
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-capture/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-capture/output.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-capture/output.js
@@ -0,0 +1,9 @@
+var i;
+var _loop = function (i) {
+  (function () {
+    return i;
+  });
+};
+for (var _i = 4; _i < 6; _i++) {
+  _loop(_i);
+}
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-update/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-update/exec.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-update/exec.js
@@ -0,0 +1,10 @@
+let res = [];
+
+let i = 0;
+for (let i = 1; i < 6; i++) {
+  let y = i;
+  res.push((() => y)());
+  i++;
+}
+
+expect(res).toEqual([1, 3, 5]);
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-update/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-update/input.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-update/input.js
@@ -0,0 +1,6 @@
+let i;
+for (let i = 4; i < 6; i++) {
+  let y = i;
+  () => y;
+  i++;
+}
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-update/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-update/output.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-and-update/output.js
@@ -0,0 +1,12 @@
+var i;
+var _loop = function (_i2) {
+  var y = _i2;
+  (function () {
+    return y;
+  });
+  _i2++;
+  _i = _i2;
+};
+for (var _i = 4; _i < 6; _i++) {
+  _loop(_i);
+}
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-original-sibling-scope/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-original-sibling-scope/exec.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-original-sibling-scope/exec.js
@@ -0,0 +1,13 @@
+let res = [];
+
+for (let i = 1; i < 3; i++) {
+  let x = i;
+  res.push((() => x)());
+}
+
+for (let i = 4; i < 6; i++) {
+  let y = i;
+  res.push((() => y)());
+}
+
+expect(res).toEqual([1, 2, 4, 5]);
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-original-sibling-scope/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-original-sibling-scope/input.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-original-sibling-scope/input.js
@@ -0,0 +1,9 @@
+for (let i = 1; i < 3; i++) {
+  let x = i;
+  () => x;
+}
+
+for (let i = 4; i < 6; i++) {
+  let y = i;
+  () => y;
+}
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-original-sibling-scope/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-original-sibling-scope/output.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow-original-sibling-scope/output.js
@@ -0,0 +1,18 @@
+var _loop = function () {
+  var x = i;
+  (function () {
+    return x;
+  });
+};
+for (var i = 1; i < 3; i++) {
+  _loop();
+}
+var _loop2 = function () {
+  var y = _i;
+  (function () {
+    return y;
+  });
+};
+for (var _i = 4; _i < 6; _i++) {
+  _loop2();
+}
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow/exec.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow/exec.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow/exec.js
@@ -0,0 +1,9 @@
+let res = [];
+
+let i = 0;
+for (let i = 4; i < 6; i++) {
+  let y = i;
+  res.push((() => y)());
+}
+
+expect(res).toEqual([4, 5]);
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow/input.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow/input.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow/input.js
@@ -0,0 +1,5 @@
+let i;
+for (let i = 4; i < 6; i++) {
+  let y = i;
+  () => y;
+}
diff --git a/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow/output.js b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow/output.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-block-scoping/test/fixtures/general/issue-15308-for-variable-shadow/output.js
@@ -0,0 +1,10 @@
+var i;
+var _loop = function () {
+  var y = _i;
+  (function () {
+    return y;
+  });
+};
+for (var _i = 4; _i < 6; _i++) {
+  _loop();
+}