diff --git a/crates/swc_ecma_transforms_typescript/src/transform.rs b/crates/swc_ecma_transforms_typescript/src/transform.rs index a74f77b04d542..46ae58083af6b 100644 --- a/crates/swc_ecma_transforms_typescript/src/transform.rs +++ b/crates/swc_ecma_transforms_typescript/src/transform.rs @@ -1,13 +1,13 @@ -use std::mem; +use std::{mem, vec}; use swc_common::{collections::AHashSet, util::take::Take, Mark, DUMMY_SP}; use swc_ecma_ast::*; -use swc_ecma_utils::constructor::inject_after_super; +use swc_ecma_utils::{constructor::inject_after_super, ExprFactory}; use swc_ecma_visit::{as_folder, noop_visit_mut_type, Fold, VisitMut, VisitMutWith}; use crate::{ import_export_assign::import_export_assign, - utils::{make_assign_to_this, MerageableDecl}, + utils::{make_assign_to_this, AsDecl, AsEnumOrModule}, TsImportExportAssignConfig, }; @@ -42,7 +42,6 @@ pub(crate) struct Transform { unresolved_mark: Mark, import_export_assign_config: TsImportExportAssignConfig, - scope_depth: u16, namespace_id: Option, } @@ -162,42 +161,43 @@ impl VisitMut for Transform { } fn visit_mut_stmts(&mut self, n: &mut Vec) { - self.scope_depth += 1; n.visit_mut_children_with(self); - self.scope_depth -= 1; self.transform_stmts(n); } fn visit_mut_module_items(&mut self, n: &mut Vec) { - self.scope_depth += 1; n.visit_mut_children_with(self); - self.scope_depth -= 1; self.transform_module_items(n); } fn visit_mut_ts_namespace_decl(&mut self, n: &mut TsNamespaceDecl) { let id = n.id.to_id(); - self.namespace_id = Some(id); + let old_id = self.namespace_id.replace(id); n.body.visit_mut_with(self); + + self.namespace_id = old_id; } fn visit_mut_ts_module_decl(&mut self, n: &mut TsModuleDecl) { - if let Some(id) = n.id.as_ident().map(Ident::to_id) { - self.namespace_id = Some(id); - } + let id = + n.id.as_ident() + .map(Ident::to_id) + .expect("Only ambient modules can use quoted names."); + + let old_id = self.namespace_id.replace(id); n.body.visit_mut_with(self); + + self.namespace_id = old_id; } } impl Transform { fn transform_stmts(&mut self, n: &mut Vec) { - let found = (**n) - .iter() - .any(|stmt| matches!(stmt, Stmt::Decl(Decl::TsModule(..) | Decl::TsEnum(..)))); + let found = (**n).iter().any(|stmt| stmt.is_enum_or_module()); if !found { return; @@ -206,16 +206,15 @@ impl Transform { let mut decl_id = AHashSet::::default(); for mut stmt in n.take().drain(..) { - let id = stmt.get_decl_id(); - - if let Some(id) = &id { + if let Some(id) = stmt.get_enum_or_module_id() { let var_stmt = self - .add_var_for_enum_or_module_declaration(id, &decl_id) + .add_var_for_enum_or_module_declaration(&id, &decl_id) .map(Into::into); n.extend(var_stmt); } + let id = stmt.get_decl_id(); stmt = self.fold_stmt(stmt); decl_id.extend(id); n.push(stmt); @@ -224,22 +223,16 @@ impl Transform { fn fold_stmt(&self, n: Stmt) -> Stmt { match n { - Stmt::Decl(Decl::TsModule(ts_module)) => self.transform_ts_module(*ts_module), + Stmt::Decl(Decl::TsModule(ts_module)) => self.transform_ts_module(*ts_module, false), Stmt::Decl(Decl::TsEnum(ts_enum)) => self.transform_ts_enum(*ts_enum), stmt => stmt, } } fn transform_module_items(&mut self, n: &mut Vec) { - let found = (**n).iter().any(|module_item| { - matches!( - module_item, - ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { - decl: Decl::TsModule(..) | Decl::TsEnum(..), - .. - })) | ModuleItem::Stmt(Stmt::Decl(Decl::TsModule(..) | Decl::TsEnum(..))) - ) - }); + let found = (**n) + .iter() + .any(|module_item| module_item.is_enum_or_module()); if !found { return; @@ -248,12 +241,14 @@ impl Transform { let mut decl_id = AHashSet::::default(); for mut module_item in n.take().drain(..) { - let id = module_item.get_decl_id(); - - if let Some(id) = &id { + if let Some(id) = &module_item.get_enum_or_module_id() { let var_decl = self.add_var_for_enum_or_module_declaration(id, &decl_id); - let var_stmt = if self.scope_depth == 0 { + let var_stmt = if self.namespace_id.is_none() + && matches!( + module_item, + ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(..)) + ) { // export var foo; var_decl .map(Box::new) @@ -272,6 +267,7 @@ impl Transform { n.extend(var_stmt); } + let id = module_item.get_decl_id(); module_item = self.fold_module_item(module_item); decl_id.extend(id); n.push(module_item); @@ -283,7 +279,7 @@ impl Transform { ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl: Decl::TsModule(ts_module), .. - })) => self.transform_ts_module(*ts_module).into(), + })) => self.transform_ts_module(*ts_module, true).into(), ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl: Decl::TsEnum(ts_enum), .. @@ -302,7 +298,7 @@ impl Transform { return None; } - let kind = if self.scope_depth == 0 { + let kind = if self.namespace_id.is_none() { VarDeclKind::Var } else { VarDeclKind::Let @@ -323,11 +319,147 @@ impl Transform { Some(var_decl) } - fn transform_ts_module(&self, _ts_module: TsModuleDecl) -> Stmt { - todo!() + fn transform_ts_module(&self, ts_module: TsModuleDecl, is_export: bool) -> Stmt { + debug_assert!(!ts_module.declare); + debug_assert!(!ts_module.global); + + let TsModuleDecl { + span, + id: TsModuleName::Ident(ident), + body: Some(body), + .. + } = ts_module else { unreachable!() }; + + let body = self.transform_ts_namespace_body(ident.to_id(), body); + + let mut module_arg = self + .get_namespace_or_enum_init_arg(ident.clone(), is_export) + .into(); + + if is_export && self.namespace_id.is_some() { + module_arg = AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: PatOrExpr::Pat(Box::new(Pat::Ident(ident.into()))), + right: module_arg, + } + .into(); + } + + let expr = FnExpr { + ident: None, + function: Box::new(Function { + params: vec![], + decorators: vec![], + span: DUMMY_SP, + body: Some(body), + is_generator: false, + is_async: false, + type_params: None, + return_type: None, + }), + } + .as_call(DUMMY_SP, vec![module_arg.into()]); + + Stmt::Expr(ExprStmt { + span, + expr: expr.into(), + }) } fn transform_ts_enum(&self, _ts_enum: TsEnumDecl) -> Stmt { todo!() } + + fn transform_ts_namespace_body( + &self, + container_name: Id, + ts_namespace_body: TsNamespaceBody, + ) -> BlockStmt { + let TsNamespaceDecl { + span, + declare, + global, + id: local_name, + body, + } = match ts_namespace_body { + TsNamespaceBody::TsModuleBlock(ts_module_block) => { + return self.transform_ts_module_block(container_name, ts_module_block); + } + TsNamespaceBody::TsNamespaceDecl(ts_namespace_decl) => ts_namespace_decl, + }; + + debug_assert!(!declare); + debug_assert!(!global); + + let body = self.transform_ts_namespace_body(local_name.to_id(), *body); + + let module_arg = self.get_namespace_or_enum_init_arg(local_name, true).into(); + + let expr = FnExpr { + ident: None, + function: Box::new(Function { + params: vec![], + decorators: vec![], + span: DUMMY_SP, + body: Some(body), + is_generator: false, + is_async: false, + type_params: None, + return_type: None, + }), + } + .as_call(DUMMY_SP, vec![module_arg]); + + BlockStmt { + span, + stmts: vec![expr.into_stmt()], + } + } + + fn transform_ts_module_block(&self, _id: Id, _ts_module_block: TsModuleBlock) -> BlockStmt { + BlockStmt { + span: DUMMY_SP, + stmts: vec![], + } + } + + /// Gets the argument used in a transformed enum or namespace call expr. + /// + /// Example: + /// * `MyNamespace.MyEnum || (MyNamespace.MyEnum = {})` + /// * or `MyEnum || (MyEnum = {})` + fn get_namespace_or_enum_init_arg(&self, ident: Ident, is_export: bool) -> Expr { + let mut left: Expr = ident.clone().into(); + let mut assign_left: Pat = ident.clone().into(); + + if is_export { + if let Some(namespace_id) = self.namespace_id.clone() { + left = Ident::from(namespace_id).make_member(ident); + assign_left = Pat::Expr(Box::new(left.clone())) + } + } + + BinExpr { + span: DUMMY_SP, + op: op!("||"), + left: left.into(), + right: Box::new( + AssignExpr { + span: DUMMY_SP, + op: op!("="), + left: assign_left.into(), + right: Box::new( + ObjectLit { + span: DUMMY_SP, + props: vec![], + } + .into(), + ), + } + .into(), + ), + } + .into() + } } diff --git a/crates/swc_ecma_transforms_typescript/src/utils.rs b/crates/swc_ecma_transforms_typescript/src/utils.rs index 39ae4fdd8cff4..24163ca3ed89f 100644 --- a/crates/swc_ecma_transforms_typescript/src/utils.rs +++ b/crates/swc_ecma_transforms_typescript/src/utils.rs @@ -2,11 +2,11 @@ use swc_common::DUMMY_SP; use swc_ecma_ast::*; use swc_ecma_utils::ExprFactory; -pub(crate) trait MerageableDecl { +pub(crate) trait AsDecl { fn get_decl_id(&self) -> Option; } -impl MerageableDecl for Stmt { +impl AsDecl for Stmt { fn get_decl_id(&self) -> Option { self.as_decl().and_then(|decl| match decl { Decl::Class(ClassDecl { ident, .. }) | Decl::Fn(FnDecl { ident, .. }) => { @@ -19,7 +19,7 @@ impl MerageableDecl for Stmt { } } -impl MerageableDecl for ModuleItem { +impl AsDecl for ModuleItem { fn get_decl_id(&self) -> Option { match self { Self::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { @@ -56,6 +56,55 @@ impl MerageableDecl for ModuleItem { } } +pub(crate) trait AsEnumOrModule { + fn is_enum_or_module(&self) -> bool; + fn get_enum_or_module_id(&self) -> Option; +} + +impl AsEnumOrModule for Stmt { + fn is_enum_or_module(&self) -> bool { + matches!(self, Self::Decl(Decl::TsModule(..) | Decl::TsEnum(..))) + } + + fn get_enum_or_module_id(&self) -> Option { + self.as_decl().and_then(|decl| match decl { + Decl::TsEnum(ts_enum) => Some(ts_enum.id.to_id()), + Decl::TsModule(ts_module) => ts_module.id.as_ident().map(Ident::to_id), + _ => None, + }) + } +} + +impl AsEnumOrModule for ModuleItem { + fn is_enum_or_module(&self) -> bool { + matches!( + self, + Self::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { + decl: Decl::TsModule(..) | Decl::TsEnum(..), + .. + })) | Self::Stmt(Stmt::Decl(Decl::TsModule(..) | Decl::TsEnum(..))) + ) + } + + fn get_enum_or_module_id(&self) -> Option { + match self { + Self::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { + decl: Decl::TsEnum(ts_enum), + .. + })) => Some(ts_enum.id.to_id()), + Self::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { + decl: Decl::TsModule(ts_module), + .. + })) => ts_module.id.as_ident().map(Ident::to_id), + Self::Stmt(Stmt::Decl(Decl::TsEnum(ts_enum))) => Some(ts_enum.id.to_id()), + Self::Stmt(Stmt::Decl(Decl::TsModule(ts_module))) => { + ts_module.id.as_ident().map(Ident::to_id) + } + _ => None, + } + } +} + /// /// this.foo = foo pub(crate) fn make_assign_to_this(id: &Id) -> Box {