swc_ecma_minifier/compress/pure/
vars.rs

1use rustc_hash::FxHashSet;
2use swc_common::{util::take::Take, DUMMY_SP};
3use swc_ecma_ast::*;
4use swc_ecma_utils::{prepend_stmt, StmtLike};
5use swc_ecma_visit::{
6    noop_visit_mut_type, noop_visit_type, Visit, VisitMut, VisitMutWith, VisitWith,
7};
8
9use super::Pure;
10use crate::{
11    compress::util::{drop_invalid_stmts, is_directive},
12    util::ModuleItemExt,
13};
14
15impl Pure<'_> {
16    /// Join variables.
17    ///
18    /// This method may move variables to head of for statements like
19    ///
20    /// `var a; for(var b;;);` => `for(var a, b;;);`
21    pub(super) fn join_vars<T>(&mut self, stmts: &mut Vec<T>)
22    where
23        T: ModuleItemExt,
24    {
25        if !self.options.join_vars {
26            return;
27        }
28
29        {
30            // Check if we can join variables.
31
32            let can_work =
33                stmts
34                    .windows(2)
35                    .any(|stmts| match (stmts[0].as_stmt(), stmts[1].as_stmt()) {
36                        (Some(Stmt::Decl(Decl::Var(l))), Some(r)) => match r {
37                            Stmt::Decl(Decl::Var(r)) => l.kind == r.kind,
38                            Stmt::For(ForStmt { init: None, .. }) => l.kind == VarDeclKind::Var,
39                            Stmt::For(ForStmt {
40                                init: Some(VarDeclOrExpr::VarDecl(v)),
41                                ..
42                            }) if matches!(
43                                &**v,
44                                VarDecl {
45                                    kind: VarDeclKind::Var,
46                                    ..
47                                },
48                            ) =>
49                            {
50                                l.kind == VarDeclKind::Var
51                            }
52                            _ => false,
53                        },
54                        _ => false,
55                    });
56
57            if !can_work {
58                return;
59            }
60        }
61
62        report_change!("join_vars: Joining variables");
63        self.changed = true;
64
65        let mut cur: Option<Box<VarDecl>> = None;
66
67        let mut new: Vec<T> = Vec::with_capacity(stmts.len() * 2 + 1);
68        stmts.take().into_iter().for_each(|stmt| {
69            match stmt.try_into_stmt() {
70                Ok(stmt) => {
71                    if is_directive(&stmt) {
72                        return new.push(T::from(stmt));
73                    }
74
75                    match stmt {
76                        Stmt::Decl(Decl::Var(var)) => match &mut cur {
77                            Some(v) if var.kind == v.kind => {
78                                v.decls.extend(var.decls);
79                            }
80                            _ => {
81                                if let Some(s) = cur.take().map(|c| c.into()) {
82                                    new.push(T::from(s));
83                                }
84                                cur = Some(var);
85                            }
86                        },
87                        Stmt::For(mut stmt) => match &mut stmt.init {
88                            Some(VarDeclOrExpr::VarDecl(var))
89                                if matches!(
90                                    &**var,
91                                    VarDecl {
92                                        kind: VarDeclKind::Var,
93                                        ..
94                                    }
95                                ) =>
96                            {
97                                match &mut cur {
98                                    Some(cur) if cur.kind == var.kind => {
99                                        // Merge
100                                        cur.decls.append(&mut var.decls);
101                                        var.decls = cur.decls.take();
102
103                                        new.push(T::from(stmt.into()));
104                                    }
105                                    _ => {
106                                        if let Some(s) = cur.take() {
107                                            new.push(T::from(s.into()));
108                                        }
109                                        new.push(T::from(stmt.into()));
110                                    }
111                                }
112                            }
113                            None if cur
114                                .as_ref()
115                                .map(|v| v.kind == VarDeclKind::Var)
116                                .unwrap_or(true) =>
117                            {
118                                stmt.init = cur
119                                    .take()
120                                    .and_then(|v| if v.decls.is_empty() { None } else { Some(v) })
121                                    .map(VarDeclOrExpr::VarDecl);
122
123                                new.push(T::from(stmt.into()));
124                            }
125                            _ => {
126                                if let Some(s) = cur.take() {
127                                    new.push(T::from(s.into()));
128                                }
129                                new.push(T::from(stmt.into()));
130                            }
131                        },
132                        _ => {
133                            if let Some(s) = cur.take() {
134                                new.push(T::from(s.into()));
135                            }
136                            new.push(T::from(stmt));
137                        }
138                    }
139                }
140                Err(item) => {
141                    if let Some(s) = cur.take() {
142                        new.push(T::from(s.into()));
143                    }
144                    new.push(item);
145                }
146            }
147        });
148
149        if let Some(s) = cur.take() {
150            new.push(T::from(s.into()));
151        }
152
153        drop_invalid_stmts(&mut new);
154
155        *stmts = new;
156    }
157
158    /// TypeScript namespace results in lots of `var ts` declarations.
159    pub(super) fn remove_duplicate_vars(&mut self, vars: &mut Vec<VarDeclarator>) {
160        let mut found = FxHashSet::default();
161
162        vars.retain(|v| {
163            if v.init.is_some() {
164                return true;
165            }
166
167            match &v.name {
168                Pat::Ident(i) => found.insert(i.to_id()),
169                _ => true,
170            }
171        })
172    }
173
174    /// Collapse single-use non-constant variables, side effects permitting.
175    ///
176    /// This merges all variables to first variable declartion with an
177    /// initializer. If such variable declaration is not found, variables are
178    /// prepended to `stmts`.
179    pub(super) fn collapse_vars_without_init<T>(&mut self, stmts: &mut Vec<T>, target: VarDeclKind)
180    where
181        T: StmtLike,
182        Vec<T>:
183            VisitWith<VarWithOutInitCounter> + VisitMutWith<VarMover> + VisitMutWith<VarPrepender>,
184    {
185        if !self.options.collapse_vars {
186            return;
187        }
188
189        {
190            let mut need_work = false;
191            let mut found_vars_without_init = false;
192            let mut found_other = false;
193            let if_need_work = stmts.iter().any(|stmt| {
194                match stmt.as_stmt() {
195                    Some(Stmt::Decl(Decl::Var(v))) if v.kind == target => {
196                        if !(found_other && found_vars_without_init)
197                            && v.decls.iter().all(|v| v.init.is_none())
198                        {
199                            if found_other {
200                                need_work = true;
201                            }
202
203                            found_vars_without_init = true;
204                        } else {
205                            if found_vars_without_init && self.options.join_vars {
206                                need_work = true;
207                            }
208                            found_other = true;
209                        }
210                    }
211
212                    // Directives
213                    Some(Stmt::Expr(s)) => match &*s.expr {
214                        Expr::Lit(Lit::Str(..)) => {}
215                        _ => {
216                            found_other = true;
217                        }
218                    },
219
220                    _ => {
221                        found_other = true;
222                    }
223                }
224                need_work
225            });
226
227            // Check for nested variable declartions.
228            let visitor_need_work = if target == VarDeclKind::Var {
229                let mut v = VarWithOutInitCounter {
230                    target,
231                    need_work: Default::default(),
232                    found_var_without_init: Default::default(),
233                    found_var_with_init: Default::default(),
234                };
235                stmts.visit_with(&mut v);
236                v.need_work
237            } else {
238                false
239            };
240            if !if_need_work && !visitor_need_work {
241                return;
242            }
243        }
244
245        // TODO(kdy1): Fix this. This results in an infinite loop.
246        // self.changed = true;
247        report_change!("collapse_vars: Collapsing variables without an initializer");
248
249        let vars = {
250            let mut v = VarMover {
251                target,
252                vars: Default::default(),
253                var_decl_kind: Default::default(),
254            };
255            stmts.visit_mut_with(&mut v);
256
257            v.vars
258        };
259
260        // Prepend vars
261
262        let mut prepender = VarPrepender { target, vars };
263        if target == VarDeclKind::Var {
264            stmts.visit_mut_with(&mut prepender);
265        }
266
267        if !prepender.vars.is_empty() {
268            match stmts.get_mut(0).and_then(|v| v.as_stmt_mut()) {
269                Some(Stmt::Decl(Decl::Var(v))) if v.kind == target => {
270                    prepender.vars.append(&mut v.decls);
271                    v.decls = prepender.vars;
272                }
273                _ => {
274                    prepend_stmt(
275                        stmts,
276                        T::from(
277                            VarDecl {
278                                span: DUMMY_SP,
279                                kind: target,
280                                declare: Default::default(),
281                                decls: prepender.vars,
282                                ..Default::default()
283                            }
284                            .into(),
285                        ),
286                    );
287                }
288            }
289        }
290    }
291}
292
293/// See if there's two [VarDecl] which has [VarDeclarator] without the
294/// initializer.
295pub(super) struct VarWithOutInitCounter {
296    target: VarDeclKind,
297    need_work: bool,
298    found_var_without_init: bool,
299    found_var_with_init: bool,
300}
301
302impl Visit for VarWithOutInitCounter {
303    noop_visit_type!(fail);
304
305    fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
306
307    fn visit_constructor(&mut self, _: &Constructor) {}
308
309    fn visit_function(&mut self, _: &Function) {}
310
311    fn visit_getter_prop(&mut self, _: &GetterProp) {}
312
313    fn visit_setter_prop(&mut self, _: &SetterProp) {}
314
315    fn visit_var_decl(&mut self, v: &VarDecl) {
316        v.visit_children_with(self);
317
318        if v.kind != self.target {
319            return;
320        }
321
322        let mut found_init = false;
323        for d in &v.decls {
324            if d.init.is_some() {
325                found_init = true;
326            } else {
327                if found_init {
328                    self.need_work = true;
329                    return;
330                }
331            }
332        }
333
334        if v.decls.iter().all(|v| v.init.is_none()) {
335            if self.found_var_without_init || self.found_var_with_init {
336                self.need_work = true;
337            }
338            self.found_var_without_init = true;
339        } else {
340            self.found_var_with_init = true;
341        }
342    }
343
344    fn visit_module_item(&mut self, s: &ModuleItem) {
345        if let ModuleItem::Stmt(_) = s {
346            s.visit_children_with(self);
347        }
348    }
349
350    fn visit_block_stmt(&mut self, n: &BlockStmt) {
351        if self.target != VarDeclKind::Var {
352            // noop
353            return;
354        }
355
356        n.visit_children_with(self);
357    }
358
359    fn visit_for_head(&mut self, _: &ForHead) {}
360}
361
362/// Moves all variable without initializer.
363pub(super) struct VarMover {
364    target: VarDeclKind,
365
366    vars: Vec<VarDeclarator>,
367    var_decl_kind: Option<VarDeclKind>,
368}
369
370impl VisitMut for VarMover {
371    noop_visit_mut_type!(fail);
372
373    /// Noop
374    fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
375
376    fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
377        if self.target != VarDeclKind::Var {
378            // noop
379            return;
380        }
381
382        n.visit_mut_children_with(self);
383    }
384
385    /// Noop
386    fn visit_mut_constructor(&mut self, _: &mut Constructor) {}
387
388    fn visit_mut_for_head(&mut self, _: &mut ForHead) {}
389
390    /// Noop
391    fn visit_mut_function(&mut self, _: &mut Function) {}
392
393    fn visit_mut_getter_prop(&mut self, _: &mut GetterProp) {}
394
395    fn visit_mut_module_decl(&mut self, _: &mut ModuleDecl) {}
396
397    fn visit_mut_module_item(&mut self, s: &mut ModuleItem) {
398        if let ModuleItem::Stmt(_) = s {
399            s.visit_mut_children_with(self);
400        }
401    }
402
403    fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option<VarDeclOrExpr>) {
404        n.visit_mut_children_with(self);
405
406        if let Some(VarDeclOrExpr::VarDecl(var)) = n {
407            if var.decls.is_empty() {
408                *n = None;
409            }
410        }
411    }
412
413    fn visit_mut_setter_prop(&mut self, _: &mut SetterProp) {}
414
415    fn visit_mut_stmt(&mut self, s: &mut Stmt) {
416        s.visit_mut_children_with(self);
417
418        match s {
419            Stmt::Decl(Decl::Var(v)) if v.decls.is_empty() => {
420                s.take();
421            }
422            _ => {}
423        }
424    }
425
426    fn visit_mut_stmts(&mut self, s: &mut Vec<Stmt>) {
427        s.visit_mut_children_with(self);
428
429        s.retain(|s| !matches!(s, Stmt::Empty(..)));
430    }
431
432    fn visit_mut_var_decl(&mut self, v: &mut VarDecl) {
433        let old = self.var_decl_kind.take();
434        self.var_decl_kind = Some(v.kind);
435        v.visit_mut_children_with(self);
436        self.var_decl_kind = old;
437    }
438
439    fn visit_mut_var_declarators(&mut self, d: &mut Vec<VarDeclarator>) {
440        d.visit_mut_children_with(self);
441
442        if self.var_decl_kind != Some(self.target) {
443            return;
444        }
445
446        if d.iter().all(|v| v.init.is_some()) {
447            return;
448        }
449
450        let has_init = d.iter().any(|v| v.init.is_some());
451
452        if has_init {
453            if self.target == VarDeclKind::Let {
454                return;
455            }
456
457            let mut new = Vec::with_capacity(d.len());
458
459            d.take().into_iter().for_each(|v| {
460                if v.init.is_some() {
461                    new.push(v)
462                } else {
463                    self.vars.push(v)
464                }
465            });
466
467            *d = new;
468        }
469
470        let mut new = Vec::new();
471
472        if has_init {
473            new.append(&mut self.vars);
474        }
475
476        d.take().into_iter().for_each(|v| {
477            if v.init.is_some() {
478                new.push(v)
479            } else {
480                self.vars.push(v)
481            }
482        });
483
484        *d = new;
485    }
486}
487
488pub(super) struct VarPrepender {
489    target: VarDeclKind,
490
491    vars: Vec<VarDeclarator>,
492}
493
494impl VisitMut for VarPrepender {
495    noop_visit_mut_type!(fail);
496
497    /// Noop
498    fn visit_mut_arrow_expr(&mut self, _: &mut ArrowExpr) {}
499
500    /// Noop
501    fn visit_mut_constructor(&mut self, _: &mut Constructor) {}
502
503    /// Noop
504    fn visit_mut_function(&mut self, _: &mut Function) {}
505
506    fn visit_mut_getter_prop(&mut self, _: &mut GetterProp) {}
507
508    fn visit_mut_setter_prop(&mut self, _: &mut SetterProp) {}
509
510    fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
511        if self.target != VarDeclKind::Var {
512            // noop
513            return;
514        }
515
516        n.visit_mut_children_with(self);
517    }
518
519    fn visit_mut_var_decl(&mut self, v: &mut VarDecl) {
520        if self.vars.is_empty() {
521            return;
522        }
523
524        if v.kind != self.target {
525            return;
526        }
527
528        if v.decls.iter().any(|d| d.init.is_some()) {
529            let mut decls = self.vars.take();
530            decls.extend(v.decls.take());
531
532            v.decls = decls;
533        }
534    }
535
536    fn visit_mut_module_decl(&mut self, _: &mut ModuleDecl) {}
537}