swc_ecma_minifier/compress/pure/
sequences.rs

1use swc_common::{util::take::Take, DUMMY_SP};
2use swc_ecma_ast::*;
3use swc_ecma_utils::{ExprFactory, StmtLike};
4
5use super::Pure;
6use crate::compress::util::is_pure_undefined;
7
8impl Pure<'_> {
9    pub(super) fn drop_useless_ident_ref_in_seq(&mut self, seq: &mut SeqExpr) {
10        if !self.options.collapse_vars {
11            return;
12        }
13
14        if seq.exprs.len() < 2 {
15            return;
16        }
17
18        if let (Expr::Assign(assign @ AssignExpr { op: op!("="), .. }), Expr::Ident(ident)) = (
19            &*seq.exprs[seq.exprs.len() - 2],
20            &*seq.exprs[seq.exprs.len() - 1],
21        ) {
22            // Check if lhs is same as `ident`.
23            if let AssignTarget::Simple(SimpleAssignTarget::Ident(left)) = &assign.left {
24                if left.id.sym == ident.sym && left.id.ctxt == ident.ctxt {
25                    report_change!(
26                        "drop_useless_ident_ref_in_seq: Dropping `{}` as it's useless",
27                        left.id
28                    );
29                    self.changed = true;
30                    seq.exprs.pop();
31                }
32            }
33        }
34    }
35
36    ///
37    /// - `(path += 'foo', path)` => `(path += 'foo')`
38    pub(super) fn shift_assignment(&mut self, e: &mut SeqExpr) {
39        if e.exprs.len() < 2 {
40            return;
41        }
42
43        if let Some(last) = e.exprs.last() {
44            let last_id = match &**last {
45                Expr::Ident(i) => i,
46                _ => return,
47            };
48
49            if let Expr::Assign(assign @ AssignExpr { op: op!("="), .. }) =
50                &*e.exprs[e.exprs.len() - 2]
51            {
52                if let Some(lhs) = assign.left.as_ident() {
53                    if lhs.sym == last_id.sym && lhs.ctxt == last_id.ctxt {
54                        e.exprs.pop();
55                        self.changed = true;
56                        report_change!("sequences: Shifting assignment");
57                    }
58                };
59            }
60        }
61    }
62
63    pub(super) fn shift_void(&mut self, e: &mut SeqExpr) {
64        if e.exprs.len() < 2 {
65            return;
66        }
67
68        if let Expr::Unary(UnaryExpr {
69            op: op!("void"), ..
70        }) = &*e.exprs[e.exprs.len() - 2]
71        {
72            return;
73        }
74
75        if let Some(last) = e.exprs.last() {
76            if is_pure_undefined(self.expr_ctx, last) {
77                self.changed = true;
78                report_change!("sequences: Shifting void");
79
80                e.exprs.pop();
81                let last = e.exprs.last_mut().unwrap();
82
83                *last = UnaryExpr {
84                    span: DUMMY_SP,
85                    op: op!("void"),
86                    arg: last.take(),
87                }
88                .into()
89            }
90        }
91    }
92
93    /// Break assignments in sequences.
94    ///
95    /// This may result in less parenthesis.
96    pub(super) fn break_assignments_in_seqs<T>(&mut self, stmts: &mut Vec<T>)
97    where
98        T: StmtLike,
99    {
100        // TODO
101        if true {
102            return;
103        }
104        let need_work = stmts.iter().any(|stmt| match stmt.as_stmt() {
105            Some(Stmt::Expr(e)) => match &*e.expr {
106                Expr::Seq(seq) => {
107                    seq.exprs.len() > 1
108                        && seq.exprs.iter().all(|expr| {
109                            matches!(&**expr, Expr::Assign(AssignExpr { op: op!("="), .. }))
110                        })
111                }
112                _ => false,
113            },
114
115            _ => false,
116        });
117
118        if !need_work {
119            return;
120        }
121
122        let mut new_stmts = Vec::new();
123
124        for stmt in stmts.take() {
125            match stmt.try_into_stmt() {
126                Ok(stmt) => match stmt {
127                    Stmt::Expr(es)
128                        if match &*es.expr {
129                            Expr::Seq(seq) => {
130                                seq.exprs.len() > 1
131                                    && seq.exprs.iter().all(|expr| {
132                                        matches!(
133                                            &**expr,
134                                            Expr::Assign(AssignExpr { op: op!("="), .. })
135                                        )
136                                    })
137                            }
138                            _ => false,
139                        } =>
140                    {
141                        let span = es.span;
142                        let seq = es.expr.seq().unwrap();
143                        new_stmts.extend(
144                            seq.exprs
145                                .into_iter()
146                                .map(|expr| ExprStmt { span, expr })
147                                .map(Stmt::Expr)
148                                .map(T::from),
149                        );
150                    }
151
152                    _ => {
153                        new_stmts.push(T::from(stmt));
154                    }
155                },
156                Err(stmt) => {
157                    new_stmts.push(stmt);
158                }
159            }
160        }
161        self.changed = true;
162        report_change!(
163            "sequences: Splitted a sequence expression to multiple expression statements"
164        );
165        *stmts = new_stmts;
166    }
167
168    ///
169    /// - `(a, b, c) && d` => `a, b, c && d`
170    pub(super) fn lift_seqs_of_bin(&mut self, e: &mut Expr) {
171        let bin = match e {
172            Expr::Bin(b) => b,
173            _ => return,
174        };
175
176        if let Expr::Seq(left) = &mut *bin.left {
177            if left.exprs.is_empty() {
178                return;
179            }
180
181            self.changed = true;
182            report_change!("sequences: Lifting sequence in a binary expression");
183
184            let left_last = left.exprs.pop().unwrap();
185
186            let mut exprs = left.exprs.take();
187
188            exprs.push(
189                BinExpr {
190                    span: left.span,
191                    op: bin.op,
192                    left: left_last,
193                    right: bin.right.take(),
194                }
195                .into(),
196            );
197
198            *e = SeqExpr {
199                span: bin.span,
200                exprs,
201            }
202            .into()
203        }
204    }
205
206    ///
207    /// - `x = (foo(), bar(), baz()) ? 10 : 20` => `foo(), bar(), x = baz() ? 10
208    ///   : 20;`
209    pub(super) fn lift_seqs_of_cond_assign(&mut self, e: &mut Expr) {
210        if !self.options.sequences() {
211            return;
212        }
213
214        let assign = match e {
215            Expr::Assign(v @ AssignExpr { op: op!("="), .. }) => v,
216            _ => return,
217        };
218
219        let cond = match &mut *assign.right {
220            Expr::Cond(v) => v,
221            _ => return,
222        };
223
224        if let Expr::Seq(test) = &mut *cond.test {
225            //
226            if test.exprs.len() >= 2 {
227                let mut new_seq = Vec::new();
228                new_seq.extend(test.exprs.drain(..test.exprs.len() - 1));
229
230                self.changed = true;
231                report_change!("sequences: Lifting sequences in a assignment with cond expr");
232                let new_cond = CondExpr {
233                    span: cond.span,
234                    test: test.exprs.pop().unwrap(),
235                    cons: cond.cons.take(),
236                    alt: cond.alt.take(),
237                };
238
239                new_seq.push(
240                    AssignExpr {
241                        span: assign.span,
242                        op: assign.op,
243                        left: assign.left.take(),
244                        right: Box::new(new_cond.into()),
245                    }
246                    .into(),
247                );
248
249                *e = SeqExpr {
250                    span: assign.span,
251                    exprs: new_seq,
252                }
253                .into();
254            }
255        }
256    }
257
258    /// `(a = foo, a.apply())` => `(a = foo).apply()`
259    ///
260    /// This is useful for outputs of swc/babel
261    pub(super) fn merge_seq_call(&mut self, e: &mut SeqExpr) {
262        if !self.options.sequences() {
263            return;
264        }
265
266        for idx in 0..e.exprs.len() {
267            let (e1, e2) = e.exprs.split_at_mut(idx);
268
269            let a = match e1.last_mut() {
270                Some(v) => &mut **v,
271                None => continue,
272            };
273
274            let b = match e2.first_mut() {
275                Some(v) => &mut **v,
276                None => continue,
277            };
278
279            if let (
280                Expr::Assign(a_assign @ AssignExpr { op: op!("="), .. }),
281                Expr::Call(CallExpr {
282                    callee: Callee::Expr(b_callee),
283                    args,
284                    ..
285                }),
286            ) = (&mut *a, &mut *b)
287            {
288                let var_name = a_assign.left.as_ident();
289                let var_name = match var_name {
290                    Some(v) => v,
291                    None => continue,
292                };
293
294                match &mut **b_callee {
295                    Expr::Member(MemberExpr {
296                        obj: b_callee_obj,
297                        prop,
298                        ..
299                    }) if prop.is_ident_with("apply") || prop.is_ident_with("call") => {
300                        //
301                        if let Expr::Ident(b_callee_obj) = &**b_callee_obj {
302                            if b_callee_obj.to_id() != var_name.to_id() {
303                                continue;
304                            }
305                        } else {
306                            continue;
307                        }
308
309                        let span = a_assign.span;
310
311                        let obj = Box::new(a.take());
312
313                        let new = CallExpr {
314                            span,
315                            callee: MemberExpr {
316                                span: DUMMY_SP,
317                                obj,
318                                prop: prop.take(),
319                            }
320                            .as_callee(),
321                            args: args.take(),
322                            ..Default::default()
323                        }
324                        .into();
325                        b.take();
326                        self.changed = true;
327                        report_change!(
328                            "sequences: Reducing `(a = foo, a.call())` to `((a = foo).call())`"
329                        );
330
331                        *a = new;
332                    }
333                    _ => (),
334                };
335            }
336        }
337    }
338}