swc_ecma_minifier/compress/pure/
numbers.rs

1use swc_common::{util::take::Take, EqIgnoreSpan, Spanned, DUMMY_SP};
2use swc_ecma_ast::*;
3use swc_ecma_utils::num_from_str;
4
5use super::Pure;
6
7impl Pure<'_> {
8    pub(super) fn optimize_expr_in_num_ctx(&mut self, e: &mut Expr) {
9        match e {
10            Expr::Lit(Lit::Str(Str { span, value, .. })) => {
11                let Some(value) = value.as_str() else {
12                    return;
13                };
14                let value = if value.is_empty() {
15                    0f64
16                } else {
17                    match num_from_str(value).into_result() {
18                        Ok(f) if f.is_finite() => f,
19                        _ => return,
20                    }
21                };
22
23                self.changed = true;
24                report_change!("numbers: Converting a string literal to {:?}", value);
25                *e = Lit::Num(Number {
26                    span: *span,
27                    value,
28                    raw: None,
29                })
30                .into();
31            }
32            Expr::Unary(UnaryExpr {
33                arg,
34                op: op!(unary, "+"),
35                ..
36            }) => {
37                self.changed = true;
38                report_change!("numbers: remove useless to number");
39                let new_expr = *arg.take();
40                *e = new_expr
41            }
42            _ => (),
43        }
44    }
45
46    ///
47    /// - `1 / -0` => `- 1 / 0`
48    pub(super) fn lift_minus(&mut self, e: &mut Expr) {
49        if !self.options.evaluate {
50            return;
51        }
52
53        if let Expr::Bin(arg) = e {
54            if arg.op != op!("*") && arg.op != op!("/") {
55                return;
56            }
57
58            match &mut *arg.right {
59                Expr::Unary(UnaryExpr {
60                    op: op!(unary, "-"),
61                    arg: right_arg,
62                    ..
63                }) => {
64                    self.changed = true;
65                    report_change!("numbers: Lifting `-`");
66
67                    *e = UnaryExpr {
68                        span: arg.span,
69                        op: op!(unary, "-"),
70                        arg: BinExpr {
71                            span: arg.span,
72                            op: arg.op,
73                            left: arg.left.take(),
74                            right: right_arg.take(),
75                        }
76                        .into(),
77                    }
78                    .into();
79                }
80
81                Expr::Lit(Lit::Num(Number { span, value, .. })) => {
82                    if value.is_sign_negative() {
83                        self.changed = true;
84                        report_change!("numbers: Lifting `-` in a literal");
85
86                        *e = UnaryExpr {
87                            span: arg.span,
88                            op: op!(unary, "-"),
89                            arg: BinExpr {
90                                span: arg.span,
91                                op: arg.op,
92                                left: arg.left.take(),
93                                right: Box::new(Expr::Lit(Lit::Num(Number {
94                                    span: *span,
95                                    value: -*value,
96                                    raw: None,
97                                }))),
98                            }
99                            .into(),
100                        }
101                        .into();
102                    }
103                }
104
105                _ => {}
106            }
107        }
108    }
109
110    pub(super) fn optimize_to_number(&mut self, e: &mut Expr) {
111        match e {
112            Expr::Bin(bin) => {
113                if bin.op == op!("*")
114                    && matches!(&*bin.left, Expr::Lit(Lit::Num(Number { value: 1.0, .. })))
115                {
116                    report_change!("numbers: Turn '1 *' into '+'");
117                    self.changed = true;
118
119                    let value = bin.right.take();
120                    let span = bin.span;
121
122                    *e = Expr::Unary(UnaryExpr {
123                        span,
124                        op: op!(unary, "+"),
125                        arg: value,
126                    })
127                }
128            }
129            Expr::Assign(a @ AssignExpr { op: op!("="), .. }) => {
130                if let (
131                    AssignTarget::Simple(SimpleAssignTarget::Ident(l_id)),
132                    Expr::Unary(UnaryExpr {
133                        op: op!(unary, "+"),
134                        arg,
135                        ..
136                    }),
137                ) = (&a.left, &*a.right)
138                {
139                    if let Expr::Ident(r_id) = &**arg {
140                        if l_id.id.eq_ignore_span(r_id) {
141                            report_change!("numbers: Turn a = +a into a *= 1");
142                            self.changed = true;
143
144                            a.op = op!("*=");
145                            a.right = Box::new(Expr::Lit(Lit::Num(Number {
146                                span: DUMMY_SP,
147                                value: 1.0,
148                                raw: None,
149                            })))
150                        }
151                    }
152                }
153            }
154            _ => (),
155        }
156    }
157
158    pub(super) fn optimize_to_int(&mut self, e: &mut Expr) {
159        let span = e.span();
160
161        match e {
162            Expr::Bin(bin) => match (bin.op, &mut *bin.left, &mut *bin.right) {
163                (op!("|"), Expr::Bin(bin_inner), Expr::Lit(Lit::Num(n)))
164                | (op!("|"), Expr::Lit(Lit::Num(n)), Expr::Bin(bin_inner))
165                    if matches!(
166                        bin_inner.op,
167                        op!("<<") | op!(">>") | op!(">>>") | op!("|") | op!("^") | op!("&")
168                    ) && n.value == 0.0 =>
169                {
170                    report_change!("numbers: Turn '(a & b) | 0' into 'a & b'");
171                    self.changed = true;
172
173                    let value = bin_inner.take();
174
175                    *bin = BinExpr { span, ..value };
176                }
177
178                (
179                    op!("<<") | op!(">>") | op!(">>>") | op!("|") | op!("^") | op!("&"),
180                    e @ Expr::Bin(BinExpr { op: op!("|"), .. }),
181                    _,
182                )
183                | (
184                    op!("<<") | op!(">>") | op!(">>>") | op!("|") | op!("^") | op!("&"),
185                    _,
186                    e @ Expr::Bin(BinExpr { op: op!("|"), .. }),
187                ) => {
188                    if let Expr::Bin(bin) = e {
189                        match (&mut *bin.left, &mut *bin.right) {
190                            (Expr::Lit(Lit::Num(n)), inner) | (inner, Expr::Lit(Lit::Num(n)))
191                                if n.value == 0.0 =>
192                            {
193                                report_change!("numbers: Turn '(a | 0) ^ b' into 'a ^ b'");
194                                self.changed = true;
195
196                                let new_expr = inner.take();
197
198                                *e = new_expr
199                            }
200                            _ => {}
201                        }
202                    }
203                }
204
205                (
206                    op!("|"),
207                    Expr::Unary(u @ UnaryExpr { op: op!("~"), .. }),
208                    Expr::Lit(Lit::Num(Number { value: 0.0, .. })),
209                )
210                | (
211                    op!("|"),
212                    Expr::Lit(Lit::Num(Number { value: 0.0, .. })),
213                    Expr::Unary(u @ UnaryExpr { op: op!("~"), .. }),
214                ) => {
215                    report_change!("numbers: Turn '~a | 0' into '~a'");
216                    self.changed = true;
217
218                    let value = u.take();
219
220                    *e = Expr::Unary(value);
221                }
222
223                (
224                    op!("<<") | op!(">>") | op!("^"),
225                    _,
226                    Expr::Lit(Lit::Num(Number { value: 0.0, .. })),
227                ) => {
228                    report_change!("numbers: Turn 'a << 0' into 'a | 0'");
229                    self.changed = true;
230                    bin.op = op!("|");
231                }
232                (
233                    op!("&"),
234                    _,
235                    Expr::Lit(Lit::Num(
236                        n @ Number {
237                            value: 4294967295.0, // 2^32 - 1
238                            ..
239                        },
240                    )),
241                )
242                | (
243                    op!("&"),
244                    Expr::Lit(Lit::Num(
245                        n @ Number {
246                            value: 4294967295.0, // 2^32 - 1
247                            ..
248                        },
249                    )),
250                    _,
251                ) => {
252                    report_change!("numbers: Turn 'a & 0XFFFFFFFF' into 'a | 0'");
253                    self.changed = true;
254                    bin.op = op!("|");
255                    n.value = 0.0;
256                    n.raw = None;
257                }
258
259                _ => (),
260            },
261
262            Expr::Assign(
263                a @ AssignExpr {
264                    op: op!("<<=") | op!(">>=") | op!(">>>=") | op!("|=") | op!("^=") | op!("&="),
265                    ..
266                },
267            ) => {
268                if let Expr::Bin(BinExpr {
269                    op: op!("|"),
270                    left,
271                    right,
272                    ..
273                }) = &mut *a.right
274                {
275                    if let (e, Expr::Lit(Lit::Num(Number { value: 0.0, .. })))
276                    | (Expr::Lit(Lit::Num(Number { value: 0.0, .. })), e) =
277                        (&mut **left, &mut **right)
278                    {
279                        report_change!("numbers: Turn 'a |= b | 0' into 'a |= b'");
280                        self.changed = true;
281                        let value = e.take();
282                        *a.right = value;
283                    }
284                }
285            }
286            _ => (),
287        }
288    }
289}