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        }
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
166/// Get an [Id] which will used by expression.
167///
168/// For `React#1.createElement`, this returns `React#1`.
169fn 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        // We count `React` or pragma from config as ident usage and do not strip it
197        // from import statement.
198        // But in `verbatim_module_syntax` mode, we do not remove any unused imports.
199        // So we do not need to collect usage info.
200        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        // skip script
260    }
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}