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
26pub 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 pub module_mark: Option<Mark>,
57
58 pub top_level: bool,
62
63 pub top_retain: Vec<Atom>,
65
66 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 graph: FastDiGraphMap<u32, VarInfo>,
112 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 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 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 for &node in &cycle {
165 if self.entries.contains(&node) {
167 continue 'c;
168 }
169
170 if self.graph.neighbors_directed(node, Incoming).any(|node| {
171 !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 pub usage: u32,
205 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 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 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 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 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 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 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 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 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 n.decl.visit_mut_children_with(self);
781 }
782 }
783 }
784
785 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 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 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 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}