swc_ecma_minifier/compress/optimize/
ops.rs

1use swc_common::{util::take::Take, EqIgnoreSpan, Spanned};
2use swc_ecma_ast::*;
3use swc_ecma_utils::{ExprExt, Type, Value};
4use Value::Known;
5
6use super::{BitCtx, Optimizer};
7use crate::{compress::util::negate, util::make_bool};
8
9impl Optimizer<'_> {
10    ///
11    /// - `'12' === `foo` => '12' == 'foo'`
12    pub(super) fn optimize_bin_equal(&mut self, e: &mut BinExpr) {
13        if !self.options.comparisons {
14            return;
15        }
16
17        match e.op {
18            op!("===") | op!("==") | op!("!==") | op!("!=") => {
19                if e.left.is_ident() && e.left.eq_ignore_span(&e.right) {
20                    let id: Ident = e.left.clone().ident().unwrap();
21                    if let Some(t) = self.typeofs.get(&id.to_id()) {
22                        match &**t {
23                            "object" | "function" => {
24                                e.left = Box::new(make_bool(
25                                    e.span,
26                                    e.op == op!("===") || e.op == op!("=="),
27                                ));
28                                e.right.take();
29
30                                self.changed = true;
31                                report_change!("Evaluate comparing to self");
32
33                                return;
34                            }
35                            _ => {}
36                        }
37                    }
38                }
39            }
40
41            _ => {}
42        }
43
44        if e.op == op!("===") || e.op == op!("!==") {
45            if (e.left.is_ident() || e.left.is_member()) && e.left.eq_ignore_span(&e.right) {
46                self.changed = true;
47                report_change!("Reducing comparison of same variable ({})", e.op);
48
49                e.op = if e.op == op!("===") {
50                    op!("==")
51                } else {
52                    op!("!=")
53                };
54                return;
55            }
56        }
57
58        if e.op == op!("===") {
59            if let Known(lt) = e.left.get_type(self.ctx.expr_ctx) {
60                if let Known(rt) = e.right.get_type(self.ctx.expr_ctx) {
61                    if lt == rt {
62                        e.op = op!("==");
63                        self.changed = true;
64                        report_change!(
65                            "Reduced `===` to `==` because types of operands are identical"
66                        )
67                    }
68                }
69            }
70        }
71    }
72
73    /// x && (y && z)  ==>  x && y && z
74    /// x || (y || z)  ==>  x || y || z
75    /// x + ("y" + z)  ==>  x + "y" + z
76    /// "x" + (y + "z")==>  "x" + y + "z"
77    pub(super) fn remove_bin_paren(&mut self, n: &mut BinExpr) {
78        if let Expr::Bin(right) = &mut *n.right {
79            if right.op == n.op {
80                if n.op.may_short_circuit()
81                    || (right.left.is_str() && right.op == op!(bin, "+"))
82                    || (n.left.is_str() && right.right.is_str())
83                {
84                    self.changed = true;
85                    report_change!("Remove extra paren in binary expression");
86                    let left = n.left.take();
87                    let BinExpr {
88                        op,
89                        left: rl,
90                        right: rr,
91                        ..
92                    } = right.take();
93                    *n.left = BinExpr {
94                        span: left.span(),
95                        op,
96                        left,
97                        right: rl,
98                    }
99                    .into();
100                    n.right = rr;
101                }
102            }
103        }
104    }
105
106    ///
107    /// - `!!(a in b)` => `a in b`
108    /// - `!!(function() {})()` => `!(function() {})()`
109    pub(super) fn optimize_bangbang(&mut self, e: &mut Expr) {
110        if let Expr::Unary(UnaryExpr {
111            op: op!("!"), arg, ..
112        }) = e
113        {
114            if let Expr::Unary(UnaryExpr {
115                op: op!("!"), arg, ..
116            }) = &mut **arg
117            {
118                match &**arg {
119                    Expr::Unary(UnaryExpr { op: op!("!"), .. })
120                    | Expr::Bin(BinExpr { op: op!("in"), .. })
121                    | Expr::Bin(BinExpr {
122                        op: op!("instanceof"),
123                        ..
124                    })
125                    | Expr::Bin(BinExpr { op: op!("=="), .. })
126                    | Expr::Bin(BinExpr { op: op!("!="), .. })
127                    | Expr::Bin(BinExpr { op: op!("==="), .. })
128                    | Expr::Bin(BinExpr { op: op!("!=="), .. })
129                    | Expr::Bin(BinExpr { op: op!("<="), .. })
130                    | Expr::Bin(BinExpr { op: op!("<"), .. })
131                    | Expr::Bin(BinExpr { op: op!(">="), .. })
132                    | Expr::Bin(BinExpr { op: op!(">"), .. }) => {
133                        if let Known(Type::Bool) = arg.get_type(self.ctx.expr_ctx) {
134                            self.changed = true;
135                            report_change!("Optimizing: `!!expr` => `expr`");
136                            *e = *arg.take();
137                        }
138                    }
139
140                    _ => {}
141                }
142            }
143        }
144    }
145
146    /// TODO: Optimize based on the type.
147    pub(super) fn negate_twice(&mut self, e: &mut Expr, is_ret_val_ignored: bool) {
148        self.negate(e, is_ret_val_ignored);
149        self.negate(e, is_ret_val_ignored);
150    }
151
152    pub(super) fn negate(&mut self, e: &mut Expr, is_ret_val_ignored: bool) {
153        negate(
154            self.ctx.expr_ctx,
155            e,
156            self.ctx.bit_ctx.contains(BitCtx::InBoolCtx),
157            is_ret_val_ignored,
158        )
159    }
160
161    /// Remove meaningless literals in a binary expressions.
162    ///
163    /// # Parameters
164    ///
165    ///  - `in_bool_ctx`: True for expressions casted to bool.
166    ///
167    /// # Examples
168    ///
169    /// - `x() && true` => `!!x()`
170    pub(super) fn compress_logical_exprs_as_bang_bang(&mut self, e: &mut Expr, _in_bool_ctx: bool) {
171        if !self.options.conditionals && !self.options.reduce_vars {
172            return;
173        }
174
175        let bin = match e {
176            Expr::Bin(bin) => bin,
177            _ => return,
178        };
179
180        match bin.op {
181            op!("&&") | op!("||") => {
182                self.compress_logical_exprs_as_bang_bang(&mut bin.left, true);
183                self.compress_logical_exprs_as_bang_bang(&mut bin.right, true);
184            }
185
186            _ => {}
187        }
188
189        let lt = bin.left.get_type(self.ctx.expr_ctx);
190        match lt {
191            // Don't change type
192            Known(Type::Bool) => {}
193            _ => return,
194        }
195
196        let rt = bin.right.get_type(self.ctx.expr_ctx);
197        match rt {
198            Known(Type::Bool) => {}
199            _ => return,
200        }
201
202        match bin.op {
203            op!("&&") => {
204                let rb = bin.right.as_pure_bool(self.ctx.expr_ctx);
205                let rb = match rb {
206                    Value::Known(v) => v,
207                    _ => return,
208                };
209
210                if rb {
211                    self.changed = true;
212                    report_change!("Optimizing: e && true => !!e");
213
214                    self.negate_twice(&mut bin.left, false);
215                    *e = *bin.left.take();
216                }
217            }
218            op!("||") => {
219                let rb = bin.right.as_pure_bool(self.ctx.expr_ctx);
220                let rb = match rb {
221                    Value::Known(v) => v,
222                    _ => return,
223                };
224
225                if !rb {
226                    self.changed = true;
227                    report_change!("Optimizing: e || false => !!e");
228
229                    self.negate_twice(&mut bin.left, false);
230                    *e = *bin.left.take();
231                }
232            }
233            _ => {}
234        }
235    }
236
237    pub(super) fn compress_typeofs(&mut self, e: &mut Expr) {
238        if !self.options.typeofs {
239            return;
240        }
241
242        if let Expr::Unary(UnaryExpr {
243            span,
244            op: op!("typeof"),
245            arg,
246            ..
247        }) = e
248        {
249            match &**arg {
250                Expr::Ident(arg) => {
251                    if let Some(value) = self.typeofs.get(&arg.to_id()).cloned() {
252                        report_change!(
253                            "Converting typeof of variable to literal as we know the value"
254                        );
255                        self.changed = true;
256                        *e = Lit::Str(Str {
257                            span: *span,
258                            raw: None,
259                            value: value.into(),
260                        })
261                        .into();
262                    }
263                }
264
265                Expr::Arrow(..) | Expr::Fn(..) | Expr::Class(..) => {
266                    report_change!("Converting typeof to 'function' as we know the value");
267                    self.changed = true;
268                    *e = Lit::Str(Str {
269                        span: *span,
270                        raw: None,
271                        value: "function".into(),
272                    })
273                    .into();
274                }
275
276                Expr::Array(..) | Expr::Object(..) => {
277                    report_change!("Converting typeof to 'object' as we know the value");
278                    self.changed = true;
279                    *e = Lit::Str(Str {
280                        span: *span,
281                        raw: None,
282                        value: "object".into(),
283                    })
284                    .into();
285                }
286                _ => {}
287            }
288        }
289    }
290}