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