swc_ecma_transforms_optimization/simplify/dce/
mod.rs

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