swc_typescript/fast_dts/
mod.rs

1use std::{borrow::Cow, mem::take, sync::Arc};
2
3use rustc_hash::{FxHashMap, FxHashSet};
4use swc_atoms::Atom;
5use swc_common::{
6    comments::SingleThreadedComments, util::take::Take, BytePos, FileName, Mark, Span, Spanned,
7    DUMMY_SP,
8};
9use swc_ecma_ast::{
10    BindingIdent, Decl, DefaultDecl, ExportDefaultExpr, Ident, ImportSpecifier, ModuleDecl,
11    ModuleItem, NamedExport, Pat, Program, Script, Stmt, TsExportAssignment, VarDecl, VarDeclKind,
12    VarDeclarator,
13};
14use type_usage::TypeUsageAnalyzer;
15use util::{
16    ast_ext::MemberPropExt, expando_function_collector::ExpandoFunctionCollector, types::type_ann,
17};
18use visitors::type_usage::{self, SymbolFlags, UsedRefs};
19
20use crate::diagnostic::{DtsIssue, SourceRange};
21
22mod class;
23mod decl;
24mod r#enum;
25mod function;
26mod inferrer;
27mod types;
28mod util;
29mod visitors;
30
31/// TypeScript Isolated Declaration support.
32///
33/// ---
34///
35/// # License
36///
37/// Mostly translated from <https://github.com/oxc-project/oxc/tree/main/crates/oxc_isolated_declarations>
38///
39/// The original code is MIT licensed.
40pub struct FastDts {
41    filename: Arc<FileName>,
42    unresolved_mark: Mark,
43    diagnostics: Vec<DtsIssue>,
44    // states
45    id_counter: u32,
46    is_top_level: bool,
47    used_refs: UsedRefs,
48    internal_annotations: Option<FxHashSet<BytePos>>,
49}
50
51#[derive(Debug, Default)]
52pub struct FastDtsOptions {
53    pub internal_annotations: Option<FxHashSet<BytePos>>,
54}
55
56/// Diagnostics
57impl FastDts {
58    pub fn new(filename: Arc<FileName>, unresolved_mark: Mark, options: FastDtsOptions) -> Self {
59        let internal_annotations = options.internal_annotations;
60        Self {
61            filename,
62            unresolved_mark,
63            diagnostics: Vec::new(),
64            id_counter: 0,
65            is_top_level: true,
66            used_refs: UsedRefs::default(),
67            internal_annotations,
68        }
69    }
70
71    pub fn mark_diagnostic<T: Into<Cow<'static, str>>>(&mut self, message: T, range: Span) {
72        self.diagnostics.push(DtsIssue {
73            message: message.into(),
74            range: SourceRange {
75                filename: self.filename.clone(),
76                span: range,
77            },
78        })
79    }
80}
81
82impl FastDts {
83    pub fn transform(&mut self, program: &mut Program) -> Vec<DtsIssue> {
84        match program {
85            Program::Module(module) => self.transform_module_body(&mut module.body, false),
86            Program::Script(script) => self.transform_script(script),
87            #[cfg(swc_ast_unknown)]
88            _ => panic!("unable to access unknown nodes"),
89        }
90        take(&mut self.diagnostics)
91    }
92
93    fn transform_module_body(
94        &mut self,
95        items: &mut Vec<ModuleItem>,
96        in_global_or_lit_module: bool,
97    ) {
98        // 1. Analyze usage
99        self.used_refs.extend(TypeUsageAnalyzer::analyze(
100            items,
101            self.internal_annotations.as_ref(),
102        ));
103
104        // 2. Transform.
105        Self::remove_function_overloads_in_module(items);
106        self.transform_module_items(items);
107
108        // 3. Strip export keywords in ts module blocks
109        for item in items.iter_mut() {
110            if let Some(Stmt::Decl(Decl::TsModule(ts_module))) = item.as_mut_stmt() {
111                if ts_module.global || !ts_module.id.is_str() {
112                    continue;
113                }
114
115                if let Some(body) = ts_module
116                    .body
117                    .as_mut()
118                    .and_then(|body| body.as_mut_ts_module_block())
119                {
120                    self.strip_export(&mut body.body);
121                }
122            }
123        }
124
125        // 4. Report error for expando function and remove statements.
126        self.report_error_for_expando_function_in_module(items);
127        items.retain(|item| {
128            item.as_stmt()
129                .map(|stmt| stmt.is_decl() && !self.has_internal_annotation(stmt.span_lo()))
130                .unwrap_or(true)
131        });
132
133        // 5. Remove unused imports and decls
134        self.remove_ununsed(items, in_global_or_lit_module);
135
136        // 6. Add empty export mark if there's any declaration that is used but not
137        // exported to keep its privacy.
138        let mut has_non_exported_stmt = false;
139        let mut has_export = false;
140        for item in items.iter_mut() {
141            match item {
142                ModuleItem::Stmt(stmt) => {
143                    if stmt.as_decl().map_or(true, |decl| !decl.is_ts_module()) {
144                        has_non_exported_stmt = true;
145                    }
146                }
147                ModuleItem::ModuleDecl(
148                    ModuleDecl::ExportDefaultDecl(_)
149                    | ModuleDecl::ExportDefaultExpr(_)
150                    | ModuleDecl::ExportNamed(_)
151                    | ModuleDecl::TsExportAssignment(_),
152                ) => has_export = true,
153                _ => {}
154            }
155        }
156        if items.is_empty() || (has_non_exported_stmt && !has_export) {
157            items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
158                NamedExport {
159                    span: DUMMY_SP,
160                    specifiers: Vec::new(),
161                    src: None,
162                    type_only: false,
163                    with: None,
164                },
165            )));
166        } else if !self.is_top_level {
167            self.strip_export(items);
168        }
169    }
170
171    fn transform_script(&mut self, script: &mut Script) {
172        // 1. Transform.
173        Self::remove_function_overloads_in_script(script);
174        let body = script.body.take();
175        for mut stmt in body {
176            if self.has_internal_annotation(stmt.span_lo()) {
177                continue;
178            }
179            if let Some(decl) = stmt.as_mut_decl() {
180                self.transform_decl(decl, false);
181            }
182            script.body.push(stmt);
183        }
184
185        // 2. Report error for expando function and remove statements.
186        self.report_error_for_expando_function_in_script(&script.body);
187        script
188            .body
189            .retain(|stmt| stmt.is_decl() && !self.has_internal_annotation(stmt.span_lo()));
190    }
191
192    fn transform_module_items(&mut self, items: &mut Vec<ModuleItem>) {
193        let orig_items = take(items);
194
195        for mut item in orig_items {
196            match &mut item {
197                ModuleItem::ModuleDecl(
198                    ModuleDecl::Import(..)
199                    | ModuleDecl::TsImportEquals(_)
200                    | ModuleDecl::TsNamespaceExport(_),
201                ) => items.push(item),
202                ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(_) | ModuleDecl::ExportAll(_)) => {
203                    items.push(item);
204                }
205                ModuleItem::Stmt(stmt) => {
206                    if self.has_internal_annotation(stmt.span_lo()) {
207                        continue;
208                    }
209
210                    if let Some(decl) = stmt.as_mut_decl() {
211                        self.transform_decl(decl, true);
212                    }
213                    items.push(item);
214                }
215                ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(expor_decl)) => {
216                    if self.has_internal_annotation(expor_decl.span_lo()) {
217                        continue;
218                    }
219                    self.transform_decl(&mut expor_decl.decl, false);
220                    items.push(item);
221                }
222                ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export)) => {
223                    self.transform_default_decl(&mut export.decl);
224                    items.push(item);
225                }
226                ModuleItem::ModuleDecl(
227                    ModuleDecl::ExportDefaultExpr(_) | ModuleDecl::TsExportAssignment(_),
228                ) => {
229                    let expr = match &item {
230                        ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export)) => {
231                            &export.expr
232                        }
233                        ModuleItem::ModuleDecl(ModuleDecl::TsExportAssignment(export)) => {
234                            &export.expr
235                        }
236                        _ => unreachable!(),
237                    };
238
239                    if expr.is_ident() {
240                        items.push(item);
241                        continue;
242                    }
243
244                    let name_ident = Ident::new_no_ctxt(self.gen_unique_name("_default"), DUMMY_SP);
245                    let type_ann = self.infer_type_from_expr(expr).map(type_ann);
246                    self.used_refs
247                        .add_usage(name_ident.to_id(), SymbolFlags::Value);
248
249                    if type_ann.is_none() {
250                        self.default_export_inferred(expr.span());
251                    }
252
253                    items.push(
254                        VarDecl {
255                            span: DUMMY_SP,
256                            kind: VarDeclKind::Const,
257                            declare: true,
258                            decls: vec![VarDeclarator {
259                                span: DUMMY_SP,
260                                name: Pat::Ident(BindingIdent {
261                                    id: name_ident.clone(),
262                                    type_ann,
263                                }),
264                                init: None,
265                                definite: false,
266                            }],
267                            ..Default::default()
268                        }
269                        .into(),
270                    );
271
272                    match &item {
273                        ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export)) => items
274                            .push(
275                                ExportDefaultExpr {
276                                    span: export.span,
277                                    expr: name_ident.into(),
278                                }
279                                .into(),
280                            ),
281                        ModuleItem::ModuleDecl(ModuleDecl::TsExportAssignment(export)) => items
282                            .push(
283                                TsExportAssignment {
284                                    span: export.span,
285                                    expr: name_ident.into(),
286                                }
287                                .into(),
288                            ),
289                        _ => unreachable!(),
290                    };
291                }
292                #[cfg(swc_ast_unknown)]
293                _ => panic!("unable to access unknown nodes"),
294            }
295        }
296    }
297
298    fn report_error_for_expando_function_in_module(&mut self, items: &[ModuleItem]) {
299        let used_refs = self.used_refs.clone();
300        let mut assignable_properties_for_namespace = FxHashMap::<&str, FxHashSet<Atom>>::default();
301        let mut collector = ExpandoFunctionCollector::new(&used_refs);
302
303        for item in items {
304            let decl = match item {
305                ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
306                    if let Some(ts_module) = export_decl.decl.as_ts_module() {
307                        ts_module
308                    } else {
309                        continue;
310                    }
311                }
312                ModuleItem::Stmt(Stmt::Decl(Decl::TsModule(ts_module))) => ts_module,
313                _ => continue,
314            };
315
316            let (Some(name), Some(block)) = (
317                decl.id.as_ident(),
318                decl.body
319                    .as_ref()
320                    .and_then(|body| body.as_ts_module_block()),
321            ) else {
322                continue;
323            };
324
325            for item in &block.body {
326                // Note that all the module blocks have been transformed
327                let Some(decl) = item.as_stmt().and_then(|stmt| stmt.as_decl()) else {
328                    continue;
329                };
330
331                match &decl {
332                    Decl::Class(class_decl) => {
333                        assignable_properties_for_namespace
334                            .entry(name.sym.as_str())
335                            .or_default()
336                            .insert(class_decl.ident.sym.clone());
337                    }
338                    Decl::Fn(fn_decl) => {
339                        assignable_properties_for_namespace
340                            .entry(name.sym.as_str())
341                            .or_default()
342                            .insert(fn_decl.ident.sym.clone());
343                    }
344                    Decl::Var(var_decl) => {
345                        for decl in &var_decl.decls {
346                            if let Some(ident) = decl.name.as_ident() {
347                                assignable_properties_for_namespace
348                                    .entry(name.sym.as_str())
349                                    .or_default()
350                                    .insert(ident.sym.clone());
351                            }
352                        }
353                    }
354                    Decl::Using(using_decl) => {
355                        for decl in &using_decl.decls {
356                            if let Some(ident) = decl.name.as_ident() {
357                                assignable_properties_for_namespace
358                                    .entry(name.sym.as_str())
359                                    .or_default()
360                                    .insert(ident.sym.clone());
361                            }
362                        }
363                    }
364                    _ => {}
365                }
366            }
367        }
368
369        for item in items {
370            match item {
371                ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
372                    match &export_decl.decl {
373                        Decl::Fn(fn_decl) => collector.add_fn_decl(fn_decl, false),
374                        Decl::Var(var_decl) => collector.add_var_decl(var_decl, false),
375                        _ => (),
376                    }
377                }
378                ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export_decl)) => {
379                    if let DefaultDecl::Fn(fn_expr) = &export_decl.decl {
380                        collector.add_fn_expr(fn_expr)
381                    }
382                }
383                ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(_export_named)) => {
384                    // TODO: may be function
385                }
386                ModuleItem::Stmt(Stmt::Decl(decl)) => match decl {
387                    Decl::Fn(fn_decl) => collector.add_fn_decl(fn_decl, true),
388                    Decl::Var(var_decl) => collector.add_var_decl(var_decl, true),
389                    _ => (),
390                },
391                ModuleItem::Stmt(Stmt::Expr(expr_stmt)) => {
392                    let Some(assign_expr) = expr_stmt.expr.as_assign() else {
393                        continue;
394                    };
395                    let Some(member_expr) = assign_expr
396                        .left
397                        .as_simple()
398                        .and_then(|simple| simple.as_member())
399                    else {
400                        continue;
401                    };
402
403                    if let Some(ident) = member_expr.obj.as_ident() {
404                        if collector.contains(&ident.sym)
405                            && !assignable_properties_for_namespace
406                                .get(ident.sym.as_str())
407                                .is_some_and(|properties| {
408                                    member_expr
409                                        .prop
410                                        .static_name()
411                                        .is_some_and(|name| properties.contains(name))
412                                })
413                        {
414                            self.function_with_assigning_properties(member_expr.span);
415                        }
416                    }
417                }
418                _ => (),
419            }
420        }
421    }
422
423    fn report_error_for_expando_function_in_script(&mut self, stmts: &[Stmt]) {
424        let used_refs = self.used_refs.clone();
425        let mut collector = ExpandoFunctionCollector::new(&used_refs);
426        for stmt in stmts {
427            match stmt {
428                Stmt::Decl(decl) => match decl {
429                    Decl::Fn(fn_decl) => collector.add_fn_decl(fn_decl, false),
430                    Decl::Var(var_decl) => collector.add_var_decl(var_decl, false),
431                    _ => (),
432                },
433                Stmt::Expr(expr_stmt) => {
434                    let Some(assign_expr) = expr_stmt.expr.as_assign() else {
435                        continue;
436                    };
437                    let Some(member_expr) = assign_expr
438                        .left
439                        .as_simple()
440                        .and_then(|simple| simple.as_member())
441                    else {
442                        continue;
443                    };
444
445                    if let Some(ident) = member_expr.obj.as_ident() {
446                        if collector.contains(&ident.sym) {
447                            self.function_with_assigning_properties(member_expr.span);
448                        }
449                    }
450                }
451                _ => (),
452            }
453        }
454    }
455
456    fn strip_export(&self, items: &mut Vec<ModuleItem>) {
457        for item in items {
458            if let ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) = item {
459                *item = ModuleItem::Stmt(Stmt::Decl(export_decl.decl.clone()));
460            }
461        }
462    }
463
464    fn remove_ununsed(&self, items: &mut Vec<ModuleItem>, in_global_or_lit_module: bool) {
465        let used_refs = &self.used_refs;
466        items.retain_mut(|node| match node {
467            ModuleItem::Stmt(Stmt::Decl(decl)) if !in_global_or_lit_module => match decl {
468                Decl::Class(class_decl) => used_refs.used(&class_decl.ident.to_id()),
469                Decl::Fn(fn_decl) => used_refs.used_as_value(&fn_decl.ident.to_id()),
470                Decl::Var(var_decl) => {
471                    var_decl.decls.retain(|decl| {
472                        if let Some(ident) = decl.name.as_ident() {
473                            used_refs.used_as_value(&ident.to_id())
474                        } else {
475                            false
476                        }
477                    });
478                    !var_decl.decls.is_empty()
479                }
480                Decl::Using(using_decl) => {
481                    using_decl.decls.retain(|decl| {
482                        if let Some(ident) = decl.name.as_ident() {
483                            used_refs.used_as_value(&ident.to_id())
484                        } else {
485                            false
486                        }
487                    });
488                    !using_decl.decls.is_empty()
489                }
490                Decl::TsInterface(ts_interface_decl) => {
491                    used_refs.used_as_type(&ts_interface_decl.id.to_id())
492                }
493                Decl::TsTypeAlias(ts_type_alias_decl) => {
494                    used_refs.used_as_type(&ts_type_alias_decl.id.to_id())
495                }
496                Decl::TsEnum(ts_enum) => used_refs.used(&ts_enum.id.to_id()),
497                Decl::TsModule(ts_module_decl) => {
498                    ts_module_decl.global
499                        || ts_module_decl.id.is_str()
500                        || ts_module_decl
501                            .id
502                            .as_ident()
503                            .map_or(true, |ident| used_refs.used_as_type(&ident.to_id()))
504                }
505                #[cfg(swc_ast_unknown)]
506                _ => panic!("unable to access unknown nodes"),
507            },
508            ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) => {
509                if import_decl.specifiers.is_empty() {
510                    return true;
511                }
512
513                import_decl.specifiers.retain(|specifier| match specifier {
514                    ImportSpecifier::Named(specifier) => used_refs.used(&specifier.local.to_id()),
515                    ImportSpecifier::Default(specifier) => used_refs.used(&specifier.local.to_id()),
516                    ImportSpecifier::Namespace(specifier) => {
517                        used_refs.used(&specifier.local.to_id())
518                    }
519                    #[cfg(swc_ast_unknown)]
520                    _ => panic!("unable to access unknown nodes"),
521                });
522
523                !import_decl.specifiers.is_empty()
524            }
525            ModuleItem::ModuleDecl(ModuleDecl::TsImportEquals(ts_import_equals)) => {
526                used_refs.used(&ts_import_equals.id.to_id())
527            }
528            _ => true,
529        });
530    }
531
532    pub fn has_internal_annotation(&self, pos: BytePos) -> bool {
533        if let Some(internal_annotations) = &self.internal_annotations {
534            return internal_annotations.contains(&pos);
535        }
536        false
537    }
538
539    pub fn get_internal_annotations(comments: &SingleThreadedComments) -> FxHashSet<BytePos> {
540        let mut internal_annotations = FxHashSet::default();
541        let (leading, _) = comments.borrow_all();
542        for (pos, comment) in leading.iter() {
543            let has_internal_annotation = comment
544                .iter()
545                .any(|comment| comment.text.contains("@internal"));
546            if has_internal_annotation {
547                internal_annotations.insert(*pos);
548            }
549        }
550        internal_annotations
551    }
552
553    fn gen_unique_name(&mut self, name: &str) -> Atom {
554        self.id_counter += 1;
555        format!("{name}_{}", self.id_counter).into()
556    }
557}