1use std::{fmt::Write, num::FpCategory};
2
3use rustc_hash::FxHashSet;
4use swc_atoms::{
5 atom,
6 wtf8::{Wtf8, Wtf8Buf},
7 Atom, Wtf8Atom,
8};
9use swc_common::{iter::IdentifyLast, util::take::Take, Span, DUMMY_SP};
10use swc_ecma_ast::*;
11use swc_ecma_transforms_optimization::debug_assert_valid;
12use swc_ecma_usage_analyzer::util::is_global_var_with_pure_property_access;
13use swc_ecma_utils::{ExprCtx, ExprExt, ExprFactory, IdentUsageFinder, Type, Value};
14
15use super::Pure;
16use crate::compress::{
17 pure::{
18 strings::{convert_str_value_to_tpl_cooked, convert_str_value_to_tpl_raw},
19 Ctx,
20 },
21 util::is_pure_undefined,
22};
23
24fn is_definitely_string(expr: &Expr) -> bool {
25 match expr {
26 Expr::Lit(Lit::Str(_)) => true,
27 Expr::Tpl(_) => true,
28 Expr::Bin(BinExpr {
29 op: BinaryOp::Add,
30 left,
31 right,
32 ..
33 }) => is_definitely_string(left) || is_definitely_string(right),
34 Expr::Paren(ParenExpr { expr, .. }) => is_definitely_string(expr),
35 _ => false,
36 }
37}
38
39fn can_compress_new_regexp(args: Option<&[ExprOrSpread]>) -> bool {
44 if let Some(args) = args {
45 if let Some(first) = args.first() {
46 if first.spread.is_some() {
47 false
48 } else if is_definitely_string(&first.expr) {
49 true
50 } else if let Some(second) = args.get(1) {
51 second.spread.is_none() && is_definitely_string(&second.expr)
52 } else {
53 false
54 }
55 } else {
56 true
57 }
58 } else {
59 true
60 }
61}
62
63fn collect_exprs_from_object(obj: &mut ObjectLit) -> Vec<Box<Expr>> {
64 let mut exprs = Vec::new();
65
66 for prop in obj.props.take() {
67 if let PropOrSpread::Prop(p) = prop {
68 match *p {
69 Prop::Shorthand(p) => {
70 exprs.push(p.into());
71 }
72 Prop::KeyValue(p) => {
73 if let PropName::Computed(e) = p.key {
74 exprs.push(e.expr);
75 }
76
77 exprs.push(p.value);
78 }
79 Prop::Getter(p) => {
80 if let PropName::Computed(e) = p.key {
81 exprs.push(e.expr);
82 }
83 }
84 Prop::Setter(p) => {
85 if let PropName::Computed(e) = p.key {
86 exprs.push(e.expr);
87 }
88 }
89 Prop::Method(p) => {
90 if let PropName::Computed(e) = p.key {
91 exprs.push(e.expr);
92 }
93 }
94 _ => {}
95 }
96 }
97 }
98
99 exprs
100}
101
102#[derive(Debug)]
103enum GroupType<'a> {
104 Literals(Vec<&'a ExprOrSpread>),
105 Expression(&'a ExprOrSpread),
106}
107
108impl Pure<'_> {
109 pub(super) fn compress_bin_assignment_to_left(&mut self, e: &mut AssignExpr) {
111 if e.op != op!("=") {
112 return;
113 }
114
115 let lhs = match &e.left {
117 AssignTarget::Simple(SimpleAssignTarget::Ident(i)) => i,
118 _ => return,
119 };
120
121 let (op, right) = match &mut *e.right {
124 Expr::Bin(BinExpr {
125 left, op, right, ..
126 }) => match &**left {
127 Expr::Ident(r) if lhs.sym == r.sym && lhs.ctxt == r.ctxt => (op, right),
128 _ => return,
129 },
130 _ => return,
131 };
132
133 match op {
135 BinaryOp::LogicalOr => return,
136 BinaryOp::LogicalAnd => return,
137 BinaryOp::Exp => return,
138 BinaryOp::NullishCoalescing => return,
139 _ => {}
140 }
141
142 let op = match op {
143 BinaryOp::In | BinaryOp::InstanceOf => return,
144
145 BinaryOp::EqEq | BinaryOp::NotEq | BinaryOp::EqEqEq | BinaryOp::NotEqEq => {
146 return;
148 }
149
150 BinaryOp::Lt | BinaryOp::LtEq | BinaryOp::Gt | BinaryOp::GtEq => return,
151
152 BinaryOp::LShift => op!("<<="),
153 BinaryOp::RShift => {
154 op!(">>=")
155 }
156 BinaryOp::ZeroFillRShift => {
157 op!(">>>=")
158 }
159 BinaryOp::Add => {
160 op!("+=")
161 }
162 BinaryOp::Sub => {
163 op!("-=")
164 }
165 BinaryOp::Mul => {
166 op!("*=")
167 }
168 BinaryOp::Div => {
169 op!("/=")
170 }
171 BinaryOp::Mod => {
172 op!("%=")
173 }
174 BinaryOp::BitOr => {
175 op!("|=")
176 }
177 BinaryOp::BitXor => {
178 op!("^=")
179 }
180 BinaryOp::BitAnd => {
181 op!("&=")
182 }
183 BinaryOp::LogicalOr => {
184 op!("||=")
185 }
186 BinaryOp::LogicalAnd => {
187 op!("&&=")
188 }
189 BinaryOp::Exp => {
190 op!("**=")
191 }
192 BinaryOp::NullishCoalescing => {
193 op!("??=")
194 }
195 #[cfg(swc_ast_unknown)]
196 _ => panic!("unable to access unknown nodes"),
197 };
198
199 e.op = op;
200 e.right = right.take();
201 }
203
204 pub(super) fn compress_bin_assignment_to_right(&mut self, e: &mut AssignExpr) {
211 if e.op != op!("=") {
212 return;
213 }
214
215 let lhs = match &e.left {
217 AssignTarget::Simple(SimpleAssignTarget::Ident(i)) => i,
218 _ => return,
219 };
220
221 let (op, left) = match &mut *e.right {
222 Expr::Bin(BinExpr {
223 left, op, right, ..
224 }) => match &**right {
225 Expr::Ident(r) if lhs.sym == r.sym && lhs.ctxt == r.ctxt => {
226 match &**left {
232 Expr::This(..) | Expr::Ident(..) | Expr::Lit(..) => {}
233 _ => return,
234 }
235
236 (op, left)
237 }
238 _ => return,
239 },
240 _ => return,
241 };
242
243 let op = match op {
244 BinaryOp::Mul => {
245 op!("*=")
246 }
247 BinaryOp::BitOr => {
248 op!("|=")
249 }
250 BinaryOp::BitXor => {
251 op!("^=")
252 }
253 BinaryOp::BitAnd => {
254 op!("&=")
255 }
256 _ => return,
257 };
258
259 report_change!("Compressing: `e = 3 & e` => `e &= 3`");
260
261 self.changed = true;
262 e.op = op;
263 e.right = left.take();
264 }
265
266 pub(super) fn eval_spread_object(&mut self, e: &mut ObjectLit) {
267 fn should_skip(p: &PropOrSpread, expr_ctx: ExprCtx) -> bool {
268 match p {
269 PropOrSpread::Prop(p) => match &**p {
270 Prop::KeyValue(KeyValueProp { key, value, .. }) => {
271 key.is_computed() || value.may_have_side_effects(expr_ctx)
272 }
273 Prop::Assign(AssignProp { value, .. }) => value.may_have_side_effects(expr_ctx),
274 Prop::Method(method) => method.key.is_computed(),
275
276 Prop::Getter(..) | Prop::Setter(..) => true,
277
278 _ => false,
279 },
280
281 PropOrSpread::Spread(SpreadElement { expr, .. }) => match &**expr {
282 Expr::Object(ObjectLit { props, .. }) => {
283 props.iter().any(|p| should_skip(p, expr_ctx))
284 }
285 _ => false,
286 },
287 #[cfg(swc_ast_unknown)]
288 _ => panic!("unable to access unknown nodes"),
289 }
290 }
291
292 if e.props.iter().any(|p| should_skip(p, self.expr_ctx))
293 || !e.props.iter().any(|p| match p {
294 PropOrSpread::Spread(SpreadElement { expr, .. }) => {
295 expr.is_object() || expr.is_null()
296 }
297 _ => false,
298 })
299 {
300 return;
301 }
302
303 let mut new_props = Vec::with_capacity(e.props.len());
304
305 for prop in e.props.take() {
306 match prop {
307 PropOrSpread::Spread(SpreadElement { expr, .. })
308 if expr.is_object() || expr.is_null() =>
309 {
310 match *expr {
311 Expr::Object(ObjectLit { props, .. }) => {
312 for p in props {
313 new_props.push(p);
314 }
315 }
316
317 Expr::Lit(Lit::Null(_)) => {}
318
319 _ => {}
320 }
321 }
322
323 _ => {
324 new_props.push(prop);
325 }
326 }
327 }
328
329 e.props = new_props;
330 }
331
332 pub(super) fn eval_spread_array_in_args(&mut self, args: &mut Vec<ExprOrSpread>) {
334 if !args
335 .iter()
336 .any(|arg| arg.spread.is_some() && arg.expr.is_array())
337 {
338 return;
339 }
340
341 let mut new_args = Vec::with_capacity(args.len());
342 for arg in args.take() {
343 match arg {
344 ExprOrSpread {
345 spread: Some(spread),
346 expr,
347 } => match *expr {
348 Expr::Array(ArrayLit { elems, .. }) => {
349 for elem in elems {
350 match elem {
351 Some(ExprOrSpread { expr, spread }) => {
352 new_args.push(ExprOrSpread { spread, expr });
353 }
354 None => {
355 new_args.push(ExprOrSpread {
356 spread: None,
357 expr: Expr::undefined(DUMMY_SP),
358 });
359 }
360 }
361 }
362 }
363 _ => {
364 new_args.push(ExprOrSpread {
365 spread: Some(spread),
366 expr,
367 });
368 }
369 },
370 arg => new_args.push(arg),
371 }
372 }
373
374 self.changed = true;
375 report_change!("Compressing spread array");
376
377 *args = new_args;
378 }
379
380 pub(super) fn eval_spread_array_in_array(&mut self, args: &mut Vec<Option<ExprOrSpread>>) {
382 if !args.iter().any(|arg| {
383 arg.as_ref()
384 .is_some_and(|arg| arg.spread.is_some() && arg.expr.is_array())
385 }) {
386 return;
387 }
388
389 let mut new_args = Vec::with_capacity(args.len());
390 for arg in args.take() {
391 match arg {
392 Some(ExprOrSpread {
393 spread: Some(spread),
394 expr,
395 }) => match *expr {
396 Expr::Array(ArrayLit { elems, .. }) => {
397 for elem in elems {
398 match elem {
399 Some(ExprOrSpread { expr, spread }) => {
400 new_args.push(Some(ExprOrSpread { spread, expr }));
401 }
402 None => {
403 new_args.push(Some(ExprOrSpread {
404 spread: None,
405 expr: Expr::undefined(DUMMY_SP),
406 }));
407 }
408 }
409 }
410 }
411 _ => {
412 new_args.push(Some(ExprOrSpread {
413 spread: Some(spread),
414 expr,
415 }));
416 }
417 },
418 arg => new_args.push(arg),
419 }
420 }
421
422 self.changed = true;
423 report_change!("Compressing spread array");
424
425 *args = new_args;
426 }
427
428 pub(super) fn remove_invalid(&mut self, e: &mut Expr) {
432 match e {
433 Expr::Seq(seq) => {
434 for e in &mut seq.exprs {
435 self.remove_invalid(e);
436 }
437
438 if seq.exprs.len() == 1 {
439 *e = *seq.exprs.pop().unwrap();
440 }
441 }
442
443 Expr::Bin(BinExpr { left, right, .. }) => {
444 self.remove_invalid(left);
445 self.remove_invalid(right);
446
447 if left.is_invalid() {
448 *e = *right.take();
449 self.remove_invalid(e);
450 } else if right.is_invalid() {
451 *e = *left.take();
452 self.remove_invalid(e);
453 }
454 }
455
456 _ => {}
457 }
458 }
459
460 pub(super) fn compress_array_join(&mut self, e: &mut Expr) {
461 let call = match e {
462 Expr::Call(e) => e,
463 _ => return,
464 };
465
466 let callee = match &mut call.callee {
467 Callee::Super(_) | Callee::Import(_) => return,
468 Callee::Expr(callee) => &mut **callee,
469 #[cfg(swc_ast_unknown)]
470 _ => panic!("unable to access unknown nodes"),
471 };
472
473 let separator = if call.args.is_empty() {
474 Wtf8Atom::from(",")
475 } else if call.args.len() == 1 {
476 if call.args[0].spread.is_some() {
477 return;
478 }
479
480 if is_pure_undefined(self.expr_ctx, &call.args[0].expr) {
481 Wtf8Atom::from(",")
482 } else {
483 match &*call.args[0].expr {
484 Expr::Lit(Lit::Str(s)) => s.value.clone(),
485 Expr::Lit(Lit::Null(..)) => Wtf8Atom::from("null"),
486 _ => return,
487 }
488 }
489 } else {
490 return;
491 };
492
493 let arr = match callee {
494 Expr::Member(MemberExpr {
495 obj,
496 prop: MemberProp::Ident(IdentName { sym, .. }),
497 ..
498 }) if *sym == *"join" => {
499 if let Expr::Array(arr) = &mut **obj {
500 arr
501 } else {
502 return;
503 }
504 }
505 _ => return,
506 };
507
508 if arr.elems.iter().any(|elem| {
509 matches!(
510 elem,
511 Some(ExprOrSpread {
512 spread: Some(..),
513 ..
514 })
515 )
516 }) {
517 return;
518 }
519
520 if arr.elems.is_empty() {
522 report_change!("Compressing empty array.join()");
523 self.changed = true;
524 *e = Lit::Str(Str {
525 span: call.span,
526 raw: None,
527 value: atom!("").into(),
528 })
529 .into();
530 return;
531 }
532
533 let cannot_join_as_str_lit = arr
534 .elems
535 .iter()
536 .filter_map(|v| v.as_ref())
537 .any(|v| match &*v.expr {
538 e if is_pure_undefined(self.expr_ctx, e) => false,
539 Expr::Lit(lit) => !matches!(lit, Lit::Str(..) | Lit::Num(..) | Lit::Null(..)),
540 _ => true,
541 });
542
543 if cannot_join_as_str_lit {
544 if let Some(new_expr) =
545 self.compress_array_join_as_tpl(arr.span, &mut arr.elems, &separator)
546 {
547 self.changed = true;
548 *e = new_expr;
549 return;
550 }
551
552 if let Some(new_expr) =
554 self.compress_array_join_partial(arr.span, &mut arr.elems, &separator)
555 {
556 self.changed = true;
557 report_change!("Compressing array.join() with partial optimization");
558 *e = new_expr;
559 return;
560 }
561
562 if !self.options.unsafe_passes {
563 return;
564 }
565
566 if arr
567 .elems
568 .iter()
569 .filter_map(|v| v.as_ref())
570 .any(|v| match &*v.expr {
571 e if is_pure_undefined(self.expr_ctx, e) => false,
572 Expr::Lit(lit) => !matches!(lit, Lit::Str(..) | Lit::Num(..) | Lit::Null(..)),
573 _ => true,
575 })
576 {
577 return;
578 }
579
580 let sep: Box<Expr> = Lit::Str(Str {
581 span: DUMMY_SP,
582 raw: None,
583 value: separator,
584 })
585 .into();
586 let mut res = Lit::Str(Str {
587 span: DUMMY_SP,
588 raw: None,
589 value: atom!("").into(),
590 })
591 .into();
592
593 fn add(to: &mut Expr, right: Box<Expr>) {
594 let lhs = to.take();
595 *to = BinExpr {
596 span: DUMMY_SP,
597 left: Box::new(lhs),
598 op: op!(bin, "+"),
599 right,
600 }
601 .into();
602 }
603
604 for (last, elem) in arr.elems.take().into_iter().identify_last() {
605 if let Some(ExprOrSpread { spread: None, expr }) = elem {
606 match &*expr {
607 e if is_pure_undefined(self.expr_ctx, e) => {
608 }
612 Expr::Lit(Lit::Null(..)) => {
613 }
617 _ => {
618 add(&mut res, expr);
619 }
620 }
621 }
622
623 if !last {
624 add(&mut res, sep.clone());
625 }
626 }
627
628 *e = res;
629
630 return;
631 }
632
633 let mut res = Wtf8Buf::default();
634 for (last, elem) in arr.elems.iter().identify_last() {
635 if let Some(elem) = elem {
636 debug_assert_eq!(elem.spread, None);
637
638 match &*elem.expr {
639 Expr::Lit(Lit::Str(s)) => {
640 res.push_wtf8(&s.value);
641 }
642 Expr::Lit(Lit::Num(n)) => {
643 write!(res, "{}", n.value).unwrap();
644 }
645 e if is_pure_undefined(self.expr_ctx, e) => {}
646 Expr::Lit(Lit::Null(..)) => {}
647 _ => {
648 unreachable!(
649 "Expression {:#?} cannot be joined and it should be filtered out",
650 elem.expr
651 )
652 }
653 }
654 }
655
656 if !last {
657 res.push_wtf8(&separator);
658 }
659 }
660
661 report_change!("Compressing array.join()");
662
663 self.changed = true;
664 *e = Lit::Str(Str {
665 span: call.span,
666 raw: None,
667 value: res.into(),
668 })
669 .into()
670 }
671
672 fn compress_array_join_partial(
676 &mut self,
677 _span: Span,
678 elems: &mut Vec<Option<ExprOrSpread>>,
679 separator: &Wtf8,
680 ) -> Option<Expr> {
681 if !self.options.evaluate {
682 return None;
683 }
684
685 let has_non_literals = elems.iter().flatten().any(|elem| match &*elem.expr {
687 Expr::Lit(Lit::Str(..) | Lit::Num(..) | Lit::Null(..)) => false,
688 e if is_pure_undefined(self.expr_ctx, e) => false,
689 _ => true,
690 });
691
692 if !has_non_literals {
693 return None; }
695
696 if !separator.is_empty() {
700 let mut consecutive_literals = 0;
701 let mut max_consecutive = 0;
702
703 for elem in elems.iter().flatten() {
704 let is_literal = match &*elem.expr {
705 Expr::Lit(Lit::Str(..) | Lit::Num(..) | Lit::Null(..)) => true,
706 e if is_pure_undefined(self.expr_ctx, e) => true,
707 _ => false,
708 };
709
710 if is_literal {
711 consecutive_literals += 1;
712 max_consecutive = max_consecutive.max(consecutive_literals);
713 } else {
714 consecutive_literals = 0;
715 }
716 }
717
718 if max_consecutive < 2 {
719 return None;
720 }
721
722 if separator.len() > 1 {
725 return None;
726 }
727
728 if separator == "," && max_consecutive < 6 {
730 return None;
731 }
732 } else {
733 }
737
738 let mut groups = Vec::new();
740 let mut current_group = Vec::new();
741
742 for elem in elems.iter().flatten() {
743 let is_literal = match &*elem.expr {
744 Expr::Lit(Lit::Str(..) | Lit::Num(..) | Lit::Null(..)) => true,
745 e if is_pure_undefined(self.expr_ctx, e) => true,
746 _ => false,
747 };
748
749 if is_literal {
750 current_group.push(elem);
751 } else {
752 if !current_group.is_empty() {
753 groups.push(GroupType::Literals(current_group));
754 current_group = Vec::new();
755 }
756 groups.push(GroupType::Expression(elem));
757 }
758 }
759
760 if !current_group.is_empty() {
761 groups.push(GroupType::Literals(current_group));
762 }
763
764 if groups.iter().all(|g| matches!(g, GroupType::Expression(_))) {
766 return None;
767 }
768
769 let is_string_concat = separator.is_empty();
771
772 if is_string_concat {
773 let mut result_parts = Vec::new();
775
776 let needs_empty_string_prefix = match groups.first() {
780 Some(GroupType::Expression(first_expr)) => {
781 let first_needs_coercion = match &*first_expr.expr {
783 Expr::Bin(BinExpr {
784 op: op!(bin, "+"), ..
785 }) => false, Expr::Lit(Lit::Str(..)) => false, Expr::Call(_call) => {
788 true
790 }
791 _ => true, };
793
794 if first_needs_coercion {
797 match groups.get(1) {
798 Some(GroupType::Literals(_)) => false, _ => true, }
802 } else {
803 false
804 }
805 }
806 _ => false,
807 };
808
809 if needs_empty_string_prefix {
810 result_parts.push(Box::new(Expr::Lit(Lit::Str(Str {
811 span: DUMMY_SP,
812 raw: None,
813 value: atom!("").into(),
814 }))));
815 }
816
817 for group in groups {
818 match group {
819 GroupType::Literals(literals) => {
820 let mut joined = Wtf8Buf::new();
821 for literal in literals.iter() {
822 match &*literal.expr {
823 Expr::Lit(Lit::Str(s)) => joined.push_wtf8(&s.value),
824 Expr::Lit(Lit::Num(n)) => write!(joined, "{}", n.value).unwrap(),
825 Expr::Lit(Lit::Null(..)) => {
826 }
829 e if is_pure_undefined(self.expr_ctx, e) => {
830 }
833 _ => unreachable!(),
834 }
835 }
836
837 result_parts.push(Box::new(Expr::Lit(Lit::Str(Str {
838 span: DUMMY_SP,
839 raw: None,
840 value: joined.into(),
841 }))));
842 }
843 GroupType::Expression(expr) => {
844 result_parts.push(expr.expr.clone());
845 }
846 }
847 }
848
849 if result_parts.len() == 1 {
851 return Some(*result_parts.into_iter().next().unwrap());
852 }
853
854 let mut result = *result_parts.remove(0);
855 for part in result_parts {
856 result = Expr::Bin(BinExpr {
857 span: DUMMY_SP,
858 left: Box::new(result),
859 op: op!(bin, "+"),
860 right: part,
861 });
862 }
863
864 Some(result)
865 } else {
866 let mut new_elems = Vec::new();
868
869 for group in groups {
870 match group {
871 GroupType::Literals(literals) => {
872 let mut joined = Wtf8Buf::new();
873 for (idx, literal) in literals.iter().enumerate() {
874 if idx > 0 {
875 joined.push_wtf8(separator);
876 }
877
878 match &*literal.expr {
879 Expr::Lit(Lit::Str(s)) => joined.push_wtf8(&s.value),
880 Expr::Lit(Lit::Num(n)) => write!(joined, "{}", n.value).unwrap(),
881 Expr::Lit(Lit::Null(..)) => {
882 }
884 e if is_pure_undefined(self.expr_ctx, e) => {
885 }
887 _ => unreachable!(),
888 }
889 }
890
891 new_elems.push(Some(ExprOrSpread {
892 spread: None,
893 expr: Box::new(Expr::Lit(Lit::Str(Str {
894 span: DUMMY_SP,
895 raw: None,
896 value: joined.into(),
897 }))),
898 }));
899 }
900 GroupType::Expression(expr) => {
901 new_elems.push(Some(ExprOrSpread {
902 spread: None,
903 expr: expr.expr.clone(),
904 }));
905 }
906 }
907 }
908
909 let new_array = Expr::Array(ArrayLit {
911 span: _span,
912 elems: new_elems,
913 });
914
915 let args = if separator == "," {
917 vec![]
918 } else {
919 vec![ExprOrSpread {
920 spread: None,
921 expr: Box::new(Expr::Lit(Lit::Str(Str {
922 span: DUMMY_SP,
923 raw: None,
924 value: separator.into(),
925 }))),
926 }]
927 };
928
929 Some(Expr::Call(CallExpr {
930 span: _span,
931 ctxt: Default::default(),
932 callee: Callee::Expr(Box::new(Expr::Member(MemberExpr {
933 span: _span,
934 obj: Box::new(new_array),
935 prop: MemberProp::Ident(IdentName::new(atom!("join"), _span)),
936 }))),
937 args,
938 ..Default::default()
939 }))
940 }
941 }
942
943 pub(super) fn drop_undefined_from_return_arg(&mut self, s: &mut ReturnStmt) {
944 if let Some(e) = s.arg.as_deref() {
945 if is_pure_undefined(self.expr_ctx, e) {
946 self.changed = true;
947 report_change!("Dropped `undefined` from `return undefined`");
948 s.arg.take();
949 }
950 }
951 }
952
953 pub(super) fn remove_useless_return(&mut self, stmts: &mut Vec<Stmt>) {
954 if !self.options.dead_code {
955 return;
956 }
957
958 if let Some(Stmt::Return(ReturnStmt { arg: None, .. })) = stmts.last() {
959 self.changed = true;
960 report_change!("misc: Removing useless return");
961 stmts.pop();
962 }
963 }
964
965 fn optimize_regex(&mut self, args: &mut Vec<ExprOrSpread>, span: &mut Span) -> Option<Expr> {
967 fn valid_pattern(pattern: &Expr) -> Option<Atom> {
968 if let Expr::Lit(Lit::Str(s)) = pattern {
969 if s.value.code_points().any(|c| {
970 let Some(c) = c.to_char() else {
971 return true;
972 };
973
974 !c.is_ascii_alphanumeric()
976 && !matches!(c, '$' | '[' | ']' | '(' | ')' | '{' | '}' | '-' | '+' | '_')
977 }) {
978 None
979 } else {
980 Some(unsafe { Atom::from_wtf8_unchecked(s.value.clone()) })
984 }
985 } else {
986 None
987 }
988 }
989 fn valid_flag(flag: &Expr, es_version: EsVersion) -> Option<Atom> {
990 if let Expr::Lit(Lit::Str(s)) = flag {
991 let mut set = FxHashSet::default();
992 for c in s.value.code_points() {
993 if !(matches!(c.to_char()?, 'g' | 'i' | 'm')
994 || (es_version >= EsVersion::Es2015 && matches!(c.to_char()?, 'u' | 'y'))
995 || (es_version >= EsVersion::Es2018 && matches!(c.to_char()?, 's')))
996 || (es_version >= EsVersion::Es2022 && matches!(c.to_char()?, 'd'))
997 {
998 return None;
999 }
1000
1001 if !set.insert(c) {
1002 return None;
1003 }
1004 }
1005
1006 Some(unsafe { Atom::from_wtf8_unchecked(s.value.clone()) })
1009 } else {
1010 None
1011 }
1012 }
1013
1014 let (pattern, flag) = match args.as_slice() {
1015 [ExprOrSpread { spread: None, expr }] => (valid_pattern(expr)?, atom!("")),
1016 [ExprOrSpread {
1017 spread: None,
1018 expr: pattern,
1019 }, ExprOrSpread {
1020 spread: None,
1021 expr: flag,
1022 }] => (
1023 valid_pattern(pattern)?,
1024 valid_flag(flag, self.options.ecma)?,
1025 ),
1026 _ => return None,
1027 };
1028
1029 if pattern.is_empty() {
1030 return None;
1034 }
1035
1036 report_change!("Optimized regex");
1037
1038 Some(
1039 Lit::Regex(Regex {
1040 span: *span,
1041 exp: pattern,
1042 flags: {
1043 let flag = flag.to_string();
1044 let mut bytes = flag.into_bytes();
1045 bytes.sort_unstable();
1046
1047 String::from_utf8(bytes).unwrap().into()
1048 },
1049 })
1050 .into(),
1051 )
1052 }
1053
1054 fn optimize_array(&mut self, args: &mut Vec<ExprOrSpread>, span: &mut Span) -> Option<Expr> {
1056 if args.len() == 1 {
1057 if let ExprOrSpread { spread: None, expr } = &args[0] {
1058 match &**expr {
1059 Expr::Lit(Lit::Num(num)) => {
1060 if num.value <= 5_f64 && num.value >= 0_f64 {
1061 Some(
1062 ArrayLit {
1063 span: *span,
1064 elems: vec![None; num.value as usize],
1065 }
1066 .into(),
1067 )
1068 } else {
1069 None
1070 }
1071 }
1072 Expr::Lit(_) => Some(
1073 ArrayLit {
1074 span: *span,
1075 elems: vec![args.take().into_iter().next()],
1076 }
1077 .into(),
1078 ),
1079 _ => None,
1080 }
1081 } else {
1082 None
1083 }
1084 } else {
1085 Some(
1086 ArrayLit {
1087 span: *span,
1088 elems: args.take().into_iter().map(Some).collect(),
1089 }
1090 .into(),
1091 )
1092 }
1093 }
1094
1095 fn optimize_object(&mut self, args: &mut Vec<ExprOrSpread>, span: &mut Span) -> Option<Expr> {
1097 if args.is_empty() {
1098 Some(
1099 ObjectLit {
1100 span: *span,
1101 props: Vec::new(),
1102 }
1103 .into(),
1104 )
1105 } else {
1106 None
1107 }
1108 }
1109
1110 pub(super) fn optimize_opt_chain(&mut self, e: &mut Expr) {
1111 let opt = match e {
1112 Expr::OptChain(c) => c,
1113 _ => return,
1114 };
1115
1116 if let OptChainBase::Member(base) = &mut *opt.base {
1117 if match &*base.obj {
1118 Expr::Lit(Lit::Null(..)) => false,
1119 Expr::Lit(..) | Expr::Object(..) | Expr::Array(..) => true,
1120 _ => false,
1121 } {
1122 self.changed = true;
1123 report_change!("Optimized optional chaining expression where object is not null");
1124
1125 *e = MemberExpr {
1126 span: opt.span,
1127 obj: base.obj.take(),
1128 prop: base.prop.take(),
1129 }
1130 .into();
1131 }
1132 }
1133 }
1134
1135 pub(super) fn optimize_builtin_object(&mut self, e: &mut Expr) {
1137 if !self.options.pristine_globals {
1138 return;
1139 }
1140
1141 match e {
1142 Expr::New(NewExpr {
1143 span,
1144 callee,
1145 args: Some(args),
1146 ..
1147 })
1148 | Expr::Call(CallExpr {
1149 span,
1150 callee: Callee::Expr(callee),
1151 args,
1152 ..
1153 }) if callee.is_one_of_global_ref_to(self.expr_ctx, &["Array", "Object", "RegExp"]) => {
1154 let new_expr = match &**callee {
1155 Expr::Ident(Ident { sym, .. }) if &**sym == "RegExp" => {
1156 self.optimize_regex(args, span)
1157 }
1158 Expr::Ident(Ident { sym, .. }) if &**sym == "Array" => {
1159 self.optimize_array(args, span)
1160 }
1161 Expr::Ident(Ident { sym, .. }) if &**sym == "Object" => {
1162 self.optimize_object(args, span)
1163 }
1164 _ => unreachable!(),
1165 };
1166
1167 if let Some(new_expr) = new_expr {
1168 report_change!(
1169 "Converting Regexp/Array/Object call to native constructor into literal"
1170 );
1171 self.changed = true;
1172 *e = new_expr;
1173 return;
1174 }
1175 }
1176 Expr::Call(CallExpr {
1177 span,
1178 callee: Callee::Expr(callee),
1179 args,
1180 ..
1181 }) if callee.is_one_of_global_ref_to(
1182 self.expr_ctx,
1183 &["Boolean", "Number", "String", "Symbol"],
1184 ) =>
1185 {
1186 let new_expr = match &**callee {
1187 Expr::Ident(Ident { sym, .. }) if &**sym == "Boolean" => match &mut args[..] {
1188 [] => Some(
1189 Lit::Bool(Bool {
1190 span: *span,
1191 value: false,
1192 })
1193 .into(),
1194 ),
1195 [ExprOrSpread { spread: None, expr }] => Some(
1196 UnaryExpr {
1197 span: *span,
1198 op: op!("!"),
1199 arg: UnaryExpr {
1200 span: *span,
1201 op: op!("!"),
1202 arg: expr.take(),
1203 }
1204 .into(),
1205 }
1206 .into(),
1207 ),
1208 _ => None,
1209 },
1210 Expr::Ident(Ident { sym, .. }) if &**sym == "Number" => match &mut args[..] {
1211 [] => Some(
1212 Lit::Num(Number {
1213 span: *span,
1214 value: 0.0,
1215 raw: None,
1216 })
1217 .into(),
1218 ),
1219 [ExprOrSpread { spread: None, expr }] if self.options.unsafe_math => Some(
1221 UnaryExpr {
1222 span: *span,
1223 op: op!(unary, "+"),
1224 arg: expr.take(),
1225 }
1226 .into(),
1227 ),
1228 _ => None,
1229 },
1230 Expr::Ident(Ident { sym, .. }) if &**sym == "String" => match &mut args[..] {
1231 [] => Some(
1232 Lit::Str(Str {
1233 span: *span,
1234 value: atom!("").into(),
1235 raw: None,
1236 })
1237 .into(),
1238 ),
1239 [ExprOrSpread { spread: None, expr }] if self.options.unsafe_passes => {
1241 Some(
1242 BinExpr {
1243 span: *span,
1244 left: expr.take(),
1245 op: op!(bin, "+"),
1246 right: Lit::Str(Str {
1247 span: *span,
1248 value: atom!("").into(),
1249 raw: None,
1250 })
1251 .into(),
1252 }
1253 .into(),
1254 )
1255 }
1256 _ => None,
1257 },
1258 Expr::Ident(Ident { sym, .. }) if &**sym == "Symbol" => {
1259 if let [ExprOrSpread { spread: None, .. }] = &mut args[..] {
1260 if self.options.unsafe_symbols {
1261 args.clear();
1262 report_change!("Remove Symbol call parameter");
1263 self.changed = true;
1264 }
1265 }
1266 None
1267 }
1268 _ => unreachable!(),
1269 };
1270
1271 if let Some(new_expr) = new_expr {
1272 report_change!(
1273 "Converting Boolean/Number/String/Symbol call to native constructor to \
1274 literal"
1275 );
1276 self.changed = true;
1277 *e = new_expr;
1278 return;
1279 }
1280 }
1281 _ => {}
1282 };
1283
1284 match e {
1285 Expr::New(NewExpr {
1286 span,
1287 ctxt,
1288 callee,
1289 args,
1290 ..
1291 }) if callee.is_one_of_global_ref_to(
1292 self.expr_ctx,
1293 &[
1294 "Object",
1295 "Array",
1297 "Function",
1299 "Error",
1301 "AggregateError",
1303 "EvalError",
1305 "RangeError",
1306 "ReferenceError",
1307 "SyntaxError",
1308 "TypeError",
1309 "URIError",
1310 ],
1311 ) || (callee.is_global_ref_to(self.expr_ctx, "RegExp")
1312 && can_compress_new_regexp(args.as_deref())) =>
1313 {
1314 self.changed = true;
1315 report_change!(
1316 "new operator: Compressing `new Array/RegExp/..` => `Array()/RegExp()/..`"
1317 );
1318 *e = CallExpr {
1319 span: *span,
1320 ctxt: *ctxt,
1321 callee: callee.take().as_callee(),
1322 args: args.take().unwrap_or_default(),
1323 ..Default::default()
1324 }
1325 .into()
1326 }
1327 _ => {}
1328 }
1329 }
1330
1331 fn drop_return_value(&mut self, stmts: &mut Vec<Stmt>) -> bool {
1336 for s in stmts.iter_mut() {
1337 if let Stmt::Return(ReturnStmt {
1338 arg: arg @ Some(..),
1339 ..
1340 }) = s
1341 {
1342 self.ignore_return_value(
1343 arg.as_deref_mut().unwrap(),
1344 DropOpts::DROP_GLOBAL_REFS_IF_UNUSED
1345 .union(DropOpts::DROP_NUMBER)
1346 .union(DropOpts::DROP_STR_LIT),
1347 );
1348
1349 if let Some(Expr::Invalid(..)) = arg.as_deref() {
1350 self.changed = true;
1351 *arg = None;
1352 }
1353 }
1354 }
1355
1356 if let Some(last) = stmts.last_mut() {
1357 self.drop_return_value_of_stmt(last)
1358 } else {
1359 false
1360 }
1361 }
1362
1363 fn compress_array_join_as_tpl(
1364 &mut self,
1365 span: Span,
1366 elems: &mut Vec<Option<ExprOrSpread>>,
1367 sep: &Wtf8,
1368 ) -> Option<Expr> {
1369 if !self.options.evaluate {
1370 return None;
1371 }
1372
1373 if elems.iter().flatten().any(|elem| match &*elem.expr {
1374 Expr::Tpl(t) => t.quasis.iter().any(|q| q.cooked.is_none()),
1375 Expr::Lit(Lit::Str(..)) => false,
1376 _ => true,
1377 }) {
1378 return None;
1379 }
1380
1381 self.changed = true;
1382 report_change!("Compressing array.join() as template literal");
1383
1384 let mut new_tpl = Tpl {
1385 span,
1386 quasis: Vec::new(),
1387 exprs: Vec::new(),
1388 };
1389 let mut cur_raw = String::new();
1390 let mut cur_cooked = Wtf8Buf::default();
1391 let mut first = true;
1392
1393 for elem in elems.take().into_iter().flatten() {
1394 if first {
1395 first = false;
1396 } else {
1397 cur_raw.push_str(&convert_str_value_to_tpl_raw(sep));
1398 cur_cooked.push_wtf8(sep);
1399 }
1400
1401 match *elem.expr {
1402 Expr::Tpl(mut tpl) => {
1403 for idx in 0..(tpl.quasis.len() + tpl.exprs.len()) {
1405 if idx % 2 == 0 {
1406 let e = tpl.quasis[idx / 2].take();
1408
1409 cur_cooked.push_wtf8(&e.cooked.unwrap());
1410 cur_raw.push_str(&e.raw);
1411 } else {
1412 new_tpl.quasis.push(TplElement {
1413 span: DUMMY_SP,
1414 tail: false,
1415 cooked: Some((&*cur_cooked).into()),
1416 raw: (&*cur_raw).into(),
1417 });
1418
1419 cur_raw.clear();
1420 cur_cooked.clear();
1421
1422 let e = tpl.exprs[idx / 2].take();
1423
1424 new_tpl.exprs.push(e);
1425 }
1426 }
1427 }
1428 Expr::Lit(Lit::Str(s)) => {
1429 cur_cooked.push_wtf8(&convert_str_value_to_tpl_cooked(&s.value));
1430 cur_raw.push_str(&convert_str_value_to_tpl_raw(&s.value));
1431 }
1432 _ => {
1433 unreachable!()
1434 }
1435 }
1436 }
1437
1438 new_tpl.quasis.push(TplElement {
1439 span: DUMMY_SP,
1440 tail: false,
1441 cooked: Some(cur_cooked.into()),
1442 raw: cur_raw.into(),
1443 });
1444
1445 Some(new_tpl.into())
1446 }
1447
1448 fn drop_return_value_of_stmt(&mut self, s: &mut Stmt) -> bool {
1450 match s {
1451 Stmt::Block(s) => self.drop_return_value(&mut s.stmts),
1452 Stmt::Return(ret) => {
1453 self.changed = true;
1454 report_change!("Dropping `return` token");
1455
1456 let span = ret.span;
1457 match ret.arg.take() {
1458 Some(arg) => {
1459 *s = ExprStmt { span, expr: arg }.into();
1460 }
1461 None => {
1462 *s = EmptyStmt { span }.into();
1463 }
1464 }
1465
1466 true
1467 }
1468
1469 Stmt::Labeled(s) => self.drop_return_value_of_stmt(&mut s.body),
1470 Stmt::If(s) => {
1471 let c = self.drop_return_value_of_stmt(&mut s.cons);
1472 let a = s
1473 .alt
1474 .as_deref_mut()
1475 .map(|s| self.drop_return_value_of_stmt(s))
1476 .unwrap_or_default();
1477
1478 c || a
1479 }
1480
1481 Stmt::Try(s) => {
1482 let a = if s.finalizer.is_none() {
1483 self.drop_return_value(&mut s.block.stmts)
1484 } else {
1485 false
1486 };
1487
1488 let b = s
1489 .finalizer
1490 .as_mut()
1491 .map(|s| self.drop_return_value(&mut s.stmts))
1492 .unwrap_or_default();
1493
1494 a || b
1495 }
1496
1497 _ => false,
1498 }
1499 }
1500
1501 fn make_ignored_expr(
1502 &mut self,
1503 span: Span,
1504 exprs: impl Iterator<Item = Box<Expr>>,
1505 ) -> Option<Expr> {
1506 let mut exprs = exprs
1507 .filter_map(|mut e| {
1508 self.ignore_return_value(
1509 &mut e,
1510 DropOpts::DROP_GLOBAL_REFS_IF_UNUSED
1511 .union(DropOpts::DROP_NUMBER)
1512 .union(DropOpts::DROP_STR_LIT),
1513 );
1514
1515 if let Expr::Invalid(..) = &*e {
1516 None
1517 } else {
1518 Some(e)
1519 }
1520 })
1521 .collect::<Vec<_>>();
1522
1523 if exprs.is_empty() {
1524 return None;
1525 }
1526 if exprs.len() == 1 {
1527 let mut new = *exprs.remove(0);
1528 new.set_span(span);
1529 return Some(new);
1530 }
1531
1532 Some(
1533 SeqExpr {
1534 span: DUMMY_SP,
1535 exprs,
1536 }
1537 .into(),
1538 )
1539 }
1540
1541 pub(super) fn ignore_return_value_of_return_stmt(&mut self, s: &mut Stmt, opts: DropOpts) {
1546 match s {
1547 Stmt::Return(s) => {
1548 if let Some(arg) = &mut s.arg {
1549 self.ignore_return_value(arg, opts);
1550 if arg.is_invalid() {
1551 report_change!(
1552 "Dropped the argument of a return statement because the return value \
1553 is ignored"
1554 );
1555 s.arg = None;
1556 }
1557 }
1558 }
1559
1560 Stmt::Block(s) => {
1561 for stmt in &mut s.stmts {
1562 self.ignore_return_value_of_return_stmt(stmt, opts);
1563 }
1564 }
1565
1566 Stmt::If(s) => {
1567 self.ignore_return_value_of_return_stmt(&mut s.cons, opts);
1568 if let Some(alt) = &mut s.alt {
1569 self.ignore_return_value_of_return_stmt(alt, opts);
1570 }
1571 }
1572
1573 Stmt::Switch(s) => {
1574 for case in &mut s.cases {
1575 for stmt in &mut case.cons {
1576 self.ignore_return_value_of_return_stmt(stmt, opts);
1577 }
1578 }
1579 }
1580
1581 _ => {}
1582 }
1583 }
1584
1585 pub(super) fn ignore_return_value(&mut self, e: &mut Expr, opts: DropOpts) {
1586 if e.is_invalid() {
1587 return;
1588 }
1589
1590 if self.ctx.contains(Ctx::IN_DELETE) {
1591 return;
1592 }
1593
1594 debug_assert_valid(e);
1595
1596 self.optimize_expr_in_bool_ctx(e, true);
1597
1598 match e {
1599 Expr::Seq(seq) => {
1600 if let Some(last) = seq.exprs.last_mut() {
1601 self.ignore_return_value(last, opts);
1602 }
1603
1604 seq.exprs.retain(|expr| !expr.is_invalid());
1605
1606 if seq.exprs.is_empty() {
1607 e.take();
1608 return;
1609 }
1610 if seq.exprs.len() == 1 {
1611 *e = *seq.exprs.remove(0);
1612 }
1613 return;
1614 }
1615
1616 Expr::Call(CallExpr {
1617 span, ctxt, args, ..
1618 }) if ctxt.has_mark(self.marks.pure) => {
1619 report_change!("ignore_return_value: Dropping a pure call");
1620 self.changed = true;
1621
1622 let new =
1623 self.make_ignored_expr(*span, args.take().into_iter().map(|arg| arg.expr));
1624
1625 *e = new.unwrap_or(Invalid { span: DUMMY_SP }.into());
1626 return;
1627 }
1628
1629 Expr::Call(CallExpr {
1630 span,
1631 callee: Callee::Expr(callee),
1632 args,
1633 ..
1634 }) if callee.is_pure_callee(self.expr_ctx) => {
1635 report_change!("ignore_return_value: Dropping a pure call (callee is pure)");
1636 self.changed = true;
1637
1638 let new =
1639 self.make_ignored_expr(*span, args.take().into_iter().map(|arg| arg.expr));
1640
1641 *e = new.unwrap_or(Invalid { span: DUMMY_SP }.into());
1642 return;
1643 }
1644
1645 Expr::TaggedTpl(TaggedTpl {
1646 span, ctxt, tpl, ..
1647 }) if ctxt.has_mark(self.marks.pure) => {
1648 report_change!("ignore_return_value: Dropping a pure call");
1649 self.changed = true;
1650
1651 let new = self.make_ignored_expr(*span, tpl.exprs.take().into_iter());
1652
1653 *e = new.unwrap_or(Invalid { span: DUMMY_SP }.into());
1654 return;
1655 }
1656
1657 Expr::New(NewExpr {
1658 callee,
1659 span,
1660 ctxt,
1661 args,
1662 ..
1663 }) if callee.is_pure_callee(self.expr_ctx) || ctxt.has_mark(self.marks.pure) => {
1664 report_change!("ignore_return_value: Dropping a pure call");
1665 self.changed = true;
1666
1667 let new = self.make_ignored_expr(
1668 *span,
1669 args.take().into_iter().flatten().map(|arg| arg.expr),
1670 );
1671
1672 *e = new.unwrap_or(Invalid { span: DUMMY_SP }.into());
1673 return;
1674 }
1675
1676 _ => {}
1677 }
1678
1679 match e {
1680 Expr::Call(CallExpr {
1681 span,
1682 callee: Callee::Expr(callee),
1683 args,
1684 ..
1685 }) => {
1686 if callee.is_pure_callee(self.expr_ctx) {
1687 self.changed = true;
1688 report_change!("Dropping pure call as callee is pure");
1689 *e = self
1690 .make_ignored_expr(*span, args.take().into_iter().map(|arg| arg.expr))
1691 .unwrap_or(Invalid { span: DUMMY_SP }.into());
1692 return;
1693 }
1694 }
1695
1696 Expr::TaggedTpl(TaggedTpl {
1697 span,
1698 tag: callee,
1699 tpl,
1700 ..
1701 }) => {
1702 if callee.is_pure_callee(self.expr_ctx) {
1703 self.changed = true;
1704 report_change!("Dropping pure tag tpl as callee is pure");
1705 *e = self
1706 .make_ignored_expr(*span, tpl.exprs.take().into_iter())
1707 .unwrap_or(Invalid { span: DUMMY_SP }.into());
1708 return;
1709 }
1710 }
1711 _ => (),
1712 }
1713
1714 if self.options.conditionals || self.options.expr {
1715 if let Expr::Cond(CondExpr {
1716 span,
1717 test,
1718 cons,
1719 alt,
1720 ..
1721 }) = e
1722 {
1723 self.ignore_return_value(cons, opts);
1724 self.ignore_return_value(alt, opts);
1725
1726 if cons.is_invalid() && alt.is_invalid() {
1727 report_change!("Dropping a conditional expression");
1728 *e = *test.take();
1729 self.changed = true;
1730 return;
1731 }
1732
1733 if cons.is_invalid() {
1734 *e = Expr::Bin(BinExpr {
1735 span: *span,
1736 op: op!("||"),
1737 left: test.take(),
1738 right: alt.take(),
1739 });
1740 report_change!("Dropping the `then` branch of a conditional expression");
1741 self.changed = true;
1742 return;
1743 }
1744
1745 if alt.is_invalid() {
1746 *e = Expr::Bin(BinExpr {
1747 span: *span,
1748 op: op!("&&"),
1749 left: test.take(),
1750 right: cons.take(),
1751 });
1752 report_change!("Dropping the `else` branch of a conditional expression");
1753 self.changed = true;
1754 return;
1755 }
1756 }
1757 }
1758
1759 if opts.contains(DropOpts::DROP_NUMBER) {
1760 if let Expr::Lit(Lit::Num(n)) = e {
1761 if n.value != 0.0 && n.value.classify() == FpCategory::Normal {
1763 report_change!("Dropping a number");
1764 *e = Invalid { span: DUMMY_SP }.into();
1765 return;
1766 }
1767 }
1768 }
1769
1770 if let Expr::Ident(i) = e {
1771 if i.ctxt.outer() == self.marks.unresolved_mark {
1773 if self.options.side_effects
1774 || (self.options.unused && opts.contains(DropOpts::DROP_GLOBAL_REFS_IF_UNUSED))
1775 {
1776 if is_global_var_with_pure_property_access(&i.sym) {
1777 report_change!("Dropping a reference to a global variable");
1778 *e = Invalid { span: DUMMY_SP }.into();
1779 return;
1780 }
1781 }
1782 } else {
1783 report_change!("Dropping an identifier as it's declared");
1784 *e = Invalid { span: DUMMY_SP }.into();
1785 return;
1786 }
1787 }
1788
1789 if self.options.side_effects
1790 || self.options.dead_code
1791 || self.options.collapse_vars
1792 || self.options.expr
1793 {
1794 match e {
1795 Expr::Unary(UnaryExpr {
1796 op: op!("!"), arg, ..
1797 }) => {
1798 self.ignore_return_value(
1799 arg,
1800 DropOpts::DROP_GLOBAL_REFS_IF_UNUSED
1801 .union(DropOpts::DROP_NUMBER)
1802 .union(DropOpts::DROP_STR_LIT),
1803 );
1804
1805 if arg.is_invalid() {
1806 report_change!("Dropping an unary expression");
1807 *e = Invalid { span: DUMMY_SP }.into();
1808 return;
1809 }
1810 }
1811
1812 Expr::Unary(UnaryExpr {
1813 op: op!("void") | op!(unary, "+") | op!(unary, "-") | op!("~"),
1814 arg,
1815 ..
1816 }) => {
1817 self.ignore_return_value(
1818 arg,
1819 DropOpts::DROP_GLOBAL_REFS_IF_UNUSED
1820 .union(DropOpts::DROP_NUMBER)
1821 .union(DropOpts::DROP_STR_LIT),
1822 );
1823
1824 if arg.is_invalid() {
1825 report_change!("Dropping an unary expression");
1826 *e = Invalid { span: DUMMY_SP }.into();
1827 return;
1828 }
1829
1830 report_change!("Dropping an unary operator");
1831 *e = *arg.take();
1832 return;
1833 }
1834
1835 Expr::Bin(
1836 be @ BinExpr {
1837 op: op!("||") | op!("&&"),
1838 ..
1839 },
1840 ) => {
1841 self.ignore_return_value(&mut be.right, opts);
1842
1843 if be.right.is_invalid() {
1844 report_change!("Dropping the RHS of a binary expression ('&&' / '||')");
1845 *e = *be.left.take();
1846 return;
1847 }
1848 }
1849
1850 Expr::Tpl(tpl) => {
1851 self.changed = true;
1852 report_change!("Dropping a template literal");
1853
1854 for expr in tpl.exprs.iter_mut() {
1855 self.ignore_return_value(&mut *expr, opts);
1856 }
1857 tpl.exprs.retain(|expr| !expr.is_invalid());
1858 if tpl.exprs.is_empty() {
1859 e.take();
1860 } else {
1861 if tpl.exprs.len() == 1 {
1862 *e = *tpl.exprs.remove(0);
1863 } else {
1864 *e = SeqExpr {
1865 span: tpl.span,
1866 exprs: tpl.exprs.take(),
1867 }
1868 .into();
1869 }
1870 }
1871
1872 return;
1873 }
1874
1875 Expr::Member(MemberExpr {
1876 obj,
1877 prop: MemberProp::Ident(prop),
1878 ..
1879 }) => {
1880 if obj.is_ident_ref_to("arguments") {
1881 if &*prop.sym == "callee" {
1882 return;
1883 }
1884 e.take();
1885 return;
1886 }
1887 }
1888
1889 _ => {}
1890 }
1891 }
1892
1893 match e {
1894 Expr::Lit(Lit::Num(n)) => {
1895 if n.value == 0.0 && opts.contains(DropOpts::DROP_NUMBER) {
1896 report_change!("Dropping a zero number");
1897 *e = Invalid { span: DUMMY_SP }.into();
1898 return;
1899 }
1900 }
1901
1902 Expr::Ident(i) => {
1903 if i.ctxt.outer() != self.marks.unresolved_mark {
1904 report_change!("Dropping an identifier as it's declared");
1905
1906 self.changed = true;
1907 *e = Invalid { span: DUMMY_SP }.into();
1908 return;
1909 }
1910 }
1911
1912 Expr::Lit(Lit::Null(..) | Lit::BigInt(..) | Lit::Bool(..) | Lit::Regex(..))
1913 | Expr::This(..) => {
1914 report_change!("Dropping meaningless values");
1915
1916 self.changed = true;
1917 *e = Expr::dummy();
1918 return;
1919 }
1920
1921 Expr::Bin(
1922 bin @ BinExpr {
1923 op:
1924 op!(bin, "+")
1925 | op!(bin, "-")
1926 | op!("*")
1927 | op!("%")
1928 | op!("**")
1929 | op!("^")
1930 | op!("&")
1931 | op!("|")
1932 | op!(">>")
1933 | op!("<<")
1934 | op!(">>>")
1935 | op!("===")
1936 | op!("!==")
1937 | op!("==")
1938 | op!("!=")
1939 | op!("<")
1940 | op!("<=")
1941 | op!(">")
1942 | op!(">="),
1943 ..
1944 },
1945 ) => {
1946 self.ignore_return_value(
1947 &mut bin.left,
1948 DropOpts::DROP_GLOBAL_REFS_IF_UNUSED
1949 .union(DropOpts::DROP_NUMBER)
1950 .union(DropOpts::DROP_STR_LIT),
1951 );
1952 self.ignore_return_value(
1953 &mut bin.right,
1954 DropOpts::DROP_GLOBAL_REFS_IF_UNUSED
1955 .union(DropOpts::DROP_NUMBER)
1956 .union(DropOpts::DROP_STR_LIT),
1957 );
1958
1959 if bin.left.is_invalid() && bin.right.is_invalid() {
1960 *e = Invalid { span: DUMMY_SP }.into();
1961 return;
1962 } else if bin.right.is_invalid() {
1963 *e = *bin.left.take();
1964 return;
1965 } else if bin.left.is_invalid() {
1966 *e = *bin.right.take();
1967 return;
1968 }
1969
1970 if matches!(*bin.left, Expr::Await(..) | Expr::Update(..)) {
1971 self.changed = true;
1972 report_change!("ignore_return_value: Compressing binary as seq");
1973 *e = SeqExpr {
1974 span: bin.span,
1975 exprs: vec![bin.left.take(), bin.right.take()],
1976 }
1977 .into();
1978 return;
1979 }
1980 }
1981
1982 Expr::Assign(assign @ AssignExpr { op: op!("="), .. }) => {
1983 if let Some(l) = assign.left.as_ident() {
1985 if let Expr::Ident(r) = &*assign.right {
1986 if l.ctxt == r.ctxt
1987 && l.ctxt != self.expr_ctx.unresolved_ctxt
1988 && l.sym == r.sym
1989 {
1990 self.changed = true;
1991 *e = *assign.right.take();
1992 }
1993 }
1994 }
1995 }
1996
1997 Expr::Array(arr) => {
1998 for elem in arr.elems.iter_mut().flatten() {
1999 if elem.spread.is_none() {
2000 self.ignore_return_value(
2001 &mut elem.expr,
2002 DropOpts::DROP_NUMBER.union(DropOpts::DROP_STR_LIT),
2003 );
2004 }
2005 }
2006
2007 arr.elems
2008 .retain(|e| e.as_ref().is_some_and(|e| !e.expr.is_invalid()));
2009
2010 if arr.elems.is_empty() {
2011 *e = Expr::dummy();
2012 report_change!("Dropping an empty array literal");
2013 self.changed = true;
2014 return;
2015 }
2016 }
2017
2018 Expr::Object(obj) => {
2019 let mut has_spread = false;
2020 for prop in obj.props.iter_mut() {
2021 match prop {
2022 PropOrSpread::Spread(..) => {
2023 has_spread = true;
2024 break;
2025 }
2026 PropOrSpread::Prop(p) => match &mut **p {
2027 Prop::KeyValue(kv) => {
2028 if !kv.key.is_computed()
2029 && !kv.value.may_have_side_effects(self.expr_ctx)
2030 {
2031 **p = Prop::Shorthand(Ident::dummy());
2032 self.changed = true;
2033 report_change!(
2034 "Dropping a key-value pair in an object literal"
2035 );
2036 }
2037 }
2038
2039 Prop::Shorthand(i) => {
2040 if i.ctxt.outer() != self.marks.unresolved_mark {
2041 *i = Ident::dummy();
2042 self.changed = true;
2043 report_change!(
2044 "Dropping a shorthand property in an object literal"
2045 );
2046 }
2047 }
2048
2049 _ => {}
2050 },
2051 #[cfg(swc_ast_unknown)]
2052 _ => panic!("unable to access unknown nodes"),
2053 }
2054 }
2055
2056 obj.props.retain(|p| match p {
2057 PropOrSpread::Prop(prop) => match &**prop {
2058 Prop::Shorthand(i) => !i.is_dummy(),
2059 _ => true,
2060 },
2061 _ => true,
2062 });
2063
2064 if !has_spread {
2065 if obj.props.is_empty() {
2066 *e = Expr::dummy();
2067 report_change!("Dropping an empty object literal");
2068 self.changed = true;
2069 return;
2070 }
2071 }
2072 }
2073
2074 _ => {}
2075 }
2076
2077 match e {
2078 Expr::Lit(Lit::Str(s)) => {
2079 if (self.options.directives
2080 && !matches!(s.value.as_str(), Some(s) if s == "use strict" || s == "use asm"))
2081 || opts.contains(DropOpts::DROP_STR_LIT)
2082 || (s.value.starts_with("@swc/helpers")
2083 || s.value.starts_with("@babel/helpers"))
2084 {
2085 self.changed = true;
2086 *e = Invalid { span: DUMMY_SP }.into();
2087
2088 return;
2089 }
2090 }
2091
2092 Expr::Seq(seq) => {
2093 self.drop_useless_ident_ref_in_seq(seq);
2094
2095 if let Some(last) = seq.exprs.last_mut() {
2096 self.ignore_return_value(last, opts);
2098 }
2099
2100 let len = seq.exprs.len();
2101 seq.exprs.retain(|e| !e.is_invalid());
2102 if seq.exprs.len() != len {
2103 self.changed = true;
2104 }
2105
2106 if seq.exprs.len() == 1 {
2107 *e = *seq.exprs.remove(0);
2108 }
2109 return;
2110 }
2111
2112 Expr::Call(CallExpr {
2113 callee: Callee::Expr(callee),
2114 ..
2115 }) if callee.is_fn_expr() => match &mut **callee {
2116 Expr::Fn(callee) => {
2117 if callee.ident.is_none() {
2118 if let Some(body) = &mut callee.function.body {
2119 if self.options.side_effects {
2120 self.drop_return_value(&mut body.stmts);
2121 }
2122 }
2123 }
2124 }
2125
2126 _ => {
2127 unreachable!()
2128 }
2129 },
2130
2131 _ => {}
2132 }
2133
2134 if self.options.side_effects {
2135 if let Expr::Call(CallExpr {
2136 callee: Callee::Expr(callee),
2137 ..
2138 }) = e
2139 {
2140 match &mut **callee {
2141 Expr::Fn(callee) => {
2142 if let Some(body) = &mut callee.function.body {
2143 if let Some(ident) = &callee.ident {
2144 if IdentUsageFinder::find(ident, body) {
2145 return;
2146 }
2147 }
2148
2149 for stmt in &mut body.stmts {
2150 self.ignore_return_value_of_return_stmt(stmt, opts);
2151 }
2152 }
2153 }
2154 Expr::Arrow(callee) => match &mut *callee.body {
2155 BlockStmtOrExpr::BlockStmt(body) => {
2156 for stmt in &mut body.stmts {
2157 self.ignore_return_value_of_return_stmt(stmt, opts);
2158 }
2159 }
2160 BlockStmtOrExpr::Expr(body) => {
2161 self.ignore_return_value(body, opts);
2162
2163 if body.is_invalid() {
2164 *body = 0.into();
2165 return;
2166 }
2167 }
2168 #[cfg(swc_ast_unknown)]
2169 _ => panic!("unable to access unknown nodes"),
2170 },
2171 _ => {}
2172 }
2173 }
2174 }
2175
2176 if self.options.side_effects && self.options.pristine_globals {
2177 match e {
2178 Expr::New(NewExpr {
2179 span, callee, args, ..
2180 }) if callee.is_one_of_global_ref_to(
2181 self.expr_ctx,
2182 &[
2183 "Map", "Set", "Array", "Object", "Boolean", "Number", "String",
2184 ],
2185 ) =>
2186 {
2187 report_change!("Dropping a pure new expression");
2188
2189 self.changed = true;
2190 *e = self
2191 .make_ignored_expr(
2192 *span,
2193 args.iter_mut().flatten().map(|arg| arg.expr.take()),
2194 )
2195 .unwrap_or(Invalid { span: DUMMY_SP }.into());
2196 return;
2197 }
2198
2199 Expr::Call(CallExpr {
2200 span,
2201 callee: Callee::Expr(callee),
2202 args,
2203 ..
2204 }) if callee.is_one_of_global_ref_to(
2205 self.expr_ctx,
2206 &["Array", "Object", "Boolean", "Number"],
2207 ) =>
2208 {
2209 report_change!("Dropping a pure call expression");
2210
2211 self.changed = true;
2212 *e = self
2213 .make_ignored_expr(*span, args.iter_mut().map(|arg| arg.expr.take()))
2214 .unwrap_or(Invalid { span: DUMMY_SP }.into());
2215 return;
2216 }
2217
2218 Expr::Object(obj) => {
2219 if obj.props.iter().all(|prop| !prop.is_spread()) {
2220 let exprs = collect_exprs_from_object(obj);
2221 *e = self
2222 .make_ignored_expr(obj.span, exprs.into_iter())
2223 .unwrap_or(Invalid { span: DUMMY_SP }.into());
2224 report_change!("Ignored an object literal");
2225 self.changed = true;
2226 return;
2227 }
2228 }
2229
2230 Expr::Array(arr) => {
2231 if arr.elems.iter().any(|e| match e {
2232 Some(ExprOrSpread {
2233 spread: Some(..), ..
2234 }) => true,
2235 _ => false,
2236 }) {
2237 *e = ArrayLit {
2238 elems: arr
2239 .elems
2240 .take()
2241 .into_iter()
2242 .flatten()
2243 .filter_map(|mut e| {
2244 if e.spread.is_some() {
2245 return Some(e);
2246 }
2247
2248 self.ignore_return_value(
2249 &mut e.expr,
2250 DropOpts::DROP_GLOBAL_REFS_IF_UNUSED
2251 .union(DropOpts::DROP_NUMBER)
2252 .union(DropOpts::DROP_STR_LIT),
2253 );
2254 if e.expr.is_invalid() {
2255 return None;
2256 }
2257
2258 Some(ExprOrSpread {
2259 spread: None,
2260 expr: e.expr,
2261 })
2262 })
2263 .map(Some)
2264 .collect(),
2265 ..*arr
2266 }
2267 .into();
2268 return;
2269 }
2270
2271 let mut exprs = Vec::new();
2272
2273 for ExprOrSpread { mut expr, .. } in arr.elems.take().into_iter().flatten() {
2276 self.ignore_return_value(
2277 &mut expr,
2278 DropOpts::DROP_GLOBAL_REFS_IF_UNUSED
2279 .union(DropOpts::DROP_NUMBER)
2280 .union(DropOpts::DROP_STR_LIT),
2281 );
2282 if !expr.is_invalid() {
2283 exprs.push(expr);
2284 }
2285 }
2286
2287 *e = self
2288 .make_ignored_expr(arr.span, exprs.into_iter())
2289 .unwrap_or(Invalid { span: DUMMY_SP }.into());
2290 report_change!("Ignored an array literal");
2291 self.changed = true;
2292 return;
2293 }
2294
2295 Expr::Member(MemberExpr {
2303 span,
2304 obj,
2305 prop: MemberProp::Computed(prop),
2306 ..
2307 }) => match obj.as_mut() {
2308 Expr::Object(object) => {
2309 if object.props.iter().all(|p| match p {
2312 PropOrSpread::Spread(..) => false,
2313 PropOrSpread::Prop(p) => match &**p {
2314 Prop::Getter(..) | Prop::Setter(..) => false,
2315 _ => true,
2316 },
2317 #[cfg(swc_ast_unknown)]
2318 _ => panic!("unable to access unknown nodes"),
2319 }) {
2320 let mut exprs = collect_exprs_from_object(object);
2321 exprs.push(prop.expr.take());
2322 *e = self
2323 .make_ignored_expr(*span, exprs.into_iter())
2324 .unwrap_or(Invalid { span: DUMMY_SP }.into());
2325 return;
2326 }
2327 }
2328 Expr::Array(..) => {
2329 self.ignore_return_value(obj, opts);
2330 *e = self
2331 .make_ignored_expr(
2332 *span,
2333 vec![obj.take(), prop.expr.take()].into_iter(),
2334 )
2335 .unwrap_or(Invalid { span: DUMMY_SP }.into());
2336 return;
2337 }
2338 _ => {}
2339 },
2340 _ => {}
2341 }
2342 }
2343
2344 if self.options.pristine_globals {
2346 if let Expr::Member(MemberExpr { obj, prop, .. }) = e {
2347 if let Expr::Ident(obj) = &**obj {
2348 if obj.ctxt.outer() == self.marks.unresolved_mark {
2349 if is_pure_member_access(obj, prop) {
2350 self.changed = true;
2351 report_change!("Remving pure member access to global var");
2352 *e = Invalid { span: DUMMY_SP }.into();
2353 }
2354 }
2355 }
2356 }
2357 }
2358 }
2359
2360 pub(super) fn compress_negated_bin_eq(&self, e: &mut Expr) {
2364 let unary = match e {
2365 Expr::Unary(e @ UnaryExpr { op: op!("!"), .. }) => e,
2366 _ => return,
2367 };
2368
2369 match &mut *unary.arg {
2370 Expr::Bin(BinExpr {
2371 op: op @ op!("=="),
2372 left,
2373 right,
2374 ..
2375 })
2376 | Expr::Bin(BinExpr {
2377 op: op @ op!("==="),
2378 left,
2379 right,
2380 ..
2381 }) => {
2382 *e = BinExpr {
2383 span: unary.span,
2384 op: if *op == op!("==") {
2385 op!("!=")
2386 } else {
2387 op!("!==")
2388 },
2389 left: left.take(),
2390 right: right.take(),
2391 }
2392 .into()
2393 }
2394 _ => {}
2395 }
2396 }
2397
2398 pub(super) fn optimize_nullish_coalescing(&mut self, e: &mut Expr) {
2399 let (l, r) = match e {
2400 Expr::Bin(BinExpr {
2401 op: op!("??"),
2402 left,
2403 right,
2404 ..
2405 }) => (&mut **left, &mut **right),
2406 _ => return,
2407 };
2408
2409 match l {
2410 Expr::Lit(Lit::Null(..)) => {
2411 report_change!("Removing null from lhs of ??");
2412 self.changed = true;
2413 *e = r.take();
2414 }
2415 Expr::Lit(Lit::Num(..))
2416 | Expr::Lit(Lit::Str(..))
2417 | Expr::Lit(Lit::BigInt(..))
2418 | Expr::Lit(Lit::Bool(..))
2419 | Expr::Lit(Lit::Regex(..)) => {
2420 report_change!("Removing rhs of ?? as lhs cannot be null nor undefined");
2421 self.changed = true;
2422 *e = l.take();
2423 }
2424 _ => {}
2425 }
2426 }
2427
2428 pub(super) fn drop_neeedless_pat(&mut self, p: &mut Pat) {
2429 if let Pat::Assign(assign) = p {
2430 if is_pure_undefined(self.expr_ctx, &assign.right) {
2431 *p = *assign.left.take();
2432 self.changed = true;
2433 report_change!(
2434 "Converting an assignment pattern with undefined on RHS to a normal pattern"
2435 );
2436 }
2437 }
2438 }
2439
2440 pub(super) fn compress_useless_cond_expr(&mut self, expr: &mut Expr) {
2443 let cond = match expr {
2444 Expr::Cond(c) => c,
2445 _ => return,
2446 };
2447
2448 if (cond.cons.get_type(self.expr_ctx) != Value::Known(Type::Bool))
2449 || (cond.alt.get_type(self.expr_ctx) != Value::Known(Type::Bool))
2450 {
2451 return;
2452 }
2453
2454 let lb = cond.cons.as_pure_bool(self.expr_ctx);
2455 let rb = cond.alt.as_pure_bool(self.expr_ctx);
2456
2457 let lb = match lb {
2458 Value::Known(v) => v,
2459 Value::Unknown => return,
2460 };
2461 let rb = match rb {
2462 Value::Known(v) => v,
2463 Value::Unknown => return,
2464 };
2465
2466 if lb && !rb {
2468 self.negate(&mut cond.test, false, false);
2469 self.negate(&mut cond.test, false, false);
2470 *expr = *cond.test.take();
2471 return;
2472 }
2473
2474 if !lb && rb {
2476 self.negate(&mut cond.test, false, false);
2477 *expr = *cond.test.take();
2478 }
2479 }
2480
2481 pub(super) fn drop_needless_block(&mut self, s: &mut Stmt) {
2482 fn is_simple_stmt(s: &Stmt) -> bool {
2483 !matches!(
2484 s,
2485 Stmt::Switch(..)
2486 | Stmt::For(..)
2487 | Stmt::With(..)
2488 | Stmt::ForIn(..)
2489 | Stmt::ForOf(..)
2490 | Stmt::While(..)
2491 | Stmt::DoWhile(..)
2492 | Stmt::Try(..)
2493 )
2494 }
2495
2496 if let Stmt::Block(bs) = s {
2497 if bs.stmts.is_empty() {
2498 self.changed = true;
2499 report_change!("Dropping an empty block");
2500 *s = Stmt::dummy();
2501 return;
2502 }
2503
2504 if bs.stmts.len() == 1
2505 && !is_block_scoped_stmt(&bs.stmts[0])
2506 && is_simple_stmt(&bs.stmts[0])
2507 {
2508 *s = bs.stmts.remove(0);
2509 self.changed = true;
2510 report_change!("Dropping a needless block");
2511 }
2512 }
2513 }
2514}
2515
2516bitflags::bitflags! {
2517 #[derive(Debug, Default, Clone, Copy)]
2518 pub(super) struct DropOpts: u8 {
2519 const DROP_GLOBAL_REFS_IF_UNUSED = 1 << 0;
2522 const DROP_NUMBER = 1 << 1;
2523 const DROP_STR_LIT = 1 << 2;
2524 }
2525}
2526
2527fn is_pure_member_access(obj: &Ident, prop: &MemberProp) -> bool {
2529 macro_rules! check {
2530 (
2531 $obj:ident.
2532 $prop:ident
2533 ) => {{
2534 if &*obj.sym == stringify!($obj) {
2535 if let MemberProp::Ident(prop) = prop {
2536 if &*prop.sym == stringify!($prop) {
2537 return true;
2538 }
2539 }
2540 }
2541 }};
2542 }
2543
2544 macro_rules! pure {
2545 (
2546 $(
2547 $(
2548 $i:ident
2549 ).*
2550 ),*
2551 ) => {
2552 $(
2553 check!($($i).*);
2554 )*
2555 };
2556 }
2557
2558 pure!(
2559 Array.isArray,
2560 ArrayBuffer.isView,
2561 Boolean.toSource,
2562 Date.parse,
2563 Date.UTC,
2564 Date.now,
2565 Error.captureStackTrace,
2566 Error.stackTraceLimit,
2567 Function.bind,
2568 Function.call,
2569 Function.length,
2570 console.log,
2571 Error.name,
2572 Math.random,
2573 Number.isNaN,
2574 Object.defineProperty,
2575 String.fromCharCode
2576 );
2577
2578 false
2579}
2580
2581fn is_block_scoped_stmt(s: &Stmt) -> bool {
2582 match s {
2583 Stmt::Decl(Decl::Var(v)) if v.kind == VarDeclKind::Const || v.kind == VarDeclKind::Let => {
2584 true
2585 }
2586 Stmt::Decl(Decl::Fn(..)) | Stmt::Decl(Decl::Class(..)) => true,
2587 _ => false,
2588 }
2589}