swc_ecma_minifier/pass/
global_defs.rs

1use std::borrow::Cow;
2
3use swc_common::{pass::CompilerPass, EqIgnoreSpan, Mark, SyntaxContext};
4use swc_ecma_ast::*;
5use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith};
6
7pub fn globals_defs(
8    defs: Vec<(Box<Expr>, Box<Expr>)>,
9    unresolved_mark: Mark,
10    top_level_mark: Mark,
11) -> impl VisitMut {
12    GlobalDefs {
13        defs,
14        unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
15        top_level_ctxt: SyntaxContext::empty().apply_mark(top_level_mark),
16        ..Default::default()
17    }
18}
19
20#[derive(Default)]
21struct GlobalDefs {
22    defs: Vec<(Box<Expr>, Box<Expr>)>,
23
24    unresolved_ctxt: SyntaxContext,
25    top_level_ctxt: SyntaxContext,
26
27    in_lhs_of_assign: bool,
28}
29
30impl CompilerPass for GlobalDefs {
31    fn name(&self) -> Cow<'static, str> {
32        Cow::Borrowed("global-defs")
33    }
34}
35
36/// We use [VisitMut] instead of [swc_ecma_visit::Fold] because it's faster.
37impl VisitMut for GlobalDefs {
38    noop_visit_mut_type!();
39
40    fn visit_mut_assign_expr(&mut self, n: &mut AssignExpr) {
41        let old = self.in_lhs_of_assign;
42        self.in_lhs_of_assign = true;
43        n.left.visit_mut_with(self);
44        self.in_lhs_of_assign = false;
45        n.right.visit_mut_with(self);
46        self.in_lhs_of_assign = old;
47    }
48
49    fn visit_mut_expr(&mut self, n: &mut Expr) {
50        if self.in_lhs_of_assign {
51            return;
52        }
53
54        match n {
55            Expr::Ident(i) => {
56                if i.ctxt != self.unresolved_ctxt && i.ctxt != self.top_level_ctxt {
57                    return;
58                }
59            }
60            Expr::Member(MemberExpr { obj, .. }) => {
61                if let Expr::Ident(i) = &**obj {
62                    if i.ctxt != self.unresolved_ctxt && i.ctxt != self.top_level_ctxt {
63                        return;
64                    }
65                }
66            }
67            _ => {}
68        }
69
70        if let Some((_, new)) = self
71            .defs
72            .iter()
73            .find(|(pred, _)| Ident::within_ignored_ctxt(|| should_replace(pred, n)))
74        {
75            *n = *new.clone();
76            return;
77        }
78
79        n.visit_mut_children_with(self);
80    }
81
82    fn visit_mut_update_expr(&mut self, e: &mut UpdateExpr) {
83        match &mut *e.arg {
84            Expr::Ident(..) => {}
85
86            Expr::Member(MemberExpr { prop, .. }) if !prop.is_computed() => {
87                // TODO: Check for `obj`
88            }
89
90            _ => {
91                e.arg.visit_mut_with(self);
92            }
93        }
94    }
95}
96
97/// This is used to detect optional chaining expressions like `a?.b.c` without
98/// allocation.
99fn should_replace(pred: &Expr, node: &Expr) -> bool {
100    if pred.eq_ignore_span(node) {
101        return true;
102    }
103
104    fn match_node(node: &Expr) -> Option<(&Expr, &MemberProp)> {
105        match node {
106            Expr::Member(MemberExpr {
107                obj: node_obj,
108                prop: nodes,
109                ..
110            }) => Some((node_obj, nodes)),
111
112            Expr::OptChain(OptChainExpr { base, .. }) => {
113                let base = base.as_member()?;
114                Some((&base.obj, &base.prop))
115            }
116
117            _ => None,
118        }
119    }
120
121    match (pred, match_node(node)) {
122        // super?. is invalid
123        (
124            Expr::Member(MemberExpr {
125                obj: pred_obj,
126                prop: pred,
127                ..
128            }),
129            Some((node_obj, nodes)),
130        ) if !(pred.is_computed() || nodes.is_computed()) => {
131            if !pred.eq_ignore_span(nodes) {
132                return false;
133            }
134
135            return should_replace(pred_obj, node_obj);
136        }
137        _ => {}
138    }
139
140    false
141}