swc_ecma_transforms_typescript/
typescript.rs

1use 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        // Do not use `matches!`
121        // We should cover all cases explicitly.
122        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            #[cfg(swc_ast_unknown)]
134            _ => panic!("unable to access unknown nodes"),
135        }
136    }
137}
138
139impl EsModuleDecl for ModuleItem {
140    fn is_es_module_decl(&self) -> bool {
141        self.as_module_decl()
142            .is_some_and(ModuleDecl::is_es_module_decl)
143    }
144}
145
146pub fn tsx<C>(
147    cm: Lrc<SourceMap>,
148    config: Config,
149    tsx_config: TsxConfig,
150    comments: C,
151    unresolved_mark: Mark,
152    top_level_mark: Mark,
153) -> impl Pass
154where
155    C: Comments,
156{
157    visit_mut_pass(TypeScriptReact {
158        config,
159        tsx_config,
160        id_usage: Default::default(),
161        comments,
162        cm,
163        top_level_mark,
164        unresolved_mark,
165    })
166}
167
168/// Get an [Id] which will used by expression.
169///
170/// For `React#1.createElement`, this returns `React#1`.
171fn id_for_jsx(e: &Expr) -> Option<Id> {
172    match e {
173        Expr::Ident(i) => Some(i.to_id()),
174        Expr::Member(MemberExpr { obj, .. }) => Some(id_for_jsx(obj)).flatten(),
175        Expr::Lit(Lit::Null(..)) => Some((atom!("null"), Default::default())),
176        _ => None,
177    }
178}
179
180struct TypeScriptReact<C>
181where
182    C: Comments,
183{
184    config: Config,
185    tsx_config: TsxConfig,
186    id_usage: FxHashSet<Id>,
187    comments: C,
188    cm: Lrc<SourceMap>,
189    top_level_mark: Mark,
190    unresolved_mark: Mark,
191}
192
193impl<C> VisitMut for TypeScriptReact<C>
194where
195    C: Comments,
196{
197    fn visit_mut_module(&mut self, n: &mut Module) {
198        // We count `React` or pragma from config as ident usage and do not strip it
199        // from import statement.
200        // But in `verbatim_module_syntax` mode, we do not remove any unused imports.
201        // So we do not need to collect usage info.
202        if !self.config.verbatim_module_syntax {
203            let pragma = parse_expr_for_jsx(
204                &self.cm,
205                "pragma",
206                self.tsx_config
207                    .pragma
208                    .clone()
209                    .unwrap_or_else(|| static_str!("React.createElement")),
210                self.top_level_mark,
211            );
212
213            let pragma_frag = parse_expr_for_jsx(
214                &self.cm,
215                "pragma",
216                self.tsx_config
217                    .pragma_frag
218                    .clone()
219                    .unwrap_or_else(|| static_str!("React.Fragment")),
220                self.top_level_mark,
221            );
222
223            let pragma_id = id_for_jsx(&pragma).unwrap();
224            let pragma_frag_id = id_for_jsx(&pragma_frag).unwrap();
225
226            self.id_usage.insert(pragma_id);
227            self.id_usage.insert(pragma_frag_id);
228        }
229
230        if !self.config.verbatim_module_syntax {
231            let span = if n.shebang.is_some() {
232                n.span
233                    .with_lo(n.body.first().map(|s| s.span_lo()).unwrap_or(n.span.lo))
234            } else {
235                n.span
236            };
237
238            let JsxDirectives {
239                pragma,
240                pragma_frag,
241                ..
242            } = self.comments.with_leading(span.lo, |comments| {
243                JsxDirectives::from_comments(&self.cm, span, comments, self.top_level_mark)
244            });
245
246            if let Some(pragma) = pragma {
247                if let Some(pragma_id) = id_for_jsx(&pragma) {
248                    self.id_usage.insert(pragma_id);
249                }
250            }
251
252            if let Some(pragma_frag) = pragma_frag {
253                if let Some(pragma_frag_id) = id_for_jsx(&pragma_frag) {
254                    self.id_usage.insert(pragma_frag_id);
255                }
256            }
257        }
258    }
259
260    fn visit_mut_script(&mut self, _: &mut Script) {
261        // skip script
262    }
263
264    fn visit_mut_program(&mut self, n: &mut Program) {
265        n.visit_mut_children_with(self);
266
267        n.visit_mut_with(&mut TypeScript {
268            config: mem::take(&mut self.config),
269            unresolved_mark: self.unresolved_mark,
270            top_level_mark: self.top_level_mark,
271            id_usage: mem::take(&mut self.id_usage),
272        });
273    }
274}