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                        #[cfg(swc_ast_unknown)]
863                        _ => panic!("unable to access unknown nodes"),
864                    })
865                {
866                    debug!("Dropping class `{}` as it's not used", c.ident);
867                    self.changed = true;
868
869                    self.data.drop_ast_node(&*c);
870                    n.take();
871                }
872            }
873            _ => {}
874        }
875    }
876
877    fn visit_mut_export_decl(&mut self, n: &mut ExportDecl) {
878        match &mut n.decl {
879            Decl::Var(v) => {
880                for decl in v.decls.iter_mut() {
881                    decl.init.visit_mut_with(self);
882                }
883            }
884            _ => {
885                // Bypass visit_mut_decl
886                n.decl.visit_mut_children_with(self);
887            }
888        }
889    }
890
891    /// Noop.
892    fn visit_mut_export_default_decl(&mut self, _: &mut ExportDefaultDecl) {}
893
894    fn visit_mut_expr(&mut self, n: &mut Expr) {
895        n.visit_mut_children_with(self);
896
897        self.optimize_bin_expr(n);
898
899        if let Expr::Call(CallExpr {
900            callee: Callee::Expr(callee),
901            args,
902            ..
903        }) = n
904        {
905            //
906            if args.is_empty() {
907                match &mut **callee {
908                    Expr::Fn(FnExpr {
909                        ident: None,
910                        function: f,
911                    }) if matches!(
912                        &**f,
913                        Function {
914                            is_async: false,
915                            is_generator: false,
916                            body: Some(..),
917                            ..
918                        }
919                    ) =>
920                    {
921                        if f.params.is_empty() && f.body.as_ref().unwrap().stmts.len() == 1 {
922                            if let Stmt::Return(ReturnStmt { arg: Some(arg), .. }) =
923                                &mut f.body.as_mut().unwrap().stmts[0]
924                            {
925                                if let Expr::Object(ObjectLit { props, .. }) = &**arg {
926                                    if props.iter().all(|p| match p {
927                                        PropOrSpread::Spread(_) => false,
928                                        PropOrSpread::Prop(p) => match &**p {
929                                            Prop::Shorthand(_) => true,
930                                            Prop::KeyValue(p) => p.value.is_ident(),
931                                            _ => false,
932                                        },
933                                        #[cfg(swc_ast_unknown)]
934                                        _ => panic!("unable to access unknown nodes"),
935                                    }) {
936                                        self.changed = true;
937                                        debug!("Dropping a wrapped esm");
938                                        *n = *arg.take();
939                                        return;
940                                    }
941                                }
942                            }
943                        }
944                    }
945                    _ => (),
946                }
947            }
948        }
949
950        if let Expr::Assign(a) = n {
951            if match &a.left {
952                AssignTarget::Simple(l) => l.is_invalid(),
953                AssignTarget::Pat(l) => l.is_invalid(),
954                #[cfg(swc_ast_unknown)]
955                _ => panic!("unable to access unknown nodes"),
956            } {
957                *n = *a.right.take();
958            }
959        }
960
961        if !n.is_invalid() {
962            debug_assert_valid(n);
963        }
964    }
965
966    fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec<ExprOrSpread>) {
967        self.visit_mut_par(cpu_count() * 8, n);
968    }
969
970    fn visit_mut_exprs(&mut self, n: &mut Vec<Box<Expr>>) {
971        self.visit_mut_par(cpu_count() * 8, n);
972    }
973
974    fn visit_mut_for_head(&mut self, n: &mut ForHead) {
975        match n {
976            ForHead::VarDecl(..) | ForHead::UsingDecl(..) => {}
977            ForHead::Pat(v) => {
978                v.visit_mut_with(self);
979            }
980            #[cfg(swc_ast_unknown)]
981            _ => panic!("unable to access unknown nodes"),
982        }
983    }
984
985    fn visit_mut_function(&mut self, n: &mut Function) {
986        let old_in_fn = self.in_fn;
987        self.in_fn = true;
988        n.visit_mut_children_with(self);
989        self.in_fn = old_in_fn;
990    }
991
992    fn visit_mut_import_specifiers(&mut self, ss: &mut Vec<ImportSpecifier>) {
993        ss.retain(|s| {
994            let local = match s {
995                ImportSpecifier::Named(l) => &l.local,
996                ImportSpecifier::Default(l) => &l.local,
997                ImportSpecifier::Namespace(l) => &l.local,
998                #[cfg(swc_ast_unknown)]
999                _ => panic!("unable to access unknown nodes"),
1000            };
1001
1002            if self.can_drop_binding(local.to_id(), false) {
1003                debug!(
1004                    "Dropping import specifier `{}` because it's not used",
1005                    local
1006                );
1007                self.changed = true;
1008                return false;
1009            }
1010
1011            true
1012        });
1013    }
1014
1015    fn visit_mut_module(&mut self, m: &mut Module) {
1016        debug_assert_valid(m);
1017
1018        let _tracing = span!(Level::ERROR, "tree-shaker", pass = self.pass).entered();
1019
1020        if !self.data.initialized {
1021            let mut data = Data {
1022                initialized: true,
1023                ..Default::default()
1024            };
1025
1026            {
1027                let mut analyzer = Analyzer {
1028                    config: &self.config,
1029                    in_var_decl: false,
1030                    scope: Default::default(),
1031                    data: &mut data,
1032                    cur_class_id: Default::default(),
1033                    cur_fn_id: Default::default(),
1034                };
1035                m.visit_with(&mut analyzer);
1036            }
1037            data.subtract_cycles();
1038            self.data = data;
1039        } else {
1040            self.data.subtract_cycles();
1041        }
1042
1043        m.visit_mut_children_with(self);
1044    }
1045
1046    fn visit_mut_module_item(&mut self, n: &mut ModuleItem) {
1047        match n {
1048            ModuleItem::ModuleDecl(ModuleDecl::Import(i)) => {
1049                let is_for_side_effect = i.specifiers.is_empty();
1050
1051                i.visit_mut_with(self);
1052
1053                if !self.config.preserve_imports_with_side_effects
1054                    && !is_for_side_effect
1055                    && i.specifiers.is_empty()
1056                {
1057                    debug!("Dropping an import because it's not used");
1058                    self.changed = true;
1059                    *n = EmptyStmt { span: DUMMY_SP }.into();
1060                }
1061            }
1062            _ => {
1063                n.visit_mut_children_with(self);
1064            }
1065        }
1066        debug_assert_valid(n);
1067    }
1068
1069    fn visit_mut_module_items(&mut self, s: &mut Vec<ModuleItem>) {
1070        self.visit_mut_stmt_likes(s);
1071    }
1072
1073    fn visit_mut_opt_vec_expr_or_spreads(&mut self, n: &mut Vec<Option<ExprOrSpread>>) {
1074        self.visit_mut_par(cpu_count() * 8, n);
1075    }
1076
1077    fn visit_mut_prop_or_spreads(&mut self, n: &mut Vec<PropOrSpread>) {
1078        self.visit_mut_par(cpu_count() * 8, n);
1079    }
1080
1081    fn visit_mut_script(&mut self, m: &mut Script) {
1082        let _tracing = span!(Level::ERROR, "tree-shaker", pass = self.pass).entered();
1083
1084        if !self.data.initialized {
1085            let mut data = Data {
1086                initialized: true,
1087                ..Default::default()
1088            };
1089
1090            {
1091                let mut analyzer = Analyzer {
1092                    config: &self.config,
1093                    in_var_decl: false,
1094                    scope: Default::default(),
1095                    data: &mut data,
1096                    cur_class_id: Default::default(),
1097                    cur_fn_id: Default::default(),
1098                };
1099                m.visit_with(&mut analyzer);
1100            }
1101            data.subtract_cycles();
1102            self.data = data;
1103        } else {
1104            self.data.subtract_cycles();
1105        }
1106
1107        m.visit_mut_children_with(self);
1108    }
1109
1110    fn visit_mut_stmt(&mut self, s: &mut Stmt) {
1111        s.visit_mut_children_with(self);
1112
1113        if let Stmt::Decl(Decl::Var(v)) = s {
1114            if v.decls.is_empty() {
1115                s.take();
1116                return;
1117            }
1118        }
1119
1120        debug_assert_valid(s);
1121
1122        if let Stmt::Decl(Decl::Var(v)) = s {
1123            let span = v.span;
1124            let cnt = v.decls.len();
1125
1126            // If all name is droppable, do so.
1127            if cnt != 0
1128                && v.decls.iter().all(|vd| match &vd.name {
1129                    Pat::Ident(i) => self.can_drop_binding(i.to_id(), v.kind == VarDeclKind::Var),
1130                    _ => false,
1131                })
1132            {
1133                for decl in v.decls.iter() {
1134                    self.data.drop_ast_node(&decl.name);
1135                }
1136
1137                let exprs = v
1138                    .decls
1139                    .take()
1140                    .into_iter()
1141                    .filter_map(|v| v.init)
1142                    .collect::<Vec<_>>();
1143
1144                debug!(
1145                    count = cnt,
1146                    "Dropping names of variables as they are not used",
1147                );
1148                self.changed = true;
1149
1150                if exprs.is_empty() {
1151                    *s = EmptyStmt { span: DUMMY_SP }.into();
1152                    return;
1153                } else {
1154                    *s = ExprStmt {
1155                        span,
1156                        expr: Expr::from_exprs(exprs),
1157                    }
1158                    .into();
1159                }
1160            }
1161        }
1162
1163        if let Stmt::Decl(Decl::Var(v)) = s {
1164            if v.decls.is_empty() {
1165                *s = EmptyStmt { span: DUMMY_SP }.into();
1166            }
1167        }
1168
1169        debug_assert_valid(s);
1170    }
1171
1172    fn visit_mut_stmts(&mut self, s: &mut Vec<Stmt>) {
1173        self.visit_mut_stmt_likes(s);
1174    }
1175
1176    fn visit_mut_unary_expr(&mut self, n: &mut UnaryExpr) {
1177        if matches!(n.op, op!("delete")) {
1178            return;
1179        }
1180        n.visit_mut_children_with(self);
1181    }
1182
1183    #[cfg_attr(feature = "debug", tracing::instrument(level = "debug", skip_all))]
1184    fn visit_mut_using_decl(&mut self, n: &mut UsingDecl) {
1185        for decl in n.decls.iter_mut() {
1186            decl.init.visit_mut_with(self);
1187        }
1188    }
1189
1190    fn visit_mut_var_decl(&mut self, n: &mut VarDecl) {
1191        let old_var_decl_kind = self.var_decl_kind;
1192        self.var_decl_kind = Some(n.kind);
1193        n.visit_mut_children_with(self);
1194        self.var_decl_kind = old_var_decl_kind;
1195    }
1196
1197    fn visit_mut_var_decl_or_expr(&mut self, n: &mut VarDeclOrExpr) {
1198        match n {
1199            VarDeclOrExpr::VarDecl(..) => {}
1200            VarDeclOrExpr::Expr(v) => {
1201                v.visit_mut_with(self);
1202            }
1203            #[cfg(swc_ast_unknown)]
1204            _ => panic!("unable to access unknown nodes"),
1205        }
1206    }
1207
1208    fn visit_mut_var_declarator(&mut self, v: &mut VarDeclarator) {
1209        v.visit_mut_children_with(self);
1210
1211        if let Pat::Ident(i) = &v.name {
1212            let can_drop = if let Some(init) = &v.init {
1213                !init.may_have_side_effects(self.expr_ctx)
1214            } else {
1215                true
1216            };
1217
1218            if can_drop
1219                && self.can_drop_binding(i.to_id(), self.var_decl_kind == Some(VarDeclKind::Var))
1220            {
1221                self.changed = true;
1222                debug!("Dropping {} because it's not used", i);
1223                self.data.drop_ast_node(&*v);
1224                v.name.take();
1225            }
1226        }
1227    }
1228
1229    fn visit_mut_var_declarators(&mut self, n: &mut Vec<VarDeclarator>) {
1230        self.visit_mut_par(cpu_count() * 8, n);
1231
1232        n.retain(|v| {
1233            if v.name.is_invalid() {
1234                return false;
1235            }
1236
1237            true
1238        });
1239    }
1240
1241    fn visit_mut_with_stmt(&mut self, n: &mut WithStmt) {
1242        n.obj.visit_mut_with(self);
1243    }
1244}
1245
1246impl Scope<'_> {
1247    /// Returns true if it's not in a function or class.
1248    fn is_ast_path_empty(&self) -> bool {
1249        if !self.ast_path.is_empty() {
1250            return false;
1251        }
1252        match &self.parent {
1253            Some(p) => p.is_ast_path_empty(),
1254            None => true,
1255        }
1256    }
1257}