1use rustc_hash::{FxHashMap, FxHashSet};
2use swc_atoms::Atom;
3use swc_common::sync::Lrc;
4use swc_ecma_ast::*;
5use swc_ecma_transforms_base::perf::{ParVisitMut, Parallel};
6use swc_ecma_utils::{collect_decls, parallel::cpu_count, NodeIgnoringSpan};
7use swc_ecma_visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith};
8
9pub type GlobalExprMap = Lrc<FxHashMap<NodeIgnoringSpan<'static, Expr>, Expr>>;
12
13pub fn inline_globals(
16 envs: Lrc<FxHashMap<Atom, Expr>>,
17 globals: Lrc<FxHashMap<Atom, Expr>>,
18 typeofs: Lrc<FxHashMap<Atom, Atom>>,
19) -> impl Pass {
20 inline_globals2(envs, globals, Default::default(), typeofs)
21}
22
23pub fn inline_globals2(
30 envs: Lrc<FxHashMap<Atom, Expr>>,
31 globals: Lrc<FxHashMap<Atom, Expr>>,
32 global_exprs: GlobalExprMap,
33 typeofs: Lrc<FxHashMap<Atom, Atom>>,
34) -> impl Pass {
35 visit_mut_pass(InlineGlobals {
36 envs,
37 globals,
38 global_exprs,
39 typeofs,
40 bindings: Default::default(),
41 })
42}
43
44#[derive(Clone)]
45struct InlineGlobals {
46 envs: Lrc<FxHashMap<Atom, Expr>>,
47 globals: Lrc<FxHashMap<Atom, Expr>>,
48 global_exprs: Lrc<FxHashMap<NodeIgnoringSpan<'static, Expr>, Expr>>,
49
50 typeofs: Lrc<FxHashMap<Atom, Atom>>,
51
52 bindings: Lrc<FxHashSet<Id>>,
53}
54
55impl Parallel for InlineGlobals {
56 fn create(&self) -> Self {
57 self.clone()
58 }
59
60 fn merge(&mut self, _: Self) {}
61}
62
63impl VisitMut for InlineGlobals {
64 noop_visit_mut_type!(fail);
65
66 fn visit_mut_class_members(&mut self, members: &mut Vec<ClassMember>) {
67 self.visit_mut_par(cpu_count(), members);
68 }
69
70 fn visit_mut_expr(&mut self, expr: &mut Expr) {
71 if let Expr::Ident(Ident { ref sym, ctxt, .. }) = expr {
72 if self.bindings.contains(&(sym.clone(), *ctxt)) {
73 return;
74 }
75 }
76
77 if let Some(value) =
78 Ident::within_ignored_ctxt(|| self.global_exprs.get(&NodeIgnoringSpan::borrowed(expr)))
79 {
80 *expr = value.clone();
81 expr.visit_mut_with(self);
82 return;
83 }
84
85 expr.visit_mut_children_with(self);
86
87 match expr {
88 Expr::Ident(Ident { ref sym, .. }) => {
89 if let Some(value) = self.globals.get(sym) {
91 let mut value = value.clone();
92 value.visit_mut_with(self);
93 *expr = value;
94 }
95 }
96
97 Expr::Unary(UnaryExpr {
98 span,
99 op: op!("typeof"),
100 arg,
101 ..
102 }) => {
103 if let Expr::Ident(Ident {
104 ref sym,
105 ctxt: arg_ctxt,
106 ..
107 }) = &**arg
108 {
109 if self.bindings.contains(&(sym.clone(), *arg_ctxt)) {
110 return;
111 }
112
113 if let Some(value) = self.typeofs.get(sym).cloned() {
115 *expr = Lit::Str(Str {
116 span: *span,
117 raw: None,
118 value,
119 })
120 .into();
121 }
122 }
123 }
124
125 Expr::Member(MemberExpr { obj, prop, .. }) => match &**obj {
126 Expr::Member(MemberExpr {
127 obj: first_obj,
128 prop: inner_prop,
129 ..
130 }) if inner_prop.is_ident_with("env") => {
131 if first_obj.is_ident_ref_to("process") {
132 match prop {
133 MemberProp::Computed(ComputedPropName { expr: c, .. }) => {
134 if let Expr::Lit(Lit::Str(Str { value: sym, .. })) = &**c {
135 if let Some(env) = self.envs.get(sym) {
136 *expr = env.clone();
137 }
138 }
139 }
140
141 MemberProp::Ident(IdentName { sym, .. }) => {
142 if let Some(env) = self.envs.get(sym) {
143 *expr = env.clone();
144 }
145 }
146 _ => {}
147 }
148 }
149 }
150 _ => (),
151 },
152 _ => {}
153 }
154 }
155
156 fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec<ExprOrSpread>) {
157 self.visit_mut_par(cpu_count(), n);
158 }
159
160 fn visit_mut_exprs(&mut self, n: &mut Vec<Box<Expr>>) {
161 self.visit_mut_par(cpu_count(), n);
162 }
163
164 fn visit_mut_module(&mut self, module: &mut Module) {
165 self.bindings = Lrc::new(collect_decls(&*module));
166
167 module.visit_mut_children_with(self);
168 }
169
170 fn visit_mut_opt_vec_expr_or_spreads(&mut self, n: &mut Vec<Option<ExprOrSpread>>) {
171 self.visit_mut_par(cpu_count(), n);
172 }
173
174 fn visit_mut_prop(&mut self, p: &mut Prop) {
175 p.visit_mut_children_with(self);
176
177 if let Prop::Shorthand(i) = p {
178 if self.bindings.contains(&i.to_id()) {
179 return;
180 }
181
182 if let Some(mut value) = self.globals.get(&i.sym).cloned().map(Box::new) {
184 value.visit_mut_with(self);
185 *p = Prop::KeyValue(KeyValueProp {
186 key: PropName::Ident(i.clone().into()),
187 value,
188 });
189 }
190 }
191 }
192
193 fn visit_mut_prop_or_spreads(&mut self, n: &mut Vec<PropOrSpread>) {
194 self.visit_mut_par(cpu_count(), n);
195 }
196
197 fn visit_mut_script(&mut self, script: &mut Script) {
198 self.bindings = Lrc::new(collect_decls(&*script));
199
200 script.visit_mut_children_with(self);
201 }
202}
203
204#[cfg(test)]
205mod tests {
206 use swc_ecma_transforms_testing::{test, Tester};
207 use swc_ecma_utils::{DropSpan, StmtOrModuleItem};
208
209 use super::*;
210
211 fn mk_map(
212 tester: &mut Tester<'_>,
213 values: &[(&str, &str)],
214 is_env: bool,
215 ) -> FxHashMap<Atom, Expr> {
216 let mut m = FxHashMap::default();
217
218 for (k, v) in values {
219 let v = if is_env {
220 format!("'{}'", v)
221 } else {
222 (*v).into()
223 };
224
225 let v = tester
226 .apply_transform(
227 visit_mut_pass(DropSpan),
228 "global.js",
229 ::swc_ecma_parser::Syntax::default(),
230 None,
231 &v,
232 )
233 .unwrap();
234
235 let v = match v {
236 Program::Module(mut m) => m.body.pop().and_then(|x| x.into_stmt().ok()),
237 Program::Script(mut s) => s.body.pop(),
238 };
239 assert!(v.is_some());
240 let v = match v.unwrap() {
241 Stmt::Expr(ExprStmt { expr, .. }) => *expr,
242 _ => unreachable!(),
243 };
244
245 m.insert((*k).into(), v);
246 }
247
248 m
249 }
250
251 fn envs(tester: &mut Tester<'_>, values: &[(&str, &str)]) -> Lrc<FxHashMap<Atom, Expr>> {
252 Lrc::new(mk_map(tester, values, true))
253 }
254
255 fn globals(tester: &mut Tester<'_>, values: &[(&str, &str)]) -> Lrc<FxHashMap<Atom, Expr>> {
256 Lrc::new(mk_map(tester, values, false))
257 }
258
259 test!(
260 ::swc_ecma_parser::Syntax::default(),
261 |tester| inline_globals(envs(tester, &[]), globals(tester, &[]), Default::default(),),
262 issue_215,
263 r#"if (process.env.x === 'development') {}"#
264 );
265
266 test!(
267 ::swc_ecma_parser::Syntax::default(),
268 |tester| inline_globals(
269 envs(tester, &[("NODE_ENV", "development")]),
270 globals(tester, &[]),
271 Default::default(),
272 ),
273 node_env,
274 r#"if (process.env.NODE_ENV === 'development') {}"#
275 );
276
277 test!(
278 ::swc_ecma_parser::Syntax::default(),
279 |tester| inline_globals(
280 envs(tester, &[]),
281 globals(tester, &[("__DEBUG__", "true")]),
282 Default::default(),
283 ),
284 globals_simple,
285 r#"if (__DEBUG__) {}"#
286 );
287
288 test!(
289 ::swc_ecma_parser::Syntax::default(),
290 |tester| inline_globals(
291 envs(tester, &[]),
292 globals(tester, &[("debug", "true")]),
293 Default::default(),
294 ),
295 non_global,
296 r#"if (foo.debug) {}"#
297 );
298
299 test!(
300 Default::default(),
301 |tester| inline_globals(envs(tester, &[]), globals(tester, &[]), Default::default(),),
302 issue_417_1,
303 "const test = process.env['x']"
304 );
305
306 test!(
307 Default::default(),
308 |tester| inline_globals(
309 envs(tester, &[("x", "FOO")]),
310 globals(tester, &[]),
311 Default::default(),
312 ),
313 issue_417_2,
314 "const test = process.env['x']"
315 );
316
317 test!(
318 Default::default(),
319 |tester| inline_globals(
320 envs(tester, &[("x", "BAR")]),
321 globals(tester, &[]),
322 Default::default(),
323 ),
324 issue_2499_1,
325 "process.env.x = 'foo'"
326 );
327}