swc_ecma_transforms_optimization/
inline_globals.rs

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
9/// The key will be compared using [EqIgnoreSpan::eq_ignore_span], and matched
10/// expressions will be replaced with the value.
11pub type GlobalExprMap = Lrc<FxHashMap<NodeIgnoringSpan<'static, Expr>, Expr>>;
12
13/// Create a global inlining pass, which replaces expressions with the specified
14/// value.
15pub 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
23/// Create a global inlining pass, which replaces expressions with the specified
24/// value.
25///
26/// See [GlobalExprMap] for description.
27///
28/// Note: Values specified in `global_exprs` have higher precedence than
29pub 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                // It's ok because we don't recurse into member expressions.
90                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                    // It's ok because we don't recurse into member expressions.
114                    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            // It's ok because we don't recurse into member expressions.
183            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}