swc_ecma_minifier/compress/optimize/
iife.rs

1use std::{collections::HashMap, mem::swap};
2
3use rustc_hash::FxHashMap;
4use swc_common::{pass::Either, util::take::Take, Span, Spanned, SyntaxContext, DUMMY_SP};
5use swc_ecma_ast::*;
6use swc_ecma_utils::{contains_arguments, contains_this_expr, find_pat_ids, ExprFactory};
7use swc_ecma_visit::{noop_visit_type, Visit, VisitMutWith, VisitWith};
8
9use super::{util::NormalMultiReplacer, BitCtx, Optimizer};
10#[cfg(feature = "debug")]
11use crate::debug::dump;
12use crate::{
13    program_data::{ProgramData, ScopeData, VarUsageInfoFlags},
14    util::{idents_captured_by, make_number},
15};
16
17/// Methods related to the option `negate_iife`.
18impl Optimizer<'_> {
19    /// Negates iife, while ignore return value.
20    pub(super) fn negate_iife_ignoring_ret(&mut self, e: &mut Expr) {
21        if !self.options.negate_iife
22            || self
23                .ctx
24                .bit_ctx
25                .intersects(BitCtx::InBangArg | BitCtx::DontUseNegatedIife)
26        {
27            return;
28        }
29
30        let expr = match e {
31            Expr::Call(e) => e,
32            _ => return,
33        };
34
35        let callee = match &mut expr.callee {
36            Callee::Super(_) | Callee::Import(_) => return,
37            Callee::Expr(e) => &mut **e,
38            #[cfg(swc_ast_unknown)]
39            _ => panic!("unable to access unknown nodes"),
40        };
41
42        if let Expr::Fn(..) = callee {
43            report_change!("negate_iife: Negating iife");
44            *e = UnaryExpr {
45                span: DUMMY_SP,
46                op: op!("!"),
47                arg: Box::new(e.take()),
48            }
49            .into();
50        }
51    }
52
53    /// Returns true if it did any work.
54    ///
55    ///
56    /// - `iife ? foo : bar` => `!iife ? bar : foo`
57    pub(super) fn negate_iife_in_cond(&mut self, e: &mut Expr) -> bool {
58        let cond = match e {
59            Expr::Cond(v) => v,
60            _ => return false,
61        };
62
63        let test_call = match &mut *cond.test {
64            Expr::Call(e) => e,
65            _ => return false,
66        };
67
68        let callee = match &mut test_call.callee {
69            Callee::Super(_) | Callee::Import(_) => return false,
70            Callee::Expr(e) => &mut **e,
71            #[cfg(swc_ast_unknown)]
72            _ => panic!("unable to access unknown nodes"),
73        };
74
75        match callee {
76            Expr::Fn(..) => {
77                report_change!("negate_iife: Swapping cons and alt");
78                cond.test = UnaryExpr {
79                    span: DUMMY_SP,
80                    op: op!("!"),
81                    arg: cond.test.take(),
82                }
83                .into();
84                swap(&mut cond.cons, &mut cond.alt);
85                true
86            }
87            _ => false,
88        }
89    }
90
91    pub(super) fn restore_negated_iife(&mut self, cond: &mut CondExpr) {
92        if !self.ctx.bit_ctx.contains(BitCtx::DontUseNegatedIife) {
93            return;
94        }
95
96        if let Expr::Unary(UnaryExpr {
97            op: op!("!"), arg, ..
98        }) = &mut *cond.test
99        {
100            if let Expr::Call(CallExpr {
101                span: call_span,
102                callee: Callee::Expr(callee),
103                args,
104                ..
105            }) = &mut **arg
106            {
107                if let Expr::Fn(..) = &**callee {
108                    cond.test = CallExpr {
109                        span: *call_span,
110                        callee: callee.take().as_callee(),
111                        args: args.take(),
112                        ..Default::default()
113                    }
114                    .into();
115                    swap(&mut cond.cons, &mut cond.alt);
116                }
117            }
118        };
119    }
120}
121
122/// Methods related to iife.
123impl Optimizer<'_> {
124    /// # Example
125    ///
126    /// ## Input
127    ///
128    /// ```ts
129    /// (function(x) {
130    ///     (function(y) {
131    ///         console.log(7);
132    ///     })(7);
133    /// })(7);
134    /// ```
135    ///
136    ///
137    /// ## Output
138    ///
139    /// ```ts
140    /// (function(x) {
141    ///     (function(y) {
142    ///         console.log(y);
143    ///     })(x);
144    /// })(7);
145    /// ```
146    #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip(self, e)))]
147    pub(super) fn inline_args_of_iife(&mut self, e: &mut CallExpr) {
148        if self.options.inline == 0 && !self.options.reduce_vars && !self.options.reduce_fns {
149            return;
150        }
151
152        let has_spread_arg = e.args.iter().any(|v| v.spread.is_some());
153        if has_spread_arg {
154            return;
155        }
156
157        let callee = match &mut e.callee {
158            Callee::Super(_) | Callee::Import(_) => return,
159            Callee::Expr(e) => &mut **e,
160            #[cfg(swc_ast_unknown)]
161            _ => panic!("unable to access unknown nodes"),
162        };
163
164        if let Some(scope) = find_scope(self.data, callee) {
165            if scope.contains(ScopeData::USED_ARGUMENTS) {
166                log_abort!("iife: [x] Found usage of arguments");
167                return;
168            }
169        }
170
171        fn clean_params(callee: &mut Expr) {
172            match callee {
173                Expr::Arrow(callee) => {
174                    // Drop invalid nodes
175                    callee.params.retain(|p| !p.is_invalid())
176                }
177                Expr::Fn(callee) => {
178                    // Drop invalid nodes
179                    callee.function.params.retain(|p| !p.pat.is_invalid())
180                }
181                _ => {}
182            }
183        }
184
185        if let Expr::Fn(FnExpr {
186            ident: Some(ident), ..
187        }) = callee
188        {
189            if self
190                .data
191                .vars
192                .get(&ident.to_id())
193                .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY))
194                .is_some()
195            {
196                log_abort!("iife: [x] Recursive?");
197                return;
198            }
199        }
200
201        let params = find_params(callee);
202        if let Some(mut params) = params {
203            let mut vars = HashMap::default();
204            // We check for parameter and argument
205            for (idx, param) in params.iter_mut().enumerate() {
206                match &mut **param {
207                    Pat::Ident(param) => {
208                        if param.sym == "arguments" {
209                            continue;
210                        }
211                        if let Some(usage) = self.data.vars.get(&param.to_id()) {
212                            if usage.flags.contains(VarUsageInfoFlags::REASSIGNED) {
213                                continue;
214                            }
215                        }
216
217                        let arg = e.args.get(idx).map(|v| &v.expr);
218
219                        if let Some(arg) = arg {
220                            match &**arg {
221                                Expr::Lit(Lit::Regex(..)) => continue,
222                                Expr::Lit(Lit::Str(s)) if s.value.len() > 3 => continue,
223                                Expr::Lit(..) => {}
224                                _ => continue,
225                            }
226
227                            let should_be_inlined = self.can_be_inlined_for_iife(arg);
228                            if should_be_inlined {
229                                trace_op!(
230                                    "iife: Trying to inline argument ({}{:?})",
231                                    param.id.sym,
232                                    param.id.ctxt
233                                );
234                                vars.insert(param.to_id(), arg.clone());
235                            } else {
236                                trace_op!(
237                                    "iife: Trying to inline argument ({}{:?}) (not inlinable)",
238                                    param.id.sym,
239                                    param.id.ctxt
240                                );
241                            }
242                        } else {
243                            trace_op!(
244                                "iife: Trying to inline argument ({}{:?}) (undefined)",
245                                param.id.sym,
246                                param.id.ctxt
247                            );
248
249                            vars.insert(param.to_id(), Expr::undefined(param.span()));
250                        }
251                    }
252
253                    Pat::Rest(rest_pat) => {
254                        if let Pat::Ident(param_id) = &*rest_pat.arg {
255                            if let Some(usage) = self.data.vars.get(&param_id.to_id()) {
256                                if usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
257                                    || usage.ref_count != 1
258                                    || !usage.flags.contains(VarUsageInfoFlags::HAS_PROPERTY_ACCESS)
259                                {
260                                    continue;
261                                }
262
263                                if e.args.iter().skip(idx).any(|arg| {
264                                    if arg.spread.is_some() {
265                                        return true;
266                                    }
267
268                                    match &*arg.expr {
269                                        Expr::Lit(Lit::Str(s)) if s.value.len() > 3 => true,
270                                        Expr::Lit(..) => false,
271                                        _ => true,
272                                    }
273                                }) {
274                                    continue;
275                                }
276
277                                vars.insert(
278                                    param_id.to_id(),
279                                    ArrayLit {
280                                        span: param_id.span,
281                                        elems: e
282                                            .args
283                                            .iter()
284                                            .skip(idx)
285                                            .map(|arg| Some(arg.clone()))
286                                            .collect(),
287                                    }
288                                    .into(),
289                                );
290                                param.take();
291                            }
292                        }
293                    }
294                    _ => (),
295                }
296            }
297
298            if vars.is_empty() {
299                log_abort!("vars is empty");
300                return;
301            }
302
303            let ctx = self
304                .ctx
305                .clone()
306                .with(BitCtx::InFnLike, true)
307                .with(BitCtx::TopLevel, false);
308            let mut optimizer = self.with_ctx(ctx);
309            match find_body(callee) {
310                Some(Either::Left(body)) => {
311                    trace_op!("inline: Inlining arguments");
312                    optimizer.inline_vars_in_node(body, vars);
313                }
314                Some(Either::Right(body)) => {
315                    trace_op!("inline: Inlining arguments");
316                    optimizer.inline_vars_in_node(body, vars);
317                }
318                _ => {
319                    unreachable!("find_body and find_params should match")
320                }
321            }
322
323            clean_params(callee);
324        }
325    }
326
327    /// If a parameter is not used, we can ignore return value of the
328    /// corresponding argument.
329    pub(super) fn ignore_unused_args_of_iife(&mut self, e: &mut CallExpr) {
330        if !self.options.unused && !self.options.reduce_vars {
331            return;
332        }
333
334        let callee = match &mut e.callee {
335            Callee::Super(_) | Callee::Import(_) => return,
336            Callee::Expr(e) => &mut **e,
337            #[cfg(swc_ast_unknown)]
338            _ => panic!("unable to access unknown nodes"),
339        };
340
341        match find_body(callee) {
342            Some(body) => match body {
343                Either::Left(body) => {
344                    if contains_arguments(body) {
345                        return;
346                    }
347                }
348                Either::Right(body) => {
349                    if contains_arguments(body) {
350                        return;
351                    }
352                }
353            },
354            None => return,
355        }
356
357        if let Expr::Fn(FnExpr {
358            ident: Some(ident), ..
359        }) = callee
360        {
361            if self
362                .data
363                .vars
364                .get(&ident.to_id())
365                .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY))
366                .is_some()
367            {
368                return;
369            }
370        }
371
372        let mut removed = Vec::new();
373        let params = find_params(callee);
374        if let Some(mut params) = params {
375            // We check for parameter and argument
376            for (idx, param) in params.iter_mut().enumerate() {
377                if let Pat::Ident(param) = &mut **param {
378                    if let Some(usage) = self.data.vars.get(&param.to_id()) {
379                        if usage.ref_count == 0 {
380                            removed.push(idx);
381                        }
382                    }
383                }
384            }
385
386            if removed.is_empty() {
387                log_abort!("`removed` is empty");
388                return;
389            }
390        } else {
391            unreachable!("find_body and find_params should match")
392        }
393
394        for idx in removed {
395            if let Some(arg) = e.args.get_mut(idx) {
396                if arg.spread.is_some() {
397                    break;
398                }
399
400                // Optimize
401                let new = self.ignore_return_value(&mut arg.expr);
402
403                if let Some(new) = new {
404                    arg.expr = Box::new(new);
405                } else {
406                    // Use `0` if it's removed.
407                    arg.expr = Number {
408                        span: arg.expr.span(),
409                        value: 0.0,
410                        raw: None,
411                    }
412                    .into();
413                }
414            } else {
415                break;
416            }
417        }
418    }
419
420    #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip_all))]
421    pub(super) fn inline_vars_in_node<N>(&mut self, n: &mut N, mut vars: FxHashMap<Id, Box<Expr>>)
422    where
423        N: for<'aa> VisitMutWith<NormalMultiReplacer<'aa>>,
424    {
425        trace_op!("inline: inline_vars_in_node");
426
427        let mut v = NormalMultiReplacer::new(&mut vars, false);
428        n.visit_mut_with(&mut v);
429        self.changed |= v.changed;
430    }
431
432    fn may_invoke_iife(&self, call: &mut CallExpr) -> bool {
433        if self.options.inline == 0
434            && !(self.options.reduce_vars && self.options.reduce_fns && self.options.evaluate)
435        {
436            if !call.callee.span().is_dummy() {
437                log_abort!("skip");
438                return false;
439            }
440        }
441
442        trace_op!("iife: Checking noinline");
443
444        if self.has_noinline(call.ctxt) {
445            log_abort!("iife: Has no inline mark");
446            return false;
447        }
448
449        let callee = match &call.callee {
450            Callee::Super(_) | Callee::Import(_) => return false,
451            Callee::Expr(e) => &**e,
452            #[cfg(swc_ast_unknown)]
453            _ => panic!("unable to access unknown nodes"),
454        };
455
456        if self.ctx.bit_ctx.contains(BitCtx::DontInvokeIife) {
457            log_abort!("iife: Inline is prevented");
458            return false;
459        }
460
461        for arg in &call.args {
462            if arg.spread.is_some() {
463                log_abort!("iife: Found spread argument");
464                return false;
465            }
466        }
467
468        trace_op!("iife: Checking callee");
469
470        match callee {
471            Expr::Arrow(f) => {
472                if f.is_async {
473                    log_abort!("iife: Cannot inline async fn");
474                    return false;
475                }
476
477                if f.is_generator {
478                    log_abort!("iife: Cannot inline generator");
479                    return false;
480                }
481
482                if self.ctx.bit_ctx.contains(BitCtx::InParam) && !f.params.is_empty() {
483                    log_abort!("iife: We don't invoke IIFE with params in function params");
484                    return false;
485                }
486
487                if f.params.iter().any(|param| !param.is_ident()) {
488                    return false;
489                }
490            }
491
492            Expr::Fn(f) => {
493                if f.function.is_async {
494                    log_abort!("iife: [x] Cannot inline async fn");
495                    return false;
496                }
497
498                if f.function.is_generator {
499                    log_abort!("iife: [x] Cannot inline generator");
500                    return false;
501                }
502
503                if self.ctx.bit_ctx.contains(BitCtx::InParam) && !f.function.params.is_empty() {
504                    log_abort!("iife: We don't invoke IIFE with params in function params");
505                    return false;
506                }
507
508                // Abort if a parameter is complex
509                if f.function.params.iter().any(|param| !param.pat.is_ident()) {
510                    return false;
511                }
512
513                trace_op!("iife: Checking recursiveness");
514
515                if let Some(i) = &f.ident {
516                    if self
517                        .data
518                        .vars
519                        .get(&i.to_id())
520                        .filter(|usage| usage.flags.contains(VarUsageInfoFlags::USED_RECURSIVELY))
521                        .is_some()
522                    {
523                        log_abort!("iife: [x] Recursive?");
524                        return false;
525                    }
526                }
527
528                let body = f.function.body.as_ref().unwrap();
529                if contains_this_expr(body) || contains_arguments(body) {
530                    return false;
531                }
532            }
533
534            _ => return false,
535        };
536
537        true
538    }
539
540    /// Fully inlines iife.
541    ///
542    /// # Example
543    ///
544    /// ## Input
545    ///
546    /// ```ts
547    /// (function () {
548    ///     return {};
549    /// })().x = 10;
550    /// ```
551    ///
552    /// ## Output
553    ///
554    /// ```ts
555    /// ({
556    /// }).x = 10;
557    /// ```
558    pub(super) fn invoke_iife(&mut self, e: &mut Expr) {
559        trace_op!("iife: invoke_iife");
560
561        let call = match e {
562            Expr::Call(v) => v,
563            _ => return,
564        };
565
566        if !self.may_invoke_iife(call) {
567            return;
568        }
569
570        let callee = match &mut call.callee {
571            Callee::Super(_) | Callee::Import(_) => return,
572            Callee::Expr(e) => &mut **e,
573            #[cfg(swc_ast_unknown)]
574            _ => panic!("unable to access unknown nodes"),
575        };
576
577        match callee {
578            Expr::Arrow(f) => {
579                let param_ids = f.params.iter().map(|p| &p.as_ident().unwrap().id);
580
581                match &mut *f.body {
582                    BlockStmtOrExpr::BlockStmt(body) => {
583                        let new =
584                            self.inline_fn_like(param_ids, f.params.len(), body, &mut call.args);
585                        if let Some(new) = new {
586                            self.changed = true;
587                            report_change!("inline: Inlining a function call (arrow)");
588
589                            *e = new;
590                        }
591                    }
592                    BlockStmtOrExpr::Expr(body) => {
593                        if !self.can_extract_param(param_ids.clone()) {
594                            return;
595                        }
596
597                        if let Expr::Lit(Lit::Num(..)) = &**body {
598                            if self.ctx.bit_ctx.contains(BitCtx::InObjOfNonComputedMember) {
599                                return;
600                            }
601                        }
602
603                        self.changed = true;
604                        report_change!("inline: Inlining a function call (arrow)");
605
606                        let mut exprs = vec![Box::new(make_number(DUMMY_SP, 0.0))];
607
608                        let vars = self.inline_fn_param(
609                            param_ids,
610                            f.params.len(),
611                            &mut call.args,
612                            &mut exprs,
613                        );
614
615                        if !vars.is_empty() {
616                            self.prepend_stmts.push(
617                                VarDecl {
618                                    span: DUMMY_SP,
619                                    kind: VarDeclKind::Let,
620                                    declare: Default::default(),
621                                    decls: vars,
622                                    ..Default::default()
623                                }
624                                .into(),
625                            )
626                        }
627
628                        if call.args.len() > f.params.len() {
629                            for arg in &mut call.args[f.params.len()..] {
630                                exprs.push(arg.expr.take());
631                            }
632                        }
633                        if self.vars.inline_with_multi_replacer(body) {
634                            self.changed = true;
635                        }
636                        exprs.push(body.take());
637
638                        report_change!("inline: Inlining a call to an arrow function");
639                        *e = *Expr::from_exprs(exprs);
640                        e.visit_mut_with(self);
641                    }
642                    #[cfg(swc_ast_unknown)]
643                    _ => panic!("unable to access unknown nodes"),
644                }
645            }
646            Expr::Fn(f) => {
647                trace_op!("iife: Empty function");
648
649                let body = f.function.body.as_mut().unwrap();
650                if body.stmts.is_empty() && call.args.is_empty() {
651                    self.changed = true;
652                    report_change!("iife: Inlining an empty function call as `undefined`");
653                    *e = *Expr::undefined(f.function.span);
654                    return;
655                }
656
657                let param_ids = f
658                    .function
659                    .params
660                    .iter()
661                    .map(|p| &p.pat.as_ident().unwrap().id);
662
663                #[cfg(feature = "debug")]
664                let param_ids_for_debug = param_ids.clone();
665
666                let new =
667                    self.inline_fn_like(param_ids, f.function.params.len(), body, &mut call.args);
668                if let Some(new) = new {
669                    self.changed = true;
670                    report_change!(
671                        "inline: Inlining a function call (params = {:?})",
672                        param_ids_for_debug
673                    );
674
675                    dump_change_detail!("{}", dump(&new, false));
676
677                    *e = new;
678                }
679
680                //
681            }
682            _ => {}
683        }
684    }
685
686    pub(super) fn invoke_iife_stmt(&mut self, e: &mut Expr, is_return: bool) -> Option<BlockStmt> {
687        trace_op!("iife: invoke_iife");
688
689        let call = match e {
690            Expr::Call(v) => v,
691            Expr::Unary(UnaryExpr { arg, .. }) if !is_return => {
692                if let Expr::Call(v) = &mut **arg {
693                    v
694                } else {
695                    return None;
696                }
697            }
698            _ => return None,
699        };
700
701        if !self.may_invoke_iife(call) {
702            return None;
703        }
704
705        let callee = match &mut call.callee {
706            Callee::Super(_) | Callee::Import(_) => return None,
707            Callee::Expr(e) => &mut **e,
708            #[cfg(swc_ast_unknown)]
709            _ => panic!("unable to access unknown nodes"),
710        };
711
712        match callee {
713            Expr::Arrow(f) => {
714                match &mut *f.body {
715                    // it's very likely to be processed in invoke_iife
716                    BlockStmtOrExpr::Expr(_) => None,
717                    BlockStmtOrExpr::BlockStmt(block_stmt) => {
718                        let param_ids = f.params.iter().map(|p| &p.as_ident().unwrap().id);
719                        self.inline_fn_like_stmt(
720                            param_ids,
721                            f.params.len(),
722                            block_stmt,
723                            &mut call.args,
724                            is_return,
725                            call.span,
726                        )
727                    }
728                    #[cfg(swc_ast_unknown)]
729                    _ => panic!("unable to access unknown nodes"),
730                }
731            }
732            Expr::Fn(f) => {
733                let body = f.function.body.as_mut().unwrap();
734                let param_ids = f
735                    .function
736                    .params
737                    .iter()
738                    .map(|p| &p.pat.as_ident().unwrap().id);
739                self.inline_fn_like_stmt(
740                    param_ids,
741                    f.function.params.len(),
742                    body,
743                    &mut call.args,
744                    is_return,
745                    call.span,
746                )
747            }
748            _ => None,
749        }
750    }
751
752    fn is_return_arg_simple_enough_for_iife_eval(&self, e: &Expr) -> bool {
753        match e {
754            Expr::Lit(..) | Expr::Ident(..) => true,
755
756            Expr::Bin(BinExpr { op, .. }) if op.may_short_circuit() => false,
757
758            Expr::Bin(e) => {
759                self.is_return_arg_simple_enough_for_iife_eval(&e.left)
760                    && self.is_return_arg_simple_enough_for_iife_eval(&e.right)
761            }
762
763            Expr::Cond(e) => {
764                self.is_return_arg_simple_enough_for_iife_eval(&e.test)
765                    && self.is_return_arg_simple_enough_for_iife_eval(&e.cons)
766                    && self.is_return_arg_simple_enough_for_iife_eval(&e.alt)
767            }
768
769            _ => false,
770        }
771    }
772
773    fn can_extract_param<'a>(&self, param_ids: impl Iterator<Item = &'a Ident> + Clone) -> bool {
774        // Don't create top-level variables.
775        if !self.may_add_ident() {
776            for pid in param_ids.clone() {
777                if let Some(usage) = self.data.vars.get(&pid.to_id()) {
778                    if usage.ref_count > 1
779                        || usage.assign_count > 0
780                        || usage.flags.contains(VarUsageInfoFlags::INLINE_PREVENTED)
781                    {
782                        log_abort!("iife: [x] Cannot inline because of usage of `{}`", pid);
783                        return false;
784                    }
785                }
786            }
787        }
788
789        for pid in param_ids {
790            if self.ident_reserved(&pid.sym) {
791                log_abort!(
792                    "iife: [x] Cannot inline because of reservation of `{}`",
793                    pid
794                );
795                return false;
796            }
797        }
798
799        true
800    }
801
802    fn can_inline_fn_stmt(&self, stmt: &Stmt, for_stmt: bool) -> bool {
803        match stmt {
804            Stmt::Decl(Decl::Var(var)) => {
805                for decl in &var.decls {
806                    for id in find_pat_ids::<_, Id>(&decl.name) {
807                        if self.ident_reserved(&id.0) {
808                            log_abort!("iife: [x] Cannot inline because reservation of `{}`", id.0);
809                            return false;
810                        }
811                    }
812                }
813
814                if matches!(
815                    &**var,
816                    VarDecl {
817                        kind: VarDeclKind::Var | VarDeclKind::Let,
818                        ..
819                    }
820                ) {
821                    for decl in &var.decls {
822                        match &decl.name {
823                            Pat::Ident(id) if id.sym == "arguments" => return false,
824                            Pat::Ident(id) => {
825                                if self.vars.has_pending_inline_for(&id.to_id()) {
826                                    log_abort!(
827                                        "iife: [x] Cannot inline because pending inline of `{}`",
828                                        id.id
829                                    );
830                                    return false;
831                                }
832                            }
833
834                            _ => return false,
835                        }
836                    }
837
838                    if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) {
839                        return false;
840                    }
841
842                    true
843                } else {
844                    for_stmt
845                }
846            }
847
848            Stmt::Expr(_) => true,
849
850            Stmt::Return(ReturnStmt { arg, .. }) => match arg.as_deref() {
851                Some(Expr::Lit(Lit::Num(..))) => {
852                    for_stmt || !self.ctx.bit_ctx.contains(BitCtx::InObjOfNonComputedMember)
853                }
854                _ => true,
855            },
856            Stmt::Block(b) => b
857                .stmts
858                .iter()
859                .all(|stmt| self.can_inline_fn_stmt(stmt, for_stmt)),
860            Stmt::If(i) if for_stmt => {
861                self.can_inline_fn_stmt(&i.cons, for_stmt)
862                    && i.alt
863                        .as_ref()
864                        .map(|a| self.can_inline_fn_stmt(a, for_stmt))
865                        .unwrap_or(true)
866            }
867            Stmt::Switch(s) if for_stmt => s
868                .cases
869                .iter()
870                .flat_map(|case| case.cons.iter())
871                .all(|stmt| self.can_inline_fn_stmt(stmt, for_stmt)),
872            _ => for_stmt,
873        }
874    }
875
876    fn can_inline_fn_like<'a>(
877        &self,
878        param_ids: impl Iterator<Item = &'a Ident> + Clone,
879        params_len: usize,
880        body: &BlockStmt,
881        for_stmt: bool,
882    ) -> bool {
883        trace_op!("can_inline_fn_like");
884
885        if body.stmts.len() == 1 {
886            if let Stmt::Return(ReturnStmt { arg: Some(arg), .. }) = &body.stmts[0] {
887                if self.is_return_arg_simple_enough_for_iife_eval(arg) {
888                    return true;
889                }
890            }
891        }
892
893        if !self.can_extract_param(param_ids.clone()) {
894            return false;
895        }
896
897        // Abort on eval.
898        // See https://github.com/swc-project/swc/pull/6478
899        //
900        // We completely abort on eval, because we cannot know whether a variable in
901        // upper scope will be afftected by eval.
902        // https://github.com/swc-project/swc/issues/6628
903        if self.data.top.contains(ScopeData::HAS_EVAL_CALL) {
904            log_abort!("iife: [x] Aborting because of eval");
905            return false;
906        }
907
908        if !self.may_add_ident() {
909            let has_decl = if for_stmt {
910                // we check it later
911                false
912            } else {
913                body.stmts.iter().any(|stmt| matches!(stmt, Stmt::Decl(..)))
914            };
915            if has_decl {
916                log_abort!("iife: [x] Found decl");
917                return false;
918            }
919        }
920
921        if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) {
922            if params_len != 0 {
923                let captured = idents_captured_by(body);
924
925                for param in param_ids {
926                    if captured.contains(&param.to_id()) {
927                        log_abort!(
928                            "iife: [x] Cannot inline because of the capture of `{}`",
929                            param
930                        );
931                        return false;
932                    }
933                }
934            }
935        }
936
937        if let Some(stmt) = body.stmts.first() {
938            if stmt.is_use_strict() {
939                return false;
940            }
941        }
942
943        if !body
944            .stmts
945            .iter()
946            .all(|stmt| self.can_inline_fn_stmt(stmt, for_stmt))
947        {
948            return false;
949        }
950
951        true
952    }
953
954    fn inline_fn_param<'a>(
955        &mut self,
956        params: impl Iterator<Item = &'a Ident>,
957        params_len: usize,
958        args: &mut [ExprOrSpread],
959        exprs: &mut Vec<Box<Expr>>,
960    ) -> Vec<VarDeclarator> {
961        let mut vars = Vec::with_capacity(params_len);
962
963        for (idx, param) in params.enumerate() {
964            let arg = args.get_mut(idx).map(|arg| arg.expr.take());
965
966            let no_arg = arg.is_none();
967
968            if let Some(arg) = arg {
969                if let Some(usage) = self.data.vars.get_mut(&param.to_id()) {
970                    if usage.ref_count == 1
971                        && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
972                        && usage.property_mutation_count == 0
973                        && matches!(
974                            &*arg,
975                            Expr::Lit(
976                                Lit::Num(..) | Lit::Str(..) | Lit::Bool(..) | Lit::BigInt(..)
977                            )
978                        )
979                    {
980                        // We don't need to create a variable in this case
981                        self.vars.vars_for_inlining.insert(param.to_id(), arg);
982                        continue;
983                    }
984
985                    usage.ref_count += 1;
986                }
987
988                exprs.push(
989                    AssignExpr {
990                        span: DUMMY_SP,
991                        op: op!("="),
992                        left: (*param).clone().into(),
993                        right: arg,
994                    }
995                    .into(),
996                )
997            };
998
999            vars.push(VarDeclarator {
1000                span: DUMMY_SP,
1001                name: (*param).clone().into(),
1002                init: if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) && no_arg {
1003                    Some(Expr::undefined(DUMMY_SP))
1004                } else {
1005                    None
1006                },
1007                definite: Default::default(),
1008            });
1009        }
1010
1011        vars
1012    }
1013
1014    fn inline_fn_param_stmt<'a>(
1015        &mut self,
1016        params: impl Iterator<Item = &'a Ident>,
1017        params_len: usize,
1018        args: &mut [ExprOrSpread],
1019    ) -> Vec<VarDeclarator> {
1020        let mut vars = Vec::with_capacity(params_len);
1021
1022        for (idx, param) in params.enumerate() {
1023            let mut arg = args.get_mut(idx).map(|arg| arg.expr.take());
1024
1025            if let Some(arg) = &mut arg {
1026                if let Some(usage) = self.data.vars.get_mut(&param.to_id()) {
1027                    if usage.ref_count == 1
1028                        && !usage.flags.contains(VarUsageInfoFlags::REASSIGNED)
1029                        && usage.property_mutation_count == 0
1030                        && matches!(
1031                            &**arg,
1032                            Expr::Lit(
1033                                Lit::Num(..) | Lit::Str(..) | Lit::Bool(..) | Lit::BigInt(..)
1034                            )
1035                        )
1036                    {
1037                        // We don't need to create a variable in this case
1038                        self.vars
1039                            .vars_for_inlining
1040                            .insert(param.to_id(), arg.take());
1041                        continue;
1042                    }
1043
1044                    usage.ref_count += 1;
1045                }
1046            };
1047
1048            vars.push(VarDeclarator {
1049                span: DUMMY_SP,
1050                name: param.clone().into(),
1051                init: if self.ctx.bit_ctx.contains(BitCtx::ExecutedMultipleTime) && arg.is_none() {
1052                    Some(Expr::undefined(DUMMY_SP))
1053                } else {
1054                    arg
1055                },
1056                definite: Default::default(),
1057            });
1058        }
1059
1060        vars
1061    }
1062
1063    fn inline_into_expr(&mut self, stmts: Vec<Stmt>, exprs: &mut Vec<Box<Expr>>) -> Option<Expr> {
1064        for mut stmt in stmts {
1065            match stmt {
1066                Stmt::Decl(Decl::Var(ref mut var)) => {
1067                    for decl in &mut var.decls {
1068                        if decl.init.is_some() {
1069                            let ids = find_pat_ids(decl);
1070
1071                            for id in ids {
1072                                if let Some(usage) = self.data.vars.get_mut(&id) {
1073                                    // as we turn var declaration into assignment
1074                                    // we need to maintain correct var usage
1075                                    usage.ref_count += 1;
1076                                }
1077                            }
1078
1079                            exprs.push(
1080                                AssignExpr {
1081                                    span: DUMMY_SP,
1082                                    op: op!("="),
1083                                    left: decl.name.clone().try_into().unwrap(),
1084                                    right: decl.init.take().unwrap(),
1085                                }
1086                                .into(),
1087                            )
1088                        }
1089                    }
1090
1091                    self.prepend_stmts.push(stmt);
1092                }
1093
1094                Stmt::Expr(stmt) => {
1095                    exprs.push(stmt.expr);
1096                }
1097
1098                Stmt::Block(stmt) => {
1099                    if let Some(e) = self.inline_into_expr(stmt.stmts, exprs) {
1100                        return Some(e);
1101                    }
1102                }
1103
1104                Stmt::Return(stmt) => {
1105                    let span = stmt.span;
1106                    let val = *stmt.arg.unwrap_or_else(|| Expr::undefined(span));
1107                    exprs.push(Box::new(val));
1108
1109                    let mut e = SeqExpr {
1110                        span: DUMMY_SP,
1111                        exprs: exprs.take(),
1112                    };
1113                    self.merge_sequences_in_seq_expr(&mut e);
1114
1115                    let mut e = e.into();
1116                    self.normalize_expr(&mut e);
1117                    return Some(e);
1118                }
1119                _ => {}
1120            }
1121        }
1122
1123        None
1124    }
1125
1126    fn inline_fn_like<'a>(
1127        &mut self,
1128        params: impl Iterator<Item = &'a Ident> + Clone,
1129        params_len: usize,
1130        body: &mut BlockStmt,
1131        args: &mut [ExprOrSpread],
1132    ) -> Option<Expr> {
1133        if !self.can_inline_fn_like(params.clone(), params_len, &*body, false) {
1134            return None;
1135        }
1136
1137        if self.vars.inline_with_multi_replacer(body) {
1138            self.changed = true;
1139        }
1140
1141        let mut exprs = Vec::new();
1142        let vars = self.inline_fn_param(params, params_len, args, &mut exprs);
1143
1144        if args.len() > params_len {
1145            for arg in &mut args[params_len..] {
1146                exprs.push(arg.expr.take());
1147            }
1148        }
1149
1150        if !vars.is_empty() {
1151            trace_op!("iife: Creating variables: {:?}", vars);
1152
1153            self.prepend_stmts.push(
1154                VarDecl {
1155                    span: DUMMY_SP,
1156                    kind: VarDeclKind::Var,
1157                    declare: Default::default(),
1158                    decls: vars,
1159                    ..Default::default()
1160                }
1161                .into(),
1162            );
1163        }
1164
1165        if let Some(e) = self.inline_into_expr(body.stmts.take(), &mut exprs) {
1166            return Some(e);
1167        }
1168
1169        if let Some(last) = exprs.last_mut() {
1170            *last = UnaryExpr {
1171                span: DUMMY_SP,
1172                op: op!("void"),
1173                arg: last.take(),
1174            }
1175            .into();
1176        } else {
1177            return Some(*Expr::undefined(body.span));
1178        }
1179
1180        let mut e = SeqExpr {
1181            span: DUMMY_SP,
1182            exprs,
1183        };
1184        self.merge_sequences_in_seq_expr(&mut e);
1185
1186        let mut e = e.into();
1187        self.normalize_expr(&mut e);
1188        Some(e)
1189    }
1190
1191    fn inline_fn_like_stmt<'a>(
1192        &mut self,
1193        params: impl Iterator<Item = &'a Ident> + Clone + std::fmt::Debug,
1194        params_len: usize,
1195        body: &mut BlockStmt,
1196        args: &mut [ExprOrSpread],
1197        is_return: bool,
1198        span: Span,
1199    ) -> Option<BlockStmt> {
1200        if !self.can_inline_fn_like(params.clone(), params_len, body, true) {
1201            return None;
1202        }
1203
1204        let mut decl = DeclVisitor { count: 0 };
1205
1206        body.visit_with(&mut decl);
1207
1208        if !self.may_add_ident() && decl.count > 0 {
1209            return None;
1210        }
1211
1212        if decl.count
1213            + (params_len.saturating_sub(
1214                args.iter()
1215                    .filter(|a| {
1216                        a.expr.is_ident() || a.expr.as_lit().map(|l| !l.is_regex()).unwrap_or(false)
1217                    })
1218                    .count(),
1219            )) * 2
1220            > 4
1221        {
1222            return None;
1223        }
1224
1225        let mut has_return = ReturnVisitor { found: false };
1226
1227        if !is_return {
1228            body.visit_with(&mut has_return);
1229
1230            if has_return.found {
1231                return None;
1232            }
1233        }
1234
1235        self.changed = true;
1236        report_change!("inline: Inlining a function call (params = {params:?})");
1237
1238        let mut stmts = Vec::with_capacity(body.stmts.len() + 2);
1239
1240        let param_decl = self.inline_fn_param_stmt(params, params_len, args);
1241
1242        if !param_decl.is_empty() {
1243            let param_decl = Stmt::Decl(Decl::Var(Box::new(VarDecl {
1244                span: DUMMY_SP,
1245                ctxt: SyntaxContext::empty(),
1246                kind: VarDeclKind::Var,
1247                declare: false,
1248                decls: param_decl,
1249            })));
1250
1251            stmts.push(param_decl);
1252        }
1253
1254        if args.len() > params_len {
1255            let mut exprs = Vec::new();
1256            for arg in args[params_len..].iter_mut() {
1257                exprs.push(arg.expr.take())
1258            }
1259
1260            let expr = Stmt::Expr(ExprStmt {
1261                span: DUMMY_SP,
1262                expr: Box::new(Expr::Seq(SeqExpr {
1263                    span: DUMMY_SP,
1264                    exprs,
1265                })),
1266            });
1267
1268            stmts.push(expr);
1269        }
1270
1271        stmts.extend(body.stmts.take());
1272
1273        Some(BlockStmt {
1274            span,
1275            ctxt: SyntaxContext::empty().apply_mark(self.marks.fake_block),
1276            stmts,
1277        })
1278    }
1279
1280    fn can_be_inlined_for_iife(&self, arg: &Expr) -> bool {
1281        match arg {
1282            Expr::Lit(..) => true,
1283
1284            Expr::Unary(UnaryExpr {
1285                op: op!("void"),
1286                arg,
1287                ..
1288            })
1289            | Expr::Unary(UnaryExpr {
1290                op: op!("!"), arg, ..
1291            }) => self.can_be_inlined_for_iife(arg),
1292
1293            Expr::Ident(..) => true,
1294
1295            Expr::Member(MemberExpr { obj, prop, .. }) if !prop.is_computed() => {
1296                self.can_be_inlined_for_iife(obj)
1297            }
1298
1299            Expr::Bin(BinExpr {
1300                op, left, right, ..
1301            }) => match op {
1302                op!(bin, "+") | op!("*") => {
1303                    self.can_be_inlined_for_iife(left) && self.can_be_inlined_for_iife(right)
1304                }
1305                _ => false,
1306            },
1307
1308            Expr::Object(ObjectLit { props, .. }) => {
1309                for prop in props {
1310                    match prop {
1311                        PropOrSpread::Spread(_) => return false,
1312                        PropOrSpread::Prop(p) => match &**p {
1313                            Prop::Shorthand(_) => {}
1314                            Prop::KeyValue(kv) => {
1315                                if let PropName::Computed(key) = &kv.key {
1316                                    if !self.can_be_inlined_for_iife(&key.expr) {
1317                                        return false;
1318                                    }
1319                                }
1320
1321                                if !self.can_be_inlined_for_iife(&kv.value) {
1322                                    return false;
1323                                }
1324                            }
1325                            Prop::Assign(p) => {
1326                                if !self.can_be_inlined_for_iife(&p.value) {
1327                                    return false;
1328                                }
1329                            }
1330                            _ => return false,
1331                        },
1332                        #[cfg(swc_ast_unknown)]
1333                        _ => panic!("unable to access unknown nodes"),
1334                    }
1335                }
1336
1337                true
1338            }
1339
1340            Expr::Arrow(ArrowExpr {
1341                params,
1342                body,
1343                is_async: false,
1344                is_generator: false,
1345                ..
1346            }) if body.is_expr() => {
1347                params.iter().all(|p| p.is_ident())
1348                    && self.can_be_inlined_for_iife(body.as_expr().unwrap())
1349            }
1350
1351            _ => false,
1352        }
1353    }
1354}
1355
1356fn find_scope<'a>(data: &'a ProgramData, callee: &Expr) -> Option<&'a ScopeData> {
1357    match callee {
1358        Expr::Arrow(callee) => data.scopes.get(&callee.ctxt),
1359        Expr::Fn(callee) => data.scopes.get(&callee.function.ctxt),
1360        _ => None,
1361    }
1362}
1363
1364fn find_params(callee: &mut Expr) -> Option<Vec<&mut Pat>> {
1365    match callee {
1366        Expr::Arrow(callee) => Some(callee.params.iter_mut().collect()),
1367        Expr::Fn(callee) => Some(
1368            callee
1369                .function
1370                .params
1371                .iter_mut()
1372                .map(|param| &mut param.pat)
1373                .collect(),
1374        ),
1375        _ => None,
1376    }
1377}
1378fn find_body(callee: &mut Expr) -> Option<Either<&mut BlockStmt, &mut Expr>> {
1379    match callee {
1380        Expr::Arrow(e) => match &mut *e.body {
1381            BlockStmtOrExpr::BlockStmt(b) => Some(Either::Left(b)),
1382            BlockStmtOrExpr::Expr(b) => Some(Either::Right(&mut **b)),
1383            #[cfg(swc_ast_unknown)]
1384            _ => panic!("unable to access unknown nodes"),
1385        },
1386        Expr::Fn(e) => Some(Either::Left(e.function.body.as_mut().unwrap())),
1387        _ => None,
1388    }
1389}
1390
1391pub struct ReturnVisitor {
1392    found: bool,
1393}
1394
1395impl Visit for ReturnVisitor {
1396    noop_visit_type!(fail);
1397
1398    /// Don't recurse into constructor
1399    fn visit_constructor(&mut self, _: &Constructor) {}
1400
1401    /// Don't recurse into fn
1402    fn visit_fn_decl(&mut self, _: &FnDecl) {}
1403
1404    /// Don't recurse into fn
1405    fn visit_fn_expr(&mut self, _: &FnExpr) {}
1406
1407    /// Don't recurse into fn
1408    fn visit_function(&mut self, _: &Function) {}
1409
1410    /// Don't recurse into fn
1411    fn visit_getter_prop(&mut self, n: &GetterProp) {
1412        n.key.visit_with(self);
1413    }
1414
1415    /// Don't recurse into fn
1416    fn visit_method_prop(&mut self, n: &MethodProp) {
1417        n.key.visit_with(self);
1418        n.function.visit_with(self);
1419    }
1420
1421    /// Don't recurse into fn
1422    fn visit_setter_prop(&mut self, n: &SetterProp) {
1423        n.key.visit_with(self);
1424        n.param.visit_with(self);
1425    }
1426
1427    fn visit_expr(&mut self, _: &Expr) {}
1428
1429    fn visit_return_stmt(&mut self, _: &ReturnStmt) {
1430        self.found = true;
1431    }
1432}
1433
1434pub struct DeclVisitor {
1435    count: usize,
1436}
1437
1438impl Visit for DeclVisitor {
1439    noop_visit_type!(fail);
1440
1441    /// Don't recurse into constructor
1442    fn visit_constructor(&mut self, _: &Constructor) {}
1443
1444    /// Don't recurse into fn
1445    fn visit_fn_decl(&mut self, _: &FnDecl) {}
1446
1447    /// Don't recurse into fn
1448    fn visit_fn_expr(&mut self, _: &FnExpr) {}
1449
1450    /// Don't recurse into fn
1451    fn visit_function(&mut self, _: &Function) {}
1452
1453    /// Don't recurse into fn
1454    fn visit_getter_prop(&mut self, n: &GetterProp) {
1455        n.key.visit_with(self);
1456    }
1457
1458    /// Don't recurse into fn
1459    fn visit_method_prop(&mut self, n: &MethodProp) {
1460        n.key.visit_with(self);
1461        n.function.visit_with(self);
1462    }
1463
1464    /// Don't recurse into fn
1465    fn visit_setter_prop(&mut self, n: &SetterProp) {
1466        n.key.visit_with(self);
1467        n.param.visit_with(self);
1468    }
1469
1470    fn visit_expr(&mut self, _: &Expr) {}
1471
1472    fn visit_decl(&mut self, d: &Decl) {
1473        self.count += match d {
1474            Decl::Class(_) | Decl::Fn(_) => 1,
1475            Decl::Var(var_decl) => var_decl.decls.len(),
1476            Decl::Using(using_decl) => using_decl.decls.len(),
1477            Decl::TsInterface(_) | Decl::TsTypeAlias(_) | Decl::TsEnum(_) | Decl::TsModule(_) => 0,
1478            #[cfg(swc_ast_unknown)]
1479            _ => panic!("unable to access unknown nodes"),
1480        };
1481    }
1482
1483    fn visit_var_decl_or_expr(&mut self, node: &VarDeclOrExpr) {
1484        if let VarDeclOrExpr::VarDecl(v) = node {
1485            self.count += v.decls.len()
1486        }
1487    }
1488}
1489
1490/// Enhanced IIFE invocation for sequence expressions
1491impl Optimizer<'_> {
1492    /// Specifically handles IIFE invocation for arrow functions within sequence
1493    /// expressions. This addresses the issue where arrow function IIFEs in
1494    /// sequences aren't optimized as aggressively as standalone IIFEs.
1495    #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip_all))]
1496    pub(super) fn invoke_iife_in_seq_expr(&mut self, seq: &mut SeqExpr) {
1497        trace_op!("iife: invoke_iife_in_seq_expr");
1498
1499        // Process each expression in the sequence to look for IIFE opportunities
1500        for expr in &mut seq.exprs {
1501            // Apply IIFE optimization to each expression in the sequence
1502            self.invoke_iife(expr);
1503        }
1504
1505        // Additional optimization: look for specific patterns in sequences
1506        // where arrow function IIFEs can be further optimized
1507        self.optimize_arrow_iife_patterns_in_seq(seq);
1508    }
1509
1510    /// Optimizes specific patterns of arrow function IIFEs in sequence
1511    /// expressions
1512    fn optimize_arrow_iife_patterns_in_seq(&mut self, seq: &mut SeqExpr) {
1513        let mut changed = false;
1514
1515        for i in 0..seq.exprs.len() {
1516            // First, check if this expression can be optimized without borrowing conflicts
1517            let can_optimize = if let Expr::Call(call) = &*seq.exprs[i] {
1518                if let Callee::Expr(callee) = &call.callee {
1519                    if let Expr::Arrow(arrow) = &**callee {
1520                        // For expression-style arrow functions in sequences,
1521                        // we can be more aggressive with optimization
1522                        if let BlockStmtOrExpr::Expr(_body) = &*arrow.body {
1523                            self.can_optimize_arrow_iife_in_seq(arrow, call)
1524                        } else {
1525                            false
1526                        }
1527                    } else {
1528                        false
1529                    }
1530                } else {
1531                    false
1532                }
1533            } else {
1534                false
1535            };
1536
1537            // If we can optimize, perform the mutation
1538            if can_optimize {
1539                // Extract a fresh mutable reference to avoid borrowing conflicts
1540                // This block ensures all previous borrows are dropped before we create new ones
1541                let expr = &mut seq.exprs[i];
1542                if let Expr::Call(call) = &mut **expr {
1543                    if let Callee::Expr(callee) = &call.callee {
1544                        if let Expr::Arrow(arrow) = &**callee {
1545                            if let BlockStmtOrExpr::Expr(_body) = &*arrow.body {
1546                                let new_expr = self.optimize_single_arrow_iife_in_seq(arrow, call);
1547
1548                                if let Some(new_expr) = new_expr {
1549                                    **expr = new_expr;
1550                                    changed = true;
1551                                }
1552                            }
1553                        }
1554                    }
1555                }
1556            }
1557        }
1558
1559        if changed {
1560            self.changed = true;
1561            report_change!(
1562                "iife: Enhanced optimization of arrow functions in sequence expressions"
1563            );
1564        }
1565    }
1566
1567    /// Checks if an arrow function IIFE in a sequence can be optimized
1568    fn can_optimize_arrow_iife_in_seq(&self, arrow: &ArrowExpr, call: &CallExpr) -> bool {
1569        // Only optimize simple arrow functions with expression bodies
1570        if arrow.is_async || arrow.is_generator {
1571            return false;
1572        }
1573
1574        // Check if parameters are simple identifiers
1575        if !arrow.params.iter().all(|p| p.is_ident()) {
1576            return false;
1577        }
1578
1579        // Ensure no spread arguments
1580        if call.args.iter().any(|arg| arg.spread.is_some()) {
1581            return false;
1582        }
1583
1584        // Check if the arrow function body is simple enough for sequence optimization
1585        if let BlockStmtOrExpr::Expr(body) = &*arrow.body {
1586            self.is_simple_expr_for_seq_optimization(body)
1587        } else {
1588            false
1589        }
1590    }
1591
1592    /// Checks if an expression is simple enough to be optimized in a sequence
1593    fn is_simple_expr_for_seq_optimization(&self, expr: &Expr) -> bool {
1594        match expr {
1595            Expr::Lit(..) | Expr::Ident(..) => true,
1596            Expr::Bin(bin) if !bin.op.may_short_circuit() => {
1597                self.is_simple_expr_for_seq_optimization(&bin.left)
1598                    && self.is_simple_expr_for_seq_optimization(&bin.right)
1599            }
1600            Expr::Unary(unary) => self.is_simple_expr_for_seq_optimization(&unary.arg),
1601            Expr::Member(member) if !member.prop.is_computed() => {
1602                self.is_simple_expr_for_seq_optimization(&member.obj)
1603            }
1604            _ => false,
1605        }
1606    }
1607
1608    /// Optimizes a single arrow IIFE in a sequence expression
1609    fn optimize_single_arrow_iife_in_seq(
1610        &mut self,
1611        arrow: &ArrowExpr,
1612        call: &CallExpr,
1613    ) -> Option<Expr> {
1614        if let BlockStmtOrExpr::Expr(body) = &*arrow.body {
1615            // For simple arrow functions with no parameters in sequences,
1616            // we can directly replace the IIFE with its body
1617            if arrow.params.is_empty() && call.args.is_empty() {
1618                return Some((**body).clone());
1619            }
1620
1621            // For arrow functions with simple parameters, inline them
1622            if arrow.params.len() == call.args.len() {
1623                let can_inline = arrow.params.iter().zip(&call.args).all(|(param, arg)| {
1624                    param.is_ident() && self.is_simple_expr_for_seq_optimization(&arg.expr)
1625                });
1626
1627                if can_inline {
1628                    // Create a simple substitution for the parameters
1629                    let mut substitutions = FxHashMap::default();
1630                    for (param, arg) in arrow.params.iter().zip(&call.args) {
1631                        if let Pat::Ident(ident) = param {
1632                            substitutions.insert(ident.to_id(), arg.expr.clone());
1633                        }
1634                    }
1635
1636                    // Apply substitutions to the body
1637                    let mut new_body = (**body).clone();
1638                    if !substitutions.is_empty() {
1639                        let mut replacer = NormalMultiReplacer::new(&mut substitutions, false);
1640                        new_body.visit_mut_with(&mut replacer);
1641                    }
1642
1643                    return Some(new_body);
1644                }
1645            }
1646        }
1647
1648        None
1649    }
1650}