diff --git a/crates/swc_ecma_transforms_typescript/src/lib.rs b/crates/swc_ecma_transforms_typescript/src/lib.rs index fae5cdaf3fc12..ad2c76c106ad9 100644 --- a/crates/swc_ecma_transforms_typescript/src/lib.rs +++ b/crates/swc_ecma_transforms_typescript/src/lib.rs @@ -7,7 +7,7 @@ mod import_export_assign; mod inline_enum; mod macros; pub mod strip; -mod strip_import; +mod strip_import_export; mod strip_type; mod transform; pub mod typescript; diff --git a/crates/swc_ecma_transforms_typescript/src/strip_import.rs b/crates/swc_ecma_transforms_typescript/src/strip_import.rs deleted file mode 100644 index 30fbd72f549a0..0000000000000 --- a/crates/swc_ecma_transforms_typescript/src/strip_import.rs +++ /dev/null @@ -1,134 +0,0 @@ -use swc_common::collections::AHashSet; -use swc_ecma_ast::*; -use swc_ecma_visit::{noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith}; - -/// https://www.typescriptlang.org/tsconfig#importsNotUsedAsValues -/// -/// The tsc drop import statements which only reference types by default. -#[derive(Debug, Default)] -pub(crate) struct StripImport { - id_usage: AHashSet, -} - -impl VisitMut for StripImport { - fn visit_mut_module(&mut self, n: &mut Module) { - n.visit_with(self); - n.visit_mut_children_with(self); - } - - fn visit_mut_module_items(&mut self, n: &mut Vec) { - n.retain_mut(|module_item| match module_item { - ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { - specifiers, - type_only: false, - .. - })) if !specifiers.is_empty() => { - specifiers.retain(|import_specifier| match import_specifier { - ImportSpecifier::Named(named) => { - !named.is_type_only && self.id_usage.contains(&named.local.to_id()) - } - ImportSpecifier::Default(default) => { - self.id_usage.contains(&default.local.to_id()) - } - ImportSpecifier::Namespace(namespace) => { - self.id_usage.contains(&namespace.local.to_id()) - } - }); - - !specifiers.is_empty() - } - _ => true, - }); - } -} - -impl Visit for StripImport { - noop_visit_type!(); - - fn visit_ident(&mut self, n: &Ident) { - self.id_usage.insert(n.to_id()); - } - - fn visit_binding_ident(&mut self, _: &BindingIdent) { - // skip - } - - fn visit_class(&mut self, n: &Class) { - // skip implements - n.decorators.visit_with(self); - n.body.visit_with(self); - n.super_class.visit_with(self); - } - - fn visit_fn_decl(&mut self, n: &FnDecl) { - // skip function ident - n.function.visit_with(self); - } - - fn visit_fn_expr(&mut self, n: &FnExpr) { - // skip function ident - n.function.visit_with(self); - } - - fn visit_class_decl(&mut self, n: &ClassDecl) { - // skip class ident - n.class.visit_with(self); - } - - fn visit_class_expr(&mut self, n: &ClassExpr) { - // skip class ident - n.class.visit_with(self); - } - - fn visit_import_decl(&mut self, _: &ImportDecl) { - // skip - } - - fn visit_ts_import_equals_decl(&mut self, n: &TsImportEqualsDecl) { - if n.is_type_only { - return; - } - - // skip id visit - - let TsModuleRef::TsEntityName(ts_entity_name) = &n.module_ref else { - return; - }; - - get_module_ident(ts_entity_name).visit_with(self); - } - - fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { - if n.is_type_only { - return; - } - - n.orig.visit_with(self); - } - - fn visit_named_export(&mut self, n: &NamedExport) { - if n.type_only || n.src.is_some() { - return; - } - - n.visit_children_with(self); - } - - fn visit_jsx_element_name(&mut self, n: &JSXElementName) { - if matches!(n, JSXElementName::Ident(i) if i.sym.starts_with(|c: char| c.is_ascii_lowercase()) ) - { - return; - } - - n.visit_children_with(self); - } -} - -fn get_module_ident(ts_entity_name: &TsEntityName) -> &Ident { - match ts_entity_name { - TsEntityName::TsQualifiedName(ts_qualified_name) => { - get_module_ident(&ts_qualified_name.left) - } - TsEntityName::Ident(ident) => ident, - } -} diff --git a/crates/swc_ecma_transforms_typescript/src/strip_import_export.rs b/crates/swc_ecma_transforms_typescript/src/strip_import_export.rs new file mode 100644 index 0000000000000..560a95940ea01 --- /dev/null +++ b/crates/swc_ecma_transforms_typescript/src/strip_import_export.rs @@ -0,0 +1,228 @@ +use swc_common::collections::AHashSet; +use swc_ecma_ast::*; +use swc_ecma_visit::{noop_visit_type, Visit, VisitMut, VisitWith}; + +#[derive(Debug, Default)] +struct UsageCollect { + id_usage: AHashSet, +} + +impl Visit for UsageCollect { + noop_visit_type!(); + + fn visit_ident(&mut self, n: &Ident) { + self.id_usage.insert(n.to_id()); + } + + fn visit_binding_ident(&mut self, _: &BindingIdent) { + // skip + } + + fn visit_class(&mut self, n: &Class) { + // skip implements + n.decorators.visit_with(self); + n.body.visit_with(self); + n.super_class.visit_with(self); + } + + fn visit_fn_decl(&mut self, n: &FnDecl) { + // skip function ident + n.function.visit_with(self); + } + + fn visit_fn_expr(&mut self, n: &FnExpr) { + // skip function ident + n.function.visit_with(self); + } + + fn visit_class_decl(&mut self, n: &ClassDecl) { + // skip class ident + n.class.visit_with(self); + } + + fn visit_class_expr(&mut self, n: &ClassExpr) { + // skip class ident + n.class.visit_with(self); + } + + fn visit_import_decl(&mut self, _: &ImportDecl) { + // skip + } + + fn visit_ts_import_equals_decl(&mut self, n: &TsImportEqualsDecl) { + if n.is_type_only { + return; + } + + // skip id visit + + let TsModuleRef::TsEntityName(ts_entity_name) = &n.module_ref else { + return; + }; + + get_module_ident(ts_entity_name).visit_with(self); + } + + fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) { + if n.is_type_only { + return; + } + + n.orig.visit_with(self); + } + + fn visit_named_export(&mut self, n: &NamedExport) { + if n.type_only || n.src.is_some() { + return; + } + + n.visit_children_with(self); + } + + fn visit_jsx_element_name(&mut self, n: &JSXElementName) { + if matches!(n, JSXElementName::Ident(i) if i.sym.starts_with(|c: char| c.is_ascii_lowercase()) ) + { + return; + } + + n.visit_children_with(self); + } +} + +fn get_module_ident(ts_entity_name: &TsEntityName) -> &Ident { + match ts_entity_name { + TsEntityName::TsQualifiedName(ts_qualified_name) => { + get_module_ident(&ts_qualified_name.left) + } + TsEntityName::Ident(ident) => ident, + } +} + +#[derive(Debug, Default)] +struct DeclareCollect { + id_declare: AHashSet, +} + +// Only scan the top level of the module +impl Visit for DeclareCollect { + noop_visit_type!(); + + fn visit_binding_ident(&mut self, n: &BindingIdent) { + self.id_declare.insert(n.to_id()); + } + + fn visit_fn_decl(&mut self, n: &FnDecl) { + self.id_declare.insert(n.ident.to_id()); + } + + fn visit_class_decl(&mut self, n: &ClassDecl) { + self.id_declare.insert(n.ident.to_id()); + } + + fn visit_export_default_decl(&mut self, n: &ExportDefaultDecl) { + match &n.decl { + DefaultDecl::Class(ClassExpr { + ident: Some(ident), .. + }) => { + self.id_declare.insert(ident.to_id()); + } + DefaultDecl::Fn(FnExpr { + ident: Some(ident), .. + }) => { + self.id_declare.insert(ident.to_id()); + } + _ => {} + }; + } + + fn visit_ts_import_equals_decl(&mut self, n: &TsImportEqualsDecl) { + if n.is_type_only { + return; + } + + self.id_declare.insert(n.id.to_id()); + } + + fn visit_ts_module_decl(&mut self, n: &TsModuleDecl) { + if n.global { + return; + } + + if let TsModuleName::Ident(ident) = &n.id { + self.id_declare.insert(ident.to_id()); + } + } + + fn visit_stmts(&mut self, _: &[Stmt]) { + // skip + } + + fn visit_block_stmt(&mut self, _: &BlockStmt) { + // skip + } +} + +/// https://www.typescriptlang.org/tsconfig#importsNotUsedAsValues +/// +/// The tsc drop import statements which only reference types by default. +pub(crate) struct StripImportExport; + +impl VisitMut for StripImportExport { + fn visit_mut_module_items(&mut self, n: &mut Vec) { + let mut usage_collect = UsageCollect::default(); + let mut declare_collect = DeclareCollect::default(); + + n.visit_with(&mut usage_collect); + n.visit_with(&mut declare_collect); + + let UsageCollect { id_usage } = usage_collect; + let DeclareCollect { id_declare } = declare_collect; + + n.retain_mut(|module_item| match module_item { + ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl { + specifiers, + type_only: false, + .. + })) if !specifiers.is_empty() => { + specifiers.retain(|import_specifier| match import_specifier { + ImportSpecifier::Named(named) => { + !named.is_type_only && id_usage.contains(&named.local.to_id()) + } + ImportSpecifier::Default(default) => id_usage.contains(&default.local.to_id()), + ImportSpecifier::Namespace(namespace) => { + id_usage.contains(&namespace.local.to_id()) + } + }); + + !specifiers.is_empty() + } + ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport { + specifiers, + src: None, + type_only: false, + .. + })) => { + specifiers.retain(|export_specifier| match export_specifier { + ExportSpecifier::Namespace(..) => true, + ExportSpecifier::Default(default) => { + id_declare.contains(&default.exported.to_id()) + } + ExportSpecifier::Named(ExportNamedSpecifier { + orig: ModuleExportName::Ident(ident), + .. + }) => id_declare.contains(&ident.to_id()), + ExportSpecifier::Named(ExportNamedSpecifier { is_type_only, .. }) => { + !is_type_only + } + }); + + !specifiers.is_empty() + } + _ => true, + }); + } + + fn visit_mut_script(&mut self, _: &mut Script) { + // skip + } +} diff --git a/crates/swc_ecma_transforms_typescript/src/typescript.rs b/crates/swc_ecma_transforms_typescript/src/typescript.rs index 836325455c4b5..1ce411b0f29da 100644 --- a/crates/swc_ecma_transforms_typescript/src/typescript.rs +++ b/crates/swc_ecma_transforms_typescript/src/typescript.rs @@ -4,8 +4,8 @@ use swc_ecma_ast::*; use swc_ecma_visit::{as_folder, Fold, VisitMut, VisitMutWith, VisitWith}; use crate::{ - collect::Collect, strip_import::StripImport, strip_type::StripType, transform::transform, - TsImportExportAssignConfig, + collect::Collect, strip_import_export::StripImportExport, strip_type::StripType, + transform::transform, TsImportExportAssignConfig, }; #[derive(Debug, Default, Serialize, Deserialize)] @@ -51,7 +51,7 @@ pub(crate) struct TypeScript { impl VisitMut for TypeScript { fn visit_mut_program(&mut self, n: &mut Program) { if !self.config.verbatim_module_syntax { - n.visit_mut_with(&mut StripImport::default()); + n.visit_mut_with(&mut StripImportExport); } n.visit_mut_with(&mut StripType::default());