swc_ecma_minifier/compress/optimize/
bools.rs

1use swc_common::{util::take::Take, EqIgnoreSpan, Span, Spanned, DUMMY_SP};
2use swc_ecma_ast::*;
3use swc_ecma_utils::{ExprExt, Type, Value};
4
5use super::Optimizer;
6use crate::program_data::VarUsageInfoFlags;
7
8/// Methods related to the options `bools` and `bool_as_ints`.
9impl Optimizer<'_> {
10    ///
11    /// - `"undefined" == typeof value;` => `void 0 === value`
12    pub(super) fn compress_typeof_undefined(&mut self, e: &mut BinExpr) {
13        fn opt(o: &mut Optimizer, l: &mut Expr, r: &mut Expr) -> bool {
14            match (&mut *l, &mut *r) {
15                (
16                    Expr::Lit(Lit::Str(Str { value: l_v, .. })),
17                    Expr::Unary(UnaryExpr {
18                        op: op!("typeof"),
19                        arg,
20                        ..
21                    }),
22                ) if &**l_v == "undefined" => {
23                    // TODO?
24                    if let Expr::Ident(arg) = &**arg {
25                        if let Some(usage) = o.data.vars.get(&arg.to_id()) {
26                            if !usage.flags.contains(VarUsageInfoFlags::DECLARED) {
27                                return false;
28                            }
29                        }
30                    }
31
32                    *l = *Expr::undefined(l.span());
33                    *r = *arg.take();
34                    true
35                }
36                _ => false,
37            }
38        }
39
40        match e.op {
41            op!("==") | op!("!=") | op!("===") | op!("!==") => {}
42            _ => return,
43        }
44
45        if opt(self, &mut e.left, &mut e.right) || opt(self, &mut e.right, &mut e.left) {
46            e.op = match e.op {
47                op!("==") => {
48                    op!("===")
49                }
50                op!("!=") => {
51                    op!("!==")
52                }
53                _ => e.op,
54            };
55        }
56    }
57
58    ///
59    /// - `a === undefined || a === null` => `a == null`
60    pub(super) fn optimize_optional_chain_generated(&mut self, e: &mut BinExpr) {
61        if e.op == op!("||") || e.op == op!("&&") {
62            {
63                let res = self.optimize_optional_chain_generated_inner(
64                    e.span,
65                    e.op,
66                    &mut e.left,
67                    &mut e.right,
68                );
69                if let Some(res) = res {
70                    self.changed = true;
71                    report_change!("bools: Optimizing `=== null || === undefined` to `== null`");
72                    *e = res;
73                    return;
74                }
75            }
76
77            if let (Expr::Bin(left), right) = (&mut *e.left, &mut *e.right) {
78                if e.op == left.op {
79                    let res = self.optimize_optional_chain_generated_inner(
80                        right.span(),
81                        e.op,
82                        &mut left.right,
83                        &mut *right,
84                    );
85                    if let Some(res) = res {
86                        self.changed = true;
87                        report_change!(
88                            "bools: Optimizing `=== null || === undefined` to `== null`"
89                        );
90                        *e = BinExpr {
91                            span: e.span,
92                            op: e.op,
93                            left: left.left.take(),
94                            right: res.into(),
95                        };
96                    }
97                }
98            }
99        }
100    }
101
102    fn optimize_optional_chain_generated_inner(
103        &mut self,
104        span: Span,
105        top_op: BinaryOp,
106        e_left: &mut Expr,
107        e_right: &mut Expr,
108    ) -> Option<BinExpr> {
109        let (cmp, op, left, right) = match &mut *e_left {
110            Expr::Bin(left_bin) => {
111                if left_bin.op != op!("===") && left_bin.op != op!("!==") {
112                    return None;
113                }
114
115                if top_op == op!("&&") && left_bin.op == op!("===") {
116                    return None;
117                }
118                if top_op == op!("||") && left_bin.op == op!("!==") {
119                    return None;
120                }
121
122                match &*left_bin.right {
123                    Expr::Ident(..) | Expr::Lit(..) => {}
124                    Expr::Assign(AssignExpr {
125                        left: AssignTarget::Simple(SimpleAssignTarget::Ident(_)),
126                        ..
127                    }) => (),
128                    Expr::Member(MemberExpr {
129                        obj,
130                        prop: MemberProp::Ident(..),
131                        ..
132                    }) => {
133                        if self.should_preserve_property_access(
134                            obj,
135                            super::unused::PropertyAccessOpts {
136                                allow_getter: false,
137                                only_ident: false,
138                            },
139                        ) {
140                            return None;
141                        }
142                    }
143                    _ => {
144                        return None;
145                    }
146                }
147
148                let right = match &mut *e_right {
149                    Expr::Bin(right_bin) => {
150                        if right_bin.op != left_bin.op {
151                            return None;
152                        }
153
154                        let same_assign = if let (
155                            Expr::Assign(AssignExpr {
156                                left: AssignTarget::Simple(SimpleAssignTarget::Ident(l_id)),
157                                ..
158                            }),
159                            Expr::Ident(r_id),
160                        ) = (&*left_bin.right, &*right_bin.right)
161                        {
162                            l_id.id.eq_ignore_span(r_id)
163                        } else {
164                            false
165                        };
166
167                        if !(same_assign || right_bin.right.eq_ignore_span(&left_bin.right)) {
168                            return None;
169                        }
170
171                        &mut *right_bin.left
172                    }
173                    _ => return None,
174                };
175
176                (
177                    &mut left_bin.right,
178                    left_bin.op,
179                    &mut *left_bin.left,
180                    &mut *right,
181                )
182            }
183            _ => return None,
184        };
185
186        let lt = left.get_type(self.ctx.expr_ctx);
187        let rt = right.get_type(self.ctx.expr_ctx);
188        if let (Value::Known(Type::Undefined), Value::Known(Type::Null))
189        | (Value::Known(Type::Null), Value::Known(Type::Undefined)) = (lt, rt)
190        {
191            if op == op!("===") {
192                report_change!("Reducing `!== null || !== undefined` check to `!= null`");
193                return Some(BinExpr {
194                    span,
195                    op: op!("=="),
196                    left: cmp.take(),
197                    right: Lit::Null(Null { span: DUMMY_SP }).into(),
198                });
199            } else {
200                report_change!("Reducing `=== null || === undefined` check to `== null`");
201                return Some(BinExpr {
202                    span,
203                    op: op!("!="),
204                    left: cmp.take(),
205                    right: Lit::Null(Null { span: DUMMY_SP }).into(),
206                });
207            }
208        }
209
210        None
211    }
212}