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 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 self.program.mutate(&mut compressor(
91 marks,
92 &CompressOptions {
93 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 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 Expr::TsTypeAssertion(e) => {
150 return self.eval(&e.expr);
151 }
152
153 Expr::TsConstAssertion(e) => {
154 return self.eval(&e.expr);
155 }
156
157 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}