1use std::{borrow::Cow, iter::once, mem::take};
2
3use swc_common::{
4 pass::{CompilerPass, Repeated},
5 util::{move_map::MoveMap, take::Take},
6 Mark, Spanned, SyntaxContext, DUMMY_SP,
7};
8use swc_ecma_ast::*;
9use swc_ecma_transforms_base::perf::{cpu_count, Parallel, ParallelExt};
10use swc_ecma_utils::{
11 extract_var_ids, is_literal, prepend_stmt, ExprCtx, ExprExt, ExprFactory, Hoister, IsEmpty,
12 StmtExt, StmtLike, Value::Known,
13};
14use swc_ecma_visit::{
15 noop_visit_mut_type, noop_visit_type, visit_mut_pass, Visit, VisitMut, VisitMutWith, VisitWith,
16};
17use tracing::{debug, trace};
18
19#[cfg(test)]
20mod tests;
21
22pub fn dead_branch_remover(
26 unresolved_mark: Mark,
27) -> impl Repeated + Pass + CompilerPass + VisitMut + 'static {
28 visit_mut_pass(Remover {
29 changed: false,
30 normal_block: Default::default(),
31 expr_ctx: ExprCtx {
32 unresolved_ctxt: SyntaxContext::empty().apply_mark(unresolved_mark),
33 is_unresolved_ref_safe: false,
34 in_strict: false,
35 remaining_depth: 3,
36 },
37 })
38}
39
40impl CompilerPass for Remover {
41 fn name(&self) -> Cow<'static, str> {
42 Cow::Borrowed("dead_branch_remover")
43 }
44}
45
46impl Repeated for Remover {
47 fn changed(&self) -> bool {
48 self.changed
49 }
50
51 fn reset(&mut self) {
52 self.changed = false;
53 }
54}
55
56#[derive(Debug)]
57struct Remover {
58 changed: bool,
59 normal_block: bool,
60
61 expr_ctx: ExprCtx,
62}
63
64impl Parallel for Remover {
65 fn create(&self) -> Self {
66 Self { ..*self }
67 }
68
69 fn merge(&mut self, _: Self) {}
70}
71
72impl VisitMut for Remover {
73 noop_visit_mut_type!();
74
75 fn visit_mut_class_members(&mut self, members: &mut Vec<ClassMember>) {
76 self.maybe_par(cpu_count(), members, |v, member| {
77 member.visit_mut_with(v);
78 });
79 }
80
81 fn visit_mut_expr(&mut self, e: &mut Expr) {
82 e.visit_mut_children_with(self);
83
84 match e {
85 Expr::Seq(s) => {
86 if s.exprs.is_empty() {
87 *e = Expr::dummy();
88 } else if s.exprs.len() == 1 {
89 *e = *s.exprs.pop().unwrap();
90 }
91 }
92
93 Expr::Assign(AssignExpr {
94 op: op!("="),
95 left: AssignTarget::Simple(l),
96 right: r,
97 ..
98 }) if match &*l {
99 SimpleAssignTarget::Ident(l) => match &**r {
100 Expr::Ident(r) => l.sym == r.sym && l.ctxt == r.ctxt,
101 _ => false,
102 },
103 _ => false,
104 } =>
105 {
106 if cfg!(feature = "debug") {
107 debug!("Dropping assignment to the same variable");
108 }
109 *e = r.take().ident().unwrap().into();
110 }
111
112 Expr::Assign(AssignExpr {
113 op: op!("="),
114 left: AssignTarget::Pat(left),
115 right,
116 ..
117 }) if match &*left {
118 AssignTargetPat::Array(arr) => {
119 arr.elems.is_empty() || arr.elems.iter().all(|v| v.is_none())
120 }
121 _ => false,
122 } =>
123 {
124 if cfg!(feature = "debug") {
125 debug!("Dropping assignment to an empty array pattern");
126 }
127 *e = *right.take();
128 }
129
130 Expr::Assign(AssignExpr {
131 op: op!("="),
132 left: AssignTarget::Pat(left),
133 right,
134 ..
135 }) if match &*left {
136 AssignTargetPat::Object(obj) => obj.props.is_empty(),
137 _ => false,
138 } =>
139 {
140 if cfg!(feature = "debug") {
141 debug!("Dropping assignment to an empty object pattern");
142 }
143 *e = *right.take();
144 }
145
146 Expr::Cond(cond)
147 if !cond.test.may_have_side_effects(self.expr_ctx)
148 && (cond.cons.is_undefined(self.expr_ctx)
149 || matches!(*cond.cons, Expr::Unary(UnaryExpr {
150 op: op!("void"),
151 ref arg,
152 ..
153 }) if !arg.may_have_side_effects(self.expr_ctx)))
154 && (cond.alt.is_undefined(self.expr_ctx)
155 || matches!(*cond.alt, Expr::Unary(UnaryExpr {
156 op: op!("void"),
157 ref arg,
158 ..
159 }) if !arg.may_have_side_effects(self.expr_ctx))) =>
160 {
161 if cfg!(feature = "debug") {
162 debug!("Dropping side-effect-free expressions");
163 }
164 *e = *cond.cons.take();
165 }
166
167 Expr::Bin(be) => match (be.left.is_invalid(), be.right.is_invalid()) {
168 (true, true) => {
169 *e = Expr::dummy();
170 }
171 (true, false) => {
172 *e = *be.right.take();
173 }
174 (false, true) => {
175 *e = *be.left.take();
176 }
177 _ => {}
178 },
179
180 _ => {}
181 }
182 }
183
184 fn visit_mut_expr_or_spreads(&mut self, n: &mut Vec<ExprOrSpread>) {
185 self.maybe_par(cpu_count() * 8, n, |v, n| {
186 n.visit_mut_with(v);
187 })
188 }
189
190 fn visit_mut_exprs(&mut self, n: &mut Vec<Box<Expr>>) {
191 self.maybe_par(cpu_count() * 8, n, |v, n| {
192 n.visit_mut_with(v);
193 })
194 }
195
196 fn visit_mut_for_stmt(&mut self, s: &mut ForStmt) {
197 s.visit_mut_children_with(self);
198
199 s.init = s.init.take().and_then(|e| match e {
200 VarDeclOrExpr::Expr(e) => {
201 ignore_result(e, true, self.expr_ctx).map(VarDeclOrExpr::from)
202 }
203 _ => Some(e),
204 });
205
206 s.update = s
207 .update
208 .take()
209 .and_then(|e| ignore_result(e, true, self.expr_ctx));
210
211 s.test = s.test.take().and_then(|e| {
212 let span = e.span();
213 if let Known(value) = e.as_pure_bool(self.expr_ctx) {
214 return if value {
215 None
216 } else {
217 Some(Lit::Bool(Bool { span, value: false }).into())
218 };
219 }
220
221 Some(e)
222 });
223 }
224
225 fn visit_mut_module_items(&mut self, n: &mut Vec<ModuleItem>) {
226 if cfg!(feature = "debug") {
227 debug!("Removing dead branches");
228 }
229 self.fold_stmt_like(n);
230 }
231
232 fn visit_mut_object_pat(&mut self, p: &mut ObjectPat) {
233 p.visit_mut_children_with(self);
234
235 if p.props.iter().any(|p| matches!(p, ObjectPatProp::Rest(..))) {
237 return;
238 }
239
240 fn is_computed(k: &PropName) -> bool {
241 matches!(k, PropName::Computed(..))
242 }
243
244 p.props.retain(|p| match p {
245 ObjectPatProp::KeyValue(KeyValuePatProp { key, value, .. })
246 if match &**value {
247 Pat::Object(p) => !is_computed(key) && p.props.is_empty(),
248 _ => false,
249 } =>
250 {
251 if cfg!(feature = "debug") {
252 debug!(
253 "Dropping key-value pattern property because it's an empty object pattern"
254 );
255 }
256 false
257 }
258
259 ObjectPatProp::KeyValue(KeyValuePatProp { key, value, .. })
260 if match &**value {
261 Pat::Array(p) => !is_computed(key) && p.elems.is_empty(),
262 _ => false,
263 } =>
264 {
265 if cfg!(feature = "debug") {
266 debug!(
267 "Dropping key-value pattern property because it's an empty array pattern"
268 );
269 }
270 false
271 }
272 _ => true,
273 });
274 }
275
276 fn visit_mut_object_pat_prop(&mut self, p: &mut ObjectPatProp) {
277 p.visit_mut_children_with(self);
278
279 match p {
280 ObjectPatProp::Assign(AssignPatProp {
281 span,
282 key,
283 value: Some(expr),
284 }) if expr.is_undefined(self.expr_ctx)
285 || match **expr {
286 Expr::Unary(UnaryExpr {
287 op: op!("void"),
288 ref arg,
289 ..
290 }) => is_literal(&**arg),
291 _ => false,
292 } =>
293 {
294 *p = ObjectPatProp::Assign(AssignPatProp {
295 span: *span,
296 key: key.take(),
297 value: None,
298 });
299 }
300
301 _ => {}
302 }
303 }
304
305 fn visit_mut_opt_var_decl_or_expr(&mut self, n: &mut Option<VarDeclOrExpr>) {
306 n.visit_mut_children_with(self);
307
308 if let Some(VarDeclOrExpr::Expr(e)) = n {
309 if e.is_invalid() {
310 *n = None;
311 }
312 }
313 }
314
315 fn visit_mut_opt_vec_expr_or_spreads(&mut self, n: &mut Vec<Option<ExprOrSpread>>) {
316 self.maybe_par(cpu_count() * 8, n, |v, n| {
317 n.visit_mut_with(v);
318 })
319 }
320
321 fn visit_mut_pat(&mut self, p: &mut Pat) {
322 p.visit_mut_children_with(self);
323
324 match p {
325 Pat::Assign(assign)
326 if assign.right.is_undefined(self.expr_ctx)
327 || match *assign.right {
328 Expr::Unary(UnaryExpr {
329 op: op!("void"),
330 ref arg,
331 ..
332 }) => is_literal(&**arg),
333 _ => false,
334 } =>
335 {
336 *p = *assign.left.take();
337 }
338
339 _ => {}
340 }
341 }
342
343 fn visit_mut_prop_or_spreads(&mut self, n: &mut Vec<PropOrSpread>) {
344 self.maybe_par(cpu_count() * 8, n, |v, n| {
345 n.visit_mut_with(v);
346 })
347 }
348
349 fn visit_mut_seq_expr(&mut self, e: &mut SeqExpr) {
350 e.visit_mut_children_with(self);
351 if e.exprs.is_empty() {
352 return;
353 }
354
355 let last = e.exprs.pop().unwrap();
356
357 let should_preserved_this = last.directness_matters();
358
359 let mut exprs = if should_preserved_this {
360 e.exprs
361 .take()
362 .into_iter()
363 .enumerate()
364 .filter_map(|(idx, e)| {
365 if idx == 0 {
366 return Some(e);
367 }
368
369 ignore_result(e, true, self.expr_ctx)
370 })
371 .collect()
372 } else {
373 e.exprs
374 .take()
375 .move_flat_map(|e| ignore_result(e, false, self.expr_ctx))
376 };
377
378 exprs.push(last);
379
380 e.exprs = exprs;
381 }
382
383 fn visit_mut_stmt(&mut self, stmt: &mut Stmt) {
384 stmt.visit_mut_children_with(self);
385
386 stmt.map_with_mut(|stmt| {
387 match stmt {
388 Stmt::If(IfStmt {
389 span,
390 test,
391 cons,
392 alt,
393 }) => {
394 if let Stmt::If(IfStmt { alt: Some(..), .. }) = *cons {
395 return IfStmt {
396 test,
397 cons: Box::new(
398 BlockStmt {
399 stmts: vec![*cons],
400 ..Default::default()
401 }
402 .into(),
403 ),
404 alt,
405 span,
406 }
407 .into();
408 }
409
410 let mut stmts = Vec::new();
411 if let (p, Known(v)) = test.cast_to_bool(self.expr_ctx) {
412 if cfg!(feature = "debug") {
413 trace!("The condition for if statement is always {}", v);
414 }
415
416 if !p.is_pure() {
418 if let Some(expr) = ignore_result(test, true, self.expr_ctx) {
419 stmts.push(ExprStmt { span, expr }.into())
420 }
421 }
422
423 if v {
424 if let Some(var) = alt.and_then(|alt| alt.extract_var_ids_as_var()) {
426 stmts.push(var.into())
427 }
428 stmts.push(*cons);
429 } else {
430 if let Some(var) = cons.extract_var_ids_as_var() {
431 stmts.push(var.into())
432 }
433
434 if let Some(alt) = alt {
435 stmts.push(*alt)
436 }
437 }
438
439 if stmts.is_empty() {
440 return EmptyStmt { span }.into();
441 }
442
443 if cfg!(feature = "debug") {
444 debug!("Optimized an if statement with known condition");
445 }
446
447 self.changed = true;
448
449 let mut block: Stmt = BlockStmt {
450 span,
451 stmts,
452 ..Default::default()
453 }
454 .into();
455 block.visit_mut_with(self);
456 return block;
457 }
458
459 let alt = match &alt {
460 Some(stmt) if stmt.is_empty() => None,
461 _ => alt,
462 };
463 if alt.is_none() {
464 if let Stmt::Empty(..) = *cons {
465 self.changed = true;
466
467 return if let Some(expr) = ignore_result(test, true, self.expr_ctx) {
468 ExprStmt { span, expr }.into()
469 } else {
470 EmptyStmt { span }.into()
471 };
472 }
473 }
474
475 IfStmt {
476 span,
477 test,
478 cons,
479 alt,
480 }
481 .into()
482 }
483
484 Stmt::Decl(Decl::Var(v)) if v.decls.is_empty() => {
485 if cfg!(feature = "debug") {
486 debug!("Dropping an empty var declaration");
487 }
488 EmptyStmt { span: v.span }.into()
489 }
490
491 Stmt::Labeled(LabeledStmt {
492 label, span, body, ..
493 }) if body.is_empty() => {
494 debug!("Dropping an empty label statement: `{}`", label);
495 EmptyStmt { span }.into()
496 }
497
498 Stmt::Labeled(LabeledStmt {
499 span,
500 body,
501 ref label,
502 ..
503 }) if match &*body {
504 Stmt::Break(BreakStmt { label: Some(b), .. }) => label.sym == b.sym,
505 _ => false,
506 } =>
507 {
508 debug!("Dropping a label statement with instant break: `{}`", label);
509 EmptyStmt { span }.into()
510 }
511
512 Stmt::Expr(ExprStmt { span, expr, .. }) => {
514 if let Expr::Lit(Lit::Str(..)) = &*expr {
516 return ExprStmt { span, expr }.into();
517 }
518
519 match ignore_result(expr, false, self.expr_ctx) {
520 Some(e) => ExprStmt { span, expr: e }.into(),
521 None => EmptyStmt { span: DUMMY_SP }.into(),
522 }
523 }
524
525 Stmt::Block(BlockStmt { span, stmts, ctxt }) => {
526 if stmts.is_empty() {
527 if cfg!(feature = "debug") {
528 debug!("Drooping an empty block statement");
529 }
530
531 EmptyStmt { span }.into()
532 } else if stmts.len() == 1
533 && !is_block_scoped_stuff(&stmts[0])
534 && stmt_depth(&stmts[0]) <= 1
535 {
536 if cfg!(feature = "debug") {
537 debug!("Optimizing a block statement with a single statement");
538 }
539
540 let mut v = stmts.into_iter().next().unwrap();
541 v.visit_mut_with(self);
542 v
543 } else {
544 BlockStmt { span, stmts, ctxt }.into()
545 }
546 }
547 Stmt::Try(s) => {
548 let TryStmt {
549 span,
550 block,
551 handler,
552 finalizer,
553 } = *s;
554
555 if block.is_empty() {
557 let var = handler.and_then(|h| Stmt::from(h.body).extract_var_ids_as_var());
558
559 return if let Some(mut finalizer) = finalizer {
560 if let Some(var) = var.map(Box::new).map(Decl::from).map(Stmt::from) {
561 prepend_stmt(&mut finalizer.stmts, var);
562 }
563 finalizer.into()
564 } else {
565 var.map(Box::new)
566 .map(Decl::from)
567 .map(Stmt::from)
568 .unwrap_or_else(|| EmptyStmt { span }.into())
569 };
570 }
571
572 if handler.is_none() && finalizer.is_empty() {
575 if cfg!(feature = "debug") {
576 debug!("Converting a try statement to a block statement");
577 }
578
579 return block.into();
580 }
581
582 TryStmt {
583 span,
584 block,
585 handler,
586 finalizer,
587 }
588 .into()
589 }
590
591 Stmt::Switch(mut s) => {
592 if s.cases
593 .iter()
594 .any(|case| matches!(case.test.as_deref(), Some(Expr::Update(..))))
595 {
596 return s.into();
597 }
598 if let Expr::Update(..) = &*s.discriminant {
599 if s.cases.len() != 1 {
600 return s.into();
601 }
602 }
603
604 let remove_break = |stmts: Vec<Stmt>| {
605 debug_assert!(
606 !has_conditional_stopper(&stmts) || has_unconditional_stopper(&stmts)
607 );
608
609 let mut done = false;
610 stmts.move_flat_map(|s| {
611 if done {
612 match s {
613 Stmt::Decl(Decl::Var(var))
614 if matches!(
615 &*var,
616 VarDecl {
617 kind: VarDeclKind::Var,
618 ..
619 }
620 ) =>
621 {
622 return Some(
623 VarDecl {
624 span: DUMMY_SP,
625 kind: VarDeclKind::Var,
626 decls: var.decls.move_map(|decl| VarDeclarator {
627 init: None,
628 ..decl
629 }),
630 ..Default::default()
631 }
632 .into(),
633 );
634 }
635 _ => (),
636 }
637
638 return None;
639 }
640 match s {
641 Stmt::Break(BreakStmt { label: None, .. }) => {
642 done = true;
643 None
644 }
645 Stmt::Return(..) | Stmt::Throw(..) => {
646 done = true;
647 Some(s)
648 }
649 _ => Some(s),
650 }
651 })
652 };
653
654 let is_matching_literal = match *s.discriminant {
655 Expr::Lit(Lit::Str(..))
656 | Expr::Lit(Lit::Null(..))
657 | Expr::Lit(Lit::Num(..)) => true,
658 ref e if e.is_nan() || e.is_global_ref_to(self.expr_ctx, "undefined") => {
659 true
660 }
661 _ => false,
662 };
663
664 if s.cases.is_empty() {
666 if cfg!(feature = "debug") {
667 debug!("Removing an empty switch statement");
668 }
669 return match ignore_result(s.discriminant, true, self.expr_ctx) {
670 Some(expr) => ExprStmt { span: s.span, expr }.into(),
671 None => EmptyStmt { span: s.span }.into(),
672 };
673 }
674
675 if s.cases.len() == 1
677 && s.cases[0].test.is_none()
678 && !has_conditional_stopper(&s.cases[0].cons)
679 {
680 if cfg!(feature = "debug") {
681 debug!("Switch -> Block as default is the only case");
682 }
683
684 let mut stmts = remove_break(s.cases.remove(0).cons);
685 if let Some(expr) = ignore_result(s.discriminant, true, self.expr_ctx) {
686 prepend_stmt(&mut stmts, expr.into_stmt());
687 }
688
689 let mut block: Stmt = BlockStmt {
690 span: s.span,
691 stmts,
692 ..Default::default()
693 }
694 .into();
695 block.visit_mut_with(self);
696 return block;
697 }
698
699 let mut non_constant_case_idx = None;
700 let selected = {
701 let mut i = 0;
702 s.cases.iter().position(|case| {
703 if non_constant_case_idx.is_some() {
704 i += 1;
705 return false;
706 }
707
708 if let Some(ref test) = case.test {
709 let v = match (&**test, &*s.discriminant) {
710 (
711 &Expr::Lit(Lit::Str(Str {
712 value: ref test, ..
713 })),
714 &Expr::Lit(Lit::Str(Str { value: ref d, .. })),
715 ) => *test == *d,
716 (
717 &Expr::Lit(Lit::Num(Number { value: test, .. })),
718 &Expr::Lit(Lit::Num(Number { value: d, .. })),
719 ) => (test - d).abs() < 1e-10,
720 (
721 &Expr::Lit(Lit::Bool(Bool { value: test, .. })),
722 &Expr::Lit(Lit::Bool(Bool { value: d, .. })),
723 ) => test == d,
724 (&Expr::Lit(Lit::Null(..)), &Expr::Lit(Lit::Null(..))) => true,
725
726 _ => {
727 if !test.is_nan()
728 && !test.is_global_ref_to(self.expr_ctx, "undefined")
729 {
730 non_constant_case_idx = Some(i);
731 }
732
733 false
734 }
735 };
736
737 i += 1;
738 return v;
739 }
740
741 i += 1;
742 false
743 })
744 };
745
746 let are_all_tests_known = s
747 .cases
748 .iter()
749 .map(|case| case.test.as_deref())
750 .all(|s| matches!(s, Some(Expr::Lit(..)) | None));
751
752 let mut var_ids = Vec::new();
753 if let Some(i) = selected {
754 if !has_conditional_stopper(&s.cases[i].cons) {
755 let mut exprs = Vec::new();
756 exprs.extend(ignore_result(s.discriminant, true, self.expr_ctx));
757
758 let mut stmts = s.cases[i].cons.take();
759 let mut cases = s.cases.drain((i + 1)..);
760
761 for case in cases.by_ref() {
762 let should_stop = has_unconditional_stopper(&case.cons);
763 stmts.extend(case.cons);
764 if should_stop {
766 break;
767 }
768 }
769
770 let mut stmts = remove_break(stmts);
771
772 let decls = cases
773 .flat_map(|case| {
774 exprs.extend(
775 case.test
776 .and_then(|e| ignore_result(e, true, self.expr_ctx)),
777 );
778 case.cons
779 })
780 .flat_map(|stmt| stmt.extract_var_ids())
781 .map(|i| VarDeclarator {
782 span: DUMMY_SP,
783 name: i.into(),
784 init: None,
785 definite: false,
786 })
787 .collect::<Vec<_>>();
788
789 if !decls.is_empty() {
790 prepend_stmt(
791 &mut stmts,
792 VarDecl {
793 span: DUMMY_SP,
794 kind: VarDeclKind::Var,
795 decls,
796 declare: false,
797 ..Default::default()
798 }
799 .into(),
800 );
801 }
802
803 if !exprs.is_empty() {
804 prepend_stmt(
805 &mut stmts,
806 ExprStmt {
807 span: DUMMY_SP,
808 expr: if exprs.len() == 1 {
809 exprs.remove(0)
810 } else {
811 SeqExpr {
812 span: DUMMY_SP,
813 exprs,
814 }
815 .into()
816 },
817 }
818 .into(),
819 );
820 }
821
822 if cfg!(feature = "debug") {
823 debug!("Switch -> Block as we know discriminant");
824 }
825 let mut block: Stmt = BlockStmt {
826 span: s.span,
827 stmts,
828 ..Default::default()
829 }
830 .into();
831 block.visit_mut_with(self);
832 return block;
833 }
834 } else if are_all_tests_known {
835 let mut vars = Vec::new();
836
837 if let Expr::Lit(..) = *s.discriminant {
838 let idx = s.cases.iter().position(|v| v.test.is_none());
839
840 if let Some(i) = idx {
841 for case in &s.cases[..i] {
842 for cons in &case.cons {
843 vars.extend(cons.extract_var_ids().into_iter().map(
844 |name| VarDeclarator {
845 span: DUMMY_SP,
846 name: name.into(),
847 init: None,
848 definite: Default::default(),
849 },
850 ));
851 }
852 }
853
854 if !has_conditional_stopper(&s.cases[i].cons) {
855 let stmts = s.cases.remove(i).cons;
856 let mut stmts = remove_break(stmts);
857
858 if !vars.is_empty() {
859 prepend_stmt(
860 &mut stmts,
861 VarDecl {
862 span: DUMMY_SP,
863 kind: VarDeclKind::Var,
864 declare: Default::default(),
865 decls: take(&mut vars),
866 ..Default::default()
867 }
868 .into(),
869 )
870 }
871
872 let mut block: Stmt = BlockStmt {
873 span: s.span,
874 stmts,
875 ..Default::default()
876 }
877 .into();
878
879 block.visit_mut_with(self);
880
881 if cfg!(feature = "debug") {
882 debug!("Switch -> Block as the discriminant is a literal");
883 }
884 return block;
885 }
886 }
887 }
888 }
889
890 if is_matching_literal {
891 let mut idx = 0usize;
892 let mut breaked = false;
893 s.cases = s.cases.move_flat_map(|case| {
895 if non_constant_case_idx.is_some()
896 && idx >= non_constant_case_idx.unwrap()
897 {
898 idx += 1;
899 return Some(case);
900 }
901
902 if selected.is_some() && selected <= Some(idx) {
904 if breaked {
906 idx += 1;
907 if cfg!(feature = "debug") {
908 debug!("Dropping case because it is unreachable");
909 }
910 return None;
911 }
912
913 if !breaked {
914 breaked |= has_unconditional_stopper(&case.cons);
916 }
917
918 idx += 1;
919 return Some(case);
920 }
921
922 let res = match case.test {
923 Some(e)
924 if matches!(
925 &*e,
926 Expr::Lit(Lit::Num(..))
927 | Expr::Lit(Lit::Str(..))
928 | Expr::Lit(Lit::Null(..))
929 ) =>
930 {
931 case.cons
932 .into_iter()
933 .for_each(|stmt| var_ids.extend(stmt.extract_var_ids()));
934
935 if cfg!(feature = "debug") {
936 debug!(
937 "Dropping case because it is unreachable (literal \
938 test)"
939 );
940 }
941 None
942 }
943 _ => Some(case),
944 };
945 idx += 1;
946 res
947 });
948 }
949
950 let is_default_last =
951 matches!(s.cases.last(), Some(SwitchCase { test: None, .. }));
952
953 {
954 let is_all_case_empty = s
956 .cases
957 .iter()
958 .all(|case| case.test.is_none() || case.cons.is_empty());
959
960 let is_all_case_side_effect_free = s.cases.iter().all(|case| {
961 case.test
962 .as_ref()
963 .map(|e| e.is_ident() || !e.may_have_side_effects(self.expr_ctx))
964 .unwrap_or(true)
965 });
966
967 if is_default_last
968 && is_all_case_empty
969 && is_all_case_side_effect_free
970 && !has_conditional_stopper(&s.cases.last().unwrap().cons)
971 {
972 let mut exprs = Vec::new();
973 exprs.extend(ignore_result(s.discriminant, true, self.expr_ctx));
974
975 exprs.extend(
976 s.cases
977 .iter_mut()
978 .filter_map(|case| case.test.take())
979 .filter_map(|e| ignore_result(e, true, self.expr_ctx)),
980 );
981
982 let stmts = s.cases.pop().unwrap().cons;
983 let mut stmts = remove_break(stmts);
984
985 if !exprs.is_empty() {
986 prepend_stmt(
987 &mut stmts,
988 ExprStmt {
989 span: DUMMY_SP,
990 expr: if exprs.len() == 1 {
991 exprs.remove(0)
992 } else {
993 SeqExpr {
994 span: DUMMY_SP,
995 exprs,
996 }
997 .into()
998 },
999 }
1000 .into(),
1001 );
1002 }
1003
1004 if cfg!(feature = "debug") {
1005 debug!("Stmt -> Block as all cases are empty");
1006 }
1007 let mut block: Stmt = BlockStmt {
1008 span: s.span,
1009 stmts,
1010 ..Default::default()
1011 }
1012 .into();
1013 block.visit_mut_with(self);
1014 return block;
1015 }
1016 }
1017
1018 if is_matching_literal
1019 && s.cases.iter().all(|case| match &case.test {
1020 Some(e)
1021 if matches!(
1022 &**e,
1023 Expr::Lit(Lit::Str(..))
1024 | Expr::Lit(Lit::Null(..))
1025 | Expr::Lit(Lit::Num(..))
1026 ) =>
1027 {
1028 true
1029 }
1030 _ => false,
1031 })
1032 {
1033 if s.cases
1035 .iter()
1036 .all(|case| !has_conditional_stopper(&case.cons))
1037 {
1038 if cfg!(feature = "debug") {
1039 debug!("Removing swtich because all cases are unreachable");
1040 }
1041
1042 let decls: Vec<_> = s
1044 .cases
1045 .into_iter()
1046 .flat_map(|case| extract_var_ids(&case.cons))
1047 .chain(var_ids)
1048 .map(|i| VarDeclarator {
1049 span: i.span,
1050 name: i.into(),
1051 init: None,
1052 definite: false,
1053 })
1054 .collect();
1055 if !decls.is_empty() {
1056 return VarDecl {
1057 span: DUMMY_SP,
1058 kind: VarDeclKind::Var,
1059 decls,
1060 declare: false,
1061 ..Default::default()
1062 }
1063 .into();
1064 }
1065 return EmptyStmt { span: s.span }.into();
1066 }
1067 }
1068
1069 s.into()
1070 }
1071
1072 Stmt::For(s)
1073 if match &s.test {
1074 Some(test) => {
1075 matches!(&**test, Expr::Lit(Lit::Bool(Bool { value: false, .. })))
1076 }
1077 _ => false,
1078 } =>
1079 {
1080 if cfg!(feature = "debug") {
1081 debug!("Optimizing a for statement with a false test");
1082 }
1083
1084 let decl = s.body.extract_var_ids_as_var();
1085 let body = if let Some(var) = decl {
1086 var.into()
1087 } else {
1088 EmptyStmt { span: s.span }.into()
1089 };
1090
1091 if s.init.is_some() {
1092 ForStmt {
1093 body: Box::new(body),
1094 update: None,
1095 ..s
1096 }
1097 .into()
1098 } else {
1099 body
1100 }
1101 }
1102
1103 Stmt::While(s) => {
1104 if let (purity, Known(v)) = s.test.cast_to_bool(self.expr_ctx) {
1105 if v {
1106 if purity.is_pure() {
1107 WhileStmt {
1108 test: Lit::Bool(Bool {
1109 span: s.test.span(),
1110 value: true,
1111 })
1112 .into(),
1113 ..s
1114 }
1115 .into()
1116 } else {
1117 s.into()
1118 }
1119 } else {
1120 let body = s.body.extract_var_ids_as_var();
1121 let body = body.map(Box::new).map(Decl::Var).map(Stmt::Decl);
1122 let body = body.unwrap_or(EmptyStmt { span: s.span }.into());
1123
1124 if purity.is_pure() {
1125 body
1126 } else {
1127 WhileStmt {
1128 body: Box::new(body),
1129 ..s
1130 }
1131 .into()
1132 }
1133 }
1134 } else {
1135 s.into()
1136 }
1137 }
1138
1139 Stmt::DoWhile(s) => {
1140 if has_conditional_stopper(&[s.clone().into()]) {
1141 return s.into();
1142 }
1143
1144 if let Known(v) = s.test.as_pure_bool(self.expr_ctx) {
1145 if v {
1146 ForStmt {
1148 span: s.span,
1149 init: None,
1150 test: None,
1151 update: None,
1152 body: s.body,
1153 }
1154 .into()
1155 } else {
1156 let mut body = prepare_loop_body_for_inlining(*s.body);
1157 body.visit_mut_with(self);
1158
1159 if let Some(test) = ignore_result(s.test, true, self.expr_ctx) {
1160 BlockStmt {
1161 span: s.span,
1162 stmts: vec![body, test.into_stmt()],
1163 ..Default::default()
1164 }
1165 .into()
1166 } else {
1167 body
1168 }
1169 }
1170 } else {
1171 s.into()
1172 }
1173 }
1174
1175 Stmt::Decl(Decl::Var(v)) => {
1176 let decls = v.decls.move_flat_map(|v| {
1177 if !is_literal(&v.init) {
1178 return Some(v);
1179 }
1180
1181 match &v.name {
1183 Pat::Object(o) if o.props.is_empty() => {
1184 if cfg!(feature = "debug") {
1185 debug!("Dropping an object pattern in a var declaration");
1186 }
1187
1188 None
1189 }
1190 Pat::Array(a) if a.elems.is_empty() => {
1191 if cfg!(feature = "debug") {
1192 debug!("Dropping an array pattern in a var declaration");
1193 }
1194
1195 None
1196 }
1197
1198 _ => Some(v),
1199 }
1200 });
1201
1202 if decls.is_empty() {
1203 if cfg!(feature = "debug") {
1204 debug!("Dropping a useless variable declaration");
1205 }
1206
1207 return EmptyStmt { span: v.span }.into();
1208 }
1209
1210 VarDecl { decls, ..*v }.into()
1211 }
1212
1213 _ => stmt,
1214 }
1215 })
1216 }
1217
1218 fn visit_mut_stmts(&mut self, n: &mut Vec<Stmt>) {
1219 self.fold_stmt_like(n)
1220 }
1221
1222 fn visit_mut_switch_stmt(&mut self, s: &mut SwitchStmt) {
1223 s.visit_mut_children_with(self);
1224
1225 if s.cases
1226 .iter()
1227 .any(|case| matches!(case.test.as_deref(), Some(Expr::Update(..))))
1228 {
1229 return;
1230 }
1231
1232 if s.cases.iter().all(|case| {
1233 if let Some(test) = case.test.as_deref() {
1234 if test.may_have_side_effects(self.expr_ctx) {
1235 return false;
1236 }
1237 }
1238
1239 if case.cons.is_empty() {
1240 return true;
1241 }
1242
1243 matches!(case.cons[0], Stmt::Break(BreakStmt { label: None, .. }))
1244 }) {
1245 s.cases.clear();
1246 }
1247 }
1248
1249 fn visit_mut_var_declarators(&mut self, n: &mut Vec<VarDeclarator>) {
1250 self.maybe_par(cpu_count() * 8, n, |v, n| {
1251 n.visit_mut_with(v);
1252 })
1253 }
1254}
1255
1256impl Remover {
1257 fn fold_stmt_like<T>(&mut self, stmts: &mut Vec<T>)
1258 where
1259 T: StmtLike + VisitWith<Hoister> + VisitMutWith<Self>,
1260 {
1261 let orig_len = stmts.len();
1262
1263 let is_block_stmt = self.normal_block;
1264 self.normal_block = false;
1265
1266 let mut new_stmts = Vec::with_capacity(stmts.len());
1267
1268 self.maybe_par(cpu_count() * 8, &mut *stmts, |visitor, stmt| {
1269 visitor.normal_block = true;
1270 stmt.visit_mut_with(visitor);
1271 });
1272
1273 let mut iter = stmts.take().into_iter();
1274 while let Some(stmt_like) = iter.next() {
1275 let stmt_like = match stmt_like.try_into_stmt() {
1276 Ok(stmt) => {
1277 let stmt = match stmt {
1278 Stmt::Empty(..) => continue,
1280
1281 Stmt::Expr(ExprStmt { ref expr, .. })
1282 if match &**expr {
1283 Expr::Lit(Lit::Str(..)) => false,
1284 Expr::Lit(..) => true,
1285 _ => false,
1286 } && is_block_stmt =>
1287 {
1288 continue
1289 }
1290
1291 Stmt::Throw(..)
1293 | Stmt::Return { .. }
1294 | Stmt::Continue { .. }
1295 | Stmt::Break { .. } => {
1296 let mut decls = Vec::new();
1298 let mut hoisted_fns = Vec::new();
1299 for t in iter {
1300 match t.try_into_stmt() {
1301 Ok(Stmt::Decl(Decl::Fn(f))) => {
1302 hoisted_fns.push(T::from(f.into()));
1303 }
1304 Ok(t) => {
1305 let ids = extract_var_ids(&t).into_iter().map(|i| {
1306 VarDeclarator {
1307 span: i.span,
1308 name: i.into(),
1309 init: None,
1310 definite: false,
1311 }
1312 });
1313 decls.extend(ids);
1314 }
1315 Err(item) => new_stmts.push(item),
1316 }
1317 }
1318
1319 if !decls.is_empty() {
1320 new_stmts.push(T::from(
1321 VarDecl {
1322 span: DUMMY_SP,
1323 kind: VarDeclKind::Var,
1324 decls,
1325 declare: false,
1326 ..Default::default()
1327 }
1328 .into(),
1329 ));
1330 }
1331
1332 let stmt_like = T::from(stmt);
1333 new_stmts.push(stmt_like);
1334 new_stmts.extend(hoisted_fns);
1335
1336 *stmts = new_stmts;
1337 if stmts.len() != orig_len {
1338 self.changed = true;
1339
1340 if cfg!(feature = "debug") {
1341 debug!("Dropping statements after a control keyword");
1342 }
1343 }
1344
1345 return;
1346 }
1347
1348 Stmt::Block(BlockStmt {
1349 span,
1350 mut stmts,
1351 ctxt,
1352 ..
1353 }) => {
1354 if stmts.is_empty() {
1355 continue;
1356 }
1357
1358 if !is_ok_to_inline_block(&stmts) {
1359 stmts.visit_mut_with(self);
1360 BlockStmt { span, stmts, ctxt }.into()
1361 } else {
1362 new_stmts.extend(
1363 stmts
1364 .into_iter()
1365 .filter(|s| !matches!(s, Stmt::Empty(..)))
1366 .map(T::from),
1367 );
1368 continue;
1369 }
1370 }
1371
1372 Stmt::If(IfStmt {
1374 test,
1375 cons,
1376 alt,
1377 span,
1378 }) => {
1379 match test.cast_to_bool(self.expr_ctx) {
1381 (purity, Known(val)) => {
1382 self.changed = true;
1383 if !purity.is_pure() {
1384 let expr = ignore_result(test, true, self.expr_ctx);
1385
1386 if let Some(expr) = expr {
1387 new_stmts.push(T::from(
1388 ExprStmt {
1389 span: DUMMY_SP,
1390 expr,
1391 }
1392 .into(),
1393 ));
1394 }
1395 }
1396
1397 if val {
1398 if let Some(var) =
1400 alt.and_then(|alt| alt.extract_var_ids_as_var())
1401 {
1402 new_stmts.push(T::from(var.into()))
1403 }
1404 *cons
1405 } else {
1406 if let Some(var) = cons.extract_var_ids_as_var() {
1408 new_stmts.push(T::from(var.into()))
1409 }
1410 match alt {
1411 Some(alt) => *alt,
1412 None => continue,
1413 }
1414 }
1415 }
1416 _ => IfStmt {
1417 test,
1418 cons,
1419 alt,
1420 span,
1421 }
1422 .into(),
1423 }
1424 }
1425
1426 _ => stmt,
1427 };
1428
1429 T::from(stmt)
1430 }
1431 Err(stmt_like) => stmt_like,
1432 };
1433
1434 new_stmts.push(stmt_like);
1435 }
1436
1437 *stmts = new_stmts
1438 }
1439}
1440
1441#[inline(never)]
1447fn ignore_result(e: Box<Expr>, drop_str_lit: bool, ctx: ExprCtx) -> Option<Box<Expr>> {
1448 match *e {
1449 Expr::Lit(Lit::Num(..))
1450 | Expr::Lit(Lit::Bool(..))
1451 | Expr::Lit(Lit::Null(..))
1452 | Expr::Lit(Lit::Regex(..)) => None,
1453
1454 Expr::Lit(Lit::Str(ref v)) if drop_str_lit || v.value.is_empty() => None,
1455
1456 Expr::Paren(ParenExpr { expr, .. }) => ignore_result(expr, true, ctx),
1457
1458 Expr::Assign(AssignExpr {
1459 op: op!("="),
1460 left: AssignTarget::Simple(left),
1461 right,
1462 ..
1463 }) if match &left {
1464 SimpleAssignTarget::Ident(l) => match &*right {
1465 Expr::Ident(r) => l.sym == r.sym && l.ctxt == r.ctxt,
1466 _ => false,
1467 },
1468 _ => false,
1469 } =>
1470 {
1471 None
1472 }
1473
1474 Expr::Bin(BinExpr {
1475 span,
1476 left,
1477 op,
1478 right,
1479 }) if !op.may_short_circuit() => {
1480 let left = ignore_result(left, true, ctx);
1481 let right = ignore_result(right, true, ctx);
1482
1483 match (left, right) {
1484 (Some(l), Some(r)) => ignore_result(
1485 ctx.preserve_effects(span, Expr::undefined(span), vec![l, r]),
1486 true,
1487 ctx,
1488 ),
1489 (Some(l), None) => Some(l),
1490 (None, Some(r)) => Some(r),
1491 (None, None) => None,
1492 }
1493 }
1494
1495 Expr::Bin(BinExpr {
1496 span,
1497 left,
1498 op,
1499 right,
1500 }) => {
1501 if op == op!("&&") {
1502 let right = if let Some(right) = ignore_result(right, true, ctx) {
1503 right
1504 } else {
1505 return ignore_result(left, true, ctx);
1506 };
1507
1508 let l = left.as_pure_bool(ctx);
1509
1510 if let Known(l) = l {
1511 if l {
1512 Some(right)
1513 } else {
1514 None
1515 }
1516 } else {
1517 Some(
1518 BinExpr {
1519 span,
1520 left,
1521 op,
1522 right,
1523 }
1524 .into(),
1525 )
1526 }
1527 } else {
1528 debug_assert!(op == op!("||") || op == op!("??"));
1529
1530 let l = left.as_pure_bool(ctx);
1531
1532 if let Known(l) = l {
1533 if l {
1534 None
1535 } else {
1536 ignore_result(right, true, ctx)
1537 }
1538 } else {
1539 let right = ignore_result(right, true, ctx);
1540 if let Some(right) = right {
1541 Some(
1542 BinExpr {
1543 span,
1544 left,
1545 op,
1546 right,
1547 }
1548 .into(),
1549 )
1550 } else {
1551 ignore_result(left, true, ctx)
1552 }
1553 }
1554 }
1555 }
1556
1557 Expr::Unary(UnaryExpr { span, op, arg }) => match op {
1558 op!("!")
1560 if match &*arg {
1561 Expr::Call(call) => match &call.callee {
1562 Callee::Expr(callee) => matches!(&**callee, Expr::Fn(..)),
1563 _ => false,
1564 },
1565 _ => false,
1566 } =>
1567 {
1568 Some(UnaryExpr { span, op, arg }.into())
1569 }
1570
1571 op!("void") | op!(unary, "+") | op!(unary, "-") | op!("!") | op!("~") => {
1572 ignore_result(arg, true, ctx)
1573 }
1574 _ => Some(UnaryExpr { span, op, arg }.into()),
1575 },
1576
1577 Expr::Array(ArrayLit { span, elems, .. }) => {
1578 let mut has_spread = false;
1579 let elems = elems.move_flat_map(|v| match v {
1580 Some(ExprOrSpread {
1581 spread: Some(..), ..
1582 }) => {
1583 has_spread = true;
1584 Some(v)
1585 }
1586 None => None,
1587 Some(ExprOrSpread { spread: None, expr }) => ignore_result(expr, true, ctx)
1588 .map(|expr| Some(ExprOrSpread { spread: None, expr })),
1589 });
1590
1591 if elems.is_empty() {
1592 None
1593 } else if has_spread {
1594 Some(ArrayLit { span, elems }.into())
1595 } else {
1596 ignore_result(
1597 ctx.preserve_effects(
1598 span,
1599 Expr::undefined(span),
1600 elems.into_iter().map(|v| v.unwrap().expr),
1601 ),
1602 true,
1603 ctx,
1604 )
1605 }
1606 }
1607
1608 Expr::Object(ObjectLit { span, props, .. }) => {
1609 let props = props.move_flat_map(|v| match v {
1610 PropOrSpread::Spread(..) => Some(v),
1611 PropOrSpread::Prop(ref p) => {
1612 if is_literal(&**p) {
1613 None
1614 } else {
1615 Some(v)
1616 }
1617 }
1618 });
1619
1620 if props.is_empty() {
1621 None
1622 } else {
1623 ignore_result(
1624 ctx.preserve_effects(
1625 span,
1626 Expr::undefined(DUMMY_SP),
1627 once(ObjectLit { span, props }.into()),
1628 ),
1629 true,
1630 ctx,
1631 )
1632 }
1633 }
1634
1635 Expr::New(NewExpr {
1636 span,
1637 ref callee,
1638 args,
1639 ..
1640 }) if callee.is_pure_callee(ctx) => ignore_result(
1641 ArrayLit {
1642 span,
1643 elems: args
1644 .map(|args| args.into_iter().map(Some).collect())
1645 .unwrap_or_else(Default::default),
1646 }
1647 .into(),
1648 true,
1649 ctx,
1650 ),
1651
1652 Expr::Call(CallExpr {
1653 span,
1654 callee: Callee::Expr(ref callee),
1655 args,
1656 ..
1657 }) if callee.is_pure_callee(ctx) => ignore_result(
1658 ArrayLit {
1659 span,
1660 elems: args.into_iter().map(Some).collect(),
1661 }
1662 .into(),
1663 true,
1664 ctx,
1665 ),
1666
1667 Expr::Tpl(Tpl { span, exprs, .. }) => ignore_result(
1668 ctx.preserve_effects(span, Expr::undefined(span), exprs),
1669 true,
1670 ctx,
1671 ),
1672
1673 Expr::TaggedTpl(TaggedTpl { span, tag, tpl, .. }) if tag.is_pure_callee(ctx) => {
1674 ignore_result(
1675 ctx.preserve_effects(span, Expr::undefined(span), tpl.exprs),
1676 true,
1677 ctx,
1678 )
1679 }
1680
1681 Expr::Fn(..) => None,
1688
1689 Expr::Seq(SeqExpr {
1690 span, mut exprs, ..
1691 }) => {
1692 if exprs.is_empty() {
1693 return None;
1694 }
1695
1696 let last = ignore_result(exprs.pop().unwrap(), true, ctx);
1697
1698 exprs.extend(last);
1699
1700 if exprs.is_empty() {
1701 return None;
1702 }
1703
1704 if exprs.len() == 1 {
1705 return Some(exprs.pop().unwrap());
1706 }
1707
1708 Some(SeqExpr { span, exprs }.into())
1709 }
1710
1711 Expr::Cond(CondExpr {
1712 span,
1713 test,
1714 cons,
1715 alt,
1716 }) => {
1717 let alt = if let Some(alt) = ignore_result(alt, true, ctx) {
1718 alt
1719 } else {
1720 return ignore_result(
1721 BinExpr {
1722 span,
1723 left: test,
1724 op: op!("&&"),
1725 right: cons,
1726 }
1727 .into(),
1728 true,
1729 ctx,
1730 );
1731 };
1732
1733 let cons = if let Some(cons) = ignore_result(cons, true, ctx) {
1734 cons
1735 } else {
1736 return ignore_result(
1737 BinExpr {
1738 span,
1739 left: test,
1740 op: op!("||"),
1741 right: alt,
1742 }
1743 .into(),
1744 true,
1745 ctx,
1746 );
1747 };
1748
1749 Some(
1750 CondExpr {
1751 span,
1752 test,
1753 cons,
1754 alt,
1755 }
1756 .into(),
1757 )
1758 }
1759
1760 _ => Some(e),
1761 }
1762}
1763
1764fn is_ok_to_inline_block(s: &[Stmt]) -> bool {
1800 if s.iter().any(is_block_scoped_stuff) {
1802 return false;
1803 }
1804
1805 let last_var = s.iter().rposition(|s| match s {
1807 Stmt::Decl(Decl::Var(v))
1808 if matches!(
1809 &**v,
1810 VarDecl {
1811 kind: VarDeclKind::Var,
1812 ..
1813 },
1814 ) =>
1815 {
1816 true
1817 }
1818 _ => false,
1819 });
1820
1821 let last_var = if let Some(pos) = last_var {
1822 pos
1823 } else {
1824 return true;
1825 };
1826
1827 let last_stopper = s.iter().rposition(|s| {
1828 matches!(
1829 s,
1830 Stmt::Return(..) | Stmt::Throw(..) | Stmt::Break(..) | Stmt::Continue(..)
1831 )
1832 });
1833
1834 if let Some(last_stopper) = last_stopper {
1835 last_stopper > last_var
1836 } else {
1837 true
1838 }
1839}
1840
1841fn is_block_scoped_stuff(s: &Stmt) -> bool {
1842 match s {
1843 Stmt::Decl(Decl::Var(v)) if v.kind == VarDeclKind::Const || v.kind == VarDeclKind::Let => {
1844 true
1845 }
1846 Stmt::Decl(Decl::Fn(..)) | Stmt::Decl(Decl::Class(..)) => true,
1847 _ => false,
1848 }
1849}
1850
1851fn prepare_loop_body_for_inlining(stmt: Stmt) -> Stmt {
1852 let span = stmt.span();
1853 let mut stmts = match stmt {
1854 Stmt::Block(BlockStmt { stmts, .. }) => stmts,
1855 _ => vec![stmt],
1856 };
1857
1858 let mut done = false;
1859 stmts.retain(|stmt| {
1860 if done {
1861 return false;
1862 }
1863
1864 match stmt {
1865 Stmt::Break(BreakStmt { label: None, .. })
1866 | Stmt::Continue(ContinueStmt { label: None, .. }) => {
1867 done = true;
1868 false
1869 }
1870
1871 Stmt::Return(..) | Stmt::Throw(..) => {
1872 done = true;
1873 true
1874 }
1875
1876 _ => true,
1877 }
1878 });
1879
1880 BlockStmt {
1881 span,
1882 stmts,
1883 ..Default::default()
1884 }
1885 .into()
1886}
1887
1888fn has_unconditional_stopper(s: &[Stmt]) -> bool {
1889 check_for_stopper(s, false)
1890}
1891
1892fn has_conditional_stopper(s: &[Stmt]) -> bool {
1893 check_for_stopper(s, true)
1894}
1895
1896fn check_for_stopper(s: &[Stmt], only_conditional: bool) -> bool {
1897 struct Visitor {
1898 in_cond: bool,
1899 found: bool,
1900 }
1901
1902 impl Visit for Visitor {
1903 noop_visit_type!();
1904
1905 fn visit_switch_case(&mut self, node: &SwitchCase) {
1906 let old = self.in_cond;
1907 self.in_cond = true;
1908 node.cons.visit_with(self);
1909 self.in_cond = old;
1910 }
1911
1912 fn visit_break_stmt(&mut self, s: &BreakStmt) {
1913 if self.in_cond && s.label.is_none() {
1914 self.found = true
1915 }
1916 }
1917
1918 fn visit_continue_stmt(&mut self, s: &ContinueStmt) {
1919 if self.in_cond && s.label.is_none() {
1920 self.found = true
1921 }
1922 }
1923
1924 fn visit_return_stmt(&mut self, _: &ReturnStmt) {
1925 if self.in_cond {
1926 self.found = true
1927 }
1928 }
1929
1930 fn visit_throw_stmt(&mut self, _: &ThrowStmt) {
1931 if self.in_cond {
1932 self.found = true
1933 }
1934 }
1935
1936 fn visit_class(&mut self, _: &Class) {}
1937
1938 fn visit_function(&mut self, _: &Function) {}
1939
1940 fn visit_if_stmt(&mut self, node: &IfStmt) {
1941 let old = self.in_cond;
1942 self.in_cond = true;
1943 node.cons.visit_with(self);
1944 self.in_cond = true;
1945 node.alt.visit_with(self);
1946 self.in_cond = old;
1947 }
1948 }
1949
1950 let mut v = Visitor {
1951 in_cond: !only_conditional,
1952 found: false,
1953 };
1954 v.visit_stmts(s);
1955 v.found
1956}
1957
1958fn stmt_depth(s: &Stmt) -> u32 {
1960 let mut depth = 0;
1961
1962 match s {
1963 Stmt::Block(_) | Stmt::Labeled(_) | Stmt::Switch(_) | Stmt::Decl(_) | Stmt::Expr(_) => {}
1965 Stmt::If(i) => {
1967 depth += 1;
1968 if let Some(alt) = &i.alt {
1969 depth += std::cmp::max(stmt_depth(&i.cons), stmt_depth(alt));
1970 } else {
1971 depth += stmt_depth(&i.cons);
1972 }
1973 }
1974 Stmt::With(WithStmt { body, .. })
1976 | Stmt::While(WhileStmt { body, .. })
1977 | Stmt::DoWhile(DoWhileStmt { body, .. })
1978 | Stmt::For(ForStmt { body, .. })
1979 | Stmt::ForIn(ForInStmt { body, .. })
1980 | Stmt::ForOf(ForOfStmt { body, .. }) => {
1981 depth += 1;
1982 depth += stmt_depth(body);
1983 }
1984 _ => depth += 1,
1986 }
1987
1988 depth
1989}