Repository: swc-project/swc (rust)

Issue:
Nested `await` in a dynamic import is  not handled
### Describe the bug

SWC is generating invalid code for nested `await` statements on the same line which is resulting in syntax errors when running the generated code 

### Input code

```typescript
let pipeline = await import(await resolve(file))
```


### Config

```json
{
  "jsc": {
    "parser": {
      "syntax": "typescript"
    },
    "target": "es2018"
  },
  "module": {
    "type": "commonjs"
  }
}
```


### Playground link

https://play.swc.rs/?version=1.2.157&code=H4sIAAAAAAAAA8tJLVEoyCxIzcnMS1WwVUgsT8wsUcjMLcgvKtGAcIpSi%2FNzylI10jJzUjU1AV3i8sMwAAAA&config=H4sIAAAAAAAAA0WMSwrDMAxE76K1F21WJXfoIYSjBAf%2FkBSoMb57bXDISmLmzatwioW1QkYW4vFJiYo%2FWIFsQLHssoLpWI929ELNgCIfpAOR5fX%2B9NqnJDQBA8FFt5chsylkJpGnwnj4m2zdFdJ2jaCCltzvmIQUT4H2iObYyXfSyhe1P0Sd89u9AAAA

### Expected behavior

Nested `await` statements on the same line does not result in a syntax error.


### Actual behavior

Outputs an `await` statement inside a regular function causing a syntax error.

The actual generated code:
```js
let pipeline = await Promise.resolve().then(function() {
    return _interopRequireWildcard(require(await resolve(file)));
});
```

### Version

"@swc/cli": "^0.1.57",  "@swc/core": "^1.2.163",  "@swc/register": "^0.1.10",

### Additional context

_No response_

Pull request:
fix(es/module): TLA in dynamic import
<!-- 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:**

I initially thought check if it contains await expr would be enough, but later I realize it may cause evalution order problem...

**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 #4253 

Patch:
diff --git a/crates/swc_bundler/src/bundler/chunk/computed_key.rs b/crates/swc_bundler/src/bundler/chunk/computed_key.rs
index c1cb25e35c..8858aa4f13 100644
--- a/crates/swc_bundler/src/bundler/chunk/computed_key.rs
+++ b/crates/swc_bundler/src/bundler/chunk/computed_key.rs
@@ -4,8 +4,8 @@ use anyhow::{bail, Error};
 use swc_atoms::js_word;
 use swc_common::{SyntaxContext, DUMMY_SP};
 use swc_ecma_ast::*;
-use swc_ecma_utils::{find_ids, private_ident, ExprFactory};
-use swc_ecma_visit::{noop_fold_type, noop_visit_type, Fold, Visit};
+use swc_ecma_utils::{contains_top_level_await, find_ids, private_ident, ExprFactory};
+use swc_ecma_visit::{noop_fold_type, Fold};
 
 use crate::{bundler::chunk::merge::Ctx, modules::Modules, Bundler, Load, ModuleId, Resolve};
 
@@ -43,11 +43,7 @@ where
         };
         let injected_ctxt = self.injected_ctxt;
 
-        let is_async = {
-            let mut v = TopLevelAwaitFinder { found: false };
-            module.visit_with(&mut v);
-            v.found
-        };
+        let is_async = module.iter().any(|m| contains_top_level_await(m.1));
 
         let mut additional_items = vec![];
 
@@ -182,24 +178,6 @@ where
     }
 }
 
-struct TopLevelAwaitFinder {
-    found: bool,
-}
-
-impl Visit for TopLevelAwaitFinder {
-    noop_visit_type!();
-
-    fn visit_function(&mut self, _: &Function) {}
-
-    fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
-
-    fn visit_class_member(&mut self, _: &ClassMember) {}
-
-    fn visit_await_expr(&mut self, _: &AwaitExpr) {
-        self.found = true;
-    }
-}
-
 struct ExportToReturn {
     return_props: Vec<PropOrSpread>,
     synthesized_ctxt: SyntaxContext,
diff --git a/crates/swc_bundler/src/bundler/finalize.rs b/crates/swc_bundler/src/bundler/finalize.rs
index b32c132d76..778356056d 100644
--- a/crates/swc_bundler/src/bundler/finalize.rs
+++ b/crates/swc_bundler/src/bundler/finalize.rs
@@ -11,8 +11,8 @@ use swc_ecma_transforms_base::{
     helpers::{inject_helpers, HELPERS},
     hygiene::hygiene,
 };
-use swc_ecma_utils::{find_ids, private_ident, ExprFactory};
-use swc_ecma_visit::{noop_fold_type, noop_visit_type, Fold, FoldWith, Visit, VisitWith};
+use swc_ecma_utils::{contains_top_level_await, find_ids, private_ident, ExprFactory};
+use swc_ecma_visit::{noop_fold_type, Fold, FoldWith};
 
 use crate::{hash::calc_hash, Bundle, BundleKind, Bundler, Load, ModuleType, Resolve};
 
@@ -148,10 +148,7 @@ where
             return module;
         }
 
-        let mut top_level_await_finder = TopLevelAwaitFinder::default();
-        module.visit_with(&mut top_level_await_finder);
-
-        let is_async = top_level_await_finder.found;
+        let is_async = contains_top_level_await(&module);
 
         // Properties of returned object
         let mut props = vec![];
@@ -365,21 +362,6 @@ where
     }
 }
 
-#[derive(Default)]
-struct TopLevelAwaitFinder {
-    found: bool,
-}
-
-impl Visit for TopLevelAwaitFinder {
-    noop_visit_type!();
-
-    fn visit_stmts(&mut self, _: &[Stmt]) {}
-
-    fn visit_await_expr(&mut self, _: &AwaitExpr) {
-        self.found = true;
-    }
-}
-
 /// Import renamer. This pass changes import path.
 struct Renamer<'a, R>
 where
diff --git a/crates/swc_ecma_transforms_module/src/common_js.rs b/crates/swc_ecma_transforms_module/src/common_js.rs
index 686d7871d3..9797455d99 100644
--- a/crates/swc_ecma_transforms_module/src/common_js.rs
+++ b/crates/swc_ecma_transforms_module/src/common_js.rs
@@ -1062,13 +1062,55 @@ impl ModulePass for CommonJs {
 /// ```
 pub(super) fn handle_dynamic_import(
     span: Span,
-    args: Vec<ExprOrSpread>,
+    mut args: Vec<ExprOrSpread>,
     es_module_interop: bool,
 ) -> Expr {
+    // there's a evalution order problem here
+    let (resolve_arg, then_arg, require_arg) = if let Expr::Lit(Lit::Str(_)) = &*args[0].expr {
+        (Vec::new(), Vec::new(), args)
+    } else {
+        let arg = private_ident!("s");
+        let rest = args.split_off(1);
+        let import = args.into_iter().next().unwrap();
+        (
+            vec![ExprOrSpread {
+                spread: None,
+                expr: if import.expr.is_tpl() {
+                    import.expr
+                } else {
+                    Box::new(Expr::Tpl(Tpl {
+                        span: DUMMY_SP,
+                        exprs: vec![import.expr],
+                        quasis: vec![
+                            TplElement {
+                                span: DUMMY_SP,
+                                tail: true,
+                                cooked: None,
+                                raw: "".into(),
+                            },
+                            TplElement {
+                                span: DUMMY_SP,
+                                tail: true,
+                                cooked: None,
+                                raw: "".into(),
+                            },
+                        ],
+                    }))
+                },
+            }],
+            vec![arg.clone().into()],
+            {
+                let mut require_arg = vec![arg.as_arg()];
+                require_arg.extend(rest);
+                require_arg
+            },
+        )
+    };
+
     let resolve_call = CallExpr {
         span: DUMMY_SP,
         callee: member_expr!(DUMMY_SP, Promise.resolve).as_callee(),
-        args: Default::default(),
+        args: resolve_arg,
         type_args: Default::default(),
     };
     // Promise.resolve().then
@@ -1083,7 +1125,7 @@ pub(super) fn handle_dynamic_import(
                 ident: None,
                 function: Function {
                     span: DUMMY_SP,
-                    params: vec![],
+                    params: then_arg,
                     is_generator: false,
                     is_async: false,
                     type_params: Default::default(),
@@ -1097,7 +1139,7 @@ pub(super) fn handle_dynamic_import(
                                 let mut expr = Box::new(Expr::Call(CallExpr {
                                     span: DUMMY_SP,
                                     callee: quote_ident!("require").as_callee(),
-                                    args,
+                                    args: require_arg,
                                     type_args: Default::default(),
                                 }));
 
diff --git a/crates/swc_ecma_transforms_module/src/util.rs b/crates/swc_ecma_transforms_module/src/util.rs
index 766296ec3d..c4eb7e2001 100644
--- a/crates/swc_ecma_transforms_module/src/util.rs
+++ b/crates/swc_ecma_transforms_module/src/util.rs
@@ -571,8 +571,8 @@ impl Scope {
                 // TODO: import assertion
                 && args.len() == 1 =>
             {
-                let expr = args.pop().unwrap().expr.fold_with(folder);
-                let expr = match &*expr {
+                let mut expr = args.pop().unwrap().expr.fold_with(folder);
+                let expr = match &mut *expr {
                     Expr::Lit(Lit::Str(s)) => {
                         let src = folder.resolver().resolve(s.value.clone());
 
@@ -582,15 +582,11 @@ impl Scope {
                             ..s.clone()
                         })))
                     }
-                    _ => expr,
-                };
-
-                let expr = match *expr {
-                    Expr::Ident(ident) => match Self::fold_ident(folder, ident) {
+                    Expr::Ident(ident) => Box::new(match Self::fold_ident(folder, ident.take()) {
                         Ok(expr) => expr,
                         Err(ident) => Expr::Ident(ident),
-                    },
-                    expr => expr,
+                    }),
+                    _ => expr,
                 };
 
                 folder.make_dynamic_import(span, vec![expr.as_arg()])
diff --git a/crates/swc_ecma_utils/src/lib.rs b/crates/swc_ecma_utils/src/lib.rs
index 13138420cf..16c06fc755 100644
--- a/crates/swc_ecma_utils/src/lib.rs
+++ b/crates/swc_ecma_utils/src/lib.rs
@@ -2375,6 +2375,48 @@ where
     v.bindings
 }
 
+pub struct TopLevelAwait {
+    found: bool,
+}
+
+impl Visit for TopLevelAwait {
+    noop_visit_type!();
+
+    fn visit_stmts(&mut self, _: &[Stmt]) {}
+
+    fn visit_param(&mut self, _: &Param) {}
+
+    fn visit_function(&mut self, _: &Function) {}
+
+    fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
+
+    fn visit_class_member(&mut self, prop: &ClassMember) {
+        match prop {
+            ClassMember::ClassProp(ClassProp {
+                key: PropName::Computed(computed),
+                ..
+            })
+            | ClassMember::Method(ClassMethod {
+                key: PropName::Computed(computed),
+                ..
+            }) => computed.visit_children_with(self),
+            _ => (),
+        };
+    }
+
+    fn visit_await_expr(&mut self, _: &AwaitExpr) {
+        self.found = true;
+    }
+}
+
+pub fn contains_top_level_await<V: VisitWith<TopLevelAwait>>(t: &V) -> bool {
+    let mut finder = TopLevelAwait { found: false };
+
+    t.visit_with(&mut finder);
+
+    finder.found
+}
+
 #[cfg(test)]
 mod test {
     use swc_common::{input::StringInput, BytePos};


Test patch:
diff --git a/crates/swc/tests/fixture/issues-1xxx/issue-1682/case1/output/index.ts b/crates/swc/tests/fixture/issues-1xxx/issue-1682/case1/output/index.ts
index be30d320ce..d28b9cd6fc 100644
--- a/crates/swc/tests/fixture/issues-1xxx/issue-1682/case1/output/index.ts
+++ b/crates/swc/tests/fixture/issues-1xxx/issue-1682/case1/output/index.ts
@@ -6,8 +6,8 @@ exports.getPackage = getPackage;
 var swcHelpers = require("@swc/helpers");
 var _path = require("path");
 async function getPackage() {
-    const pkg = await Promise.resolve().then(function() {
-        return swcHelpers.interopRequireWildcard(require((0, _path).join(process.cwd(), 'package.json')));
+    const pkg = await Promise.resolve(`${(0, _path).join(process.cwd(), 'package.json')}`).then(function(s) {
+        return swcHelpers.interopRequireWildcard(require(s));
     });
     return pkg.default || pkg;
 }
diff --git a/crates/swc/tests/fixture/issues-1xxx/issue-1682/case2/output/index.ts b/crates/swc/tests/fixture/issues-1xxx/issue-1682/case2/output/index.ts
index be30d320ce..d28b9cd6fc 100644
--- a/crates/swc/tests/fixture/issues-1xxx/issue-1682/case2/output/index.ts
+++ b/crates/swc/tests/fixture/issues-1xxx/issue-1682/case2/output/index.ts
@@ -6,8 +6,8 @@ exports.getPackage = getPackage;
 var swcHelpers = require("@swc/helpers");
 var _path = require("path");
 async function getPackage() {
-    const pkg = await Promise.resolve().then(function() {
-        return swcHelpers.interopRequireWildcard(require((0, _path).join(process.cwd(), 'package.json')));
+    const pkg = await Promise.resolve(`${(0, _path).join(process.cwd(), 'package.json')}`).then(function(s) {
+        return swcHelpers.interopRequireWildcard(require(s));
     });
     return pkg.default || pkg;
 }
diff --git a/crates/swc_ecma_transforms_module/tests/common_js.rs b/crates/swc_ecma_transforms_module/tests/common_js.rs
index 2c6372f0dd..a3939b7731 100644
--- a/crates/swc_ecma_transforms_module/tests/common_js.rs
+++ b/crates/swc_ecma_transforms_module/tests/common_js.rs
@@ -4411,8 +4411,8 @@ test!(
     ",
     r#""use strict";
     var _bar = require("bar");
-    Promise.resolve().then(function() {
-        return _interopRequireWildcard(require(_bar.foo));
+    Promise.resolve(`${_bar.foo}`).then(function(s) {
+        return _interopRequireWildcard(require(s));
     });
     "#
 );
@@ -4429,8 +4429,8 @@ test!(
   ",
     r#""use strict";
   var _bar = _interopRequireDefault(require("bar"));
-  Promise.resolve().then(function() {
-      return _interopRequireWildcard(require(_bar.default));
+  Promise.resolve(`${_bar.default}`).then(function(s) {
+      return _interopRequireWildcard(require(s));
   });
   "#
 );
@@ -4447,8 +4447,8 @@ test!(
   ",
     r#""use strict";
   var _bar = require("bar");
-  Promise.resolve().then(function() {
-    return _interopRequireWildcard(require(`world/${(0, _bar).foo(baz)}.js`));
+  Promise.resolve(`world/${(0, _bar).foo(baz)}.js`).then(function(s) {
+    return _interopRequireWildcard(require(s));
   });
   "#
 );
@@ -5393,6 +5393,21 @@ test!(
   "
 );
 
+test!(
+    syntax(),
+    |_| tr(Config {
+        ..Default::default()
+    }),
+    issue_4253,
+    "let pipeline = await import(await resolve(file))",
+    "
+\"use strict\";
+let pipeline = await Promise.resolve(`${await resolve(file)}`).then(function(s) {
+  return _interopRequireWildcard(require(s));
+});
+"
+);
+
 #[testing::fixture("tests/fixture/commonjs/**/input.js")]
 fn fixture(input: PathBuf) {
     let dir = input.parent().unwrap().to_path_buf();