swc_ecma_transforms_optimization/simplify/dce/
mod.rs

1use std::{borrow::Cow, sync::Arc};
2
3use indexmap::IndexSet;
4use petgraph::{algo::tarjan_scc, Direction::Incoming};
5use rustc_hash::{FxBuildHasher, FxHashMap, FxHashSet};
6use swc_atoms::{atom, Atom};
7use swc_common::{
8    pass::{CompilerPass, Repeated},
9    util::take::Take,
10    Mark, SyntaxContext, DUMMY_SP,
11};
12use swc_ecma_ast::*;
13use swc_ecma_transforms_base::perf::{cpu_count, Parallel};
14use swc_ecma_utils::{
15    collect_decls, find_pat_ids, parallel::ParallelExt, ExprCtx, ExprExt, IsEmpty, ModuleItemLike,
16    StmtLike, Value::Known,
17};
18use swc_ecma_visit::{
19    noop_visit_mut_type, noop_visit_type, visit_mut_pass, Visit, VisitMut, VisitMutWith, VisitWith,
20};
21use swc_fast_graph::digraph::FastDiGraphMap;
22use tracing::{debug, span, Level};
23
24use crate::debug_assert_valid;
25
26/// Note: This becomes parallel if `concurrent` feature is enabled.
27pub fn dce(
28    config: Config,
29    unresolved_mark: Mark,
30) -> impl Pass + VisitMut + Repeated + CompilerPass {
31    visit_mut_pass(TreeShaker {
32        expr_ctx: ExprCtx {
33            unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
34            is_unresolved_ref_safe: false,
35            in_strict: false,
36            remaining_depth: 2,
37        },
38        config,
39        changed: false,
40        pass: 0,
41        in_fn: false,
42        in_block_stmt: false,
43        var_decl_kind: None,
44        data: Default::default(),
45        bindings: Default::default(),
46    })
47}
48
49#[derive(Debug, Clone, PartialEq, Eq, Hash)]
50pub struct Config {
51    /// If this [Mark] is applied to a function expression, it's treated as a
52    /// separate module.
53    ///
54    /// **Note**: This is hack to make operation parallel while allowing invalid
55    /// module produced by the `swc_bundler`.
56    pub module_mark: Option<Mark>,
57
58    /// If true, top-level items will be removed if they are not used.
59    ///
60    /// Defaults to `true`.
61    pub top_level: bool,
62
63    /// Declarations with a symbol in this set will be preserved.
64    pub top_retain: Vec<Atom>,
65
66    /// If false, imports with side effects will be removed.
67    pub preserve_imports_with_side_effects: bool,
68}
69
70impl Default for Config {
71    fn default() -> Self {
72        Self {
73            module_mark: Default::default(),
74            top_level: true,
75            top_retain: Default::default(),
76            preserve_imports_with_side_effects: true,
77        }
78    }
79}
80
81struct TreeShaker {
82    expr_ctx: ExprCtx,
83
84    config: Config,
85    changed: bool,
86    pass: u16,
87
88    in_fn: bool,
89    in_block_stmt: bool,
90    var_decl_kind: Option<VarDeclKind>,
91
92    data: Arc<Data>,
93
94    bindings: Arc<FxHashSet<Id>>,
95}
96
97impl CompilerPass for TreeShaker {
98    fn name(&self) -> Cow<'static, str> {
99        Cow::Borrowed("tree-shaker")
100    }
101}
102
103#[derive(Default)]
104struct Data {
105    used_names: FxHashMap<Id, VarInfo>,
106
107    /// Variable usage graph
108    ///
109    /// We use `u32` because [FastDiGraphMap] stores types as `(N, 1 bit)` so if
110    /// we use u32 it fits into the cache line of cpu.
111    graph: FastDiGraphMap<u32, VarInfo>,
112    /// Entrypoints.
113    entries: FxHashSet<u32>,
114
115    graph_ix: IndexSet<Id, FxBuildHasher>,
116}
117
118impl Data {
119    fn node(&mut self, id: &Id) -> u32 {
120        self.graph_ix.get_index_of(id).unwrap_or_else(|| {
121            let ix = self.graph_ix.len();
122            self.graph_ix.insert_full(id.clone());
123            ix
124        }) as _
125    }
126
127    /// Add an edge to dependency graph
128    fn add_dep_edge(&mut self, from: &Id, to: &Id, assign: bool) {
129        let from = self.node(from);
130        let to = self.node(to);
131
132        match self.graph.edge_weight_mut(from, to) {
133            Some(info) => {
134                if assign {
135                    info.assign += 1;
136                } else {
137                    info.usage += 1;
138                }
139            }
140            None => {
141                self.graph.add_edge(
142                    from,
143                    to,
144                    VarInfo {
145                        usage: u32::from(!assign),
146                        assign: u32::from(assign),
147                    },
148                );
149            }
150        };
151    }
152
153    /// Traverse the graph and subtract usages from `used_names`.
154    fn subtract_cycles(&mut self) {
155        let cycles = tarjan_scc(&self.graph);
156
157        'c: for cycle in cycles {
158            if cycle.len() == 1 {
159                continue;
160            }
161
162            // We have to exclude cycle from remove list if an outer node refences an item
163            // of cycle.
164            for &node in &cycle {
165                // It's referenced by an outer node.
166                if self.entries.contains(&node) {
167                    continue 'c;
168                }
169
170                if self.graph.neighbors_directed(node, Incoming).any(|node| {
171                    // Node in cycle does not matter
172                    !cycle.contains(&node)
173                }) {
174                    continue 'c;
175                }
176            }
177
178            for &i in &cycle {
179                for &j in &cycle {
180                    if i == j {
181                        continue;
182                    }
183
184                    let id = self.graph_ix.get_index(j as _);
185                    let id = match id {
186                        Some(id) => id,
187                        None => continue,
188                    };
189
190                    if let Some(w) = self.graph.edge_weight(i, j) {
191                        let e = self.used_names.entry(id.clone()).or_default();
192                        e.usage -= w.usage;
193                        e.assign -= w.assign;
194                    }
195                }
196            }
197        }
198    }
199}
200
201#[derive(Debug, Default)]
202struct VarInfo {
203    /// This does not include self-references in a function.
204    pub usage: u32,
205    /// This does not include self-references in a function.
206    pub assign: u32,
207}
208
209struct Analyzer<'a> {
210    #[allow(dead_code)]
211    config: &'a Config,
212    in_var_decl: bool,
213    scope: Scope<'a>,
214    data: &'a mut Data,
215    cur_class_id: Option<Id>,
216    cur_fn_id: Option<Id>,
217}
218
219#[derive(Debug, Default)]
220struct Scope<'a> {
221    parent: Option<&'a Scope<'a>>,
222    kind: ScopeKind,
223
224    bindings_affected_by_eval: FxHashSet<Id>,
225    found_direct_eval: bool,
226
227    found_arguemnts: bool,
228    bindings_affected_by_arguements: Vec<Id>,
229
230    /// Used to construct a graph.
231    ///
232    /// This includes all bindings to current node.
233    ast_path: Vec<Id>,
234}
235
236#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
237enum ScopeKind {
238    Fn,
239    ArrowFn,
240}
241
242impl Default for ScopeKind {
243    fn default() -> Self {
244        Self::Fn
245    }
246}
247
248impl Analyzer<'_> {
249    fn with_ast_path<F>(&mut self, ids: Vec<Id>, op: F)
250    where
251        F: for<'aa> FnOnce(&mut Analyzer<'aa>),
252    {
253        let prev_len = self.scope.ast_path.len();
254
255        self.scope.ast_path.extend(ids);
256
257        op(self);
258
259        self.scope.ast_path.truncate(prev_len);
260    }
261
262    fn with_scope<F>(&mut self, kind: ScopeKind, op: F)
263    where
264        F: for<'aa> FnOnce(&mut Analyzer<'aa>),
265    {
266        let child_scope = {
267            let child = Scope {
268                parent: Some(&self.scope),
269                ..Default::default()
270            };
271
272            let mut v = Analyzer {
273                scope: child,
274                data: self.data,
275                cur_fn_id: self.cur_fn_id.clone(),
276                cur_class_id: self.cur_class_id.clone(),
277                ..*self
278            };
279
280            op(&mut v);
281
282            Scope {
283                parent: None,
284                ..v.scope
285            }
286        };
287
288        // If we found eval, mark all declarations in scope and upper as used
289        if child_scope.found_direct_eval {
290            for id in child_scope.bindings_affected_by_eval {
291                self.data.used_names.entry(id).or_default().usage += 1;
292            }
293
294            self.scope.found_direct_eval = true;
295        }
296
297        if child_scope.found_arguemnts {
298            // Parameters
299
300            for id in child_scope.bindings_affected_by_arguements {
301                self.data.used_names.entry(id).or_default().usage += 1;
302            }
303
304            if !matches!(kind, ScopeKind::Fn) {
305                self.scope.found_arguemnts = true;
306            }
307        }
308    }
309
310    /// Mark `id` as used
311    fn add(&mut self, id: Id, assign: bool) {
312        if id.0 == atom!("arguments") {
313            self.scope.found_arguemnts = true;
314        }
315
316        if let Some(f) = &self.cur_fn_id {
317            if id == *f {
318                return;
319            }
320        }
321        if let Some(f) = &self.cur_class_id {
322            if id == *f {
323                return;
324            }
325        }
326
327        if self.scope.is_ast_path_empty() {
328            // Add references from top level items into graph
329            let idx = self.data.node(&id);
330            self.data.entries.insert(idx);
331        } else {
332            let mut scope = Some(&self.scope);
333
334            while let Some(s) = scope {
335                for component in &s.ast_path {
336                    self.data.add_dep_edge(component, &id, assign);
337                }
338
339                if s.kind == ScopeKind::Fn && !s.ast_path.is_empty() {
340                    break;
341                }
342
343                scope = s.parent;
344            }
345        }
346
347        if assign {
348            self.data.used_names.entry(id).or_default().assign += 1;
349        } else {
350            self.data.used_names.entry(id).or_default().usage += 1;
351        }
352    }
353}
354
355impl Visit for Analyzer<'_> {
356    noop_visit_type!();
357
358    fn visit_callee(&mut self, n: &Callee) {
359        n.visit_children_with(self);
360
361        if let Callee::Expr(e) = n {
362            if e.is_ident_ref_to("eval") {
363                self.scope.found_direct_eval = true;
364            }
365        }
366    }
367
368    fn visit_assign_pat_prop(&mut self, n: &AssignPatProp) {
369        n.visit_children_with(self);
370
371        self.add(n.key.to_id(), true);
372    }
373
374    fn visit_class_decl(&mut self, n: &ClassDecl) {
375        self.with_ast_path(vec![n.ident.to_id()], |v| {
376            let old = v.cur_class_id.take();
377            v.cur_class_id = Some(n.ident.to_id());
378            n.visit_children_with(v);
379            v.cur_class_id = old;
380
381            if !n.class.decorators.is_empty() {
382                v.add(n.ident.to_id(), false);
383            }
384        })
385    }
386
387    fn visit_class_expr(&mut self, n: &ClassExpr) {
388        n.visit_children_with(self);
389
390        if !n.class.decorators.is_empty() {
391            if let Some(i) = &n.ident {
392                self.add(i.to_id(), false);
393            }
394        }
395    }
396
397    fn visit_export_named_specifier(&mut self, n: &ExportNamedSpecifier) {
398        if let ModuleExportName::Ident(orig) = &n.orig {
399            self.add(orig.to_id(), false);
400        }
401    }
402
403    fn visit_export_decl(&mut self, n: &ExportDecl) {
404        let name = match &n.decl {
405            Decl::Class(c) => vec![c.ident.to_id()],
406            Decl::Fn(f) => vec![f.ident.to_id()],
407            Decl::Var(v) => v
408                .decls
409                .iter()
410                .flat_map(|d| find_pat_ids(d).into_iter())
411                .collect(),
412            _ => Vec::new(),
413        };
414        for ident in name {
415            self.add(ident, false);
416        }
417
418        n.visit_children_with(self)
419    }
420
421    fn visit_expr(&mut self, e: &Expr) {
422        let old_in_var_decl = self.in_var_decl;
423
424        self.in_var_decl = false;
425        e.visit_children_with(self);
426
427        if let Expr::Ident(i) = e {
428            self.add(i.to_id(), false);
429        }
430
431        self.in_var_decl = old_in_var_decl;
432    }
433
434    fn visit_assign_expr(&mut self, n: &AssignExpr) {
435        match n.op {
436            op!("=") => {
437                if let Some(i) = n.left.as_ident() {
438                    self.add(i.to_id(), true);
439                    n.right.visit_with(self);
440                } else {
441                    n.visit_children_with(self);
442                }
443            }
444            _ => {
445                if let Some(i) = n.left.as_ident() {
446                    self.add(i.to_id(), false);
447                    self.add(i.to_id(), true);
448                    n.right.visit_with(self);
449                } else {
450                    n.visit_children_with(self);
451                }
452            }
453        }
454    }
455
456    fn visit_jsx_element_name(&mut self, e: &JSXElementName) {
457        e.visit_children_with(self);
458
459        if let JSXElementName::Ident(i) = e {
460            self.add(i.to_id(), false);
461        }
462    }
463
464    fn visit_jsx_object(&mut self, e: &JSXObject) {
465        e.visit_children_with(self);
466
467        if let JSXObject::Ident(i) = e {
468            self.add(i.to_id(), false);
469        }
470    }
471
472    fn visit_arrow_expr(&mut self, n: &ArrowExpr) {
473        self.with_scope(ScopeKind::ArrowFn, |v| {
474            n.visit_children_with(v);
475
476            if v.scope.found_direct_eval {
477                v.scope.bindings_affected_by_eval = collect_decls(n);
478            }
479        })
480    }
481
482    fn visit_function(&mut self, n: &Function) {
483        self.with_scope(ScopeKind::Fn, |v| {
484            n.visit_children_with(v);
485
486            if v.scope.found_direct_eval {
487                v.scope.bindings_affected_by_eval = collect_decls(n);
488            }
489
490            if v.scope.found_arguemnts {
491                v.scope.bindings_affected_by_arguements = find_pat_ids(&n.params);
492            }
493        })
494    }
495
496    fn visit_fn_decl(&mut self, n: &FnDecl) {
497        self.with_ast_path(vec![n.ident.to_id()], |v| {
498            let old = v.cur_fn_id.take();
499            v.cur_fn_id = Some(n.ident.to_id());
500            n.visit_children_with(v);
501            v.cur_fn_id = old;
502
503            if !n.function.decorators.is_empty() {
504                v.add(n.ident.to_id(), false);
505            }
506        })
507    }
508
509    fn visit_fn_expr(&mut self, n: &FnExpr) {
510        n.visit_children_with(self);
511
512        if !n.function.decorators.is_empty() {
513            if let Some(i) = &n.ident {
514                self.add(i.to_id(), false);
515            }
516        }
517    }
518
519    fn visit_pat(&mut self, p: &Pat) {
520        p.visit_children_with(self);
521
522        if !self.in_var_decl {
523            if let Pat::Ident(i) = p {
524                self.add(i.to_id(), true);
525            }
526        }
527    }
528
529    fn visit_prop(&mut self, p: &Prop) {
530        p.visit_children_with(self);
531
532        if let Prop::Shorthand(i) = p {
533            self.add(i.to_id(), false);
534        }
535    }
536
537    fn visit_var_declarator(&mut self, n: &VarDeclarator) {
538        let old = self.in_var_decl;
539
540        self.in_var_decl = true;
541        n.name.visit_with(self);
542
543        self.in_var_decl = false;
544        n.init.visit_with(self);
545
546        self.in_var_decl = old;
547    }
548}
549
550impl Repeated for TreeShaker {
551    fn changed(&self) -> bool {
552        self.changed
553    }
554
555    fn reset(&mut self) {
556        self.pass += 1;
557        self.changed = false;
558        self.data = Default::default();
559    }
560}
561
562impl Parallel for TreeShaker {
563    fn create(&self) -> Self {
564        Self {
565            expr_ctx: self.expr_ctx,
566            data: self.data.clone(),
567            config: self.config.clone(),
568            bindings: self.bindings.clone(),
569            ..*self
570        }
571    }
572
573    fn merge(&mut self, other: Self) {
574        self.changed |= other.changed;
575    }
576}
577
578impl TreeShaker {
579    fn visit_mut_stmt_likes<T>(&mut self, stmts: &mut Vec<T>)
580    where
581        T: StmtLike + ModuleItemLike + VisitMutWith<Self> + Send + Sync,
582        Vec<T>: VisitMutWith<Self>,
583    {
584        if let Some(Stmt::Expr(ExprStmt { expr, .. })) = stmts.first().and_then(|s| s.as_stmt()) {
585            if let Expr::Lit(Lit::Str(v)) = &**expr {
586                if &*v.value == "use asm" {
587                    return;
588                }
589            }
590        }
591
592        self.visit_mut_par(cpu_count() * 8, stmts);
593
594        stmts.retain(|s| match s.as_stmt() {
595            Some(Stmt::Empty(..)) => false,
596            Some(Stmt::Block(s)) if s.is_empty() => {
597                debug!("Dropping an empty block statement");
598                false
599            }
600            _ => true,
601        });
602    }
603
604    fn can_drop_binding(&self, name: Id, is_var: bool) -> bool {
605        if !self.config.top_level {
606            if is_var {
607                if !self.in_fn {
608                    return false;
609                }
610            } else if !self.in_block_stmt {
611                return false;
612            }
613        }
614
615        if self.config.top_retain.contains(&name.0) {
616            return false;
617        }
618
619        match self.data.used_names.get(&name) {
620            Some(v) => v.usage == 0 && v.assign == 0,
621            None => true,
622        }
623    }
624
625    fn can_drop_assignment_to(&self, name: Id, is_var: bool) -> bool {
626        if !self.config.top_level {
627            if is_var {
628                if !self.in_fn {
629                    return false;
630                }
631            } else if !self.in_block_stmt {
632                return false;
633            }
634
635            // Abort if the variable is declared on top level scope.
636            let ix = self.data.graph_ix.get_index_of(&name);
637            if let Some(ix) = ix {
638                if self.data.entries.contains(&(ix as u32)) {
639                    return false;
640                }
641            }
642        }
643
644        if self.config.top_retain.contains(&name.0) {
645            return false;
646        }
647
648        self.bindings.contains(&name)
649            && self
650                .data
651                .used_names
652                .get(&name)
653                .map(|v| v.usage == 0)
654                .unwrap_or_default()
655    }
656
657    /// Drops RHS from `null && foo`
658    fn optimize_bin_expr(&mut self, n: &mut Expr) {
659        let Expr::Bin(b) = n else {
660            return;
661        };
662
663        if b.op == op!("&&") && b.left.as_pure_bool(self.expr_ctx) == Known(false) {
664            *n = *b.left.take();
665            self.changed = true;
666            return;
667        }
668
669        if b.op == op!("||") && b.left.as_pure_bool(self.expr_ctx) == Known(true) {
670            *n = *b.left.take();
671            self.changed = true;
672        }
673    }
674
675    fn visit_mut_par<N>(&mut self, threshold: usize, nodes: &mut [N])
676    where
677        N: Send + Sync + VisitMutWith<Self>,
678    {
679        self.maybe_par(threshold, nodes, |v, n| n.visit_mut_with(v));
680    }
681}
682
683impl VisitMut for TreeShaker {
684    noop_visit_mut_type!();
685
686    fn visit_mut_assign_expr(&mut self, n: &mut AssignExpr) {
687        n.visit_mut_children_with(self);
688
689        if let Some(id) = n.left.as_ident() {
690            // TODO: `var`
691            if self.can_drop_assignment_to(id.to_id(), false)
692                && !n.right.may_have_side_effects(self.expr_ctx)
693            {
694                self.changed = true;
695                debug!("Dropping an assignment to `{}` because it's not used", id);
696
697                n.left.take();
698            }
699        }
700    }
701
702    fn visit_mut_block_stmt(&mut self, n: &mut BlockStmt) {
703        let old_in_block_stmt = self.in_block_stmt;
704        self.in_block_stmt = true;
705        n.visit_mut_children_with(self);
706        self.in_block_stmt = old_in_block_stmt;
707    }
708
709    fn visit_mut_class_members(&mut self, members: &mut Vec<ClassMember>) {
710        self.visit_mut_par(cpu_count() * 8, members);
711    }
712
713    fn visit_mut_decl(&mut self, n: &mut Decl) {
714        n.visit_mut_children_with(self);
715
716        match n {
717            Decl::Fn(f) => {
718                if self.can_drop_binding(f.ident.to_id(), true) {
719                    debug!("Dropping function `{}` as it's not used", f.ident);
720                    self.changed = true;
721
722                    n.take();
723                }
724            }
725            Decl::Class(c) => {
726                if self.can_drop_binding(c.ident.to_id(), false)
727                    && c.class
728                        .super_class
729                        .as_deref()
730                        .map_or(true, |e| !e.may_have_side_effects(self.expr_ctx))
731                    && c.class.body.iter().all(|m| match m {
732                        ClassMember::Method(m) => !matches!(m.key, PropName::Computed(..)),
733                        ClassMember::ClassProp(m) => {
734                            !matches!(m.key, PropName::Computed(..))
735                                && !m
736                                    .value
737                                    .as_deref()
738                                    .map_or(false, |e| e.may_have_side_effects(self.expr_ctx))
739                        }
740                        ClassMember::AutoAccessor(m) => {
741                            !matches!(m.key, Key::Public(PropName::Computed(..)))
742                                && !m
743                                    .value
744                                    .as_deref()
745                                    .map_or(false, |e| e.may_have_side_effects(self.expr_ctx))
746                        }
747
748                        ClassMember::PrivateProp(m) => !m
749                            .value
750                            .as_deref()
751                            .map_or(false, |e| e.may_have_side_effects(self.expr_ctx)),
752
753                        ClassMember::StaticBlock(_) => false,
754
755                        ClassMember::TsIndexSignature(_)
756                        | ClassMember::Empty(_)
757                        | ClassMember::Constructor(_)
758                        | ClassMember::PrivateMethod(_) => true,
759                    })
760                {
761                    debug!("Dropping class `{}` as it's not used", c.ident);
762                    self.changed = true;
763
764                    n.take();
765                }
766            }
767            _ => {}
768        }
769    }
770
771    fn visit_mut_export_decl(&mut self, n: &mut ExportDecl) {
772        match &mut n.decl {
773            Decl::Var(v) => {
774                for decl in v.decls.iter_mut() {
775                    decl.init.visit_mut_with(self);
776                }
777            }
778            _ => {
779                // Bypass visit_mut_decl
780                n.decl.visit_mut_children_with(self);
781            }
782        }
783    }
784
785    /// Noop.
786    fn visit_mut_export_default_decl(&mut self, _: &mut ExportDefaultDecl) {}
787
788    fn visit_mut_expr(&mut self, n: &mut Expr) {
789        n.visit_mut_children_with(self);
790
791        self.optimize_bin_expr(n);
792
793        if let Expr::Call(CallExpr {
794            callee: Callee::Expr(callee),
795            args,
796            ..
797        }) = n
798        {
799            //
800            if args.is_empty() {
801                match &mut **callee {
802                    Expr::Fn(FnExpr {
803                        ident: None,
804                        function: f,
805                    }) if matches!(
806                        &**f,
807                        Function {
808                            is_async: false,
809                            is_generator: false,
810                            body: Some(..),
811                            ..
812                        }
813                    ) =>
814                    {
815                        if f.params.is_empty() && f.body.as_ref().unwrap().stmts.len() == 1 {
816                            if let Stmt::Return(ReturnStmt { arg: Some(arg), .. }) =
817                                &mut f.body.as_mut().unwrap().stmts[0]
818                            {
819                                if let Expr::Object(ObjectLit { props, .. }) = &**arg {
820                                    if props.iter().all(|p| match p {
821                                        PropOrSpread::Spread(_) => false,
822                                        PropOrSpread::Prop(p) => match &**p {
823                                            Prop::Shorthand(_) => true,
824                                            Prop::KeyValue(p) => p.value.is_ident(),
825                                            _ => false,
826                                        },
827                                    }) {
828                                        self.changed = true;
829                                        debug!("Dropping a wrapped esm");
830                                        *n = *arg.take();
831                                        return;
832                                    }
833                                }
834                            }
835                        }
836                    }
837                    _ => (),
838                }
839            }
840        }
841
842        if let Expr::Assign(a) = n {
843            if match &a.left {
844                AssignTarget::Simple(l) => l.is_invalid(),
845                AssignTarget::Pat(l) => l.is_invalid(),
846            } {
847                *n = *a.right.take();
848            }
849        }
850
851        if !n.is_invalid() {
852            debug_assert_valid(n);
853        }
854    }
855
856    fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec<ExprOrSpread>) {
857        self.visit_mut_par(cpu_count() * 8, n);
858    }
859
860    fn visit_mut_exprs(&mut self, n: &mut Vec<Box<Expr>>) {
861        self.visit_mut_par(cpu_count() * 8, n);
862    }
863
864    fn visit_mut_for_head(&mut self, n: &mut ForHead) {
865        match n {
866            ForHead::VarDecl(..) | ForHead::UsingDecl(..) => {}
867            ForHead::Pat(v) => {
868                v.visit_mut_with(self);
869            }
870        }
871    }
872
873    fn visit_mut_function(&mut self, n: &mut Function) {
874        let old_in_fn = self.in_fn;
875        self.in_fn = true;
876        n.visit_mut_children_with(self);
877        self.in_fn = old_in_fn;
878    }
879
880    fn visit_mut_import_specifiers(&mut self, ss: &mut Vec<ImportSpecifier>) {
881        ss.retain(|s| {
882            let local = match s {
883                ImportSpecifier::Named(l) => &l.local,
884                ImportSpecifier::Default(l) => &l.local,
885                ImportSpecifier::Namespace(l) => &l.local,
886            };
887
888            if self.can_drop_binding(local.to_id(), false) {
889                debug!(
890                    "Dropping import specifier `{}` because it's not used",
891                    local
892                );
893                self.changed = true;
894                return false;
895            }
896
897            true
898        });
899    }
900
901    fn visit_mut_module(&mut self, m: &mut Module) {
902        debug_assert_valid(m);
903
904        let _tracing = span!(Level::ERROR, "tree-shaker", pass = self.pass).entered();
905
906        if self.bindings.is_empty() {
907            self.bindings = Arc::new(collect_decls(&*m))
908        }
909
910        let mut data = Default::default();
911
912        {
913            let mut analyzer = Analyzer {
914                config: &self.config,
915                in_var_decl: false,
916                scope: Default::default(),
917                data: &mut data,
918                cur_class_id: Default::default(),
919                cur_fn_id: Default::default(),
920            };
921            m.visit_with(&mut analyzer);
922        }
923        data.subtract_cycles();
924        self.data = Arc::new(data);
925
926        m.visit_mut_children_with(self);
927    }
928
929    fn visit_mut_module_item(&mut self, n: &mut ModuleItem) {
930        match n {
931            ModuleItem::ModuleDecl(ModuleDecl::Import(i)) => {
932                let is_for_side_effect = i.specifiers.is_empty();
933
934                i.visit_mut_with(self);
935
936                if !self.config.preserve_imports_with_side_effects
937                    && !is_for_side_effect
938                    && i.specifiers.is_empty()
939                {
940                    debug!("Dropping an import because it's not used");
941                    self.changed = true;
942                    *n = EmptyStmt { span: DUMMY_SP }.into();
943                }
944            }
945            _ => {
946                n.visit_mut_children_with(self);
947            }
948        }
949        debug_assert_valid(n);
950    }
951
952    fn visit_mut_module_items(&mut self, s: &mut Vec<ModuleItem>) {
953        self.visit_mut_stmt_likes(s);
954    }
955
956    fn visit_mut_opt_vec_expr_or_spreads(&mut self, n: &mut Vec<Option<ExprOrSpread>>) {
957        self.visit_mut_par(cpu_count() * 8, n);
958    }
959
960    fn visit_mut_prop_or_spreads(&mut self, n: &mut Vec<PropOrSpread>) {
961        self.visit_mut_par(cpu_count() * 8, n);
962    }
963
964    fn visit_mut_script(&mut self, m: &mut Script) {
965        let _tracing = span!(Level::ERROR, "tree-shaker", pass = self.pass).entered();
966
967        if self.bindings.is_empty() {
968            self.bindings = Arc::new(collect_decls(&*m))
969        }
970
971        let mut data = Default::default();
972
973        {
974            let mut analyzer = Analyzer {
975                config: &self.config,
976                in_var_decl: false,
977                scope: Default::default(),
978                data: &mut data,
979                cur_class_id: Default::default(),
980                cur_fn_id: Default::default(),
981            };
982            m.visit_with(&mut analyzer);
983        }
984        data.subtract_cycles();
985        self.data = Arc::new(data);
986
987        m.visit_mut_children_with(self);
988    }
989
990    fn visit_mut_stmt(&mut self, s: &mut Stmt) {
991        s.visit_mut_children_with(self);
992
993        if let Stmt::Decl(Decl::Var(v)) = s {
994            if v.decls.is_empty() {
995                s.take();
996                return;
997            }
998        }
999
1000        debug_assert_valid(s);
1001
1002        if let Stmt::Decl(Decl::Var(v)) = s {
1003            let span = v.span;
1004            let cnt = v.decls.len();
1005
1006            // If all name is droppable, do so.
1007            if cnt != 0
1008                && v.decls.iter().all(|vd| match &vd.name {
1009                    Pat::Ident(i) => self.can_drop_binding(i.to_id(), v.kind == VarDeclKind::Var),
1010                    _ => false,
1011                })
1012            {
1013                let exprs = v
1014                    .decls
1015                    .take()
1016                    .into_iter()
1017                    .filter_map(|v| v.init)
1018                    .collect::<Vec<_>>();
1019
1020                debug!(
1021                    count = cnt,
1022                    "Dropping names of variables as they are not used",
1023                );
1024                self.changed = true;
1025
1026                if exprs.is_empty() {
1027                    *s = EmptyStmt { span: DUMMY_SP }.into();
1028                    return;
1029                } else {
1030                    *s = ExprStmt {
1031                        span,
1032                        expr: Expr::from_exprs(exprs),
1033                    }
1034                    .into();
1035                }
1036            }
1037        }
1038
1039        if let Stmt::Decl(Decl::Var(v)) = s {
1040            if v.decls.is_empty() {
1041                *s = EmptyStmt { span: DUMMY_SP }.into();
1042            }
1043        }
1044
1045        debug_assert_valid(s);
1046    }
1047
1048    fn visit_mut_stmts(&mut self, s: &mut Vec<Stmt>) {
1049        self.visit_mut_stmt_likes(s);
1050    }
1051
1052    fn visit_mut_unary_expr(&mut self, n: &mut UnaryExpr) {
1053        if matches!(n.op, op!("delete")) {
1054            return;
1055        }
1056        n.visit_mut_children_with(self);
1057    }
1058
1059    #[cfg_attr(feature = "debug", tracing::instrument(skip_all))]
1060    fn visit_mut_using_decl(&mut self, n: &mut UsingDecl) {
1061        for decl in n.decls.iter_mut() {
1062            decl.init.visit_mut_with(self);
1063        }
1064    }
1065
1066    fn visit_mut_var_decl(&mut self, n: &mut VarDecl) {
1067        let old_var_decl_kind = self.var_decl_kind;
1068        self.var_decl_kind = Some(n.kind);
1069        n.visit_mut_children_with(self);
1070        self.var_decl_kind = old_var_decl_kind;
1071    }
1072
1073    fn visit_mut_var_decl_or_expr(&mut self, n: &mut VarDeclOrExpr) {
1074        match n {
1075            VarDeclOrExpr::VarDecl(..) => {}
1076            VarDeclOrExpr::Expr(v) => {
1077                v.visit_mut_with(self);
1078            }
1079        }
1080    }
1081
1082    fn visit_mut_var_declarator(&mut self, v: &mut VarDeclarator) {
1083        v.visit_mut_children_with(self);
1084
1085        if let Pat::Ident(i) = &v.name {
1086            let can_drop = if let Some(init) = &v.init {
1087                !init.may_have_side_effects(self.expr_ctx)
1088            } else {
1089                true
1090            };
1091
1092            if can_drop
1093                && self.can_drop_binding(i.to_id(), self.var_decl_kind == Some(VarDeclKind::Var))
1094            {
1095                self.changed = true;
1096                debug!("Dropping {} because it's not used", i);
1097                v.name.take();
1098            }
1099        }
1100    }
1101
1102    fn visit_mut_var_declarators(&mut self, n: &mut Vec<VarDeclarator>) {
1103        self.visit_mut_par(cpu_count() * 8, n);
1104
1105        n.retain(|v| {
1106            if v.name.is_invalid() {
1107                return false;
1108            }
1109
1110            true
1111        });
1112    }
1113
1114    fn visit_mut_with_stmt(&mut self, n: &mut WithStmt) {
1115        n.obj.visit_mut_with(self);
1116    }
1117}
1118
1119impl Scope<'_> {
1120    /// Returns true if it's not in a function or class.
1121    fn is_ast_path_empty(&self) -> bool {
1122        if !self.ast_path.is_empty() {
1123            return false;
1124        }
1125        match &self.parent {
1126            Some(p) => p.is_ast_path_empty(),
1127            None => true,
1128        }
1129    }
1130}