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
24pub 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 pub module_mark: Option<Mark>,
54
55 pub top_level: bool,
59
60 pub top_retain: Vec<Atom>,
62
63 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 graph: GraphMap<u32, VarInfo, Directed, FxBuildHasher>,
109 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 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 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 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 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 for &node in &cycle {
200 if self.entries.contains(&node) {
202 continue 'c;
203 }
204
205 if self.graph.neighbors_directed(node, Incoming).any(|source| {
208 !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
238impl 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 pub usage: u32,
304 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 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 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 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 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 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 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 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 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 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 n.decl.visit_mut_children_with(self);
885 }
886 }
887 }
888
889 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 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 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 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}