swc_ecma_minifier/compress/pure/
conds.rs

1use std::mem::take;
2
3use swc_common::{util::take::Take, EqIgnoreSpan, Spanned, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_utils::{ExprExt, IsEmpty, StmtExt, Type, Value};
6
7use super::{DropOpts, Pure};
8use crate::{compress::util::can_absorb_negate, util::make_bool};
9
10impl Pure<'_> {
11    pub(super) fn merge_nested_if(&mut self, s: &mut IfStmt) {
12        if !self.options.conditionals && !self.options.bools {
13            return;
14        }
15
16        if s.alt.is_some() {
17            return;
18        }
19
20        if let Stmt::If(IfStmt {
21            test,
22            cons,
23            alt: None,
24            ..
25        }) = &mut *s.cons
26        {
27            self.changed = true;
28            report_change!("if_return: Merging nested if statements");
29
30            s.test = BinExpr {
31                span: s.test.span(),
32                op: op!("&&"),
33                left: s.test.take(),
34                right: test.take(),
35            }
36            .into();
37            s.cons = cons.take();
38        }
39    }
40
41    pub(super) fn optimize_const_cond(&mut self, e: &mut Expr) {
42        let Expr::Cond(cond) = e else {
43            return;
44        };
45
46        let (p, Value::Known(v)) = cond.test.cast_to_bool(self.expr_ctx) else {
47            return;
48        };
49
50        if p.is_pure() {
51            if v {
52                self.changed = true;
53                report_change!("conditionals: `true ? foo : bar` => `foo` (pure test)");
54                *e = if cond.cons.directness_matters() {
55                    Expr::Seq(SeqExpr {
56                        span: cond.span,
57                        exprs: vec![0.into(), cond.cons.take()],
58                    })
59                } else {
60                    *cond.cons.take()
61                };
62            } else {
63                self.changed = true;
64                report_change!("conditionals: `false ? foo : bar` => `bar` (pure test)");
65                *e = if cond.alt.directness_matters() {
66                    Expr::Seq(SeqExpr {
67                        span: cond.span,
68                        exprs: vec![0.into(), cond.alt.take()],
69                    })
70                } else {
71                    *cond.alt.take()
72                };
73            }
74        } else {
75            self.ignore_return_value(
76                &mut cond.test,
77                DropOpts::DROP_NUMBER.union(DropOpts::DROP_STR_LIT),
78            );
79
80            self.changed = true;
81
82            let mut exprs = Vec::with_capacity(2);
83            if !cond.test.is_invalid() {
84                exprs.push(take(&mut cond.test));
85            }
86
87            if v {
88                report_change!("conditionals: `true ? foo : bar` => `true, foo`");
89                exprs.push(take(&mut cond.cons));
90            } else {
91                report_change!("conditionals: `false ? foo : bar` => `false, bar`");
92                exprs.push(take(&mut cond.alt));
93            }
94
95            *e = *Expr::from_exprs(exprs);
96        }
97    }
98
99    ///
100    /// - `foo ? bar : false` => `!!foo && bar`
101    /// - `!foo ? true : bar` => `!foo || bar`
102    /// - `foo ? false : bar` => `!foo && bar`
103    pub(super) fn compress_conds_as_logical(&mut self, e: &mut Expr) {
104        if !self.options.conditionals {
105            return;
106        }
107
108        let Expr::Cond(cond) = e else { return };
109
110        if let Value::Known(Type::Bool) = cond.cons.get_type(self.expr_ctx) {
111            let lb = cond.cons.as_pure_bool(self.expr_ctx);
112            if let Value::Known(true) = lb {
113                report_change!("conditionals: `foo ? true : bar` => `!!foo || bar`");
114
115                // Negate twice to convert `test` to boolean.
116                self.negate_twice(&mut cond.test, false);
117
118                self.changed = true;
119                *e = BinExpr {
120                    span: cond.span,
121                    op: op!("||"),
122                    left: cond.test.take(),
123                    right: cond.alt.take(),
124                }
125                .into();
126                return;
127            }
128
129            // TODO: Verify this rule.
130            if let Value::Known(false) = lb {
131                report_change!("conditionals: `foo ? false : bar` => `!foo && bar`");
132
133                self.changed = true;
134                self.negate(&mut cond.test, false, false);
135
136                *e = BinExpr {
137                    span: cond.span,
138                    op: op!("&&"),
139                    left: cond.test.take(),
140                    right: cond.alt.take(),
141                }
142                .into();
143                return;
144            }
145        }
146
147        let rt = cond.alt.get_type(self.expr_ctx);
148        if let Value::Known(Type::Bool) = rt {
149            let rb = cond.alt.as_pure_bool(self.expr_ctx);
150            if let Value::Known(false) = rb {
151                report_change!("conditionals: `foo ? bar : false` => `!!foo && bar`");
152                self.changed = true;
153
154                // Negate twice to convert `test` to boolean.
155                self.negate_twice(&mut cond.test, false);
156
157                *e = BinExpr {
158                    span: cond.span,
159                    op: op!("&&"),
160                    left: cond.test.take(),
161                    right: cond.cons.take(),
162                }
163                .into();
164                return;
165            }
166
167            if let Value::Known(true) = rb {
168                report_change!("conditionals: `foo ? bar : true` => `!foo || bar");
169                self.changed = true;
170
171                self.negate(&mut cond.test, false, false);
172
173                *e = BinExpr {
174                    span: cond.span,
175                    op: op!("||"),
176                    left: cond.test.take(),
177                    right: cond.cons.take(),
178                }
179                .into();
180            }
181        }
182    }
183
184    pub(super) fn compress_cond_with_logical_as_logical(&mut self, e: &mut Expr) {
185        if !self.options.conditionals {
186            return;
187        }
188
189        let Expr::Cond(cond) = e else { return };
190
191        match (&mut *cond.cons, &mut *cond.alt) {
192            (Expr::Bin(cons @ BinExpr { op: op!("||"), .. }), alt)
193                if (*cons.right).eq_ignore_span(&*alt) =>
194            {
195                let cons_span = cons.span;
196
197                report_change!("conditionals: `x ? y || z : z` => `x || y && z`");
198                self.changed = true;
199
200                *e = BinExpr {
201                    span: cond.span,
202                    op: op!("||"),
203                    left: BinExpr {
204                        span: cons_span,
205                        op: op!("&&"),
206                        left: cond.test.take(),
207                        right: cons.left.take(),
208                    }
209                    .into(),
210                    right: cons.right.take(),
211                }
212                .into();
213            }
214            _ => {}
215        }
216    }
217
218    ///
219    /// - `foo ? num : 0` => `num * !!foo`
220    /// - `foo ? 0 : num` => `num * !foo`
221    pub(super) fn compress_conds_as_arithmetic(&mut self, e: &mut Expr) {
222        if !self.options.conditionals {
223            return;
224        }
225
226        let Expr::Cond(cond) = e else { return };
227        let span = cond.span;
228
229        match (&mut *cond.cons, &mut *cond.alt) {
230            (
231                Expr::Lit(Lit::Num(Number { value, .. })),
232                Expr::Lit(Lit::Num(Number { value: 0.0, .. })),
233            ) if *value > 0.0
234                && (!cond.test.is_bin()
235                    || cond.test.get_type(self.expr_ctx) == Value::Known(Type::Bool)) =>
236            {
237                report_change!("conditionals: `foo ? num : 0` => `num * !!foo`");
238                self.changed = true;
239
240                let left = cond.cons.take();
241                let mut right = cond.test.take();
242                self.negate_twice(&mut right, false);
243
244                *e = Expr::Bin(BinExpr {
245                    span,
246                    op: op!("*"),
247                    left,
248                    right,
249                })
250            }
251            (
252                Expr::Lit(Lit::Num(Number { value: 0.0, .. })),
253                Expr::Lit(Lit::Num(Number { value, .. })),
254            ) if *value > 0.0
255                && (!cond.test.is_bin() || can_absorb_negate(&cond.test, self.expr_ctx)) =>
256            {
257                report_change!("conditionals: `foo ? 0 : num` => `num * !foo`");
258                self.changed = true;
259
260                let left = cond.alt.take();
261                let mut right = cond.test.take();
262                self.negate(&mut right, false, false);
263
264                *e = Expr::Bin(BinExpr {
265                    span,
266                    op: op!("*"),
267                    left,
268                    right,
269                })
270            }
271            _ => (),
272        }
273    }
274
275    /// Removes useless operands of an logical expressions.
276    pub(super) fn drop_logical_operands(&mut self, e: &mut Expr) {
277        if !self.options.conditionals {
278            return;
279        }
280
281        let bin = match e {
282            Expr::Bin(b) => b,
283            _ => return,
284        };
285
286        if bin.op != op!("||") && bin.op != op!("&&") {
287            return;
288        }
289
290        if bin.left.may_have_side_effects(self.expr_ctx) {
291            return;
292        }
293
294        let lt = bin.left.get_type(self.expr_ctx);
295        let rt = bin.right.get_type(self.expr_ctx);
296
297        let _lb = bin.left.as_pure_bool(self.expr_ctx);
298        let rb = bin.right.as_pure_bool(self.expr_ctx);
299
300        if bin.op == op!("||") {
301            if let (Value::Known(Type::Bool), Value::Known(Type::Bool)) = (lt, rt) {
302                // `!!b || true` => true
303                if let Value::Known(true) = rb {
304                    self.changed = true;
305                    report_change!("conditionals: `!!foo || true` => `true`");
306                    *e = make_bool(bin.span, true);
307                }
308            }
309        }
310    }
311
312    pub(super) fn optimize_empty_try_stmt(&mut self, s: &mut Stmt) {
313        if !self.options.dead_code {
314            return;
315        }
316
317        let Stmt::Try(ts) = s else {
318            return;
319        };
320
321        if !ts.block.stmts.is_empty() {
322            return;
323        }
324
325        report_change!("conditionals: Optimizing empty try block");
326        self.changed = true;
327
328        let mut vars = None;
329
330        if ts.handler.is_some() {
331            let vec = ts
332                .handler
333                .iter()
334                .flat_map(|c| c.body.stmts.iter())
335                .flat_map(|s| s.extract_var_ids())
336                .map(|i| VarDeclarator {
337                    span: DUMMY_SP,
338                    name: i.into(),
339                    init: None,
340                    definite: false,
341                })
342                .collect::<Vec<_>>();
343            if !vec.is_empty() {
344                vars = Some(vec);
345            }
346        }
347
348        *s = ts.finalizer.take().map(Stmt::from).unwrap_or_default();
349
350        if let Some(vars) = vars {
351            *s = Stmt::Block(BlockStmt {
352                stmts: vec![
353                    Stmt::Decl(Decl::Var(Box::new(VarDecl {
354                        span: DUMMY_SP,
355                        ctxt: Default::default(),
356                        kind: VarDeclKind::Var,
357                        declare: false,
358                        decls: vars,
359                    }))),
360                    take(s),
361                ],
362                ..Default::default()
363            });
364        }
365    }
366
367    pub(super) fn optimize_meaningless_try(&mut self, s: &mut Stmt) {
368        let Stmt::Try(ts) = s else {
369            return;
370        };
371
372        // If catch block is not specified and finally block is empty, fold it to simple
373        // block.
374        if ts.handler.is_none() && ts.finalizer.is_empty() {
375            report_change!("conditionals: Optimizing meaningless try block");
376            self.changed = true;
377            *s = take(&mut ts.block).into();
378        }
379    }
380}