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
16pub(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 fn visit_module_decl(&mut self, _: &ModuleDecl) {}
64
65 fn visit_function(&mut self, _: &Function) {}
67
68 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 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}