swc_ecma_minifier/compress/optimize/
conditionals.rs

1use std::mem::swap;
2
3use swc_common::{util::take::Take, EqIgnoreSpan, Spanned, SyntaxContext, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_transforms_base::ext::ExprRefExt;
6use swc_ecma_transforms_optimization::debug_assert_valid;
7use swc_ecma_utils::{ExprExt, ExprFactory, IdentUsageFinder, StmtExt, StmtLike};
8
9use super::Optimizer;
10use crate::{
11    compress::{
12        optimize::BitCtx,
13        util::{negate, negate_cost},
14    },
15    program_data::VarUsageInfoFlags,
16    DISABLE_BUGGY_PASSES,
17};
18
19/// Methods related to the option `conditionals`. All methods are noop if
20/// `conditionals` is false.
21impl Optimizer<'_> {
22    /// Negates the condition of a `if` statement to reduce body size.
23    pub(super) fn negate_if_stmt(&mut self, stmt: &mut IfStmt) {
24        let alt = match stmt.alt.as_deref_mut() {
25            Some(v) => v,
26            _ => return,
27        };
28
29        match &*stmt.cons {
30            Stmt::Return(..) | Stmt::Continue(ContinueStmt { label: None, .. }) => return,
31            _ => {}
32        }
33
34        if negate_cost(self.ctx.expr_ctx, &stmt.test, true, false) < 0 {
35            report_change!("if_return: Negating `cond` of an if statement which has cons and alt");
36            let ctx = self.ctx.clone().with(BitCtx::InBoolCtx, true);
37            self.with_ctx(ctx).negate(&mut stmt.test, false);
38            swap(alt, &mut *stmt.cons);
39            return;
40        }
41
42        match &*alt {
43            Stmt::Return(..) | Stmt::Continue(ContinueStmt { label: None, .. }) => {
44                self.changed = true;
45                report_change!(
46                    "if_return: Negating an if statement because the alt is return / continue"
47                );
48                self.negate(&mut stmt.test, false);
49                swap(alt, &mut *stmt.cons);
50            }
51            _ => {}
52        }
53    }
54
55    /// This method may change return value.
56    ///
57    /// - `a ? b : false` => `a && b`
58    pub(super) fn compress_cond_to_logical_ignoring_return_value(&mut self, e: &mut Expr) {
59        let cond = match e {
60            Expr::Cond(cond) => cond,
61            _ => return,
62        };
63
64        if !cond.cons.may_have_side_effects(self.ctx.expr_ctx) {
65            self.changed = true;
66            report_change!("conditionals: `cond ? useless : alt` => `cond || alt`");
67            *e = BinExpr {
68                span: cond.span,
69                op: op!("||"),
70                left: cond.test.take(),
71                right: cond.alt.take(),
72            }
73            .into();
74            return;
75        }
76
77        if !cond.alt.may_have_side_effects(self.ctx.expr_ctx) {
78            self.changed = true;
79            report_change!("conditionals: `cond ? cons : useless` => `cond && cons`");
80            *e = BinExpr {
81                span: cond.span,
82                op: op!("&&"),
83                left: cond.test.take(),
84                right: cond.cons.take(),
85            }
86            .into();
87        }
88    }
89
90    ///
91    /// # Example
92    ///
93    /// ## Input
94    ///
95    /// ```ts
96    /// if (foo) return;
97    /// if (bar) return;
98    /// if (baz) return;
99    /// if (baa) return;
100    /// ```
101    ///
102    /// ## Output
103    ///
104    /// ```ts
105    /// if (foo || bar || baz || baa) return;
106    /// ```
107    pub(super) fn merge_similar_ifs<T>(&mut self, stmts: &mut Vec<T>)
108    where
109        T: StmtLike,
110    {
111        if !self.options.conditionals {
112            return;
113        }
114
115        let has_work =
116            stmts
117                .windows(2)
118                .any(|stmts| match (&stmts[0].as_stmt(), &stmts[1].as_stmt()) {
119                    (
120                        Some(Stmt::If(l @ IfStmt { alt: None, .. })),
121                        Some(Stmt::If(r @ IfStmt { alt: None, .. })),
122                    ) => SyntaxContext::within_ignored_ctxt(|| {
123                        l.cons.eq_ignore_span(&r.cons) && l.cons.terminates()
124                    }),
125                    _ => false,
126                });
127        if !has_work {
128            return;
129        }
130
131        self.changed = true;
132        report_change!("conditionals: Merging if statements with same `cons`");
133
134        let mut cur: Option<IfStmt> = None;
135        let mut new = Vec::with_capacity(stmts.len());
136        for stmt in stmts.take() {
137            match stmt.try_into_stmt() {
138                Ok(stmt) => {
139                    match stmt {
140                        Stmt::If(mut stmt @ IfStmt { alt: None, .. }) => {
141                            //
142
143                            match &mut cur {
144                                Some(cur_if) => {
145                                    // If cons is same, we merge conditions.
146                                    if SyntaxContext::within_ignored_ctxt(|| {
147                                        cur_if.cons.eq_ignore_span(&stmt.cons)
148                                    }) {
149                                        cur_if.test = BinExpr {
150                                            span: DUMMY_SP,
151                                            left: cur_if.test.take(),
152                                            op: op!("||"),
153                                            right: stmt.test.take(),
154                                        }
155                                        .into();
156                                    } else {
157                                        new.extend(cur.take().map(Stmt::If).map(T::from));
158
159                                        cur = Some(stmt);
160                                    }
161                                }
162                                None => {
163                                    cur = Some(stmt);
164                                }
165                            }
166                        }
167                        _ => {
168                            new.extend(cur.take().map(Stmt::If).map(T::from));
169
170                            new.push(T::from(stmt));
171                        }
172                    }
173                }
174                Err(item) => {
175                    new.extend(cur.take().map(Stmt::If).map(T::from));
176
177                    new.push(item);
178                }
179            }
180        }
181
182        new.extend(cur.map(Stmt::If).map(T::from));
183
184        *stmts = new;
185    }
186
187    ///
188    /// # Examples
189    ///
190    /// ## Input
191    ///
192    /// ```ts
193    /// function foo(do_something, some_condition) {
194    ///     if (some_condition) do_something(x);
195    ///     else do_something(y);
196    ///     if (some_condition) side_effects(x);
197    ///     else side_effects(y);
198    /// }
199    /// ```
200    ///
201    /// ## Output
202    ///
203    /// ```ts
204    /// function foo(do_something, some_condition) {
205    ///     do_something(some_condition ? x : y);
206    ///     some_condition ? side_effects(x) : side_effects(y);
207    /// }
208    /// ```
209    pub(super) fn compress_if_stmt_as_cond(&mut self, s: &mut Stmt) {
210        let stmt = match s {
211            Stmt::If(v) => v,
212            _ => return,
213        };
214
215        if let Stmt::Empty(..) = &*stmt.cons {
216            if (self.options.conditionals || self.options.unused) && stmt.alt.is_none() {
217                *s = ExprStmt {
218                    span: stmt.span,
219                    expr: stmt.test.take(),
220                }
221                .into();
222                self.changed = true;
223                report_change!("conditionals: `if (foo);` => `foo` ");
224                return;
225            }
226        }
227
228        // If alt does not exist, an if statement is better than a conditional
229        // expression.
230        let alt = match &mut stmt.alt {
231            Some(v) => &mut **v,
232            None => {
233                return;
234            }
235        };
236        let alt = match extract_expr_stmt(alt) {
237            Some(v) => v,
238            None => return,
239        };
240
241        // if (!foo); else bar();
242        // =>
243        // foo && bar()
244        if let Stmt::Empty(..) = &*stmt.cons {
245            match &mut *stmt.test {
246                Expr::Unary(UnaryExpr {
247                    op: op!("!"), arg, ..
248                }) => {
249                    report_change!("Optimizing `if (!foo); else bar();` as `foo && bar();`");
250
251                    let mut expr = BinExpr {
252                        span: DUMMY_SP,
253                        left: arg.take(),
254                        op: op!("&&"),
255                        right: Box::new(alt.take()),
256                    }
257                    .into();
258                    self.compress_logical_exprs_as_bang_bang(&mut expr, true);
259                    *s = ExprStmt {
260                        span: stmt.span,
261                        expr: expr.into(),
262                    }
263                    .into();
264                }
265                _ => {
266                    report_change!("Optimizing `if (foo); else bar();` as `foo || bar();`");
267
268                    let mut expr = BinExpr {
269                        span: DUMMY_SP,
270                        left: stmt.test.take(),
271                        op: op!("||"),
272                        right: Box::new(alt.take()),
273                    }
274                    .into();
275                    self.compress_logical_exprs_as_bang_bang(&mut expr, false);
276                    *s = ExprStmt {
277                        span: stmt.span,
278                        expr: expr.into(),
279                    }
280                    .into();
281                }
282            }
283            return;
284        }
285
286        let cons = match extract_expr_stmt(&mut stmt.cons) {
287            Some(v) => v,
288            None => return,
289        };
290
291        let new_expr = self.compress_similar_cons_alt(&mut stmt.test, cons, alt, true);
292
293        if let Some(v) = new_expr {
294            debug_assert_valid(&v);
295
296            self.changed = true;
297            report_change!("conditionals: Merging cons and alt as only one argument differs");
298            *s = ExprStmt {
299                span: stmt.span,
300                expr: Box::new(v),
301            }
302            .into();
303            return;
304        }
305
306        if self.options.conditionals || self.options.bools {
307            // if (a) b(); else c(); => a ? b() : c()
308            report_change!(
309                "Compressing if statement as conditional expression (even though cons and alt is \
310                 not compressable)"
311            );
312            self.changed = true;
313            *s = ExprStmt {
314                span: stmt.span,
315                expr: CondExpr {
316                    span: DUMMY_SP,
317                    test: stmt.test.take(),
318                    cons: Box::new(cons.take()),
319                    alt: Box::new(alt.take()),
320                }
321                .into(),
322            }
323            .into()
324        }
325    }
326
327    /// Compress a conditional expression if cons and alt is simillar
328    pub(super) fn compress_cond_expr_if_similar(&mut self, e: &mut Expr) {
329        if !self.options.conditionals {
330            return;
331        }
332
333        let cond = match e {
334            Expr::Cond(expr) => expr,
335            _ => return,
336        };
337
338        let compressed =
339            self.compress_similar_cons_alt(&mut cond.test, &mut cond.cons, &mut cond.alt, false);
340
341        if let Some(v) = compressed {
342            *e = v;
343            self.changed = true;
344            return;
345        }
346
347        // x ? x : y => x || y
348        if cond.test.is_ident() && cond.test.eq_ignore_span(&cond.cons) {
349            report_change!("Compressing `x ? x : y` as `x || y`");
350            self.changed = true;
351            *e = BinExpr {
352                span: cond.span,
353                op: op!("||"),
354                left: cond.test.take(),
355                right: cond.alt.take(),
356            }
357            .into();
358        }
359    }
360
361    fn compress_similar_cons_alt(
362        &mut self,
363        test: &mut Box<Expr>,
364        cons: &mut Expr,
365        alt: &mut Expr,
366        is_for_if_stmt: bool,
367    ) -> Option<Expr> {
368        debug_assert_valid(cons);
369        debug_assert_valid(alt);
370
371        if cons.eq_ignore_span(alt) && !matches!(&*cons, Expr::Yield(..) | Expr::Fn(..)) {
372            report_change!("conditionals: cons is same as alt");
373            return Some(
374                SeqExpr {
375                    span: DUMMY_SP,
376                    exprs: vec![test.take(), Box::new(cons.take())],
377                }
378                .into(),
379            );
380        }
381
382        match (cons, alt) {
383            (Expr::Call(cons), Expr::Call(alt)) => {
384                // Test expr may change the variables that cons and alt **may use** in their
385                // common args. For example:
386                // from (a = 1) ? f(a, true) : f(a, false)
387                // to   f(a, a = 1 ? true : false)
388                let side_effects_in_test = test.may_have_side_effects(self.ctx.expr_ctx);
389
390                if self.data.contains_unresolved(test) {
391                    return None;
392                }
393
394                let cons_callee = cons.callee.as_expr().and_then(|e| e.as_ident())?;
395                if IdentUsageFinder::find(cons_callee, &**test) {
396                    return None;
397                }
398                //
399
400                if !cons.callee.eq_ignore_span(&alt.callee) {
401                    return None;
402                }
403
404                let side_effect_free = self
405                    .data
406                    .vars
407                    .get(&cons_callee.to_id())
408                    .map(|v| {
409                        v.flags.contains(
410                            VarUsageInfoFlags::IS_FN_LOCAL.union(VarUsageInfoFlags::DECLARED),
411                        )
412                    })
413                    .unwrap_or(false);
414
415                if side_effect_free
416                    && cons.args.len() == alt.args.len()
417                    && cons.args.iter().all(|arg| arg.spread.is_none())
418                    && alt.args.iter().all(|arg| arg.spread.is_none())
419                {
420                    let mut diff_count = 0;
421                    let mut diff_idx = None;
422
423                    for (idx, (cons, alt)) in cons.args.iter().zip(alt.args.iter()).enumerate() {
424                        if !cons.eq_ignore_span(alt) {
425                            diff_count += 1;
426                            diff_idx = Some(idx);
427                        } else {
428                            // See the comments for `side_effects_in_test`
429                            if side_effects_in_test && !cons.expr.is_pure(self.ctx.expr_ctx) {
430                                return None;
431                            }
432                        }
433                    }
434
435                    if diff_count == 1 {
436                        let diff_idx = diff_idx.unwrap();
437
438                        report_change!(
439                            "conditionals: Merging cons and alt as only one argument differs"
440                        );
441                        self.changed = true;
442
443                        let mut new_args = Vec::new();
444
445                        for (idx, arg) in cons.args.take().into_iter().enumerate() {
446                            if idx == diff_idx {
447                                // Inject conditional.
448                                new_args.push(ExprOrSpread {
449                                    spread: None,
450                                    expr: CondExpr {
451                                        span: arg.expr.span(),
452                                        test: test.take(),
453                                        cons: arg.expr,
454                                        alt: alt.args[idx].expr.take(),
455                                    }
456                                    .into(),
457                                })
458                            } else {
459                                new_args.push(arg)
460                            }
461                        }
462
463                        return Some(
464                            CallExpr {
465                                span: test.span(),
466                                callee: cons_callee.clone().as_callee(),
467                                args: new_args,
468                                ..Default::default()
469                            }
470                            .into(),
471                        );
472                    }
473                }
474
475                if side_effect_free
476                    && cons.args.len() == 1
477                    && alt.args.len() == 1
478                    && cons.args.iter().all(|arg| arg.spread.is_none())
479                    && alt.args.iter().all(|arg| arg.spread.is_none())
480                {
481                    // if (some_condition) do_something(x);
482                    // else do_something(y);
483                    //
484                    // =>
485                    //
486                    // do_something(some_condition ? x : y);
487                    //
488
489                    let args = vec![CondExpr {
490                        span: DUMMY_SP,
491                        test: test.take(),
492                        cons: cons.args[0].expr.take(),
493                        alt: alt.args[0].expr.take(),
494                    }
495                    .as_arg()];
496
497                    report_change!(
498                        "Compressing if into cond as there's no side effect and the number of \
499                         arguments is 1"
500                    );
501                    return Some(
502                        CallExpr {
503                            span: DUMMY_SP,
504                            callee: cons.callee.take(),
505                            args,
506                            ..Default::default()
507                        }
508                        .into(),
509                    );
510                }
511
512                if !side_effect_free && is_for_if_stmt {
513                    report_change!("Compressing if into cond while preserving side effects");
514                    return Some(
515                        CondExpr {
516                            span: DUMMY_SP,
517                            test: test.take(),
518                            cons: cons.take().into(),
519                            alt: alt.take().into(),
520                        }
521                        .into(),
522                    );
523                }
524
525                None
526            }
527
528            (Expr::New(cons), Expr::New(alt)) => {
529                if self.data.contains_unresolved(test) {
530                    return None;
531                }
532
533                // TODO: Handle new expression with no args.
534
535                if cons.callee.eq_ignore_span(&alt.callee)
536                    && cons.args.as_ref().map(|v| v.len() <= 1).unwrap_or(true)
537                    && alt.args.as_ref().map(|v| v.len() <= 1).unwrap_or(true)
538                    && cons.args.as_ref().map(|v| v.len()).unwrap_or(0)
539                        == alt.args.as_ref().map(|v| v.len()).unwrap_or(0)
540                    && (cons.args.is_some()
541                        && cons
542                            .args
543                            .as_ref()
544                            .unwrap()
545                            .iter()
546                            .all(|arg| arg.spread.is_none()))
547                    && (alt.args.is_some()
548                        && alt
549                            .args
550                            .as_ref()
551                            .unwrap()
552                            .iter()
553                            .all(|arg| arg.spread.is_none()))
554                {
555                    let mut args = Vec::new();
556
557                    if cons.args.as_ref().map(|v| v.len()).unwrap_or(0) == 1 {
558                        args = vec![ExprOrSpread {
559                            spread: None,
560                            expr: Box::new(Expr::Cond(CondExpr {
561                                span: DUMMY_SP,
562                                test: test.take(),
563                                cons: cons.args.as_mut().unwrap()[0].expr.take(),
564                                alt: alt.args.as_mut().unwrap()[0].expr.take(),
565                            })),
566                        }];
567                    }
568
569                    report_change!(
570                        "Compressing if statement into a conditional expression of `new` as \
571                         there's no side effect and the number of arguments is 1"
572                    );
573                    return Some(
574                        NewExpr {
575                            span: DUMMY_SP,
576                            callee: cons.callee.take(),
577                            args: Some(args),
578                            ..Default::default()
579                        }
580                        .into(),
581                    );
582                }
583
584                None
585            }
586
587            (
588                Expr::Assign(cons @ AssignExpr { op: op!("="), .. }),
589                Expr::Assign(alt @ AssignExpr { op: op!("="), .. }),
590            ) if cons.left.eq_ignore_span(&alt.left) && cons.left.as_ident().is_some() => {
591                if self
592                    .data
593                    .ident_is_unresolved(&cons.left.as_ident().unwrap().id)
594                {
595                    return None;
596                }
597
598                report_change!("Merging assignments in cons and alt of if statement");
599                Some(
600                    AssignExpr {
601                        span: DUMMY_SP,
602                        op: cons.op,
603                        left: cons.left.take(),
604                        right: CondExpr {
605                            span: DUMMY_SP,
606                            test: test.take(),
607                            cons: cons.right.take(),
608                            alt: alt.right.take(),
609                        }
610                        .into(),
611                    }
612                    .into(),
613                )
614            }
615
616            // a ? b ? c() : d() : d() => a && b ? c() : d()
617            (Expr::Cond(cons), alt) if (*cons.alt).eq_ignore_span(&*alt) => {
618                report_change!("conditionals: a ? b ? c() : d() : d() => a && b ? c() : d()");
619                Some(
620                    CondExpr {
621                        span: DUMMY_SP,
622                        test: BinExpr {
623                            span: DUMMY_SP,
624                            left: test.take(),
625                            op: op!("&&"),
626                            right: cons.test.take(),
627                        }
628                        .into(),
629                        cons: cons.cons.take(),
630                        alt: cons.alt.take(),
631                    }
632                    .into(),
633                )
634            }
635
636            // a ? c() : b ? c() : d() => a || b ? c() : d()
637            (cons, Expr::Cond(alt)) if cons.eq_ignore_span(&*alt.cons) => {
638                report_change!("conditionals: a ? c() : b ? c() : d() => a || b ? c() : d()");
639                Some(
640                    CondExpr {
641                        span: DUMMY_SP,
642                        test: BinExpr {
643                            span: DUMMY_SP,
644                            left: test.take(),
645                            op: op!("||"),
646                            right: alt.test.take(),
647                        }
648                        .into(),
649                        cons: alt.cons.take(),
650                        alt: alt.alt.take(),
651                    }
652                    .into(),
653                )
654            }
655
656            // z ? "fuji" : (condition(), "fuji");
657            // =>
658            // (z || condition(), "fuji");
659            (cons, Expr::Seq(alt)) if (**alt.exprs.last().unwrap()).eq_ignore_span(&*cons) => {
660                self.changed = true;
661                report_change!("conditionals: Reducing seq expr in alt");
662                //
663                alt.exprs.pop();
664                let first = BinExpr {
665                    span: DUMMY_SP,
666                    left: test.take(),
667                    op: op!("||"),
668                    right: Expr::from_exprs(alt.exprs.take()),
669                }
670                .into();
671                Some(
672                    SeqExpr {
673                        span: DUMMY_SP,
674                        exprs: vec![first, Box::new(cons.take())],
675                    }
676                    .into(),
677                )
678            }
679
680            // z ? (condition(), "fuji") : "fuji"
681            // =>
682            // (z && condition(), "fuji");
683            (Expr::Seq(cons), alt) if (**cons.exprs.last().unwrap()).eq_ignore_span(&*alt) => {
684                self.changed = true;
685                report_change!("conditionals: Reducing seq expr in cons");
686                //
687                cons.exprs.pop();
688                let first = BinExpr {
689                    span: DUMMY_SP,
690                    left: test.take(),
691                    op: op!("&&"),
692                    right: Expr::from_exprs(cons.exprs.take()),
693                }
694                .into();
695                Some(
696                    SeqExpr {
697                        span: DUMMY_SP,
698                        exprs: vec![first, Box::new(alt.take())],
699                    }
700                    .into(),
701                )
702            }
703
704            (Expr::Seq(left), Expr::Seq(right)) => {
705                let left_len = left.exprs.len();
706                let right_len = right.exprs.len();
707                let min_len = left_len.min(right_len);
708
709                let mut idx = 0;
710
711                while idx < min_len
712                    && left.exprs[left_len - idx - 1]
713                        .eq_ignore_span(&right.exprs[right_len - idx - 1])
714                    && !matches!(
715                        &*left.exprs[left_len - idx - 1],
716                        Expr::Yield(..) | Expr::Fn(..)
717                    )
718                {
719                    idx += 1;
720                }
721
722                if idx == 0 {
723                    None
724                } else if idx == left_len {
725                    self.changed = true;
726                    report_change!("conditionals: Reducing similar seq expr in cons");
727
728                    let mut alt = right.exprs.take();
729
730                    alt.truncate(alt.len() - idx);
731
732                    let mut seq = vec![Box::new(Expr::Bin(BinExpr {
733                        span: DUMMY_SP,
734                        left: test.take(),
735                        op: op!("||"),
736                        right: Expr::from_exprs(alt),
737                    }))];
738                    seq.append(&mut left.exprs);
739
740                    Some(
741                        SeqExpr {
742                            span: DUMMY_SP,
743                            exprs: seq,
744                        }
745                        .into(),
746                    )
747                } else if idx == right_len {
748                    self.changed = true;
749                    report_change!("conditionals: Reducing similar seq expr in alt");
750
751                    let mut cons = left.exprs.take();
752
753                    cons.truncate(cons.len() - idx);
754
755                    let mut seq = vec![Box::new(Expr::Bin(BinExpr {
756                        span: DUMMY_SP,
757                        left: test.take(),
758                        op: op!("&&"),
759                        right: Expr::from_exprs(cons),
760                    }))];
761                    seq.append(&mut right.exprs);
762
763                    Some(
764                        SeqExpr {
765                            span: DUMMY_SP,
766                            exprs: seq,
767                        }
768                        .into(),
769                    )
770                } else {
771                    self.changed = true;
772                    report_change!("conditionals: Reducing similar seq expr");
773                    let _ = left.exprs.split_off(left_len - idx);
774                    let mut common = right.exprs.split_off(right_len - idx);
775
776                    let mut seq = vec![Box::new(Expr::Cond(CondExpr {
777                        span: DUMMY_SP,
778                        test: test.take(),
779                        cons: Box::new(Expr::Seq(left.take())),
780                        alt: Box::new(Expr::Seq(right.take())),
781                    }))];
782                    seq.append(&mut common);
783
784                    Some(
785                        SeqExpr {
786                            span: DUMMY_SP,
787                            exprs: seq,
788                        }
789                        .into(),
790                    )
791                }
792            }
793
794            _ => None,
795        }
796    }
797
798    /// Currently disabled.
799    pub(super) fn inject_else(&mut self, stmts: &mut Vec<Stmt>) {
800        if DISABLE_BUGGY_PASSES {
801            return;
802        }
803
804        let len = stmts.len();
805
806        let pos_of_if = stmts.iter().enumerate().rposition(|(idx, s)| {
807            idx != len - 1
808                && match s {
809                    Stmt::If(IfStmt {
810                        cons, alt: None, ..
811                    }) => match &**cons {
812                        Stmt::Block(b) => {
813                            b.stmts.len() == 2
814                                && !matches!(b.stmts.first(), Some(Stmt::If(..) | Stmt::Expr(..)))
815                                && matches!(
816                                    b.stmts.last(),
817                                    Some(Stmt::Return(ReturnStmt { arg: None, .. }))
818                                )
819                        }
820                        _ => false,
821                    },
822                    _ => false,
823                }
824        });
825
826        let pos_of_if = match pos_of_if {
827            Some(v) => v,
828            _ => return,
829        };
830
831        self.changed = true;
832        report_change!("if_return: Injecting else because it's shorter");
833
834        let mut new = Vec::with_capacity(pos_of_if + 1);
835        new.extend(stmts.drain(..pos_of_if));
836        let alt = stmts.drain(1..).collect::<Vec<_>>();
837
838        let if_stmt = stmts.take().into_iter().next().unwrap();
839        match if_stmt {
840            Stmt::If(mut s) => {
841                match &mut *s.cons {
842                    Stmt::Block(cons) => {
843                        cons.stmts.pop();
844                    }
845                    _ => {
846                        unreachable!()
847                    }
848                }
849
850                assert_eq!(s.alt, None);
851
852                s.alt = Some(if alt.len() == 1 {
853                    Box::new(alt.into_iter().next().unwrap())
854                } else {
855                    Box::new(
856                        BlockStmt {
857                            span: DUMMY_SP,
858                            stmts: alt,
859                            ..Default::default()
860                        }
861                        .into(),
862                    )
863                });
864
865                new.push(s.into())
866            }
867            _ => {
868                unreachable!()
869            }
870        }
871
872        *stmts = new;
873    }
874
875    /// if (foo) return bar()
876    /// else baz()
877    ///
878    /// `else` token can be removed from the code above.
879    /// if (foo) return bar()
880    /// else baz()
881    ///
882    /// `else` token can be removed from the code above.
883    pub(super) fn drop_else_token<T>(&mut self, stmts: &mut Vec<T>)
884    where
885        T: StmtLike,
886    {
887        // Find an if statement with else token.
888        let need_work = stmts.iter().any(|stmt| match stmt.as_stmt() {
889            Some(Stmt::If(IfStmt {
890                cons,
891                alt: Some(..),
892                ..
893            })) => cons.terminates(),
894            _ => false,
895        });
896        if !need_work {
897            return;
898        }
899        //
900        let mut new_stmts = Vec::with_capacity(stmts.len() * 2);
901        stmts.take().into_iter().for_each(|stmt| {
902            match stmt.try_into_stmt() {
903                Ok(stmt) => match stmt {
904                    Stmt::If(IfStmt {
905                        span,
906                        mut test,
907                        mut cons,
908                        alt: Some(mut alt),
909                        ..
910                    }) if cons.terminates() => {
911                        if let (
912                            Stmt::Return(ReturnStmt { arg: None, .. }),
913                            Stmt::Decl(Decl::Fn(..)),
914                        ) = (&*cons, &*alt)
915                        {
916                            // I don't know why, but terser behaves differently
917                            negate(self.ctx.expr_ctx, &mut test, true, false);
918
919                            swap(&mut cons, &mut alt);
920                        }
921
922                        new_stmts.push(T::from(
923                            IfStmt {
924                                span,
925                                test,
926                                cons,
927                                alt: None,
928                            }
929                            .into(),
930                        ));
931                        new_stmts.push(T::from(*alt));
932                    }
933                    _ => {
934                        new_stmts.push(T::from(stmt));
935                    }
936                },
937                Err(stmt) => new_stmts.push(stmt),
938            }
939        });
940
941        self.changed = true;
942        report_change!("conditionals: Dropped useless `else` token");
943        *stmts = new_stmts;
944    }
945}
946
947fn extract_expr_stmt(s: &mut Stmt) -> Option<&mut Expr> {
948    match s {
949        Stmt::Expr(e) => Some(&mut *e.expr),
950        _ => None,
951    }
952}