swc_ecma_minifier/compress/pure/
misc.rs

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
39/// Check whether we can compress `new RegExp(…)` to `RegExp(…)`. That's sound
40/// unless the first argument is already a RegExp object and the second is
41/// undefined. We check for the common case where we can prove one of the first
42/// two arguments is a string.
43fn 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    /// `a = a + 1` => `a += 1`.
110    pub(super) fn compress_bin_assignment_to_left(&mut self, e: &mut AssignExpr) {
111        if e.op != op!("=") {
112            return;
113        }
114
115        // TODO: Handle pure properties.
116        let lhs = match &e.left {
117            AssignTarget::Simple(SimpleAssignTarget::Ident(i)) => i,
118            _ => return,
119        };
120
121        // If left operand of a binary expression is not same as lhs, this method has
122        // nothing to do.
123        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        // Don't break code for old browsers.
134        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                // TODO(kdy1): Check if this is optimizable.
147                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        // Now we can compress it to an assignment
202    }
203
204    /// This method does
205    ///
206    /// - `x *= 3` => `x = 3 * x`
207    /// - `x = 3 | x` `x |= 3`
208    /// - `x = 3 & x` => `x &= 3;`
209    /// - `x ^= 3` => `x = 3 ^ x`
210    pub(super) fn compress_bin_assignment_to_right(&mut self, e: &mut AssignExpr) {
211        if e.op != op!("=") {
212            return;
213        }
214
215        // TODO: Handle pure properties.
216        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                    // We need this check because a function call like below can change value of
227                    // operand.
228                    //
229                    // x = g() * x;
230
231                    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    /// `foo(...[1, 2])`` => `foo(1, 2)`
333    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    /// `foo(...[1, 2])`` => `foo(1, 2)`
381    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    /// This function will be costly if the expr is a very long binary expr.
429    /// Call it only when necessary.
430    /// See also compress::optimize::remove_invalid_bin
431    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        // Handle empty array case first
521        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            // Try partial optimization (grouping consecutive literals)
553            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                    // All other expressions can potentially be null/undefined
574                    _ => 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                            // null and undefined should become empty strings in
609                            // join
610                            // Don't add anything for empty string join
611                        }
612                        Expr::Lit(Lit::Null(..)) => {
613                            // null and undefined should become empty strings in
614                            // join
615                            // Don't add anything for empty string join
616                        }
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    /// Performs partial optimization on array.join() when there are mixed
673    /// literals and expressions. Groups consecutive literals into string
674    /// concatenations.
675    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        // Check if we have any non-literal elements
686        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; // Pure literal case will be handled elsewhere
694        }
695
696        // For non-empty separators, only optimize if we have at least 2 consecutive
697        // literals This prevents infinite loop and ensures meaningful
698        // optimization
699        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            // Only optimize for single-character separators to avoid bloating the code
723            // Long separators like "really-long-separator" should not be optimized
724            if separator.len() > 1 {
725                return None;
726            }
727
728            // For comma separator, require a higher threshold to avoid infinite loops
729            if separator == "," && max_consecutive < 6 {
730                return None;
731            }
732        } else {
733            // For empty string joins, optimize more aggressively since we're
734            // doing string concatenation We can always optimize
735            // these as long as there are mixed expressions and literals
736        }
737
738        // Group consecutive literals and create a string concatenation expression
739        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 we don't have any grouped literals, no optimization possible
765        if groups.iter().all(|g| matches!(g, GroupType::Expression(_))) {
766            return None;
767        }
768
769        // Handle different separators
770        let is_string_concat = separator.is_empty();
771
772        if is_string_concat {
773            // Convert to string concatenation
774            let mut result_parts = Vec::new();
775
776            // Only add empty string prefix when the first element is a non-string
777            // expression that needs coercion to string AND there's no string
778            // literal early enough to provide coercion
779            let needs_empty_string_prefix = match groups.first() {
780                Some(GroupType::Expression(first_expr)) => {
781                    // Check if the first expression is already a string concatenation
782                    let first_needs_coercion = match &*first_expr.expr {
783                        Expr::Bin(BinExpr {
784                            op: op!(bin, "+"), ..
785                        }) => false, // Already string concat
786                        Expr::Lit(Lit::Str(..)) => false, // Already a string literal
787                        Expr::Call(_call) => {
788                            // Function calls may return any type and need string coercion
789                            true
790                        }
791                        _ => true, // Other expressions need string coercion
792                    };
793
794                    // If the first element needs coercion, check if the second element is a string
795                    // literal that can provide the coercion
796                    if first_needs_coercion {
797                        match groups.get(1) {
798                            Some(GroupType::Literals(_)) => false, /* String literals will */
799                            // provide coercion
800                            _ => true, // No string literal to provide coercion
801                        }
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                                    // For string concatenation, null becomes
827                                    // empty string
828                                }
829                                e if is_pure_undefined(self.expr_ctx, e) => {
830                                    // undefined becomes empty string in string
831                                    // context
832                                }
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            // Create string concatenation expression
850            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            // For non-empty separator, create a more compact array
867            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                                    // null becomes empty string
883                                }
884                                e if is_pure_undefined(self.expr_ctx, e) => {
885                                    // undefined becomes empty string
886                                }
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            // Create a new array.join() call with the original separator
910            let new_array = Expr::Array(ArrayLit {
911                span: _span,
912                elems: new_elems,
913            });
914
915            // For comma separator, use .join() without arguments (shorter)
916            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    /// `new RegExp("([Sap]+)", "ig")` => `/([Sap]+)/gi`
966    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                    // allowlist
975                    !c.is_ascii_alphanumeric()
976                        && !matches!(c, '$' | '[' | ']' | '(' | ')' | '{' | '}' | '-' | '+' | '_')
977                }) {
978                    None
979                } else {
980                    // SAFETY: We've verified that the string is valid UTF-8 in the if condition
981                    // above. For this branch, the string contains only ASCII alphanumeric
982                    // characters and a few special characters.
983                    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                // SAFETY: matches above ensure that the string is valid UTF-8 ('g', 'i', 'm',
1007                // 'u', 'y', 's', 'd')
1008                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            // For some expressions `RegExp()` and `RegExp("")`
1031            // Theoretically we can use `/(?:)/` to achieve shorter code
1032            // But some browsers released in 2015 don't support them yet.
1033            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    /// Array() -> []
1055    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    /// Object -> {}
1096    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    /// new Array(...) -> Array(...)
1136    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                        // this is indeed very unsafe in case of BigInt
1220                        [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                        // this is also very unsafe in case of Symbol
1240                        [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                    // https://262.ecma-international.org/12.0/#sec-array-constructor
1296                    "Array",
1297                    // https://262.ecma-international.org/12.0/#sec-function-constructor
1298                    "Function",
1299                    // https://262.ecma-international.org/12.0/#sec-error-constructor
1300                    "Error",
1301                    // https://262.ecma-international.org/12.0/#sec-aggregate-error-constructor
1302                    "AggregateError",
1303                    // https://262.ecma-international.org/12.0/#sec-nativeerror-object-structure
1304                    "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    /// Removes last return statement. This should be callled only if the return
1332    /// value of function is ignored.
1333    ///
1334    /// Returns true if something is modified.
1335    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                    //
1404                    for idx in 0..(tpl.quasis.len() + tpl.exprs.len()) {
1405                        if idx % 2 == 0 {
1406                            // quasis
1407                            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    /// Returns true if something is modified.
1449    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    /// Calls [`Self::ignore_return_value`] on the arguments of return
1542    /// statemetns.
1543    ///
1544    /// This function is recursive but does not go into nested scopes.
1545    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                // Skip 0
1762                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 it's not a top level, it's a reference to a declared variable.
1772            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                // Convert `a = a` to `a`.
1984                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                    // Non-last elements are already processed.
2097                    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                    //
2274
2275                    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                // terser compiles
2296                //
2297                //  [1,foo(),...bar()][{foo}]
2298                //
2299                // as
2300                //
2301                //  foo(),basr(),foo;
2302                Expr::Member(MemberExpr {
2303                    span,
2304                    obj,
2305                    prop: MemberProp::Computed(prop),
2306                    ..
2307                }) => match obj.as_mut() {
2308                    Expr::Object(object) => {
2309                        // Accessing getters and setters may cause side effect
2310                        // More precision is possible if comparing the lit prop names
2311                        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        // Remove pure member expressions.
2345        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    ///
2361    /// - `!(x == y)` => `x != y`
2362    /// - `!(x === y)` => `x !== y`
2363    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    ///
2441    /// - `a ? true : false` => `!!a`
2442    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        // `cond ? true : false` => !!cond
2467        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        // `cond ? false : true` => !cond
2475        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        /// If true and `unused` option is enabled, references to global variables
2520        /// will be dropped, even if `side_effects` is false.
2521        const DROP_GLOBAL_REFS_IF_UNUSED = 1 << 0;
2522        const DROP_NUMBER = 1 << 1;
2523        const DROP_STR_LIT = 1 << 2;
2524    }
2525}
2526
2527/// `obj` should have top level syntax context.
2528fn 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}