Repository: babel/babel (TypeScript)

Issue:
[Bug]: `plugin-transform-object-super` incorrect output where object created in a loop
### 💻

- [ ] Would you like to work on a fix?

### How are you using Babel?

Programmatic API (`babel.transform`, `babel.parse`)

### Input code

```js
const objects = [];
for (const proto of [{x: 0}, {x: 1}]) {
  objects.push({
    __proto__: proto,
    foo() {
      return super.x
    }
  });
}
assert(objects[0].foo() === 0);
assert(objects[1].foo() === 1);
```

[REPL](https://babeljs.io/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=MYewdgzgLgBCBGArApsKEYF4YG0C6A3AFABmIATjABSiSwAO5IUIcJuA3gB4BcMADAF8ANDG58AjILwBKMURhwkqdADp6AVwgALKhwWKYAfSONmIE3zMthBxWRBU5-w4fLIoG8mBgQN9ZHJVLjsYQQNBGWJwgEMICECoKgQUNAgcfjxVBycsTGx-KKI4hPIklJV0iSycuXzsCSKgA&debug=false&forceAllTransforms=false&modules=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=&prettier=false&targets=&version=7.22.14&externalPlugins=%40babel%2Fplugin-transform-object-super%407.18.6&assumptions=%7B%7D)

### Configuration file name

_No response_

### Configuration

_No response_

### Current and expected behavior

In the original code, the assertions pass. When transformed with [@babel/plugin-transform-object-super](https://babeljs.io/docs/babel-plugin-transform-object-super), the first assertion fails.

The transformed output is:

```js
const objects = [];
for (const proto of [{x: 0}, {x: 1}]) {
  var _obj;
  objects.push(
    (_obj = {
      __proto__: proto,
      foo() {
        return _get(_getPrototypeOf(_obj), "x", this);
      }
    })
  );
}
assert(objects[0].foo() === 0);
assert(objects[1].foo() === 1);
```

### Environment

REPL

### Possible solution

The problem is that `_obj` for the 1st turn of the loop is over-written in 2nd turn, because `_obj` is not block scoped. Could be solved by changing `var _obj` to `let _obj`.

Please see [REPL](https://babeljs.io/repl#?browsers=defaults%2C%20not%20ie%2011%2C%20not%20ie_mob%2011&build=&builtIns=false&corejs=3.21&spec=false&loose=false&code_lz=MYewdgzgLgBCBGArApsKEYF4YG0C6A3AFABmIATjABSiSwAO5IUIcJuA3gB4BcMADAF8ANDG58AjILwBKMURhwkqdADp6AVwgALKhwWKYAfSONmIE3zMthBxWRBU5-w4fLIoG8mBgQN9ZHJVLjsYQQNBGWJwgEMICECoKgQUNAgcfjxVBycsTGx-KKI4hPIklJV0iSycuXzsCSKgA&debug=false&forceAllTransforms=false&modules=false&shippedProposals=false&circleciRepo=&evaluate=false&fileSize=false&timeTravel=false&sourceType=module&lineWrap=true&presets=&prettier=false&targets=&version=7.22.14&externalPlugins=%40babel%2Fplugin-transform-object-super%407.18.6&assumptions=%7B%7D).

### Additional context

_No response_


Hints:
Hey @overlookmotel! 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-object-super/src/index.ts b/packages/babel-plugin-transform-object-super/src/index.ts
--- a/packages/babel-plugin-transform-object-super/src/index.ts
+++ b/packages/babel-plugin-transform-object-super/src/index.ts
@@ -24,11 +24,30 @@ export default declare(api => {
       ? PACKAGE_JSON.version
       : 7,
   );
+  const newLets = new Set<{
+    scopePath: NodePath;
+    id: t.Identifier;
+  }>();
 
   return {
     name: "transform-object-super",
 
     visitor: {
+      Loop: {
+        exit(path) {
+          newLets.forEach(v => {
+            if (v.scopePath === path) {
+              path.scope.push({
+                id: v.id,
+                kind: "let",
+              });
+              path.scope.crawl();
+              path.requeue();
+              newLets.delete(v);
+            }
+          });
+        },
+      },
       ObjectExpression(path, state) {
         let objectRef: t.Identifier;
         const getObjectRef = () =>
@@ -41,7 +60,20 @@ export default declare(api => {
         });
 
         if (objectRef) {
-          path.scope.push({ id: t.cloneNode(objectRef) });
+          const scopePath = path.findParent(
+            p => p.isFunction() || p.isProgram() || p.isLoop(),
+          );
+          const useLet = scopePath.isLoop();
+          // For transform-block-scoping
+          if (useLet) {
+            newLets.add({ scopePath, id: t.cloneNode(objectRef) });
+          } else {
+            path.scope.push({
+              id: t.cloneNode(objectRef),
+              kind: "var",
+            });
+          }
+
           path.replaceWith(
             t.assignmentExpression("=", t.cloneNode(objectRef), path.node),
           );


Test patch:
diff --git a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/exec.js b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/exec.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/exec.js
@@ -0,0 +1,13 @@
+const objects = [];
+for (const proto of [{x: 0}, {x: 1}]) {
+  if(true) {
+    objects.push({
+      __proto__: proto,
+      foo() {
+        return super.x
+      }
+    });
+  }
+}
+expect(objects[0].foo()).toBe(0);
+expect(objects[1].foo()).toBe(1);
diff --git a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/input.js b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/input.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/input.js
@@ -0,0 +1,13 @@
+const objects = [];
+for (const proto of [{x: 0}, {x: 1}]) {
+  if(true) {
+    objects.push({
+      __proto__: proto,
+      foo() {
+        return super.x
+      }
+    });
+  }
+}
+expect(objects[0].foo()).toBe(0);
+expect(objects[1].foo()).toBe(1);
diff --git a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/options.json b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/options.json
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/options.json
@@ -0,0 +1,7 @@
+{
+  "plugins": [
+    "transform-object-super",
+    "transform-shorthand-properties",
+    "transform-block-scoping"
+  ]
+}
diff --git a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/output.js b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/output.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop-es5-compat/output.js
@@ -0,0 +1,21 @@
+var objects = [];
+var _loop = function () {
+  var _obj;
+  if (true) {
+    objects.push(_obj = {
+      __proto__: proto,
+      foo: function () {
+        return babelHelpers.get(babelHelpers.getPrototypeOf(_obj), "x", this);
+      }
+    });
+  }
+};
+for (var proto of [{
+  x: 0
+}, {
+  x: 1
+}]) {
+  _loop();
+}
+expect(objects[0].foo()).toBe(0);
+expect(objects[1].foo()).toBe(1);
diff --git a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop/exec.js b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop/exec.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop/exec.js
@@ -0,0 +1,13 @@
+const objects = [];
+for (const proto of [{x: 0}, {x: 1}]) {
+  if(true) {
+    objects.push({
+      __proto__: proto,
+      foo() {
+        return super.x
+      }
+    });
+  }
+}
+expect(objects[0].foo()).toBe(0);
+expect(objects[1].foo()).toBe(1);
diff --git a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop/input.js b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop/input.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop/input.js
@@ -0,0 +1,13 @@
+const objects = [];
+for (const proto of [{x: 0}, {x: 1}]) {
+  if(true) {
+    objects.push({
+      __proto__: proto,
+      foo() {
+        return super.x
+      }
+    });
+  }
+}
+expect(objects[0].foo()).toBe(0);
+expect(objects[1].foo()).toBe(1);
diff --git a/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop/output.js b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop/output.js
new file mode 100644
--- /dev/null
+++ b/packages/babel-plugin-transform-object-super/test/fixtures/object-super/loop/output.js
@@ -0,0 +1,18 @@
+const objects = [];
+for (const proto of [{
+  x: 0
+}, {
+  x: 1
+}]) {
+  let _obj;
+  if (true) {
+    objects.push(_obj = {
+      __proto__: proto,
+      foo: function () {
+        return babelHelpers.get(babelHelpers.getPrototypeOf(_obj), "x", this);
+      }
+    });
+  }
+}
+expect(objects[0].foo()).toBe(0);
+expect(objects[1].foo()).toBe(1);