swc_ecma_minifier/
eval.rs

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