swc_ecma_minifier/
eval.rs

1use std::sync::Arc;
2
3use parking_lot::Mutex;
4use rustc_hash::FxHashMap;
5use swc_atoms::atom;
6use swc_common::{SyntaxContext, DUMMY_SP};
7use swc_ecma_ast::*;
8use swc_ecma_usage_analyzer::marks::Marks;
9use swc_ecma_utils::{ExprCtx, ExprExt};
10use swc_ecma_visit::VisitMutWith;
11
12use crate::{
13    compress::{compressor, pure_optimizer, PureOptimizerConfig},
14    mode::Mode,
15    option::{CompressOptions, TopLevelOptions},
16};
17
18pub struct Evaluator {
19    expr_ctx: ExprCtx,
20
21    program: Program,
22    marks: Marks,
23    data: Eval,
24    /// We run minification only once.
25    done: bool,
26}
27
28impl Evaluator {
29    pub fn new(module: Module, marks: Marks) -> Self {
30        Evaluator {
31            expr_ctx: ExprCtx {
32                unresolved_ctxt: SyntaxContext::empty().apply_mark(marks.unresolved_mark),
33                is_unresolved_ref_safe: false,
34                in_strict: true,
35                remaining_depth: 3,
36            },
37
38            program: Program::Module(module),
39            marks,
40            data: Default::default(),
41            done: Default::default(),
42        }
43    }
44}
45
46#[derive(Default, Clone)]
47struct Eval {
48    store: Arc<Mutex<EvalStore>>,
49}
50
51#[derive(Default)]
52struct EvalStore {
53    cache: FxHashMap<Id, Box<Expr>>,
54}
55
56#[derive(Debug, Clone, PartialEq, Eq)]
57pub enum EvalResult {
58    Lit(Lit),
59    Undefined,
60}
61
62impl Mode for Eval {
63    fn store(&self, id: Id, value: &Expr) {
64        let mut w = self.store.lock();
65        w.cache.insert(id, Box::new(value.clone()));
66    }
67
68    fn preserve_vars(&self) -> bool {
69        true
70    }
71
72    fn should_be_very_correct(&self) -> bool {
73        false
74    }
75
76    fn force_str_for_tpl(&self) -> bool {
77        true
78    }
79}
80
81impl Evaluator {
82    #[tracing::instrument(name = "Evaluator::run", level = "debug", skip_all)]
83    fn run(&mut self) {
84        if !self.done {
85            self.done = true;
86
87            let marks = self.marks;
88            let data = self.data.clone();
89            //
90            self.program.mutate(&mut compressor(
91                marks,
92                &CompressOptions {
93                    // We should not drop unused variables.
94                    unused: false,
95                    top_level: Some(TopLevelOptions { functions: true }),
96                    ..Default::default()
97                },
98                None,
99                &data,
100            ));
101        }
102    }
103
104    pub fn eval(&mut self, e: &Expr) -> Option<EvalResult> {
105        match e {
106            Expr::Seq(s) => return self.eval(s.exprs.last()?),
107
108            Expr::Lit(
109                l @ Lit::Num(..)
110                | l @ Lit::Str(..)
111                | l @ Lit::BigInt(..)
112                | l @ Lit::Bool(..)
113                | l @ Lit::Null(..),
114            ) => return Some(EvalResult::Lit(l.clone())),
115
116            Expr::Tpl(t) => {
117                return self.eval_tpl(t);
118            }
119
120            Expr::TaggedTpl(t) => {
121                // Handle `String.raw`
122
123                match &*t.tag {
124                    Expr::Member(MemberExpr {
125                        obj: tag_obj,
126                        prop: MemberProp::Ident(prop),
127                        ..
128                    }) if tag_obj.is_global_ref_to(self.expr_ctx, "String")
129                        && prop.sym == *"raw" =>
130                    {
131                        return self.eval_tpl(&t.tpl);
132                    }
133
134                    _ => {}
135                }
136            }
137
138            Expr::Cond(c) => {
139                let test = self.eval(&c.test)?;
140
141                if is_truthy(&test)? {
142                    return self.eval(&c.cons);
143                } else {
144                    return self.eval(&c.alt);
145                }
146            }
147
148            // TypeCastExpression, ExpressionStatement etc
149            Expr::TsTypeAssertion(e) => {
150                return self.eval(&e.expr);
151            }
152
153            Expr::TsConstAssertion(e) => {
154                return self.eval(&e.expr);
155            }
156
157            // "foo".length
158            Expr::Member(MemberExpr { obj, prop, .. })
159                if obj.is_lit() && prop.is_ident_with("length") => {}
160
161            Expr::Unary(UnaryExpr {
162                op: op!("void"), ..
163            }) => return Some(EvalResult::Undefined),
164
165            Expr::Unary(UnaryExpr {
166                op: op!("!"), arg, ..
167            }) => {
168                let arg = self.eval(arg)?;
169
170                if is_truthy(&arg)? {
171                    return Some(EvalResult::Lit(Lit::Bool(Bool {
172                        span: DUMMY_SP,
173                        value: false,
174                    })));
175                } else {
176                    return Some(EvalResult::Lit(Lit::Bool(Bool {
177                        span: DUMMY_SP,
178                        value: true,
179                    })));
180                }
181            }
182
183            _ => {}
184        }
185
186        Some(EvalResult::Lit(self.eval_as_expr(e)?.lit()?))
187    }
188
189    fn eval_as_expr(&mut self, e: &Expr) -> Option<Box<Expr>> {
190        match e {
191            Expr::Ident(i) => {
192                self.run();
193
194                let lock = self.data.store.lock();
195                let val = lock.cache.get(&i.to_id())?;
196
197                return Some(val.clone());
198            }
199
200            Expr::Member(MemberExpr {
201                span, obj, prop, ..
202            }) if !prop.is_computed() => {
203                let obj = self.eval_as_expr(obj)?;
204
205                let mut e: Expr = MemberExpr {
206                    span: *span,
207                    obj,
208                    prop: prop.clone(),
209                }
210                .into();
211
212                e.visit_mut_with(&mut pure_optimizer(
213                    &Default::default(),
214                    self.marks,
215                    PureOptimizerConfig {
216                        enable_join_vars: false,
217                        force_str_for_tpl: self.data.force_str_for_tpl(),
218                    },
219                ));
220                return Some(Box::new(e));
221            }
222            _ => {}
223        }
224
225        None
226    }
227
228    pub fn eval_tpl(&mut self, q: &Tpl) -> Option<EvalResult> {
229        self.run();
230
231        let mut exprs = Vec::new();
232
233        for expr in &q.exprs {
234            let res = self.eval(expr)?;
235            exprs.push(match res {
236                EvalResult::Lit(v) => v.into(),
237                EvalResult::Undefined => Expr::undefined(DUMMY_SP),
238            });
239        }
240
241        let mut e: Box<Expr> = Tpl {
242            span: q.span,
243            exprs,
244            quasis: q.quasis.clone(),
245        }
246        .into();
247
248        {
249            e.visit_mut_with(&mut pure_optimizer(
250                &Default::default(),
251                self.marks,
252                PureOptimizerConfig {
253                    enable_join_vars: false,
254                    force_str_for_tpl: self.data.force_str_for_tpl(),
255                },
256            ));
257        }
258
259        Some(EvalResult::Lit(e.lit()?))
260    }
261}
262
263fn is_truthy(lit: &EvalResult) -> Option<bool> {
264    match lit {
265        EvalResult::Lit(v) => match v {
266            Lit::Str(v) => Some(v.value != atom!("")),
267            Lit::Bool(v) => Some(v.value),
268            Lit::Null(_) => Some(false),
269            Lit::Num(v) => Some(v.value != 0.0 && v.value != -0.0),
270            _ => None,
271        },
272        EvalResult::Undefined => Some(false),
273    }
274}