Repository: swc-project/swc (rust)

Issue:
Loop body extraction breaks async functions resolution since v1.3.61
### Describe the bug

Loop body extraction breaks async functions resolution since v1.3.61. The extracted body is defined as a non-async function despite of having a statement with `await` keyword. Obviously, this is illegal in JavaScript and breaks the program immedietly.

### Input code

```typescript
async function foo(a, b) {
    for (let i = 0; i < 5; i++) {
        const boo = await a();
        b(() => boo)
    }
}
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "ecmascript",
      "jsx": false
    },
    "loose": false,
    "minify": {
      "compress": false,
      "mangle": false,
      "format": {
        "comments": "all"
      }
    }
  },
  "module": {
    "type": "es6"
  },
  "minify": false,
  "isModule": true,
  "env": {
    "targets": "supports async-functions"
  }
}
```


### Playground link

https://play.swc.rs/?version=1.3.87&code=H4sIAAAAAAAAA0WKMQqAMBAEe8E%2FbHkhFjZWMf7lEgwEJAcaERH%2FboKg0wzsDm9n8gh78jlKQhAh7uAUrrZBIcgKWuaMCIveFI0YirT%2BkoqXtGU4kVLxwTGDSZn%2Fd0QKdqqFete7be4HX0cHTH0AAAA%3D&config=H4sIAAAAAAAAA2WQSw7DIAxE9zkF8rrddtE79BAWNRERP2GoGkW5ewmENFU3CL8xMzbLIARMLOEulnItRcDIFI%2B6EJ5dwnchQNIiy6hDgktXJ94khYaporUpYLxn6srOrHZazWdv6W2IxPzbuLWiGw39YeWjxXRyaB6WXNo8AI2BXVmHftaRwPpnro77omkOVJfiG3yb%2BoRHLGh%2B9Jcp5sbIvU5GGEdq8ZxD8DGxwPJp8qqyk0l7xzVgWD9NIRTDbgEAAA%3D%3D

### SWC Info output

_No response_

### Expected behavior

The following expected transformation is produced by v1.3.60:
```javascript
async function foo(a, b) {
    for(let i = 0; i < 5; i++){
        const boo = await a();
        b(()=>boo);
    }
}
```
If the new behavior is mandatory for some reason, the following transformation should be produced:
```javascript
async function foo(a, b) {
    var _loop = async function(i) {
        var boo = await a();
        b(()=>boo);
    };
    for(var i = 0; i < 5; i++)await _loop(i);
}
```

### Actual behavior

Since v1.3.61, the following transformation is produced causing an error:
```javascript
async function foo(a, b) {
    var _loop = function(i) {
        var boo = await a();
        b(()=>boo);
    };
    for(var i = 0; i < 5; i++)_loop(i);
}
```

### Version

1.3.87

### Additional context

_No response_

Pull request description:
fix(es/compat): Use `async` and `await` correctly in `block-scoping` pass
**Description:**


**Related issue:**

 - Closes #7989

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 ce18711f7e..8ddadc43ae 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
@@ -161,6 +161,7 @@ impl BlockScoping {
                 has_break: false,
                 has_return: false,
                 has_yield: false,
+                has_await: false,
                 label: IndexMap::new(),
                 inner_label: AHashSet::default(),
                 mutated,
@@ -222,7 +223,7 @@ impl BlockScoping {
                         decorators: Default::default(),
                         body: Some(body_stmt),
                         is_generator: flow_helper.has_yield,
-                        is_async: false,
+                        is_async: flow_helper.has_await,
                         type_params: None,
                         return_type: None,
                     }
@@ -243,6 +244,14 @@ impl BlockScoping {
             }
             .into();
 
+            if flow_helper.has_await {
+                call = AwaitExpr {
+                    span: DUMMY_SP,
+                    arg: call.into(),
+                }
+                .into();
+            }
+
             if flow_helper.has_yield {
                 call = YieldExpr {
                     span: DUMMY_SP,
@@ -616,6 +625,7 @@ struct FlowHelper<'a> {
     has_break: bool,
     has_return: bool,
     has_yield: bool,
+    has_await: bool,
 
     // label cannot be shadowed, so it's pretty safe to use JsWord
     label: IndexMap<JsWord, Label>,
@@ -657,18 +667,6 @@ impl VisitMut for FlowHelper<'_> {
     /// noop
     fn visit_mut_arrow_expr(&mut self, _n: &mut ArrowExpr) {}
 
-    fn visit_mut_yield_expr(&mut self, e: &mut YieldExpr) {
-        e.visit_mut_children_with(self);
-
-        self.has_yield = true;
-    }
-
-    fn visit_mut_labeled_stmt(&mut self, l: &mut LabeledStmt) {
-        self.inner_label.insert(l.label.sym.clone());
-
-        l.visit_mut_children_with(self);
-    }
-
     fn visit_mut_assign_expr(&mut self, n: &mut AssignExpr) {
         match &n.left {
             PatOrExpr::Expr(e) => {
@@ -688,6 +686,12 @@ impl VisitMut for FlowHelper<'_> {
         n.visit_mut_children_with(self);
     }
 
+    fn visit_mut_await_expr(&mut self, e: &mut AwaitExpr) {
+        e.visit_mut_children_with(self);
+
+        self.has_await = true;
+    }
+
     /// https://github.com/swc-project/swc/pull/2916
     fn visit_mut_do_while_stmt(&mut self, s: &mut DoWhileStmt) {
         let old = self.in_nested_loop;
@@ -723,6 +727,12 @@ impl VisitMut for FlowHelper<'_> {
     /// noop
     fn visit_mut_function(&mut self, _f: &mut Function) {}
 
+    fn visit_mut_labeled_stmt(&mut self, l: &mut LabeledStmt) {
+        self.inner_label.insert(l.label.sym.clone());
+
+        l.visit_mut_children_with(self);
+    }
+
     fn visit_mut_stmt(&mut self, node: &mut Stmt) {
         let span = node.span();
 
@@ -822,6 +832,12 @@ impl VisitMut for FlowHelper<'_> {
         s.visit_mut_children_with(self);
         self.in_nested_loop = old;
     }
+
+    fn visit_mut_yield_expr(&mut self, e: &mut YieldExpr) {
+        e.visit_mut_children_with(self);
+
+        self.has_yield = true;
+    }
 }
 
 struct MutationHandler<'a> {


Test patch:
diff --git a/crates/swc/tests/fixture/issues-7xxx/7989/input/.swcrc b/crates/swc/tests/fixture/issues-7xxx/7989/input/.swcrc
new file mode 100644
index 0000000000..dab8ce7295
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7989/input/.swcrc
@@ -0,0 +1,24 @@
+{
+    "jsc": {
+        "parser": {
+            "syntax": "ecmascript",
+            "jsx": false
+        },
+        "loose": false,
+        "minify": {
+            "compress": false,
+            "mangle": false,
+            "format": {
+                "comments": "all"
+            }
+        }
+    },
+    "module": {
+        "type": "es6"
+    },
+    "minify": false,
+    "isModule": true,
+    "env": {
+        "targets": "supports async-functions"
+    }
+}
\ No newline at end of file
diff --git a/crates/swc/tests/fixture/issues-7xxx/7989/input/1.js b/crates/swc/tests/fixture/issues-7xxx/7989/input/1.js
new file mode 100644
index 0000000000..b4ac1c7bae
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7989/input/1.js
@@ -0,0 +1,6 @@
+async function foo(a, b) {
+    for (let i = 0; i < 5; i++) {
+        const boo = await a();
+        b(() => boo)
+    }
+}
diff --git a/crates/swc/tests/fixture/issues-7xxx/7989/output/1.js b/crates/swc/tests/fixture/issues-7xxx/7989/output/1.js
new file mode 100644
index 0000000000..8bcb15eba0
--- /dev/null
+++ b/crates/swc/tests/fixture/issues-7xxx/7989/output/1.js
@@ -0,0 +1,7 @@
+async function foo(a, b) {
+    var _loop = async function(i) {
+        var boo = await a();
+        b(()=>boo);
+    };
+    for(var i = 0; i < 5; i++)await _loop(i);
+}
diff --git a/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-7989/input.js b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-7989/input.js
new file mode 100644
index 0000000000..b4ac1c7bae
--- /dev/null
+++ b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-7989/input.js
@@ -0,0 +1,6 @@
+async function foo(a, b) {
+    for (let i = 0; i < 5; i++) {
+        const boo = await a();
+        b(() => boo)
+    }
+}
diff --git a/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-7989/output.js b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-7989/output.js
new file mode 100644
index 0000000000..2f8d170555
--- /dev/null
+++ b/crates/swc_ecma_transforms_compat/tests/block-scoping/issue-7989/output.js
@@ -0,0 +1,7 @@
+async function foo__2(a__3, b__3) {
+    var _loop__6 = async function(i__4) {
+        var boo__5 = await a__3();
+        b__3(()=>boo__5);
+    };
+    for(var i__4 = 0; i__4 < 5; i__4++)await _loop__6(i__4);
+}