1use std::{cell::RefCell, mem::take, rc::Rc};
7
8use rustc_hash::{FxHashMap, FxHashSet};
9use swc_core::{
10 atoms::Atom,
11 common::{
12 DUMMY_SP,
13 errors::HANDLER,
14 pass::{Repeat, Repeated},
15 },
16 ecma::{
17 ast::*,
18 visit::{Fold, FoldWith, Visit, VisitWith, fold_pass, noop_fold_type, noop_visit_type},
19 },
20};
21
22#[derive(Debug, Default, Clone, Copy)]
24pub enum ExportFilter {
25 #[default]
28 StripDataExports,
29 StripDefaultExport,
31}
32
33#[derive(Debug, Default, Clone, Copy)]
34pub enum PageMode {
35 #[default]
36 None,
37 Ssr,
39 Ssg,
41}
42
43impl PageMode {
44 fn data_marker(self) -> Option<&'static str> {
46 match self {
47 PageMode::None => None,
48 PageMode::Ssr => Some("__N_SSP"),
49 PageMode::Ssg => Some("__N_SSG"),
50 }
51 }
52}
53
54pub fn next_transform_strip_page_exports(
60 filter: ExportFilter,
61 ssr_removed_packages: Rc<RefCell<FxHashSet<Atom>>>,
62) -> impl Pass {
63 fold_pass(Repeat::new(NextSsg {
64 state: State {
65 ssr_removed_packages,
66 filter,
67 ..Default::default()
68 },
69 in_lhs_of_var: false,
70 remove_expression: false,
71 }))
72}
73
74#[derive(Debug, Default)]
76struct State {
77 filter: ExportFilter,
78
79 page_mode: PageMode,
80
81 exports: FxHashMap<Id, ExportType>,
82
83 refs_from_preserved: FxHashSet<Id>,
88
89 refs_from_removed: FxHashSet<Id>,
95
96 cur_declaring: FxHashSet<Id>,
99
100 added_data_marker: bool,
102
103 should_run_again: bool,
104
105 ssr_removed_packages: Rc<RefCell<FxHashSet<Atom>>>,
108}
109
110#[derive(Debug, Clone, Copy)]
112enum ExportType {
113 Default,
114 GetServerSideProps,
115 GetStaticPaths,
116 GetStaticProps,
117}
118
119impl ExportType {
120 fn from_specifier(specifier: &ExportSpecifier) -> Option<ExportTypeResult<'_>> {
121 match specifier {
122 ExportSpecifier::Default(ExportDefaultSpecifier { exported, .. })
123 | ExportSpecifier::Namespace(ExportNamespaceSpecifier {
124 name: ModuleExportName::Ident(exported),
125 ..
126 }) => {
127 let export_type = ExportType::from_ident(exported)?;
128 Some(ExportTypeResult {
129 exported_ident: exported,
130 local_ident: None,
131 export_type,
132 })
133 }
134
135 ExportSpecifier::Named(ExportNamedSpecifier {
136 exported: Some(ModuleExportName::Ident(exported)),
137 orig: ModuleExportName::Ident(orig),
138 ..
139 })
140 | ExportSpecifier::Named(ExportNamedSpecifier {
141 orig: ModuleExportName::Ident(orig @ exported),
142 ..
143 }) => {
144 let export_type = ExportType::from_ident(exported)?;
145 Some(ExportTypeResult {
146 exported_ident: exported,
147 local_ident: Some(orig),
148 export_type,
149 })
150 }
151 _ => None,
152 }
153 }
154
155 fn from_ident(ident: &Ident) -> Option<Self> {
156 Some(match &*ident.sym {
157 "default" => ExportType::Default,
158 "getStaticProps" => ExportType::GetStaticProps,
159 "getStaticPaths" => ExportType::GetStaticPaths,
160 "getServerSideProps" => ExportType::GetServerSideProps,
161 _ => return None,
162 })
163 }
164}
165
166struct ExportTypeResult<'a> {
167 exported_ident: &'a Ident,
168 local_ident: Option<&'a Ident>,
169 export_type: ExportType,
170}
171
172impl State {
173 fn encounter_export(
174 &mut self,
175 exported_ident: &Ident,
176 local_ident: Option<&Ident>,
177 export_type: ExportType,
178 ) {
179 match export_type {
180 ExportType::GetServerSideProps => {
181 if matches!(self.page_mode, PageMode::Ssg) {
182 HANDLER.with(|handler| {
183 handler
184 .struct_span_err(
185 exported_ident.span,
186 "You can not use getStaticProps or getStaticPaths with \
187 getServerSideProps. To use SSG, please remove getServerSideProps",
188 )
189 .emit()
190 });
191 return;
192 }
193
194 self.page_mode = PageMode::Ssr;
195 }
196 ExportType::GetStaticPaths | ExportType::GetStaticProps => {
197 if matches!(self.page_mode, PageMode::Ssr) {
198 HANDLER.with(|handler| {
199 handler
200 .struct_span_err(
201 exported_ident.span,
202 "You can not use getStaticProps or getStaticPaths with \
203 getServerSideProps. To use SSG, please remove getServerSideProps",
204 )
205 .emit()
206 });
207 return;
208 }
209
210 self.page_mode = PageMode::Ssg;
211 }
212 _ => {}
213 }
214
215 let local_ident = local_ident.unwrap_or(exported_ident);
216
217 self.exports.insert(local_ident.to_id(), export_type);
218 }
219
220 fn export_type(&self, id: &Id) -> Option<ExportType> {
221 self.exports.get(id).copied()
222 }
223
224 fn should_retain_export_type(&self, export_type: ExportType) -> bool {
225 !matches!(
226 (self.filter, export_type),
227 (
228 ExportFilter::StripDataExports,
229 ExportType::GetServerSideProps
230 | ExportType::GetStaticProps
231 | ExportType::GetStaticPaths,
232 ) | (ExportFilter::StripDefaultExport, ExportType::Default)
233 )
234 }
235
236 fn should_retain_id(&self, id: &Id) -> bool {
237 if let Some(export_type) = self.export_type(id) {
238 self.should_retain_export_type(export_type)
239 } else {
240 true
241 }
242 }
243
244 fn dropping_export(&mut self, export_type: ExportType) -> bool {
245 if !self.should_retain_export_type(export_type) {
246 self.should_run_again = true;
249 true
250 } else {
251 false
252 }
253 }
254}
255
256struct Analyzer<'a> {
257 state: &'a mut State,
258 in_lhs_of_var: bool,
259 in_removed_item: bool,
260}
261
262impl Analyzer<'_> {
263 fn add_ref(&mut self, id: Id) {
264 tracing::trace!(
265 "add_ref({}{:?}, in_removed_item = {:?})",
266 id.0,
267 id.1,
268 self.in_removed_item,
269 );
270 if self.in_removed_item {
271 self.state.refs_from_removed.insert(id);
272 } else {
273 if self.state.cur_declaring.contains(&id) {
274 return;
275 }
276
277 self.state.refs_from_preserved.insert(id);
278 }
279 }
280
281 fn within_declaration<R>(&mut self, id: &Id, f: impl FnOnce(&mut Self) -> R) -> R {
282 self.state.cur_declaring.insert(id.clone());
283 let res = f(self);
284 self.state.cur_declaring.remove(id);
285 res
286 }
287
288 fn within_removed_item<R>(
289 &mut self,
290 in_removed_item: bool,
291 f: impl FnOnce(&mut Self) -> R,
292 ) -> R {
293 let old = self.in_removed_item;
294 self.in_removed_item |= in_removed_item;
296 let res = f(self);
297 self.in_removed_item = old;
298 res
299 }
300
301 fn within_lhs_of_var<R>(&mut self, in_lhs_of_var: bool, f: impl FnOnce(&mut Self) -> R) -> R {
302 let old = self.in_lhs_of_var;
303 self.in_lhs_of_var = in_lhs_of_var;
304 let res = f(self);
305 self.in_lhs_of_var = old;
306 res
307 }
308
309 fn visit_declaration<D>(&mut self, id: &Id, d: &D)
310 where
311 D: VisitWith<Self>,
312 {
313 self.within_declaration(id, |this| {
314 let in_removed_item = !this.state.should_retain_id(id);
315 this.within_removed_item(in_removed_item, |this| {
316 tracing::trace!(
317 "transform_page: Handling `{}{:?}`; in_removed_item = {:?}",
318 id.0,
319 id.1,
320 this.in_removed_item
321 );
322
323 d.visit_children_with(this);
324 });
325 });
326 }
327}
328
329impl Visit for Analyzer<'_> {
330 noop_visit_type!();
332
333 fn visit_binding_ident(&mut self, i: &BindingIdent) {
334 if !self.in_lhs_of_var || self.in_removed_item {
335 self.add_ref(i.id.to_id());
336 }
337 }
338
339 fn visit_named_export(&mut self, n: &NamedExport) {
340 for specifier in &n.specifiers {
341 if let Some(ExportTypeResult {
342 exported_ident,
343 local_ident,
344 export_type,
345 }) = ExportType::from_specifier(specifier)
346 {
347 self.state
348 .encounter_export(exported_ident, local_ident, export_type);
349
350 if let Some(local_ident) = local_ident
351 && self.state.should_retain_export_type(export_type)
352 {
353 self.add_ref(local_ident.to_id());
354 }
355 }
356 }
357 }
358
359 fn visit_export_decl(&mut self, s: &ExportDecl) {
360 match &s.decl {
361 Decl::Var(d) => {
362 for decl in &d.decls {
363 if let Pat::Ident(ident) = &decl.name {
364 if let Some(export_type) = ExportType::from_ident(ident) {
365 self.state.encounter_export(ident, None, export_type);
366
367 let retain = self.state.should_retain_export_type(export_type);
368
369 if retain {
370 self.add_ref(ident.to_id());
371 }
372
373 self.within_removed_item(!retain, |this| {
374 decl.visit_with(this);
375 });
376 } else {
377 self.add_ref(ident.to_id());
379
380 decl.visit_with(self)
381 }
382 } else {
383 decl.visit_with(self)
384 }
385 }
386 }
387 Decl::Fn(decl) => {
388 let ident = &decl.ident;
389 if let Some(export_type) = ExportType::from_ident(ident) {
390 self.state.encounter_export(ident, None, export_type);
391
392 let retain = self.state.should_retain_export_type(export_type);
393
394 if retain {
395 self.add_ref(ident.to_id());
396 }
397
398 self.within_removed_item(!retain, |this| {
399 decl.visit_with(this);
400 });
401 } else {
402 s.visit_children_with(self);
403 }
404 }
405 _ => s.visit_children_with(self),
406 }
407 }
408
409 fn visit_export_default_decl(&mut self, s: &ExportDefaultDecl) {
410 match &s.decl {
411 DefaultDecl::Class(ClassExpr {
412 ident: Some(ident), ..
413 }) => self
414 .state
415 .encounter_export(ident, Some(ident), ExportType::Default),
416 DefaultDecl::Fn(FnExpr {
417 ident: Some(ident), ..
418 }) => self
419 .state
420 .encounter_export(ident, Some(ident), ExportType::Default),
421 _ => {}
422 }
423 self.within_removed_item(
424 matches!(self.state.filter, ExportFilter::StripDefaultExport),
425 |this| {
426 s.visit_children_with(this);
427 },
428 );
429 }
430
431 fn visit_export_default_expr(&mut self, s: &ExportDefaultExpr) {
432 self.within_removed_item(
433 matches!(self.state.filter, ExportFilter::StripDefaultExport),
434 |this| {
435 s.visit_children_with(this);
436 },
437 );
438 }
439
440 fn visit_expr(&mut self, e: &Expr) {
441 e.visit_children_with(self);
442
443 if let Expr::Ident(i) = &e {
444 self.add_ref(i.to_id());
445 }
446 }
447
448 fn visit_jsx_element(&mut self, jsx: &JSXElement) {
449 fn get_leftmost_id_member_expr(e: &JSXMemberExpr) -> Id {
450 match &e.obj {
451 JSXObject::Ident(i) => i.to_id(),
452 JSXObject::JSXMemberExpr(e) => get_leftmost_id_member_expr(e),
453 }
454 }
455
456 match &jsx.opening.name {
457 JSXElementName::Ident(i) => {
458 self.add_ref(i.to_id());
459 }
460 JSXElementName::JSXMemberExpr(e) => {
461 self.add_ref(get_leftmost_id_member_expr(e));
462 }
463 _ => {}
464 }
465
466 jsx.visit_children_with(self);
467 }
468
469 fn visit_fn_decl(&mut self, f: &FnDecl) {
470 self.visit_declaration(&f.ident.to_id(), f);
471 }
472
473 fn visit_class_decl(&mut self, c: &ClassDecl) {
474 self.visit_declaration(&c.ident.to_id(), c);
475 }
476
477 fn visit_fn_expr(&mut self, f: &FnExpr) {
478 f.visit_children_with(self);
479
480 if let Some(id) = &f.ident {
481 self.add_ref(id.to_id());
482 }
483 }
484
485 fn visit_prop(&mut self, p: &Prop) {
486 p.visit_children_with(self);
487
488 if let Prop::Shorthand(i) = &p {
489 self.add_ref(i.to_id());
490 }
491 }
492
493 fn visit_var_declarator(&mut self, v: &VarDeclarator) {
494 let in_removed_item = if let Pat::Ident(name) = &v.name {
495 !self.state.should_retain_id(&name.id.to_id())
496 } else {
497 false
498 };
499
500 self.within_removed_item(in_removed_item, |this| {
501 this.within_lhs_of_var(true, |this| {
502 v.name.visit_with(this);
503 });
504
505 this.within_lhs_of_var(false, |this| {
506 v.init.visit_with(this);
507 });
508 });
509 }
510
511 fn visit_member_expr(&mut self, e: &MemberExpr) {
512 let in_removed_item = if let Some(id) = find_member_root_id(e) {
513 !self.state.should_retain_id(&id)
514 } else {
515 false
516 };
517
518 self.within_removed_item(in_removed_item, |this| {
519 e.visit_children_with(this);
520 });
521 }
522
523 fn visit_assign_expr(&mut self, e: &AssignExpr) {
524 self.within_lhs_of_var(true, |this| {
525 e.left.visit_with(this);
526 });
527
528 self.within_lhs_of_var(false, |this| {
529 e.right.visit_with(this);
530 });
531 }
532}
533
534struct NextSsg {
536 pub state: State,
537 in_lhs_of_var: bool,
538 remove_expression: bool,
541}
542
543impl NextSsg {
544 fn should_remove(&self, id: &Id) -> bool {
546 self.state.refs_from_removed.contains(id) && !self.state.refs_from_preserved.contains(id)
547 }
548
549 fn mark_as_candidate<N>(&mut self, n: &N)
551 where
552 N: for<'aa> VisitWith<Analyzer<'aa>> + std::fmt::Debug,
553 {
554 tracing::debug!("mark_as_candidate: {:?}", n);
555
556 let mut v = Analyzer {
557 state: &mut self.state,
558 in_lhs_of_var: false,
559 in_removed_item: true,
562 };
563
564 n.visit_with(&mut v);
565 self.state.should_run_again = true;
566 }
567
568 fn maybe_add_data_marker(&mut self, items: &mut Vec<ModuleItem>) {
570 if !matches!(self.state.filter, ExportFilter::StripDataExports)
571 || self.state.added_data_marker
572 || self.state.should_run_again
573 {
574 return;
575 }
576
577 let Some(data_marker) = self.state.page_mode.data_marker() else {
578 return;
579 };
580
581 self.state.added_data_marker = true;
582
583 if items.iter().any(|s| s.is_module_decl()) {
584 let insert_idx = items.iter().position(|item| {
585 matches!(
586 item,
587 ModuleItem::ModuleDecl(
588 ModuleDecl::ExportNamed(..)
589 | ModuleDecl::ExportDecl(..)
590 | ModuleDecl::ExportDefaultDecl(..)
591 | ModuleDecl::ExportDefaultExpr(..),
592 )
593 )
594 });
595
596 if let Some(insert_idx) = insert_idx {
597 items.insert(
598 insert_idx,
599 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
600 span: DUMMY_SP,
601 decl: Decl::Var(Box::new(VarDecl {
602 span: DUMMY_SP,
603 kind: VarDeclKind::Var,
604 decls: vec![VarDeclarator {
605 span: DUMMY_SP,
606 name: Pat::Ident(
607 IdentName::new(data_marker.into(), DUMMY_SP).into(),
608 ),
609 init: Some(true.into()),
610 definite: Default::default(),
611 }],
612 ..Default::default()
613 })),
614 })),
615 );
616 }
617 }
618 }
619
620 fn within_lhs_of_var<R>(&mut self, in_lhs_of_var: bool, f: impl FnOnce(&mut Self) -> R) -> R {
621 let old = self.in_lhs_of_var;
622 self.in_lhs_of_var = in_lhs_of_var;
623 let res = f(self);
624 self.in_lhs_of_var = old;
625 res
626 }
627}
628
629impl Repeated for NextSsg {
630 fn changed(&self) -> bool {
631 self.state.should_run_again
632 }
633
634 fn reset(&mut self) {
635 self.state.refs_from_preserved.clear();
636 self.state.cur_declaring.clear();
637 self.state.should_run_again = false;
638 }
639}
640
641impl Fold for NextSsg {
646 fn fold_array_pat(&mut self, mut arr: ArrayPat) -> ArrayPat {
647 arr = arr.fold_children_with(self);
648
649 if !arr.elems.is_empty() {
650 arr.elems.retain(|e| !matches!(e, Some(Pat::Invalid(..))));
651 }
652
653 arr
654 }
655
656 fn fold_assign_target_pat(&mut self, mut n: AssignTargetPat) -> AssignTargetPat {
657 n = n.fold_children_with(self);
658
659 match &n {
660 AssignTargetPat::Array(arr) if arr.elems.is_empty() => {
661 return AssignTargetPat::Invalid(Invalid { span: DUMMY_SP });
662 }
663 AssignTargetPat::Object(obj) if obj.props.is_empty() => {
664 return AssignTargetPat::Invalid(Invalid { span: DUMMY_SP });
665 }
666 _ => {}
667 }
668
669 n
670 }
671
672 fn fold_expr(&mut self, e: Expr) -> Expr {
673 match e {
674 Expr::Assign(assign_expr) => {
675 let mut retain = true;
676 let left =
677 self.within_lhs_of_var(true, |this| assign_expr.left.clone().fold_with(this));
678
679 let right = self.within_lhs_of_var(false, |this| {
680 match left {
681 AssignTarget::Simple(SimpleAssignTarget::Invalid(..))
682 | AssignTarget::Pat(AssignTargetPat::Invalid(..)) => {
683 retain = false;
684 this.mark_as_candidate(&assign_expr.right);
685 }
686
687 _ => {}
688 }
689 assign_expr.right.clone().fold_with(this)
690 });
691
692 if retain {
693 self.remove_expression = false;
694 Expr::Assign(AssignExpr {
695 left,
696 right,
697 ..assign_expr
698 })
699 } else {
700 self.remove_expression = true;
701 *right
702 }
703 }
704 _ => {
705 self.remove_expression = false;
706 e.fold_children_with(self)
707 }
708 }
709 }
710
711 fn fold_import_decl(&mut self, mut i: ImportDecl) -> ImportDecl {
712 if i.specifiers.is_empty() {
714 return i;
715 }
716
717 let import_src = &i.src.value;
718
719 i.specifiers.retain(|s| match s {
720 ImportSpecifier::Named(ImportNamedSpecifier { local, .. })
721 | ImportSpecifier::Default(ImportDefaultSpecifier { local, .. })
722 | ImportSpecifier::Namespace(ImportStarAsSpecifier { local, .. }) => {
723 if self.should_remove(&local.to_id()) {
724 if matches!(self.state.page_mode, PageMode::Ssr)
725 && matches!(self.state.filter, ExportFilter::StripDataExports)
726 && import_src.as_str().unwrap_or_default().starts_with(|c: char| c.is_ascii_lowercase() || c == '@')
729 {
730 self.state
731 .ssr_removed_packages
732 .borrow_mut()
733 .insert(import_src.clone().to_atom_lossy().into_owned());
734 }
735 tracing::trace!(
736 "Dropping import `{}{:?}` because it should be removed",
737 local.sym,
738 local.ctxt
739 );
740
741 self.state.should_run_again = true;
742 false
743 } else {
744 true
745 }
746 }
747 });
748
749 i
750 }
751
752 fn fold_module(&mut self, m: Module) -> Module {
753 tracing::info!("ssg: Start");
754 {
755 let mut v = Analyzer {
757 state: &mut self.state,
758 in_lhs_of_var: false,
759 in_removed_item: false,
760 };
761 m.visit_with(&mut v);
762 }
763
764 m.fold_children_with(self)
770 }
771
772 fn fold_module_item(&mut self, i: ModuleItem) -> ModuleItem {
773 if let ModuleItem::ModuleDecl(ModuleDecl::Import(i)) = i {
774 let is_for_side_effect = i.specifiers.is_empty();
775 let i = i.fold_with(self);
776
777 if !is_for_side_effect && i.specifiers.is_empty() {
778 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
779 }
780
781 return ModuleItem::ModuleDecl(ModuleDecl::Import(i));
782 }
783
784 let i = i.fold_children_with(self);
785
786 match &i {
787 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(e)) if e.specifiers.is_empty() => {
788 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
789 }
790 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(e)) => match &e.decl {
791 Decl::Fn(f) => {
792 if let Some(export_type) = self.state.export_type(&f.ident.to_id())
793 && self.state.dropping_export(export_type)
794 {
795 tracing::trace!(
796 "Dropping an export specifier because it's an SSR/SSG function"
797 );
798 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
799 }
800 }
801
802 Decl::Var(d) if d.decls.is_empty() => {
803 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
804 }
805 _ => {}
806 },
807
808 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(_))
809 | ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(_))
810 if self.state.dropping_export(ExportType::Default) =>
811 {
812 tracing::trace!("Dropping an export specifier because it's a default export");
813
814 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
815 }
816 _ => {}
817 }
818
819 i
820 }
821
822 fn fold_module_items(&mut self, mut items: Vec<ModuleItem>) -> Vec<ModuleItem> {
823 items = items.fold_children_with(self);
824
825 items.retain(|s| !matches!(s, ModuleItem::Stmt(Stmt::Empty(..))));
827
828 self.maybe_add_data_marker(&mut items);
829
830 items
831 }
832
833 fn fold_named_export(&mut self, mut n: NamedExport) -> NamedExport {
834 n.specifiers = n.specifiers.fold_with(self);
835
836 n.specifiers.retain(|s| {
837 let (export_type, local_ref) = match s {
838 ExportSpecifier::Default(ExportDefaultSpecifier { exported, .. })
839 | ExportSpecifier::Namespace(ExportNamespaceSpecifier {
840 name: ModuleExportName::Ident(exported),
841 ..
842 }) => (ExportType::from_ident(exported), None),
843 ExportSpecifier::Named(ExportNamedSpecifier {
844 exported: Some(ModuleExportName::Ident(exported)),
845 orig: ModuleExportName::Ident(orig),
846 ..
847 })
848 | ExportSpecifier::Named(ExportNamedSpecifier {
849 orig: ModuleExportName::Ident(orig @ exported),
850 ..
851 }) => (ExportType::from_ident(exported), Some(orig)),
852 _ => (None, None),
853 };
854
855 let Some(export_type) = export_type else {
856 return true;
857 };
858
859 let retain = self.state.should_retain_export_type(export_type);
860
861 if !retain {
862 if let Some(local_ref) = local_ref {
865 self.state.should_run_again = true;
866 self.state.refs_from_removed.insert(local_ref.to_id());
867 }
868 }
869
870 self.state.should_retain_export_type(export_type)
871 });
872
873 n
874 }
875
876 fn fold_object_pat(&mut self, mut obj: ObjectPat) -> ObjectPat {
877 obj = obj.fold_children_with(self);
878
879 if !obj.props.is_empty() {
880 obj.props = take(&mut obj.props)
881 .into_iter()
882 .filter_map(|prop| match prop {
883 ObjectPatProp::KeyValue(prop) => {
884 if prop.value.is_invalid() {
885 None
886 } else {
887 Some(ObjectPatProp::KeyValue(prop))
888 }
889 }
890 ObjectPatProp::Assign(prop) => {
891 if self.should_remove(&prop.key.to_id()) {
892 self.mark_as_candidate(&prop.value);
893
894 None
895 } else {
896 Some(ObjectPatProp::Assign(prop))
897 }
898 }
899 ObjectPatProp::Rest(prop) => {
900 if prop.arg.is_invalid() {
901 None
902 } else {
903 Some(ObjectPatProp::Rest(prop))
904 }
905 }
906 })
907 .collect();
908 }
909
910 obj
911 }
912
913 fn fold_pat(&mut self, mut p: Pat) -> Pat {
915 p = p.fold_children_with(self);
916
917 if self.in_lhs_of_var {
918 match &mut p {
919 Pat::Ident(name) if self.should_remove(&name.id.to_id()) => {
920 self.state.should_run_again = true;
921 tracing::trace!(
922 "Dropping var `{}{:?}` because it should be removed",
923 name.id.sym,
924 name.id.ctxt
925 );
926
927 return Pat::Invalid(Invalid { span: DUMMY_SP });
928 }
929 Pat::Array(arr) if arr.elems.is_empty() => {
930 return Pat::Invalid(Invalid { span: DUMMY_SP });
931 }
932 Pat::Object(obj) if obj.props.is_empty() => {
933 return Pat::Invalid(Invalid { span: DUMMY_SP });
934 }
935 Pat::Rest(rest) if rest.arg.is_invalid() => {
936 return Pat::Invalid(Invalid { span: DUMMY_SP });
937 }
938 Pat::Expr(expr) => {
939 if let Expr::Member(member_expr) = &**expr
940 && let Some(id) = find_member_root_id(member_expr)
941 && self.should_remove(&id)
942 {
943 self.state.should_run_again = true;
944 tracing::trace!(
945 "Dropping member expression object `{}{:?}` because it should be \
946 removed",
947 id.0,
948 id.1
949 );
950
951 return Pat::Invalid(Invalid { span: DUMMY_SP });
952 }
953 }
954 _ => {}
955 }
956 }
957
958 p
959 }
960
961 fn fold_simple_assign_target(&mut self, mut n: SimpleAssignTarget) -> SimpleAssignTarget {
962 n = n.fold_children_with(self);
963
964 if let SimpleAssignTarget::Ident(name) = &n
965 && self.should_remove(&name.id.to_id())
966 {
967 self.state.should_run_again = true;
968 tracing::trace!(
969 "Dropping var `{}{:?}` because it should be removed",
970 name.id.sym,
971 name.id.ctxt
972 );
973
974 return SimpleAssignTarget::Invalid(Invalid { span: DUMMY_SP });
975 }
976
977 if let SimpleAssignTarget::Member(member_expr) = &n
978 && let Some(id) = find_member_root_id(member_expr)
979 && self.should_remove(&id)
980 {
981 self.state.should_run_again = true;
982 tracing::trace!(
983 "Dropping member expression object `{}{:?}` because it should be removed",
984 id.0,
985 id.1
986 );
987
988 return SimpleAssignTarget::Invalid(Invalid { span: DUMMY_SP });
989 }
990
991 n
992 }
993
994 #[allow(clippy::single_match)]
995 fn fold_stmt(&mut self, mut s: Stmt) -> Stmt {
996 match s {
997 Stmt::Decl(Decl::Fn(f)) => {
998 if self.should_remove(&f.ident.to_id()) {
999 self.mark_as_candidate(&f.function);
1000 return Stmt::Empty(EmptyStmt { span: DUMMY_SP });
1001 }
1002
1003 s = Stmt::Decl(Decl::Fn(f));
1004 }
1005 Stmt::Decl(Decl::Class(c)) => {
1006 if self.should_remove(&c.ident.to_id()) {
1007 self.mark_as_candidate(&c.class);
1008 return Stmt::Empty(EmptyStmt { span: DUMMY_SP });
1009 }
1010
1011 s = Stmt::Decl(Decl::Class(c));
1012 }
1013 _ => {}
1014 }
1015
1016 self.remove_expression = false;
1017
1018 let s = s.fold_children_with(self);
1019
1020 match s {
1021 Stmt::Decl(Decl::Var(v)) if v.decls.is_empty() => {
1022 return Stmt::Empty(EmptyStmt { span: DUMMY_SP });
1023 }
1024 Stmt::Expr(_) if self.remove_expression => {
1025 self.remove_expression = false;
1026 return Stmt::Empty(EmptyStmt { span: DUMMY_SP });
1027 }
1028 _ => {}
1029 }
1030
1031 s
1032 }
1033
1034 fn fold_var_declarator(&mut self, d: VarDeclarator) -> VarDeclarator {
1037 let name = self.within_lhs_of_var(true, |this| d.name.clone().fold_with(this));
1038
1039 let init = self.within_lhs_of_var(false, |this| {
1040 if name.is_invalid() {
1041 this.mark_as_candidate(&d.init);
1042 }
1043 d.init.clone().fold_with(this)
1044 });
1045
1046 VarDeclarator { name, init, ..d }
1047 }
1048
1049 fn fold_var_declarators(&mut self, mut decls: Vec<VarDeclarator>) -> Vec<VarDeclarator> {
1050 decls = decls.fold_children_with(self);
1051 decls.retain(|d| !d.name.is_invalid());
1052
1053 decls
1054 }
1055
1056 noop_fold_type!();
1058}
1059
1060fn find_member_root_id(member_expr: &MemberExpr) -> Option<Id> {
1064 match &*member_expr.obj {
1065 Expr::Member(member) => find_member_root_id(member),
1066 Expr::Ident(ident) => Some(ident.to_id()),
1067 _ => None,
1068 }
1069}