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