swc_ecma_transforms_typescript/
typescript.rs1use std::mem;
2
3use rustc_hash::FxHashSet;
4use swc_atoms::atom;
5use swc_common::{comments::Comments, sync::Lrc, util::take::Take, Mark, SourceMap, Span, Spanned};
6use swc_ecma_ast::*;
7use swc_ecma_transforms_react::{parse_expr_for_jsx, JsxDirectives};
8use swc_ecma_visit::{visit_mut_pass, VisitMut, VisitMutWith};
9
10pub use crate::config::*;
11use crate::{strip_import_export::StripImportExport, strip_type::StripType, transform::transform};
12
13macro_rules! static_str {
14 ($s:expr) => {
15 $s.into()
16 };
17}
18
19pub fn typescript(config: Config, unresolved_mark: Mark, top_level_mark: Mark) -> impl Pass {
20 debug_assert_ne!(unresolved_mark, top_level_mark);
21
22 visit_mut_pass(TypeScript {
23 config,
24 unresolved_mark,
25 top_level_mark,
26 id_usage: Default::default(),
27 })
28}
29
30pub fn strip(unresolved_mark: Mark, top_level_mark: Mark) -> impl Pass {
31 typescript(Config::default(), unresolved_mark, top_level_mark)
32}
33
34pub(crate) struct TypeScript {
35 pub config: Config,
36 pub unresolved_mark: Mark,
37 pub top_level_mark: Mark,
38
39 id_usage: FxHashSet<Id>,
40}
41
42impl VisitMut for TypeScript {
43 fn visit_mut_program(&mut self, n: &mut Program) {
44 let was_module = n.as_module().and_then(|m| self.get_last_module_span(m));
45
46 if !self.config.verbatim_module_syntax {
47 n.visit_mut_with(&mut StripImportExport {
48 import_not_used_as_values: self.config.import_not_used_as_values,
49 usage_info: mem::take(&mut self.id_usage).into(),
50 ..Default::default()
51 });
52 }
53
54 n.visit_mut_with(&mut StripType::default());
55
56 n.mutate(transform(
57 self.unresolved_mark,
58 self.top_level_mark,
59 self.config.import_export_assign_config,
60 self.config.ts_enum_is_mutable,
61 self.config.verbatim_module_syntax,
62 self.config.native_class_properties,
63 ));
64
65 if let Some(span) = was_module {
66 let module = n.as_mut_module().unwrap();
67 Self::restore_esm_ctx(module, span);
68 }
69 }
70
71 fn visit_mut_script(&mut self, _: &mut Script) {
72 #[cfg(debug_assertions)]
73 unreachable!("Use Program as entry");
74 #[cfg(not(debug_assertions))]
75 unreachable!();
76 }
77
78 fn visit_mut_module(&mut self, _: &mut Module) {
79 #[cfg(debug_assertions)]
80 unreachable!("Use Program as entry");
81 #[cfg(not(debug_assertions))]
82 unreachable!();
83 }
84}
85
86impl TypeScript {
87 fn get_last_module_span(&self, n: &Module) -> Option<Span> {
88 if self.config.no_empty_export {
89 return None;
90 }
91
92 n.body
93 .iter()
94 .rev()
95 .find(|m| m.is_es_module_decl())
96 .map(Spanned::span)
97 }
98
99 fn restore_esm_ctx(n: &mut Module, span: Span) {
100 if n.body.iter().any(ModuleItem::is_es_module_decl) {
101 return;
102 }
103
104 n.body.push(
105 NamedExport {
106 span,
107 ..NamedExport::dummy()
108 }
109 .into(),
110 );
111 }
112}
113
114trait EsModuleDecl {
115 fn is_es_module_decl(&self) -> bool;
116}
117
118impl EsModuleDecl for ModuleDecl {
119 fn is_es_module_decl(&self) -> bool {
120 match self {
123 ModuleDecl::Import(..)
124 | ModuleDecl::ExportDecl(..)
125 | ModuleDecl::ExportNamed(..)
126 | ModuleDecl::ExportDefaultDecl(..)
127 | ModuleDecl::ExportDefaultExpr(..)
128 | ModuleDecl::ExportAll(..) => true,
129
130 ModuleDecl::TsImportEquals(..)
131 | ModuleDecl::TsExportAssignment(..)
132 | ModuleDecl::TsNamespaceExport(..) => false,
133 }
134 }
135}
136
137impl EsModuleDecl for ModuleItem {
138 fn is_es_module_decl(&self) -> bool {
139 self.as_module_decl()
140 .is_some_and(ModuleDecl::is_es_module_decl)
141 }
142}
143
144pub fn tsx<C>(
145 cm: Lrc<SourceMap>,
146 config: Config,
147 tsx_config: TsxConfig,
148 comments: C,
149 unresolved_mark: Mark,
150 top_level_mark: Mark,
151) -> impl Pass
152where
153 C: Comments,
154{
155 visit_mut_pass(TypeScriptReact {
156 config,
157 tsx_config,
158 id_usage: Default::default(),
159 comments,
160 cm,
161 top_level_mark,
162 unresolved_mark,
163 })
164}
165
166fn id_for_jsx(e: &Expr) -> Option<Id> {
170 match e {
171 Expr::Ident(i) => Some(i.to_id()),
172 Expr::Member(MemberExpr { obj, .. }) => Some(id_for_jsx(obj)).flatten(),
173 Expr::Lit(Lit::Null(..)) => Some((atom!("null"), Default::default())),
174 _ => None,
175 }
176}
177
178struct TypeScriptReact<C>
179where
180 C: Comments,
181{
182 config: Config,
183 tsx_config: TsxConfig,
184 id_usage: FxHashSet<Id>,
185 comments: C,
186 cm: Lrc<SourceMap>,
187 top_level_mark: Mark,
188 unresolved_mark: Mark,
189}
190
191impl<C> VisitMut for TypeScriptReact<C>
192where
193 C: Comments,
194{
195 fn visit_mut_module(&mut self, n: &mut Module) {
196 if !self.config.verbatim_module_syntax {
201 let pragma = parse_expr_for_jsx(
202 &self.cm,
203 "pragma",
204 self.tsx_config
205 .pragma
206 .clone()
207 .unwrap_or_else(|| static_str!("React.createElement")),
208 self.top_level_mark,
209 );
210
211 let pragma_frag = parse_expr_for_jsx(
212 &self.cm,
213 "pragma",
214 self.tsx_config
215 .pragma_frag
216 .clone()
217 .unwrap_or_else(|| static_str!("React.Fragment")),
218 self.top_level_mark,
219 );
220
221 let pragma_id = id_for_jsx(&pragma).unwrap();
222 let pragma_frag_id = id_for_jsx(&pragma_frag).unwrap();
223
224 self.id_usage.insert(pragma_id);
225 self.id_usage.insert(pragma_frag_id);
226 }
227
228 if !self.config.verbatim_module_syntax {
229 let span = if n.shebang.is_some() {
230 n.span
231 .with_lo(n.body.first().map(|s| s.span_lo()).unwrap_or(n.span.lo))
232 } else {
233 n.span
234 };
235
236 let JsxDirectives {
237 pragma,
238 pragma_frag,
239 ..
240 } = self.comments.with_leading(span.lo, |comments| {
241 JsxDirectives::from_comments(&self.cm, span, comments, self.top_level_mark)
242 });
243
244 if let Some(pragma) = pragma {
245 if let Some(pragma_id) = id_for_jsx(&pragma) {
246 self.id_usage.insert(pragma_id);
247 }
248 }
249
250 if let Some(pragma_frag) = pragma_frag {
251 if let Some(pragma_frag_id) = id_for_jsx(&pragma_frag) {
252 self.id_usage.insert(pragma_frag_id);
253 }
254 }
255 }
256 }
257
258 fn visit_mut_script(&mut self, _: &mut Script) {
259 }
261
262 fn visit_mut_program(&mut self, n: &mut Program) {
263 n.visit_mut_children_with(self);
264
265 n.visit_mut_with(&mut TypeScript {
266 config: mem::take(&mut self.config),
267 unresolved_mark: self.unresolved_mark,
268 top_level_mark: self.top_level_mark,
269 id_usage: mem::take(&mut self.id_usage),
270 });
271 }
272}