swc_ecma_minifier/compress/optimize/
evaluate.rs1use std::num::FpCategory;
2
3use swc_atoms::atom;
4use swc_common::{util::take::Take, Spanned, SyntaxContext, DUMMY_SP};
5use swc_ecma_ast::*;
6use swc_ecma_utils::{ExprExt, Value::Known};
7
8use super::{BitCtx, Optimizer};
9use crate::{
10 compress::util::eval_as_number, program_data::VarUsageInfoFlags, DISABLE_BUGGY_PASSES,
11};
12
13impl Optimizer<'_> {
15 pub(super) fn evaluate(&mut self, e: &mut Expr) {
19 self.eval_global_vars(e);
20
21 self.eval_fn_props(e);
22
23 self.eval_numbers(e);
24
25 self.eval_known_static_method_call(e);
26 }
27
28 fn eval_fn_props(&mut self, e: &mut Expr) -> Option<()> {
29 if self
30 .ctx
31 .bit_ctx
32 .intersects(BitCtx::IsDeleteArg | BitCtx::IsUpdateArg | BitCtx::IsLhsOfAssign)
33 {
34 return None;
35 }
36
37 if let Expr::Member(MemberExpr {
38 span,
39 obj,
40 prop: MemberProp::Ident(prop),
41 ..
42 }) = e
43 {
44 if let Expr::Ident(obj) = &**obj {
45 let metadata = *self.functions.get(&obj.to_id())?;
46
47 let usage = self.data.vars.get(&obj.to_id())?;
48
49 if usage.flags.contains(VarUsageInfoFlags::REASSIGNED) {
50 return None;
51 }
52
53 if self.options.unsafe_passes {
54 match &*prop.sym {
55 "length" => {
56 report_change!("evaluate: function.length");
57
58 *e = Lit::Num(Number {
59 span: *span,
60 value: metadata.len as _,
61 raw: None,
62 })
63 .into();
64 self.changed = true;
65 }
66
67 "name" => {
68 report_change!("evaluate: function.name");
69
70 *e = Lit::Str(Str {
71 span: *span,
72 value: obj.sym.clone().into(),
73 raw: None,
74 })
75 .into();
76 self.changed = true;
77 }
78
79 _ => {}
80 }
81 }
82 }
83 }
84
85 None
86 }
87
88 fn eval_global_vars(&mut self, e: &mut Expr) {
89 if self.options.ie8 {
90 return;
91 }
92
93 if self.ctx.bit_ctx.intersects(
94 BitCtx::IsDeleteArg | BitCtx::IsUpdateArg | BitCtx::IsLhsOfAssign | BitCtx::InWithStmt,
95 ) {
96 return;
97 }
98
99 if let Expr::Ident(i) = e {
101 if self
102 .data
103 .vars
104 .get(&i.to_id())
105 .map(|var| var.flags.contains(VarUsageInfoFlags::DECLARED))
106 .unwrap_or(false)
107 {
108 return;
109 }
110 }
111
112 match e {
113 Expr::Ident(Ident { span, sym, .. }) if &**sym == "undefined" => {
114 report_change!("evaluate: `undefined` -> `void 0`");
115 self.changed = true;
116 *e = *Expr::undefined(*span);
117 }
118
119 Expr::Ident(Ident { span, sym, .. }) if &**sym == "Infinity" => {
120 report_change!("evaluate: `Infinity` -> `1 / 0`");
121 self.changed = true;
122 *e = BinExpr {
123 span: *span,
124 op: op!("/"),
125 left: Lit::Num(Number {
126 span: DUMMY_SP,
127 value: 1.0,
128 raw: None,
129 })
130 .into(),
131 right: Lit::Num(Number {
132 span: DUMMY_SP,
133 value: 0.0,
134 raw: None,
135 })
136 .into(),
137 }
138 .into();
139 }
140
141 Expr::Member(MemberExpr {
142 obj,
143 prop: MemberProp::Ident(prop),
144 span,
145 ..
146 }) if matches!(obj.as_ref(), Expr::Ident(ident) if &*ident.sym == "Number") => {
147 if let Expr::Ident(number_ident) = &**obj {
148 if number_ident.ctxt != self.ctx.expr_ctx.unresolved_ctxt {
149 return;
150 }
151 }
152
153 match &*prop.sym {
154 "MIN_VALUE" => {
155 report_change!("evaluate: `Number.MIN_VALUE` -> `5e-324`");
156 self.changed = true;
157 *e = Lit::Num(Number {
158 span: *span,
159 value: 5e-324,
160 raw: None,
161 })
162 .into();
163 }
164 "NaN" => {
165 report_change!("evaluate: `Number.NaN` -> `NaN`");
166 self.changed = true;
167 *e = Ident::new(
168 atom!("NaN"),
169 *span,
170 SyntaxContext::empty().apply_mark(self.marks.unresolved_mark),
171 )
172 .into();
173 }
174 "POSITIVE_INFINITY" => {
175 report_change!("evaluate: `Number.POSITIVE_INFINITY` -> `Infinity`");
176 self.changed = true;
177 *e = Ident::new(
178 atom!("Infinity"),
179 *span,
180 SyntaxContext::empty().apply_mark(self.marks.unresolved_mark),
181 )
182 .into();
183 }
184 "NEGATIVE_INFINITY" => {
185 report_change!("evaluate: `Number.NEGATIVE_INFINITY` -> `-Infinity`");
186 self.changed = true;
187 *e = UnaryExpr {
188 span: *span,
189 op: op!(unary, "-"),
190 arg: Ident::new(
191 atom!("Infinity"),
192 *span,
193 SyntaxContext::empty().apply_mark(self.marks.unresolved_mark),
194 )
195 .into(),
196 }
197 .into();
198 }
199 _ => {}
200 }
201 }
202
203 _ => {}
204 }
205 }
206
207 fn eval_known_static_method_call(&mut self, e: &mut Expr) {
210 if !self.options.evaluate {
211 return;
212 }
213
214 if self
215 .ctx
216 .bit_ctx
217 .intersects(BitCtx::IsDeleteArg | BitCtx::IsUpdateArg | BitCtx::IsLhsOfAssign)
218 {
219 return;
220 }
221
222 let (span, callee, args) = match e {
223 Expr::Call(CallExpr {
224 span,
225 callee: Callee::Expr(callee),
226 args,
227 ..
228 }) => (span, callee, args),
229 _ => return,
230 };
231 let span = *span;
232
233 for arg in &*args {
236 if arg.spread.is_some() || arg.expr.may_have_side_effects(self.ctx.expr_ctx) {
237 return;
238 }
239 }
240
241 match &**callee {
242 Expr::Ident(Ident { sym, .. }) if &**sym == "RegExp" && self.options.unsafe_regexp => {
243 if !args.is_empty() {
244 self.optimize_expr_in_str_ctx(&mut args[0].expr);
245 }
246 if args.len() >= 2 {
247 self.optimize_expr_in_str_ctx(&mut args[1].expr);
248 }
249
250 if DISABLE_BUGGY_PASSES {
252 return;
253 }
254
255 match args.len() {
256 0 => {}
257 1 => {
258 if let Expr::Lit(Lit::Str(exp)) = &*args[0].expr {
259 let Some(value) = exp.value.as_str() else {
260 return;
261 };
262 self.changed = true;
263 report_change!(
264 "evaluate: Converting RegExpr call into a regexp literal `/{}/`",
265 exp.value
266 );
267
268 *e = Lit::Regex(Regex {
269 span,
270 exp: value.into(),
271 flags: atom!(""),
272 })
273 .into();
274 }
275 }
276 _ => {
277 if let (Expr::Lit(Lit::Str(exp)), Expr::Lit(Lit::Str(flags))) =
278 (&*args[0].expr, &*args[1].expr)
279 {
280 let Some(value) = exp.value.as_str() else {
281 return;
282 };
283 let Some(flags) = flags.value.as_str() else {
284 return;
285 };
286
287 self.changed = true;
288 report_change!(
289 "evaluate: Converting RegExpr call into a regexp literal `/{}/{}`",
290 exp.value,
291 flags.value
292 );
293
294 *e = Lit::Regex(Regex {
295 span,
296 exp: value.into(),
297 flags: flags.into(),
298 })
299 .into();
300 }
301 }
302 }
303 }
304
305 Expr::Member(MemberExpr {
306 obj,
307 prop: MemberProp::Ident(prop),
308 ..
309 }) => match &**obj {
310 Expr::Ident(Ident { sym, .. }) if &**sym == "String" => {
311 if &*prop.sym == "fromCharCode" {
312 if args.len() != 1 {
313 return;
314 }
315
316 if let Known(char_code) = args[0].expr.as_pure_number(self.ctx.expr_ctx) {
317 let v = char_code.floor() as u32;
318
319 if let Some(v) = char::from_u32(v) {
320 if !v.is_ascii() {
321 return;
322 }
323 self.changed = true;
324 report_change!(
325 "evaluate: Evaluated `String.charCodeAt({})` as `{}`",
326 char_code,
327 v
328 );
329
330 let value = v.to_string();
331
332 *e = Lit::Str(Str {
333 span: e.span(),
334 raw: None,
335 value: value.into(),
336 })
337 .into();
338 }
339 }
340 }
341 }
342
343 Expr::Ident(Ident { sym, .. }) if &**sym == "Object" => {
344 if &*prop.sym == "keys" {
345 if args.len() != 1 {
346 return;
347 }
348
349 let obj = match &*args[0].expr {
350 Expr::Object(obj) => obj,
351 _ => return,
352 };
353
354 let mut keys = Vec::new();
355
356 for prop in &obj.props {
357 match prop {
358 PropOrSpread::Spread(_) => return,
359 PropOrSpread::Prop(p) => match &**p {
360 Prop::Shorthand(p) => {
361 keys.push(Some(ExprOrSpread {
362 spread: None,
363 expr: Lit::Str(Str {
364 span: p.span,
365 raw: None,
366 value: p.sym.clone().into(),
367 })
368 .into(),
369 }));
370 }
371 Prop::KeyValue(p) => match &p.key {
372 PropName::Ident(key) => {
373 keys.push(Some(ExprOrSpread {
374 spread: None,
375 expr: Lit::Str(Str {
376 span: key.span,
377 raw: None,
378 value: key.sym.clone().into(),
379 })
380 .into(),
381 }));
382 }
383 PropName::Str(key) => {
384 keys.push(Some(ExprOrSpread {
385 spread: None,
386 expr: Lit::Str(key.clone()).into(),
387 }));
388 }
389 _ => return,
390 },
391 _ => return,
392 },
393 #[cfg(swc_ast_unknown)]
394 _ => panic!("unable to access unknown nodes"),
395 }
396 }
397
398 *e = ArrayLit { span, elems: keys }.into()
399 }
400 }
401
402 Expr::Ident(Ident { sym, .. }) => {
403 if &**sym == "console" && &*prop.sym == "log" {
404 for arg in args {
405 self.optimize_expr_in_str_ctx_unsafely(&mut arg.expr);
406 }
407 }
408 }
409
410 _ => {}
411 },
412 _ => {}
413 }
414 }
415
416 fn eval_numbers(&mut self, e: &mut Expr) {
417 if !self.options.evaluate {
418 return;
419 }
420
421 if self
422 .ctx
423 .bit_ctx
424 .intersects(BitCtx::IsDeleteArg | BitCtx::IsUpdateArg | BitCtx::IsLhsOfAssign)
425 {
426 return;
427 }
428
429 if let Expr::Call(..) = e {
430 if let Some(value) = eval_as_number(self.ctx.expr_ctx, e) {
431 self.changed = true;
432 report_change!("evaluate: Evaluated an expression as `{}`", value);
433
434 if value.is_nan() {
435 *e = Ident::new(
436 atom!("NaN"),
437 e.span(),
438 SyntaxContext::empty().apply_mark(self.marks.unresolved_mark),
439 )
440 .into();
441 return;
442 }
443
444 *e = Lit::Num(Number {
445 span: e.span(),
446 value,
447 raw: None,
448 })
449 .into();
450 return;
451 }
452 }
453
454 match e {
455 Expr::Bin(bin @ BinExpr { op: op!("**"), .. }) => {
456 let l = bin.left.as_pure_number(self.ctx.expr_ctx);
457 let r = bin.right.as_pure_number(self.ctx.expr_ctx);
458
459 if let Known(l) = l {
460 if let Known(r) = r {
461 self.changed = true;
462 report_change!("evaluate: Evaluated `{:?} ** {:?}`", l, r);
463
464 if l.is_nan() || r.is_nan() {
465 *e = Ident::new(
466 atom!("NaN"),
467 bin.span,
468 SyntaxContext::empty().apply_mark(self.marks.unresolved_mark),
469 )
470 .into();
471 } else {
472 *e = Lit::Num(Number {
473 span: bin.span,
474 value: l.powf(r),
475 raw: None,
476 })
477 .into();
478 };
479 }
480 }
481 }
482
483 Expr::Bin(bin @ BinExpr { op: op!("/"), .. }) => {
484 let ln = bin.left.as_pure_number(self.ctx.expr_ctx);
485
486 let rn = bin.right.as_pure_number(self.ctx.expr_ctx);
487 if let (Known(ln), Known(rn)) = (ln, rn) {
488 if ln == 0.0 && rn == 0.0 {
490 return;
491 }
492 if ln == 1.0 && rn == 0.0 {
494 return;
495 }
496
497 if let (FpCategory::Normal, FpCategory::Zero) = (ln.classify(), rn.classify()) {
499 self.changed = true;
500 report_change!("evaluate: `{} / 0` => `Infinity`", ln);
501
502 *e = if ln.is_sign_positive() == rn.is_sign_positive() {
504 Ident::new_no_ctxt(atom!("Infinity"), bin.span).into()
505 } else {
506 UnaryExpr {
507 span: bin.span,
508 op: op!(unary, "-"),
509 arg: Ident::new_no_ctxt(atom!("Infinity"), bin.span).into(),
510 }
511 .into()
512 };
513 }
514 }
515 }
516
517 _ => {}
518 }
519 }
520
521 pub(super) fn optimize_bin_and_or(&mut self, e: &mut BinExpr) {
524 if !self.options.evaluate {
525 return;
526 }
527 if e.left.is_invalid() || e.right.is_invalid() {
528 return;
529 }
530
531 match e.op {
532 op!("&&") | op!("||") => {}
533 _ => return,
534 }
535
536 if let Expr::Bin(left) = &mut *e.left {
537 if left.op != e.op {
538 return;
539 }
540 let v = left.right.as_pure_bool(self.ctx.expr_ctx);
543 if let Known(v) = v {
544 if v && e.op == op!("&&") {
546 self.changed = true;
547 report_change!("Removing `b` from `a && b && c` because b is always truthy");
548
549 left.right.take();
550 return;
551 }
552
553 if !v && e.op == op!("||") {
554 self.changed = true;
555 report_change!("Removing `b` from `a || b || c` because b is always falsy");
556
557 left.right.take();
558 }
559 }
560 }
561 }
562}