1use std::{cell::RefCell, mem::take, rc::Rc};
7
8use rustc_hash::{FxHashMap, FxHashSet};
9use swc_core::{
10 atoms::Atom,
11 common::{
12 errors::HANDLER,
13 pass::{Repeat, Repeated},
14 DUMMY_SP,
15 },
16 ecma::{
17 ast::*,
18 visit::{fold_pass, noop_fold_type, noop_visit_type, Fold, FoldWith, Visit, VisitWith},
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 if self.state.should_retain_export_type(export_type) {
352 self.add_ref(local_ident.to_id());
353 }
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) => {
661 if arr.elems.is_empty() {
662 return AssignTargetPat::Invalid(Invalid { span: DUMMY_SP });
663 }
664 }
665 AssignTargetPat::Object(obj) => {
666 if obj.props.is_empty() {
667 return AssignTargetPat::Invalid(Invalid { span: DUMMY_SP });
668 }
669 }
670 _ => {}
671 }
672
673 n
674 }
675
676 fn fold_expr(&mut self, e: Expr) -> Expr {
677 match e {
678 Expr::Assign(assign_expr) => {
679 let mut retain = true;
680 let left =
681 self.within_lhs_of_var(true, |this| assign_expr.left.clone().fold_with(this));
682
683 let right = self.within_lhs_of_var(false, |this| {
684 match left {
685 AssignTarget::Simple(SimpleAssignTarget::Invalid(..))
686 | AssignTarget::Pat(AssignTargetPat::Invalid(..)) => {
687 retain = false;
688 this.mark_as_candidate(&assign_expr.right);
689 }
690
691 _ => {}
692 }
693 assign_expr.right.clone().fold_with(this)
694 });
695
696 if retain {
697 self.remove_expression = false;
698 Expr::Assign(AssignExpr {
699 left,
700 right,
701 ..assign_expr
702 })
703 } else {
704 self.remove_expression = true;
705 *right
706 }
707 }
708 _ => {
709 self.remove_expression = false;
710 e.fold_children_with(self)
711 }
712 }
713 }
714
715 fn fold_import_decl(&mut self, mut i: ImportDecl) -> ImportDecl {
716 if i.specifiers.is_empty() {
718 return i;
719 }
720
721 let import_src = &i.src.value;
722
723 i.specifiers.retain(|s| match s {
724 ImportSpecifier::Named(ImportNamedSpecifier { local, .. })
725 | ImportSpecifier::Default(ImportDefaultSpecifier { local, .. })
726 | ImportSpecifier::Namespace(ImportStarAsSpecifier { local, .. }) => {
727 if self.should_remove(&local.to_id()) {
728 if matches!(self.state.page_mode, PageMode::Ssr)
729 && matches!(self.state.filter, ExportFilter::StripDataExports)
730 && import_src.starts_with(|c: char| c.is_ascii_lowercase() || c == '@')
733 {
734 self.state
735 .ssr_removed_packages
736 .borrow_mut()
737 .insert(import_src.clone());
738 }
739 tracing::trace!(
740 "Dropping import `{}{:?}` because it should be removed",
741 local.sym,
742 local.ctxt
743 );
744
745 self.state.should_run_again = true;
746 false
747 } else {
748 true
749 }
750 }
751 });
752
753 i
754 }
755
756 fn fold_module(&mut self, m: Module) -> Module {
757 tracing::info!("ssg: Start");
758 {
759 let mut v = Analyzer {
761 state: &mut self.state,
762 in_lhs_of_var: false,
763 in_removed_item: false,
764 };
765 m.visit_with(&mut v);
766 }
767
768 m.fold_children_with(self)
774 }
775
776 fn fold_module_item(&mut self, i: ModuleItem) -> ModuleItem {
777 if let ModuleItem::ModuleDecl(ModuleDecl::Import(i)) = i {
778 let is_for_side_effect = i.specifiers.is_empty();
779 let i = i.fold_with(self);
780
781 if !is_for_side_effect && i.specifiers.is_empty() {
782 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
783 }
784
785 return ModuleItem::ModuleDecl(ModuleDecl::Import(i));
786 }
787
788 let i = i.fold_children_with(self);
789
790 match &i {
791 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(e)) if e.specifiers.is_empty() => {
792 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }))
793 }
794 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(e)) => match &e.decl {
795 Decl::Fn(f) => {
796 if let Some(export_type) = self.state.export_type(&f.ident.to_id()) {
797 if self.state.dropping_export(export_type) {
798 tracing::trace!(
799 "Dropping an export specifier because it's an SSR/SSG function"
800 );
801 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
802 }
803 }
804 }
805
806 Decl::Var(d) => {
807 if d.decls.is_empty() {
808 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
809 }
810 }
811 _ => {}
812 },
813
814 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(_))
815 | ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(_)) => {
816 if self.state.dropping_export(ExportType::Default) {
817 tracing::trace!("Dropping an export specifier because it's a default export");
818
819 return ModuleItem::Stmt(Stmt::Empty(EmptyStmt { span: DUMMY_SP }));
820 }
821 }
822 _ => {}
823 }
824
825 i
826 }
827
828 fn fold_module_items(&mut self, mut items: Vec<ModuleItem>) -> Vec<ModuleItem> {
829 items = items.fold_children_with(self);
830
831 items.retain(|s| !matches!(s, ModuleItem::Stmt(Stmt::Empty(..))));
833
834 self.maybe_add_data_marker(&mut items);
835
836 items
837 }
838
839 fn fold_named_export(&mut self, mut n: NamedExport) -> NamedExport {
840 n.specifiers = n.specifiers.fold_with(self);
841
842 n.specifiers.retain(|s| {
843 let (export_type, local_ref) = match s {
844 ExportSpecifier::Default(ExportDefaultSpecifier { exported, .. })
845 | ExportSpecifier::Namespace(ExportNamespaceSpecifier {
846 name: ModuleExportName::Ident(exported),
847 ..
848 }) => (ExportType::from_ident(exported), None),
849 ExportSpecifier::Named(ExportNamedSpecifier {
850 exported: Some(ModuleExportName::Ident(exported)),
851 orig: ModuleExportName::Ident(orig),
852 ..
853 })
854 | ExportSpecifier::Named(ExportNamedSpecifier {
855 orig: ModuleExportName::Ident(orig @ exported),
856 ..
857 }) => (ExportType::from_ident(exported), Some(orig)),
858 _ => (None, None),
859 };
860
861 let Some(export_type) = export_type else {
862 return true;
863 };
864
865 let retain = self.state.should_retain_export_type(export_type);
866
867 if !retain {
868 if let Some(local_ref) = local_ref {
871 self.state.should_run_again = true;
872 self.state.refs_from_removed.insert(local_ref.to_id());
873 }
874 }
875
876 self.state.should_retain_export_type(export_type)
877 });
878
879 n
880 }
881
882 fn fold_object_pat(&mut self, mut obj: ObjectPat) -> ObjectPat {
883 obj = obj.fold_children_with(self);
884
885 if !obj.props.is_empty() {
886 obj.props = take(&mut obj.props)
887 .into_iter()
888 .filter_map(|prop| match prop {
889 ObjectPatProp::KeyValue(prop) => {
890 if prop.value.is_invalid() {
891 None
892 } else {
893 Some(ObjectPatProp::KeyValue(prop))
894 }
895 }
896 ObjectPatProp::Assign(prop) => {
897 if self.should_remove(&prop.key.to_id()) {
898 self.mark_as_candidate(&prop.value);
899
900 None
901 } else {
902 Some(ObjectPatProp::Assign(prop))
903 }
904 }
905 ObjectPatProp::Rest(prop) => {
906 if prop.arg.is_invalid() {
907 None
908 } else {
909 Some(ObjectPatProp::Rest(prop))
910 }
911 }
912 })
913 .collect();
914 }
915
916 obj
917 }
918
919 fn fold_pat(&mut self, mut p: Pat) -> Pat {
921 p = p.fold_children_with(self);
922
923 if self.in_lhs_of_var {
924 match &mut p {
925 Pat::Ident(name) => {
926 if self.should_remove(&name.id.to_id()) {
927 self.state.should_run_again = true;
928 tracing::trace!(
929 "Dropping var `{}{:?}` because it should be removed",
930 name.id.sym,
931 name.id.ctxt
932 );
933
934 return Pat::Invalid(Invalid { span: DUMMY_SP });
935 }
936 }
937 Pat::Array(arr) => {
938 if arr.elems.is_empty() {
939 return Pat::Invalid(Invalid { span: DUMMY_SP });
940 }
941 }
942 Pat::Object(obj) => {
943 if obj.props.is_empty() {
944 return Pat::Invalid(Invalid { span: DUMMY_SP });
945 }
946 }
947 Pat::Rest(rest) => {
948 if rest.arg.is_invalid() {
949 return Pat::Invalid(Invalid { span: DUMMY_SP });
950 }
951 }
952 Pat::Expr(expr) => {
953 if let Expr::Member(member_expr) = &**expr {
954 if let Some(id) = find_member_root_id(member_expr) {
955 if self.should_remove(&id) {
956 self.state.should_run_again = true;
957 tracing::trace!(
958 "Dropping member expression object `{}{:?}` because it should \
959 be removed",
960 id.0,
961 id.1
962 );
963
964 return Pat::Invalid(Invalid { span: DUMMY_SP });
965 }
966 }
967 }
968 }
969 _ => {}
970 }
971 }
972
973 p
974 }
975
976 fn fold_simple_assign_target(&mut self, mut n: SimpleAssignTarget) -> SimpleAssignTarget {
977 n = n.fold_children_with(self);
978
979 if let SimpleAssignTarget::Ident(name) = &n {
980 if self.should_remove(&name.id.to_id()) {
981 self.state.should_run_again = true;
982 tracing::trace!(
983 "Dropping var `{}{:?}` because it should be removed",
984 name.id.sym,
985 name.id.ctxt
986 );
987
988 return SimpleAssignTarget::Invalid(Invalid { span: DUMMY_SP });
989 }
990 }
991
992 if let SimpleAssignTarget::Member(member_expr) = &n {
993 if let Some(id) = find_member_root_id(member_expr) {
994 if self.should_remove(&id) {
995 self.state.should_run_again = true;
996 tracing::trace!(
997 "Dropping member expression object `{}{:?}` because it should be removed",
998 id.0,
999 id.1
1000 );
1001
1002 return SimpleAssignTarget::Invalid(Invalid { span: DUMMY_SP });
1003 }
1004 }
1005 }
1006
1007 n
1008 }
1009
1010 #[allow(clippy::single_match)]
1011 fn fold_stmt(&mut self, mut s: Stmt) -> Stmt {
1012 match s {
1013 Stmt::Decl(Decl::Fn(f)) => {
1014 if self.should_remove(&f.ident.to_id()) {
1015 self.mark_as_candidate(&f.function);
1016 return Stmt::Empty(EmptyStmt { span: DUMMY_SP });
1017 }
1018
1019 s = Stmt::Decl(Decl::Fn(f));
1020 }
1021 Stmt::Decl(Decl::Class(c)) => {
1022 if self.should_remove(&c.ident.to_id()) {
1023 self.mark_as_candidate(&c.class);
1024 return Stmt::Empty(EmptyStmt { span: DUMMY_SP });
1025 }
1026
1027 s = Stmt::Decl(Decl::Class(c));
1028 }
1029 _ => {}
1030 }
1031
1032 self.remove_expression = false;
1033
1034 let s = s.fold_children_with(self);
1035
1036 match s {
1037 Stmt::Decl(Decl::Var(v)) if v.decls.is_empty() => {
1038 return Stmt::Empty(EmptyStmt { span: DUMMY_SP });
1039 }
1040 Stmt::Expr(_) => {
1041 if self.remove_expression {
1042 self.remove_expression = false;
1043 return Stmt::Empty(EmptyStmt { span: DUMMY_SP });
1044 }
1045 }
1046 _ => {}
1047 }
1048
1049 s
1050 }
1051
1052 fn fold_var_declarator(&mut self, d: VarDeclarator) -> VarDeclarator {
1055 let name = self.within_lhs_of_var(true, |this| d.name.clone().fold_with(this));
1056
1057 let init = self.within_lhs_of_var(false, |this| {
1058 if name.is_invalid() {
1059 this.mark_as_candidate(&d.init);
1060 }
1061 d.init.clone().fold_with(this)
1062 });
1063
1064 VarDeclarator { name, init, ..d }
1065 }
1066
1067 fn fold_var_declarators(&mut self, mut decls: Vec<VarDeclarator>) -> Vec<VarDeclarator> {
1068 decls = decls.fold_children_with(self);
1069 decls.retain(|d| !d.name.is_invalid());
1070
1071 decls
1072 }
1073
1074 noop_fold_type!();
1076}
1077
1078fn find_member_root_id(member_expr: &MemberExpr) -> Option<Id> {
1082 match &*member_expr.obj {
1083 Expr::Member(member) => find_member_root_id(member),
1084 Expr::Ident(ident) => Some(ident.to_id()),
1085 _ => None,
1086 }
1087}