From 97ac282c5bd42974791836d8770490b20575f4c5 Mon Sep 17 00:00:00 2001 From: DongYun Kang Date: Sat, 7 Feb 2026 10:36:22 +0900 Subject: [PATCH] perf(es/typescript): reduce visitor overhead in ts transform --- .../src/transform.rs | 28 ++++- .../src/typescript.rs | 108 +++++++++++++----- 2 files changed, 101 insertions(+), 35 deletions(-) diff --git a/crates/swc_ecma_transforms_typescript/src/transform.rs b/crates/swc_ecma_transforms_typescript/src/transform.rs index db93eb3ae6ae..d9f582cf7784 100644 --- a/crates/swc_ecma_transforms_typescript/src/transform.rs +++ b/crates/swc_ecma_transforms_typescript/src/transform.rs @@ -590,10 +590,6 @@ impl VisitMut for Transform { } fn visit_mut_ts_module_block(&mut self, node: &mut TsModuleBlock) { - if !self.verbatim_module_syntax { - self.strip_namespace_module_items_with_semantic(&mut node.body); - } - let in_namespace = mem::replace(&mut self.in_namespace, true); node.visit_mut_children_with(self); self.in_namespace = in_namespace; @@ -1790,3 +1786,27 @@ fn get_member_key(prop: &MemberProp) -> Option { _ => panic!("unable to access unknown nodes"), } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn get_member_key_resolves_template_literal_without_expressions() { + let member_prop = MemberProp::Computed(ComputedPropName { + span: DUMMY_SP, + expr: Box::new(Expr::Tpl(Tpl { + span: DUMMY_SP, + exprs: vec![], + quasis: vec![TplElement { + span: DUMMY_SP, + tail: true, + cooked: Some("foo".into()), + raw: "foo".into(), + }], + })), + }); + + assert_eq!(get_member_key(&member_prop), Some("foo".into())); + } +} diff --git a/crates/swc_ecma_transforms_typescript/src/typescript.rs b/crates/swc_ecma_transforms_typescript/src/typescript.rs index 86b9d0f61ba8..6d1ca4accb3a 100644 --- a/crates/swc_ecma_transforms_typescript/src/typescript.rs +++ b/crates/swc_ecma_transforms_typescript/src/typescript.rs @@ -5,7 +5,6 @@ use swc_atoms::atom; use swc_common::{comments::Comments, sync::Lrc, util::take::Take, Mark, SourceMap, Span, Spanned}; use swc_ecma_ast::*; use swc_ecma_transforms_react::{parse_expr_for_jsx, JsxDirectives}; -use swc_ecma_visit::{visit_mut_pass, VisitMut, VisitMutWith}; pub use crate::config::*; use crate::{semantic::analyze_program, transform::transform}; @@ -133,7 +132,7 @@ pub fn tsx( where C: Comments, { - visit_mut_pass(TypeScriptReact { + TypeScriptReact { config, tsx_config, id_usage: Default::default(), @@ -141,7 +140,7 @@ where cm, top_level_mark, unresolved_mark, - }) + } } /// Get an [Id] which will used by expression. @@ -169,11 +168,31 @@ where unresolved_mark: Mark, } -impl VisitMut for TypeScriptReact +fn module_leading_comment_span(module: &Module) -> Span { + if module.shebang.is_some() { + module.span.with_lo( + module + .body + .first() + .map(|item| item.span_lo()) + .unwrap_or(module.span.lo), + ) + } else { + module.span + } +} + +impl TypeScriptReact where C: Comments, { - fn visit_mut_module(&mut self, n: &mut Module) { + fn insert_jsx_id_usage(&mut self, expr: &Expr) { + if let Some(id) = id_for_jsx(expr) { + self.id_usage.insert(id); + } + } + + fn collect_pragma_id_usage(&mut self, module: &Module) { // We count `React` or pragma from config as ident usage and do not strip it // from import statement. // But in `verbatim_module_syntax` mode, we do not remove any unused imports. @@ -199,20 +218,10 @@ where self.top_level_mark, ); - let pragma_id = id_for_jsx(&pragma).unwrap(); - let pragma_frag_id = id_for_jsx(&pragma_frag).unwrap(); + self.insert_jsx_id_usage(&pragma); + self.insert_jsx_id_usage(&pragma_frag); - self.id_usage.insert(pragma_id); - self.id_usage.insert(pragma_frag_id); - } - - if !self.config.verbatim_module_syntax { - let span = if n.shebang.is_some() { - n.span - .with_lo(n.body.first().map(|s| s.span_lo()).unwrap_or(n.span.lo)) - } else { - n.span - }; + let span = module_leading_comment_span(module); let JsxDirectives { pragma, @@ -223,31 +232,68 @@ where }); if let Some(pragma) = pragma { - if let Some(pragma_id) = id_for_jsx(&pragma) { - self.id_usage.insert(pragma_id); - } + self.insert_jsx_id_usage(&pragma); } if let Some(pragma_frag) = pragma_frag { - if let Some(pragma_frag_id) = id_for_jsx(&pragma_frag) { - self.id_usage.insert(pragma_frag_id); - } + self.insert_jsx_id_usage(&pragma_frag); } } } +} - fn visit_mut_script(&mut self, _: &mut Script) { - // skip script - } - - fn visit_mut_program(&mut self, n: &mut Program) { - n.visit_mut_children_with(self); +impl Pass for TypeScriptReact +where + C: Comments, +{ + fn process(&mut self, n: &mut Program) { + if let Program::Module(module) = n { + self.collect_pragma_id_usage(module); + } - n.mutate(&mut TypeScript { + let mut pass = TypeScript { config: mem::take(&mut self.config), unresolved_mark: self.unresolved_mark, top_level_mark: self.top_level_mark, id_usage: mem::take(&mut self.id_usage), + }; + + pass.process(n); + } +} + +#[cfg(test)] +mod tests { + use swc_common::{BytePos, Span, DUMMY_SP}; + + use super::*; + + fn span(lo: u32, hi: u32) -> Span { + Span::new(BytePos(lo), BytePos(hi)) + } + + #[test] + fn id_for_jsx_resolves_root_ident() { + let react = Ident::new_no_ctxt("React".into(), DUMMY_SP); + let expr = Expr::Member(MemberExpr { + span: DUMMY_SP, + obj: Box::new(Expr::Ident(react.clone())), + prop: MemberProp::Ident(IdentName::new("createElement".into(), DUMMY_SP)), }); + + assert_eq!(id_for_jsx(&expr), Some(react.to_id())); + } + + #[test] + fn module_leading_comment_span_uses_first_item_when_shebang_exists() { + let module = Module { + span: span(1, 10), + body: vec![ModuleItem::Stmt(Stmt::Empty(EmptyStmt { + span: span(4, 5), + }))], + shebang: Some("usr/bin/env node".into()), + }; + + assert_eq!(module_leading_comment_span(&module), span(4, 10)); } }