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