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