swc_ecma_minifier/compress/
hoist_decls.rs

1use par_iter::prelude::*;
2use rustc_hash::FxHashSet;
3use swc_common::{pass::Repeated, util::take::Take, DUMMY_SP};
4use swc_ecma_ast::*;
5use swc_ecma_usage_analyzer::analyzer::UsageAnalyzer;
6use swc_ecma_utils::{find_pat_ids, StmtLike};
7use swc_ecma_visit::{noop_visit_mut_type, VisitMut, VisitMutWith, VisitWith};
8
9use super::util::drop_invalid_stmts;
10use crate::{
11    program_data::{ProgramData, VarUsageInfoFlags},
12    util::{is_hoisted_var_decl_without_init, sort::is_sorted_by, IsModuleItem, ModuleItemExt},
13};
14
15pub(super) struct DeclHoisterConfig {
16    pub hoist_fns: bool,
17    pub hoist_vars: bool,
18    pub _top_level: bool,
19}
20
21pub(super) fn decl_hoister(config: DeclHoisterConfig, data: &ProgramData) -> Hoister {
22    Hoister {
23        config,
24        changed: false,
25        data,
26    }
27}
28
29pub(super) struct Hoister<'a> {
30    config: DeclHoisterConfig,
31    changed: bool,
32    data: &'a ProgramData,
33}
34
35impl Repeated for Hoister<'_> {
36    fn changed(&self) -> bool {
37        self.changed
38    }
39
40    fn reset(&mut self) {
41        self.changed = false;
42    }
43}
44
45impl Hoister<'_> {
46    fn handle_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
47    where
48        T: StmtLike + IsModuleItem + ModuleItemExt,
49        Vec<T>: for<'aa> VisitMutWith<Hoister<'aa>> + VisitWith<UsageAnalyzer<ProgramData>>,
50    {
51        stmts.visit_mut_children_with(self);
52        let len = stmts.len();
53        let should_hoist = !is_sorted_by(
54            stmts.iter().map(|stmt| match stmt.as_stmt() {
55                Some(stmt) => match stmt {
56                    Stmt::Decl(Decl::Fn(..)) if self.config.hoist_fns => 1,
57                    Stmt::Decl(Decl::Var(var)) if self.config.hoist_vars => {
58                        let ids: Vec<Id> = find_pat_ids(&var.decls);
59
60                        if ids.iter().any(|id| {
61                            self.data
62                                .vars
63                                .get(id)
64                                .map(|v| !v.flags.contains(VarUsageInfoFlags::USED_ABOVE_DECL))
65                                .unwrap_or(false)
66                        }) {
67                            2
68                        } else {
69                            3
70                        }
71                    }
72                    _ => 3,
73                },
74                None => 3,
75            }),
76            PartialOrd::partial_cmp,
77        ) || (self.config.hoist_vars
78            && if len >= *crate::LIGHT_TASK_PARALLELS {
79                stmts.par_chunks(2).any(|stmts| {
80                    is_hoisted_var_decl_without_init(&stmts[0])
81                        && is_hoisted_var_decl_without_init(&stmts[1])
82                })
83            } else {
84                stmts.windows(2).any(|stmts| {
85                    is_hoisted_var_decl_without_init(&stmts[0])
86                        && is_hoisted_var_decl_without_init(&stmts[1])
87                })
88            });
89
90        if !should_hoist {
91            return;
92        }
93        self.changed = true;
94
95        let mut var_decls = Vec::new();
96        let mut fn_decls = Vec::with_capacity(stmts.len());
97        let mut new_stmts = Vec::with_capacity(stmts.len());
98        let mut done = FxHashSet::default();
99
100        let mut found_non_var_decl = false;
101        for stmt in stmts.take() {
102            match stmt.try_into_stmt() {
103                Ok(stmt) => {
104                    // Seaarch for variable declarations.
105                    match stmt {
106                        Stmt::Decl(Decl::Fn(..)) if self.config.hoist_fns => {
107                            // Move functions to top.
108                            fn_decls.push(T::from(stmt))
109                        }
110
111                        Stmt::Decl(Decl::Var(var))
112                            if matches!(
113                                &*var,
114                                VarDecl {
115                                    kind: VarDeclKind::Var,
116                                    ..
117                                }
118                            ) && found_non_var_decl =>
119                        {
120                            let mut exprs = Vec::new();
121                            for decl in var.decls {
122                                let ids: Vec<Ident> = find_pat_ids(&decl.name);
123
124                                for id in ids {
125                                    if done.insert(id.to_id()) {
126                                        // If the enclosing function declares parameter with same
127                                        // name, we can drop a varaible.
128                                        if decl.init.is_none()
129                                            && self
130                                                .data
131                                                .vars
132                                                .get(&id.to_id())
133                                                .map(|v| {
134                                                    v.flags.contains(
135                                                        VarUsageInfoFlags::DECLARED_AS_FN_PARAM,
136                                                    )
137                                                })
138                                                .unwrap_or(false)
139                                        {
140                                            continue;
141                                        }
142
143                                        var_decls.push(VarDeclarator {
144                                            span: DUMMY_SP,
145                                            name: id.into(),
146                                            init: None,
147                                            definite: false,
148                                        })
149                                    }
150                                }
151
152                                if let Some(init) = decl.init {
153                                    //
154                                    exprs.push(
155                                        AssignExpr {
156                                            span: decl.span,
157                                            left: decl.name.try_into().unwrap(),
158                                            op: op!("="),
159                                            right: init,
160                                        }
161                                        .into(),
162                                    );
163                                }
164                            }
165
166                            if exprs.is_empty() {
167                                continue;
168                            }
169                            new_stmts.push(T::from(
170                                ExprStmt {
171                                    span: var.span,
172                                    expr: if exprs.len() == 1 {
173                                        exprs.into_iter().next().unwrap()
174                                    } else {
175                                        SeqExpr {
176                                            span: DUMMY_SP,
177                                            exprs,
178                                        }
179                                        .into()
180                                    },
181                                }
182                                .into(),
183                            ))
184                        }
185
186                        Stmt::Decl(Decl::Var(v))
187                            if matches!(
188                                &*v,
189                                VarDecl {
190                                    kind: VarDeclKind::Var,
191                                    ..
192                                }
193                            ) =>
194                        {
195                            // It can be merged because we didn't found normal statement.
196                            //
197                            // Code like
198                            //
199                            // var a = 1;
200                            // var b = 3;
201                            //
202                            // will be merged.
203                            var_decls.extend(v.decls.into_iter().filter(|decl| {
204                                // We should preserve if init exists because
205                                //
206                                // var a = 2, a = 3;
207                                //
208                                // is valid javascript code.
209
210                                let preserve = match &decl.name {
211                                    Pat::Ident(name) => {
212                                        // If the enclosing function declares parameter with same
213                                        // name, we can drop a varaible. (If it's side-effect free).
214                                        if decl.init.is_none()
215                                            && self
216                                                .data
217                                                .vars
218                                                .get(&name.to_id())
219                                                .map(|v| {
220                                                    v.flags.contains(
221                                                        VarUsageInfoFlags::DECLARED_AS_FN_PARAM,
222                                                    )
223                                                })
224                                                .unwrap_or(false)
225                                        {
226                                            return false;
227                                        }
228
229                                        done.insert(name.to_id())
230                                    }
231                                    _ => true,
232                                };
233
234                                preserve || decl.init.is_some()
235                            }));
236                        }
237
238                        Stmt::Decl(Decl::Var(..)) => new_stmts.push(T::from(stmt)),
239                        _ => {
240                            if let Stmt::Throw(..) = stmt {
241                                fn_decls.push(T::from(
242                                    VarDecl {
243                                        span: DUMMY_SP,
244                                        kind: VarDeclKind::Var,
245                                        declare: false,
246                                        decls: var_decls.take(),
247                                        ..Default::default()
248                                    }
249                                    .into(),
250                                ));
251                            }
252                            found_non_var_decl = true;
253                            new_stmts.push(T::from(stmt))
254                        }
255                    }
256                }
257                Err(stmt) => new_stmts.push(stmt),
258            }
259        }
260
261        fn_decls.push(T::from(
262            VarDecl {
263                span: DUMMY_SP,
264                kind: VarDeclKind::Var,
265                declare: false,
266                decls: var_decls,
267                ..Default::default()
268            }
269            .into(),
270        ));
271        fn_decls.extend(new_stmts);
272
273        drop_invalid_stmts(&mut fn_decls);
274
275        *stmts = fn_decls;
276    }
277}
278
279impl VisitMut for Hoister<'_> {
280    noop_visit_mut_type!(fail);
281
282    fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
283        self.handle_stmt_likes(stmts);
284    }
285
286    fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
287        self.handle_stmt_likes(stmts);
288    }
289}