swc_bundler/
inline.rs

1use rustc_hash::FxHashMap;
2use swc_common::{SyntaxContext, DUMMY_SP};
3use swc_ecma_ast::*;
4use swc_ecma_visit::{
5    noop_visit_mut_type, noop_visit_type, visit_mut_obj_and_computed, Visit, VisitMut,
6    VisitMutWith, VisitWith,
7};
8
9use crate::{id::Id, modules::Modules, util::Readonly};
10
11#[derive(Debug, Default)]
12pub(crate) struct InlineData {
13    ids: FxHashMap<Id, Id>,
14}
15
16/// Inline **injected** variables.
17pub(crate) fn inline(injected_ctxt: SyntaxContext, module: &mut Modules) {
18    tracing::debug!("Inlining injected variables");
19
20    let mut data = Default::default();
21
22    {
23        let mut analyzer = Analyzer {
24            injected_ctxt,
25            data: &mut data,
26        };
27
28        module.visit_with(&mut analyzer);
29    }
30
31    let mut v = Inliner { data: data.into() };
32    module.par_visit_mut_with(&mut v);
33    module.retain_mut(|_, s| !matches!(s, ModuleItem::Stmt(Stmt::Empty(..))));
34}
35
36#[derive(Debug)]
37#[cfg_attr(feature = "concurrent", derive(Clone))]
38struct Inliner {
39    data: Readonly<InlineData>,
40}
41
42struct Analyzer<'a> {
43    injected_ctxt: SyntaxContext,
44    data: &'a mut InlineData,
45}
46
47impl Analyzer<'_> {
48    fn store(&mut self, from: Id, to: Id) {
49        if let Some(prev) = self.data.ids.insert(from.clone(), to.clone()) {
50            unreachable!(
51                "Multiple identifiers equivalent up to span hygiene found: {:#?}\nFirst = \
52                 {:#?}\nSecond = {:#?}",
53                from, prev, to
54            )
55        }
56    }
57}
58
59impl Visit for Analyzer<'_> {
60    noop_visit_type!();
61
62    /// Noop
63    fn visit_module_decl(&mut self, _: &ModuleDecl) {}
64
65    /// Noop. We don't inline variables declared in subscopes.
66    fn visit_function(&mut self, _: &Function) {}
67
68    /// Noop. We don't inline variables declared in subscopes.
69    fn visit_block_stmt(&mut self, _: &BlockStmt) {}
70
71    fn visit_var_decl(&mut self, n: &VarDecl) {
72        if n.ctxt != self.injected_ctxt || n.kind != VarDeclKind::Const {
73            return;
74        }
75
76        n.visit_children_with(self);
77    }
78
79    fn visit_var_declarator(&mut self, n: &VarDeclarator) {
80        n.visit_children_with(self);
81        if let (Pat::Ident(from), Some(Expr::Ident(to))) = (&n.name, n.init.as_deref()) {
82            self.store(from.id.clone().into(), to.into());
83        }
84    }
85}
86
87impl VisitMut for Inliner {
88    noop_visit_mut_type!(fail);
89
90    visit_mut_obj_and_computed!();
91
92    /// Don't modify exported ident.
93    fn visit_mut_export_named_specifier(&mut self, n: &mut ExportNamedSpecifier) {
94        if n.exported.is_none() {
95            n.exported = Some(n.orig.clone());
96        }
97
98        n.orig.visit_mut_with(self);
99    }
100
101    fn visit_mut_ident(&mut self, n: &mut Ident) {
102        if let Some(mapped) = self.data.ids.get(&n.clone().into()).cloned() {
103            *n = mapped.into();
104            n.visit_mut_with(self);
105        }
106    }
107
108    fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
109        n.visit_mut_children_with(self);
110
111        n.retain(|v| !matches!(v, ModuleItem::Stmt(Stmt::Empty(..))));
112    }
113
114    fn visit_mut_prop(&mut self, n: &mut Prop) {
115        match n {
116            Prop::Shorthand(i) => {
117                let orig = i.clone();
118                i.visit_mut_with(self);
119                if i.ctxt == orig.ctxt {
120                    return;
121                }
122                if i.sym != orig.sym {
123                    *n = Prop::KeyValue(KeyValueProp {
124                        key: PropName::Ident(orig.into()),
125                        value: i.clone().into(),
126                    });
127                }
128            }
129            _ => {
130                n.visit_mut_children_with(self);
131            }
132        }
133    }
134
135    fn visit_mut_prop_name(&mut self, n: &mut PropName) {
136        match n {
137            PropName::Ident(_) => {}
138            PropName::Str(_) => {}
139            PropName::Num(_) => {}
140            PropName::Computed(e) => {
141                e.expr.visit_mut_with(self);
142            }
143            PropName::BigInt(_) => {}
144            #[cfg(swc_ast_unknown)]
145            _ => panic!("unable to access unknown nodes"),
146        }
147    }
148
149    fn visit_mut_stmt(&mut self, n: &mut Stmt) {
150        n.visit_mut_children_with(self);
151
152        match n {
153            Stmt::Decl(Decl::Var(var)) if var.decls.is_empty() => {
154                *n = EmptyStmt { span: DUMMY_SP }.into();
155            }
156            _ => {}
157        }
158    }
159
160    fn visit_mut_var_declarators(&mut self, n: &mut Vec<VarDeclarator>) {
161        n.retain(|d| {
162            if let Pat::Ident(name) = &d.name {
163                if self.data.ids.contains_key(&name.id.clone().into()) {
164                    return false;
165                }
166            }
167
168            true
169        });
170
171        n.visit_mut_children_with(self);
172    }
173}