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