1#![deny(clippy::all)]
2#![allow(clippy::boxed_local)]
3#![allow(clippy::mutable_key_type)]
4#![allow(clippy::match_like_matches_macro)]
5#![allow(clippy::vec_box)]
6#![cfg_attr(not(feature = "concurrent"), allow(unused))]
7
8#[doc(hidden)]
9pub extern crate swc_atoms;
10#[doc(hidden)]
11pub extern crate swc_common;
12#[doc(hidden)]
13pub extern crate swc_ecma_ast;
14
15use std::{borrow::Cow, hash::Hash, num::FpCategory, ops::Add};
16
17use number::ToJsString;
18use once_cell::sync::Lazy;
19use parallel::{Parallel, ParallelExt};
20use rustc_hash::{FxHashMap, FxHashSet};
21use swc_atoms::{atom, Atom};
22use swc_common::{util::take::Take, Mark, Span, Spanned, SyntaxContext, DUMMY_SP};
23use swc_ecma_ast::*;
24use swc_ecma_visit::{
25 noop_visit_mut_type, noop_visit_type, visit_mut_obj_and_computed, visit_obj_and_computed,
26 Visit, VisitMut, VisitMutWith, VisitWith,
27};
28use tracing::trace;
29
30#[allow(deprecated)]
31pub use self::{
32 factory::{ExprFactory, FunctionFactory, IntoIndirectCall},
33 value::{
34 Merge,
35 Type::{
36 self, Bool as BoolType, Null as NullType, Num as NumberType, Obj as ObjectType,
37 Str as StringType, Symbol as SymbolType, Undefined as UndefinedType,
38 },
39 Value::{self, Known, Unknown},
40 },
41 Purity::{MayBeImpure, Pure},
42};
43use crate::ident::IdentLike;
44
45#[macro_use]
46mod macros;
47pub mod constructor;
48mod factory;
49pub mod function;
50pub mod ident;
51pub mod parallel;
52mod value;
53pub mod var;
54
55mod node_ignore_span;
56pub mod number;
57pub mod stack_size;
58pub mod str;
59pub use node_ignore_span::NodeIgnoringSpan;
60
61pub struct ThisVisitor {
63 found: bool,
64}
65
66pub(crate) static CPU_COUNT: Lazy<usize> = Lazy::new(num_cpus::get);
67pub(crate) static LIGHT_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 100);
68
69impl Visit for ThisVisitor {
70 noop_visit_type!();
71
72 fn visit_constructor(&mut self, _: &Constructor) {}
74
75 fn visit_fn_decl(&mut self, _: &FnDecl) {}
77
78 fn visit_fn_expr(&mut self, _: &FnExpr) {}
80
81 fn visit_function(&mut self, _: &Function) {}
83
84 fn visit_getter_prop(&mut self, n: &GetterProp) {
86 n.key.visit_with(self);
87 }
88
89 fn visit_method_prop(&mut self, n: &MethodProp) {
91 n.key.visit_with(self);
92 n.function.visit_with(self);
93 }
94
95 fn visit_setter_prop(&mut self, n: &SetterProp) {
97 n.key.visit_with(self);
98 n.param.visit_with(self);
99 }
100
101 fn visit_this_expr(&mut self, _: &ThisExpr) {
102 self.found = true;
103 }
104}
105
106pub fn contains_this_expr<N>(body: &N) -> bool
113where
114 N: VisitWith<ThisVisitor>,
115{
116 let mut visitor = ThisVisitor { found: false };
117 body.visit_with(&mut visitor);
118 visitor.found
119}
120
121pub fn contains_ident_ref<'a, N>(body: &N, ident: &'a Ident) -> bool
122where
123 N: VisitWith<IdentRefFinder<'a>>,
124{
125 let mut visitor = IdentRefFinder {
126 found: false,
127 ident,
128 };
129 body.visit_with(&mut visitor);
130 visitor.found
131}
132
133pub struct IdentRefFinder<'a> {
134 ident: &'a Ident,
135 found: bool,
136}
137
138impl Visit for IdentRefFinder<'_> {
139 noop_visit_type!();
140
141 fn visit_expr(&mut self, e: &Expr) {
142 e.visit_children_with(self);
143
144 match *e {
145 Expr::Ident(ref i) if i.ctxt == self.ident.ctxt && i.sym == self.ident.sym => {
146 self.found = true;
147 }
148 _ => {}
149 }
150 }
151}
152
153pub fn contains_arguments<N>(body: &N) -> bool
155where
156 N: VisitWith<ArgumentsFinder>,
157{
158 let mut visitor = ArgumentsFinder { found: false };
159 body.visit_with(&mut visitor);
160 visitor.found
161}
162
163pub struct ArgumentsFinder {
164 found: bool,
165}
166
167impl Visit for ArgumentsFinder {
168 noop_visit_type!();
169
170 fn visit_constructor(&mut self, _: &Constructor) {}
172
173 fn visit_expr(&mut self, e: &Expr) {
174 e.visit_children_with(self);
175
176 if e.is_ident_ref_to("arguments") {
177 self.found = true;
178 }
179 }
180
181 fn visit_function(&mut self, _: &Function) {}
183
184 fn visit_prop(&mut self, n: &Prop) {
185 n.visit_children_with(self);
186
187 if let Prop::Shorthand(i) = n {
188 if &*i.sym == "arguments" {
189 self.found = true;
190 }
191 }
192 }
193}
194
195pub trait StmtOrModuleItem: Send + Sync + Sized {
196 fn into_stmt(self) -> Result<Stmt, ModuleDecl>;
197
198 fn as_stmt(&self) -> Result<&Stmt, &ModuleDecl>;
199
200 fn as_stmt_mut(&mut self) -> Result<&mut Stmt, &mut ModuleDecl>;
201
202 fn from_stmt(stmt: Stmt) -> Self;
203
204 fn try_from_module_decl(decl: ModuleDecl) -> Result<Self, ModuleDecl>;
205}
206
207impl StmtOrModuleItem for Stmt {
208 #[inline]
209 fn into_stmt(self) -> Result<Stmt, ModuleDecl> {
210 Ok(self)
211 }
212
213 #[inline]
214 fn as_stmt(&self) -> Result<&Stmt, &ModuleDecl> {
215 Ok(self)
216 }
217
218 #[inline]
219 fn as_stmt_mut(&mut self) -> Result<&mut Stmt, &mut ModuleDecl> {
220 Ok(self)
221 }
222
223 #[inline]
224 fn from_stmt(stmt: Stmt) -> Self {
225 stmt
226 }
227
228 #[inline]
229 fn try_from_module_decl(decl: ModuleDecl) -> Result<Self, ModuleDecl> {
230 Err(decl)
231 }
232}
233
234impl StmtOrModuleItem for ModuleItem {
235 #[inline]
236 fn into_stmt(self) -> Result<Stmt, ModuleDecl> {
237 match self {
238 ModuleItem::ModuleDecl(v) => Err(v),
239 ModuleItem::Stmt(v) => Ok(v),
240 }
241 }
242
243 #[inline]
244 fn as_stmt(&self) -> Result<&Stmt, &ModuleDecl> {
245 match self {
246 ModuleItem::ModuleDecl(v) => Err(v),
247 ModuleItem::Stmt(v) => Ok(v),
248 }
249 }
250
251 #[inline]
252 fn as_stmt_mut(&mut self) -> Result<&mut Stmt, &mut ModuleDecl> {
253 match self {
254 ModuleItem::ModuleDecl(v) => Err(v),
255 ModuleItem::Stmt(v) => Ok(v),
256 }
257 }
258
259 #[inline]
260 fn from_stmt(stmt: Stmt) -> Self {
261 stmt.into()
262 }
263
264 #[inline]
265 fn try_from_module_decl(decl: ModuleDecl) -> Result<Self, ModuleDecl> {
266 Ok(decl.into())
267 }
268}
269
270pub trait ModuleItemLike: StmtLike {
271 fn try_into_module_decl(self) -> Result<ModuleDecl, Self> {
272 Err(self)
273 }
274 fn try_from_module_decl(decl: ModuleDecl) -> Result<Self, ModuleDecl> {
275 Err(decl)
276 }
277}
278
279pub trait StmtLike: Sized + 'static + Send + Sync + From<Stmt> {
280 fn try_into_stmt(self) -> Result<Stmt, Self>;
281 fn as_stmt(&self) -> Option<&Stmt>;
282 fn as_stmt_mut(&mut self) -> Option<&mut Stmt>;
283}
284
285impl ModuleItemLike for Stmt {}
286
287impl StmtLike for Stmt {
288 #[inline]
289 fn try_into_stmt(self) -> Result<Stmt, Self> {
290 Ok(self)
291 }
292
293 #[inline]
294 fn as_stmt(&self) -> Option<&Stmt> {
295 Some(self)
296 }
297
298 #[inline]
299 fn as_stmt_mut(&mut self) -> Option<&mut Stmt> {
300 Some(self)
301 }
302}
303
304impl ModuleItemLike for ModuleItem {
305 #[inline]
306 fn try_into_module_decl(self) -> Result<ModuleDecl, Self> {
307 match self {
308 ModuleItem::ModuleDecl(decl) => Ok(decl),
309 _ => Err(self),
310 }
311 }
312
313 #[inline]
314 fn try_from_module_decl(decl: ModuleDecl) -> Result<Self, ModuleDecl> {
315 Ok(decl.into())
316 }
317}
318impl StmtLike for ModuleItem {
319 #[inline]
320 fn try_into_stmt(self) -> Result<Stmt, Self> {
321 match self {
322 ModuleItem::Stmt(stmt) => Ok(stmt),
323 _ => Err(self),
324 }
325 }
326
327 #[inline]
328 fn as_stmt(&self) -> Option<&Stmt> {
329 match self {
330 ModuleItem::Stmt(stmt) => Some(stmt),
331 _ => None,
332 }
333 }
334
335 #[inline]
336 fn as_stmt_mut(&mut self) -> Option<&mut Stmt> {
337 match &mut *self {
338 ModuleItem::Stmt(stmt) => Some(stmt),
339 _ => None,
340 }
341 }
342}
343
344pub trait StmtLikeInjector<S>
346where
347 S: StmtLike,
348{
349 fn prepend_stmt(&mut self, insert_with: S);
350 fn prepend_stmts<I>(&mut self, insert_with: I)
351 where
352 I: IntoIterator<Item = S>;
353}
354
355impl<S> StmtLikeInjector<S> for Vec<S>
356where
357 S: StmtLike,
358{
359 fn prepend_stmt(&mut self, insert_with: S) {
361 let directive_pos = self
362 .iter()
363 .position(|stmt| !stmt.as_stmt().is_some_and(is_maybe_branch_directive))
364 .unwrap_or(self.len());
365
366 self.insert(directive_pos, insert_with);
367 }
368
369 fn prepend_stmts<I>(&mut self, insert_with: I)
371 where
372 I: IntoIterator<Item = S>,
373 {
374 let directive_pos = self
375 .iter()
376 .position(|stmt| !stmt.as_stmt().is_some_and(is_maybe_branch_directive))
377 .unwrap_or(self.len());
378
379 self.splice(directive_pos..directive_pos, insert_with);
380 }
381}
382
383pub type BoolValue = Value<bool>;
384
385pub trait IsEmpty {
386 fn is_empty(&self) -> bool;
387}
388
389impl IsEmpty for BlockStmt {
390 fn is_empty(&self) -> bool {
391 self.stmts.is_empty()
392 }
393}
394impl IsEmpty for CatchClause {
395 fn is_empty(&self) -> bool {
396 self.body.stmts.is_empty()
397 }
398}
399impl IsEmpty for Stmt {
400 fn is_empty(&self) -> bool {
401 match *self {
402 Stmt::Empty(_) => true,
403 Stmt::Block(ref b) => b.is_empty(),
404 _ => false,
405 }
406 }
407}
408
409impl<T: IsEmpty> IsEmpty for Option<T> {
410 #[inline]
411 fn is_empty(&self) -> bool {
412 match *self {
413 Some(ref node) => node.is_empty(),
414 None => true,
415 }
416 }
417}
418
419impl<T: IsEmpty> IsEmpty for Box<T> {
420 #[inline]
421 fn is_empty(&self) -> bool {
422 <T as IsEmpty>::is_empty(self)
423 }
424}
425
426impl<T> IsEmpty for Vec<T> {
427 #[inline]
428 fn is_empty(&self) -> bool {
429 self.is_empty()
430 }
431}
432
433pub fn extract_var_ids<T: VisitWith<Hoister>>(node: &T) -> Vec<Ident> {
435 let mut v = Hoister { vars: Vec::new() };
436 node.visit_with(&mut v);
437 v.vars
438}
439
440pub trait StmtExt {
441 fn as_stmt(&self) -> &Stmt;
442
443 fn extract_var_ids(&self) -> Vec<Ident> {
445 extract_var_ids(self.as_stmt())
446 }
447
448 fn extract_var_ids_as_var(&self) -> Option<VarDecl> {
449 let ids = self.extract_var_ids();
450 if ids.is_empty() {
451 return None;
452 }
453
454 Some(VarDecl {
455 kind: VarDeclKind::Var,
456 decls: ids
457 .into_iter()
458 .map(|i| VarDeclarator {
459 span: i.span,
460 name: i.into(),
461 init: None,
462 definite: false,
463 })
464 .collect(),
465 ..Default::default()
466 })
467 }
468
469 fn terminates(&self) -> bool {
471 fn terminates_many(
472 stmts: &[Stmt],
473 in_switch: bool,
474 allow_break: bool,
475 allow_throw: bool,
476 ) -> Result<bool, ()> {
477 stmts
478 .iter()
479 .rev()
480 .map(|s| terminates(s, in_switch, allow_break, allow_throw))
481 .try_fold(false, |acc, x| x.map(|v| acc || v))
482 }
483
484 fn terminates(
485 stmt: &Stmt,
486 in_switch: bool,
487 allow_break: bool,
488 allow_throw: bool,
489 ) -> Result<bool, ()> {
490 Ok(match stmt {
491 Stmt::Break(_) => {
492 if in_switch {
493 return Err(());
510 } else {
511 allow_break
512 }
513 }
514 Stmt::Throw(_) => allow_throw,
515 Stmt::Continue(_) | Stmt::Return(_) => true,
516 Stmt::Block(block) => {
517 terminates_many(&block.stmts, in_switch, allow_break, allow_throw)?
518 }
519 Stmt::If(IfStmt { cons, alt, .. }) => {
520 if let Some(alt) = alt {
521 terminates(cons, in_switch, allow_break, allow_throw)?
522 && terminates(alt, in_switch, allow_break, allow_throw)?
523 } else {
524 terminates(cons, in_switch, allow_break, allow_throw)?;
525
526 false
527 }
528 }
529 Stmt::Switch(s) => {
530 let mut has_default = false;
531 let mut has_non_empty_terminates = false;
532
533 for case in &s.cases {
534 if case.test.is_none() {
535 has_default = true
536 }
537
538 if !case.cons.is_empty() {
539 let t = terminates_many(&case.cons, true, false, allow_throw)
540 .unwrap_or(false);
541
542 if t {
543 has_non_empty_terminates = true
544 } else {
545 return Ok(false);
546 }
547 }
548 }
549
550 has_default && has_non_empty_terminates
551 }
552 Stmt::Try(t) => {
553 if let Some(h) = &t.handler {
554 terminates_many(&t.block.stmts, in_switch, allow_break, false)?
555 && terminates_many(&h.body.stmts, in_switch, allow_break, allow_throw)?
556 } else {
557 terminates_many(&t.block.stmts, in_switch, allow_break, allow_throw)?
558 }
559 }
560 _ => false,
561 })
562 }
563
564 terminates(self.as_stmt(), false, true, true) == Ok(true)
565 }
566
567 fn may_have_side_effects(&self, ctx: ExprCtx) -> bool {
568 fn may_have_side_effects(stmt: &Stmt, ctx: ExprCtx) -> bool {
569 match stmt {
570 Stmt::Block(block_stmt) => block_stmt
571 .stmts
572 .iter()
573 .any(|stmt| stmt.may_have_side_effects(ctx)),
574 Stmt::Empty(_) => false,
575 Stmt::Labeled(labeled_stmt) => labeled_stmt.body.may_have_side_effects(ctx),
576 Stmt::If(if_stmt) => {
577 if_stmt.test.may_have_side_effects(ctx)
578 || if_stmt.cons.may_have_side_effects(ctx)
579 || if_stmt
580 .alt
581 .as_ref()
582 .is_some_and(|stmt| stmt.may_have_side_effects(ctx))
583 }
584 Stmt::Switch(switch_stmt) => {
585 switch_stmt.discriminant.may_have_side_effects(ctx)
586 || switch_stmt.cases.iter().any(|case| {
587 case.test
588 .as_ref()
589 .is_some_and(|expr| expr.may_have_side_effects(ctx))
590 || case.cons.iter().any(|con| con.may_have_side_effects(ctx))
591 })
592 }
593 Stmt::Try(try_stmt) => {
594 try_stmt
595 .block
596 .stmts
597 .iter()
598 .any(|stmt| stmt.may_have_side_effects(ctx))
599 || try_stmt.handler.as_ref().is_some_and(|handler| {
600 handler
601 .body
602 .stmts
603 .iter()
604 .any(|stmt| stmt.may_have_side_effects(ctx))
605 })
606 || try_stmt.finalizer.as_ref().is_some_and(|finalizer| {
607 finalizer
608 .stmts
609 .iter()
610 .any(|stmt| stmt.may_have_side_effects(ctx))
611 })
612 }
613 Stmt::Decl(decl) => match decl {
614 Decl::Class(class_decl) => class_has_side_effect(ctx, &class_decl.class),
615 Decl::Fn(_) => !ctx.in_strict,
616 Decl::Var(var_decl) => var_decl.kind == VarDeclKind::Var,
617 _ => false,
618 },
619 Stmt::Expr(expr_stmt) => expr_stmt.expr.may_have_side_effects(ctx),
620 _ => true,
621 }
622 }
623
624 may_have_side_effects(self.as_stmt(), ctx)
625 }
626}
627
628impl StmtExt for Stmt {
629 fn as_stmt(&self) -> &Stmt {
630 self
631 }
632}
633
634impl StmtExt for Box<Stmt> {
635 fn as_stmt(&self) -> &Stmt {
636 self
637 }
638}
639
640pub struct Hoister {
641 vars: Vec<Ident>,
642}
643
644impl Visit for Hoister {
645 noop_visit_type!();
646
647 fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
648
649 fn visit_assign_expr(&mut self, node: &AssignExpr) {
650 node.right.visit_children_with(self);
651 }
652
653 fn visit_assign_pat_prop(&mut self, node: &AssignPatProp) {
654 node.value.visit_with(self);
655
656 self.vars.push(node.key.clone().into());
657 }
658
659 fn visit_constructor(&mut self, _: &Constructor) {}
660
661 fn visit_fn_decl(&mut self, f: &FnDecl) {
662 self.vars.push(f.ident.clone());
663 }
664
665 fn visit_function(&mut self, _: &Function) {}
666
667 fn visit_getter_prop(&mut self, _: &GetterProp) {}
668
669 fn visit_pat(&mut self, p: &Pat) {
670 p.visit_children_with(self);
671
672 if let Pat::Ident(ref i) = *p {
673 self.vars.push(i.clone().into())
674 }
675 }
676
677 fn visit_setter_prop(&mut self, _: &SetterProp) {}
678
679 fn visit_var_decl(&mut self, v: &VarDecl) {
680 if v.kind != VarDeclKind::Var {
681 return;
682 }
683
684 v.visit_children_with(self)
685 }
686}
687
688#[derive(Debug, Clone, Copy)]
689
690pub struct ExprCtx {
691 pub unresolved_ctxt: SyntaxContext,
697
698 pub is_unresolved_ref_safe: bool,
700
701 pub in_strict: bool,
704
705 pub remaining_depth: u8,
710}
711
712pub trait ExprExt {
714 fn as_expr(&self) -> &Expr;
715
716 #[inline(always)]
718 fn is_immutable_value(&self) -> bool {
719 is_immutable_value(self.as_expr())
720 }
721
722 #[inline(always)]
723 fn is_number(&self) -> bool {
724 is_number(self.as_expr())
725 }
726
727 #[inline(always)]
729 fn is_str(&self) -> bool {
730 is_str(self.as_expr())
731 }
732
733 #[inline(always)]
734 fn is_array_lit(&self) -> bool {
735 is_array_lit(self.as_expr())
736 }
737
738 #[inline(always)]
740 fn is_nan(&self) -> bool {
741 is_nan(self.as_expr())
742 }
743
744 #[inline(always)]
745 fn is_undefined(&self, ctx: ExprCtx) -> bool {
746 is_undefined(self.as_expr(), ctx)
747 }
748
749 #[inline(always)]
750 fn is_void(&self) -> bool {
751 is_void(self.as_expr())
752 }
753
754 #[inline(always)]
756 fn is_global_ref_to(&self, ctx: ExprCtx, id: &str) -> bool {
757 is_global_ref_to(self.as_expr(), ctx, id)
758 }
759
760 #[inline(always)]
762 fn is_one_of_global_ref_to(&self, ctx: ExprCtx, ids: &[&str]) -> bool {
763 is_one_of_global_ref_to(self.as_expr(), ctx, ids)
764 }
765
766 #[inline(always)]
767 fn is_pure(&self, ctx: ExprCtx) -> bool {
768 self.as_pure_bool(ctx).is_known()
769 }
770
771 #[inline(always)]
773 fn as_pure_bool(&self, ctx: ExprCtx) -> BoolValue {
774 as_pure_bool(self.as_expr(), ctx)
775 }
776
777 #[inline(always)]
782 fn cast_to_bool(&self, ctx: ExprCtx) -> (Purity, BoolValue) {
783 cast_to_bool(self.as_expr(), ctx)
784 }
785
786 #[inline(always)]
787 fn cast_to_number(&self, ctx: ExprCtx) -> (Purity, Value<f64>) {
788 cast_to_number(self.as_expr(), ctx)
789 }
790
791 #[inline(always)]
795 fn as_pure_number(&self, ctx: ExprCtx) -> Value<f64> {
796 as_pure_number(self.as_expr(), ctx)
797 }
798
799 #[inline(always)]
801 fn as_pure_string(&self, ctx: ExprCtx) -> Value<Cow<'_, str>> {
802 as_pure_string(self.as_expr(), ctx)
803 }
804
805 #[inline(always)]
808 fn get_type(&self, ctx: ExprCtx) -> Value<Type> {
809 get_type(self.as_expr(), ctx)
810 }
811
812 #[inline(always)]
813 fn is_pure_callee(&self, ctx: ExprCtx) -> bool {
814 is_pure_callee(self.as_expr(), ctx)
815 }
816
817 #[inline(always)]
818 fn may_have_side_effects(&self, ctx: ExprCtx) -> bool {
819 may_have_side_effects(self.as_expr(), ctx)
820 }
821}
822
823pub fn class_has_side_effect(expr_ctx: ExprCtx, c: &Class) -> bool {
824 if let Some(e) = &c.super_class {
825 if e.may_have_side_effects(expr_ctx) {
826 return true;
827 }
828 }
829
830 for m in &c.body {
831 match m {
832 ClassMember::Method(p) => {
833 if let PropName::Computed(key) = &p.key {
834 if key.expr.may_have_side_effects(expr_ctx) {
835 return true;
836 }
837 }
838 }
839
840 ClassMember::ClassProp(p) => {
841 if let PropName::Computed(key) = &p.key {
842 if key.expr.may_have_side_effects(expr_ctx) {
843 return true;
844 }
845 }
846
847 if let Some(v) = &p.value {
848 if v.may_have_side_effects(expr_ctx) {
849 return true;
850 }
851 }
852 }
853 ClassMember::PrivateProp(p) => {
854 if let Some(v) = &p.value {
855 if v.may_have_side_effects(expr_ctx) {
856 return true;
857 }
858 }
859 }
860 ClassMember::StaticBlock(s) => {
861 if s.body
862 .stmts
863 .iter()
864 .any(|stmt| stmt.may_have_side_effects(expr_ctx))
865 {
866 return true;
867 }
868 }
869 _ => {}
870 }
871 }
872
873 false
874}
875
876fn and(lt: Value<Type>, rt: Value<Type>) -> Value<Type> {
877 if lt == rt {
878 return lt;
879 }
880 Unknown
881}
882
883fn may_be_str(ty: Value<Type>) -> bool {
885 match ty {
886 Known(BoolType) | Known(NullType) | Known(NumberType) | Known(UndefinedType) => false,
887 Known(ObjectType) | Known(StringType) | Unknown => true,
888 Known(SymbolType) => true,
890 }
891}
892
893pub fn num_from_str(s: &str) -> Value<f64> {
894 if s.contains('\u{000b}') {
895 return Unknown;
896 }
897
898 let s = s.trim();
899
900 if s.is_empty() {
901 return Known(0.0);
902 }
903
904 if s.len() >= 2 {
905 match &s.as_bytes()[..2] {
906 b"0x" | b"0X" => {
907 return match u64::from_str_radix(&s[2..], 16) {
908 Ok(n) => Known(n as f64),
909 Err(_) => Known(f64::NAN),
910 }
911 }
912 b"0o" | b"0O" => {
913 return match u64::from_str_radix(&s[2..], 8) {
914 Ok(n) => Known(n as f64),
915 Err(_) => Known(f64::NAN),
916 };
917 }
918 b"0b" | b"0B" => {
919 return match u64::from_str_radix(&s[2..], 2) {
920 Ok(n) => Known(n as f64),
921 Err(_) => Known(f64::NAN),
922 };
923 }
924 _ => {}
925 }
926 }
927
928 if (s.starts_with('-') || s.starts_with('+'))
929 && (s[1..].starts_with("0x") || s[1..].starts_with("0X"))
930 {
931 return Unknown;
933 }
934
935 match s {
938 "infinity" | "+infinity" | "-infinity" => return Unknown,
939 _ => {}
940 }
941
942 Known(s.parse().ok().unwrap_or(f64::NAN))
943}
944
945impl ExprExt for Box<Expr> {
946 #[inline(always)]
947 fn as_expr(&self) -> &Expr {
948 self
949 }
950}
951
952impl ExprExt for Expr {
953 #[inline(always)]
954 fn as_expr(&self) -> &Expr {
955 self
956 }
957}
958
959#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
960pub enum Purity {
961 MayBeImpure,
963 Pure,
965}
966impl Purity {
967 pub fn is_pure(self) -> bool {
969 self == Pure
970 }
971}
972
973impl Add for Purity {
974 type Output = Self;
975
976 fn add(self, rhs: Self) -> Self {
977 match (self, rhs) {
978 (Pure, Pure) => Pure,
979 _ => MayBeImpure,
980 }
981 }
982}
983
984pub fn to_int32(d: f64) -> i32 {
986 let id = d as i32;
987 if id as f64 == d {
988 return id;
990 }
991
992 if d.is_nan() || d.is_infinite() {
993 return 0;
994 }
995
996 let d = if d >= 0.0 { d.floor() } else { d.ceil() };
997
998 const TWO32: f64 = 4_294_967_296.0;
999 let d = d % TWO32;
1000 let l = d as i64;
1003 l as i32
1006}
1007
1008pub fn has_rest_pat<T: VisitWith<RestPatVisitor>>(node: &T) -> bool {
1028 let mut v = RestPatVisitor { found: false };
1029 node.visit_with(&mut v);
1030 v.found
1031}
1032
1033pub struct RestPatVisitor {
1034 found: bool,
1035}
1036
1037impl Visit for RestPatVisitor {
1038 noop_visit_type!();
1039
1040 fn visit_rest_pat(&mut self, _: &RestPat) {
1041 self.found = true;
1042 }
1043}
1044
1045pub fn is_literal<T>(node: &T) -> bool
1046where
1047 T: VisitWith<LiteralVisitor>,
1048{
1049 let (v, _) = calc_literal_cost(node, true);
1050 v
1051}
1052
1053#[inline(never)]
1054pub fn calc_literal_cost<T>(e: &T, allow_non_json_value: bool) -> (bool, usize)
1055where
1056 T: VisitWith<LiteralVisitor>,
1057{
1058 let mut v = LiteralVisitor {
1059 is_lit: true,
1060 cost: 0,
1061 allow_non_json_value,
1062 };
1063 e.visit_with(&mut v);
1064
1065 (v.is_lit, v.cost)
1066}
1067
1068pub struct LiteralVisitor {
1069 is_lit: bool,
1070 cost: usize,
1071 allow_non_json_value: bool,
1072}
1073
1074impl Visit for LiteralVisitor {
1075 noop_visit_type!();
1076
1077 fn visit_array_lit(&mut self, e: &ArrayLit) {
1078 if !self.is_lit {
1079 return;
1080 }
1081
1082 self.cost += 2 + e.elems.len();
1083
1084 e.visit_children_with(self);
1085
1086 for elem in &e.elems {
1087 if !self.allow_non_json_value && elem.is_none() {
1088 self.is_lit = false;
1089 }
1090 }
1091 }
1092
1093 fn visit_arrow_expr(&mut self, _: &ArrowExpr) {
1094 self.is_lit = false
1095 }
1096
1097 fn visit_assign_expr(&mut self, _: &AssignExpr) {
1098 self.is_lit = false;
1099 }
1100
1101 fn visit_await_expr(&mut self, _: &AwaitExpr) {
1102 self.is_lit = false
1103 }
1104
1105 fn visit_bin_expr(&mut self, _: &BinExpr) {
1106 self.is_lit = false
1107 }
1108
1109 fn visit_call_expr(&mut self, _: &CallExpr) {
1110 self.is_lit = false
1111 }
1112
1113 fn visit_class_expr(&mut self, _: &ClassExpr) {
1114 self.is_lit = false
1115 }
1116
1117 fn visit_cond_expr(&mut self, _: &CondExpr) {
1118 self.is_lit = false;
1119 }
1120
1121 fn visit_expr(&mut self, e: &Expr) {
1122 if !self.is_lit {
1123 return;
1124 }
1125
1126 match *e {
1127 Expr::Ident(..) | Expr::Lit(Lit::Regex(..)) => self.is_lit = false,
1128 Expr::Tpl(ref tpl) if !tpl.exprs.is_empty() => self.is_lit = false,
1129 _ => e.visit_children_with(self),
1130 }
1131 }
1132
1133 fn visit_fn_expr(&mut self, _: &FnExpr) {
1134 self.is_lit = false;
1135 }
1136
1137 fn visit_invalid(&mut self, _: &Invalid) {
1138 self.is_lit = false;
1139 }
1140
1141 fn visit_member_expr(&mut self, m: &MemberExpr) {
1142 if m.obj.is_ident_ref_to("Symbol") {
1143 return;
1144 }
1145
1146 self.is_lit = false;
1147 }
1148
1149 fn visit_meta_prop_expr(&mut self, _: &MetaPropExpr) {
1150 self.is_lit = false
1151 }
1152
1153 fn visit_new_expr(&mut self, _: &NewExpr) {
1154 self.is_lit = false
1155 }
1156
1157 fn visit_number(&mut self, node: &Number) {
1158 if !self.allow_non_json_value && node.value.is_infinite() {
1159 self.is_lit = false;
1160 }
1161 }
1162
1163 fn visit_opt_chain_expr(&mut self, _: &OptChainExpr) {
1164 self.is_lit = false
1165 }
1166
1167 fn visit_private_name(&mut self, _: &PrivateName) {
1168 self.is_lit = false
1169 }
1170
1171 fn visit_prop(&mut self, p: &Prop) {
1172 if !self.is_lit {
1173 return;
1174 }
1175
1176 p.visit_children_with(self);
1177
1178 match p {
1179 Prop::KeyValue(..) => {
1180 self.cost += 1;
1181 }
1182 _ => self.is_lit = false,
1183 }
1184 }
1185
1186 fn visit_prop_name(&mut self, node: &PropName) {
1187 if !self.is_lit {
1188 return;
1189 }
1190
1191 node.visit_children_with(self);
1192
1193 match node {
1194 PropName::Str(ref s) => self.cost += 2 + s.value.len(),
1195 PropName::Ident(ref id) => self.cost += 2 + id.sym.len(),
1196 PropName::Num(..) => {
1197 self.cost += 5;
1199 }
1200 PropName::BigInt(_) => self.is_lit = false,
1201 PropName::Computed(..) => self.is_lit = false,
1202 }
1203 }
1204
1205 fn visit_seq_expr(&mut self, _: &SeqExpr) {
1206 self.is_lit = false
1207 }
1208
1209 fn visit_spread_element(&mut self, _: &SpreadElement) {
1210 self.is_lit = false;
1211 }
1212
1213 fn visit_tagged_tpl(&mut self, _: &TaggedTpl) {
1214 self.is_lit = false
1215 }
1216
1217 fn visit_this_expr(&mut self, _: &ThisExpr) {
1218 self.is_lit = false;
1219 }
1220
1221 fn visit_ts_const_assertion(&mut self, _: &TsConstAssertion) {
1222 self.is_lit = false
1223 }
1224
1225 fn visit_ts_non_null_expr(&mut self, _: &TsNonNullExpr) {
1226 self.is_lit = false
1227 }
1228
1229 fn visit_unary_expr(&mut self, _: &UnaryExpr) {
1230 self.is_lit = false;
1231 }
1232
1233 fn visit_update_expr(&mut self, _: &UpdateExpr) {
1234 self.is_lit = false;
1235 }
1236
1237 fn visit_yield_expr(&mut self, _: &YieldExpr) {
1238 self.is_lit = false
1239 }
1240}
1241
1242pub fn is_simple_pure_expr(expr: &Expr, pure_getters: bool) -> bool {
1243 match expr {
1244 Expr::Ident(..) | Expr::This(..) | Expr::Lit(..) => true,
1245 Expr::Unary(UnaryExpr {
1246 op: op!("void") | op!("!"),
1247 arg,
1248 ..
1249 }) => is_simple_pure_expr(arg, pure_getters),
1250 Expr::Member(m) if pure_getters => is_simple_pure_member_expr(m, pure_getters),
1251 _ => false,
1252 }
1253}
1254
1255pub fn is_simple_pure_member_expr(m: &MemberExpr, pure_getters: bool) -> bool {
1256 match &m.prop {
1257 MemberProp::Ident(..) | MemberProp::PrivateName(..) => {
1258 is_simple_pure_expr(&m.obj, pure_getters)
1259 }
1260 MemberProp::Computed(c) => {
1261 is_simple_pure_expr(&c.expr, pure_getters) && is_simple_pure_expr(&m.obj, pure_getters)
1262 }
1263 }
1264}
1265
1266fn sym_for_expr(expr: &Expr) -> Option<String> {
1267 match expr {
1268 Expr::Lit(Lit::Str(s)) => Some(s.value.to_string()),
1269 Expr::This(_) => Some("this".to_string()),
1270
1271 Expr::Ident(ident)
1272 | Expr::Fn(FnExpr {
1273 ident: Some(ident), ..
1274 })
1275 | Expr::Class(ClassExpr {
1276 ident: Some(ident), ..
1277 }) => Some(ident.sym.to_string()),
1278
1279 Expr::OptChain(OptChainExpr { base, .. }) => match &**base {
1280 OptChainBase::Call(OptCall { callee: expr, .. }) => sym_for_expr(expr),
1281 OptChainBase::Member(MemberExpr {
1282 prop: MemberProp::Ident(ident),
1283 obj,
1284 ..
1285 }) => Some(format!(
1286 "{}_{}",
1287 sym_for_expr(obj).unwrap_or_default(),
1288 ident.sym
1289 )),
1290
1291 OptChainBase::Member(MemberExpr {
1292 prop: MemberProp::Computed(ComputedPropName { expr, .. }),
1293 obj,
1294 ..
1295 }) => Some(format!(
1296 "{}_{}",
1297 sym_for_expr(obj).unwrap_or_default(),
1298 sym_for_expr(expr).unwrap_or_default()
1299 )),
1300 _ => None,
1301 },
1302 Expr::Call(CallExpr {
1303 callee: Callee::Expr(expr),
1304 ..
1305 }) => sym_for_expr(expr),
1306
1307 Expr::SuperProp(SuperPropExpr {
1308 prop: SuperProp::Ident(ident),
1309 ..
1310 }) => Some(format!("super_{}", ident.sym)),
1311
1312 Expr::SuperProp(SuperPropExpr {
1313 prop: SuperProp::Computed(ComputedPropName { expr, .. }),
1314 ..
1315 }) => Some(format!("super_{}", sym_for_expr(expr).unwrap_or_default())),
1316
1317 Expr::Member(MemberExpr {
1318 prop: MemberProp::Ident(ident),
1319 obj,
1320 ..
1321 }) => Some(format!(
1322 "{}_{}",
1323 sym_for_expr(obj).unwrap_or_default(),
1324 ident.sym
1325 )),
1326
1327 Expr::Member(MemberExpr {
1328 prop: MemberProp::Computed(ComputedPropName { expr, .. }),
1329 obj,
1330 ..
1331 }) => Some(format!(
1332 "{}_{}",
1333 sym_for_expr(obj).unwrap_or_default(),
1334 sym_for_expr(expr).unwrap_or_default()
1335 )),
1336
1337 _ => None,
1338 }
1339}
1340
1341pub fn alias_ident_for(expr: &Expr, default: &str) -> Ident {
1343 let ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root()));
1344 let span = expr.span();
1345
1346 let mut sym = sym_for_expr(expr).unwrap_or_else(|| default.to_string());
1347
1348 if let Err(s) = Ident::verify_symbol(&sym) {
1349 sym = s;
1350 }
1351
1352 if !sym.starts_with('_') {
1353 sym = format!("_{sym}")
1354 }
1355 quote_ident!(ctxt, span, sym)
1356}
1357
1358pub fn alias_ident_for_simple_assign_tatget(expr: &SimpleAssignTarget, default: &str) -> Ident {
1360 let ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root()));
1361
1362 let span = expr.span();
1363
1364 let mut sym = match expr {
1365 SimpleAssignTarget::Ident(i) => Some(i.sym.to_string()),
1366
1367 SimpleAssignTarget::SuperProp(SuperPropExpr {
1368 prop: SuperProp::Ident(ident),
1369 ..
1370 }) => Some(format!("super_{}", ident.sym)),
1371
1372 SimpleAssignTarget::SuperProp(SuperPropExpr {
1373 prop: SuperProp::Computed(ComputedPropName { expr, .. }),
1374 ..
1375 }) => Some(format!("super_{}", sym_for_expr(expr).unwrap_or_default())),
1376
1377 SimpleAssignTarget::Member(MemberExpr {
1378 prop: MemberProp::Ident(ident),
1379 obj,
1380 ..
1381 }) => Some(format!(
1382 "{}_{}",
1383 sym_for_expr(obj).unwrap_or_default(),
1384 ident.sym
1385 )),
1386
1387 SimpleAssignTarget::Member(MemberExpr {
1388 prop: MemberProp::Computed(ComputedPropName { expr, .. }),
1389 obj,
1390 ..
1391 }) => Some(format!(
1392 "{}_{}",
1393 sym_for_expr(obj).unwrap_or_default(),
1394 sym_for_expr(expr).unwrap_or_default()
1395 )),
1396 _ => None,
1397 }
1398 .unwrap_or_else(|| default.to_string());
1399
1400 if let Err(s) = Ident::verify_symbol(&sym) {
1401 sym = s;
1402 }
1403
1404 if !sym.starts_with('_') {
1405 sym = format!("_{sym}")
1406 }
1407 quote_ident!(ctxt, span, sym)
1408}
1409
1410pub fn alias_if_required(expr: &Expr, default: &str) -> (Ident, bool) {
1412 if let Expr::Ident(ref i) = *expr {
1413 return (Ident::new(i.sym.clone(), i.span, i.ctxt), false);
1414 }
1415
1416 (alias_ident_for(expr, default), true)
1417}
1418
1419pub fn prop_name_to_expr(p: PropName) -> Expr {
1420 match p {
1421 PropName::Ident(i) => i.into(),
1422 PropName::Str(s) => Lit::Str(s).into(),
1423 PropName::Num(n) => Lit::Num(n).into(),
1424 PropName::BigInt(b) => Lit::BigInt(b).into(),
1425 PropName::Computed(c) => *c.expr,
1426 }
1427}
1428pub fn prop_name_to_expr_value(p: PropName) -> Expr {
1432 match p {
1433 PropName::Ident(i) => Lit::Str(Str {
1434 span: i.span,
1435 raw: None,
1436 value: i.sym,
1437 })
1438 .into(),
1439 PropName::Str(s) => Lit::Str(s).into(),
1440 PropName::Num(n) => Lit::Num(n).into(),
1441 PropName::BigInt(b) => Lit::BigInt(b).into(),
1442 PropName::Computed(c) => *c.expr,
1443 }
1444}
1445
1446pub fn prop_name_to_member_prop(prop_name: PropName) -> MemberProp {
1447 match prop_name {
1448 PropName::Ident(i) => MemberProp::Ident(i),
1449 PropName::Str(s) => MemberProp::Computed(ComputedPropName {
1450 span: DUMMY_SP,
1451 expr: s.into(),
1452 }),
1453 PropName::Num(n) => MemberProp::Computed(ComputedPropName {
1454 span: DUMMY_SP,
1455 expr: n.into(),
1456 }),
1457 PropName::Computed(c) => MemberProp::Computed(c),
1458 PropName::BigInt(b) => MemberProp::Computed(ComputedPropName {
1459 span: DUMMY_SP,
1460 expr: b.into(),
1461 }),
1462 }
1463}
1464
1465pub fn default_constructor_with_span(has_super: bool, super_call_span: Span) -> Constructor {
1468 trace!(has_super = has_super, "Creating a default constructor");
1469 let super_call_span = super_call_span.with_hi(super_call_span.lo);
1470
1471 Constructor {
1472 span: DUMMY_SP,
1473 key: PropName::Ident(atom!("constructor").into()),
1474 is_optional: false,
1475 params: if has_super {
1476 vec![ParamOrTsParamProp::Param(Param {
1477 span: DUMMY_SP,
1478 decorators: Vec::new(),
1479 pat: Pat::Rest(RestPat {
1480 span: DUMMY_SP,
1481 dot3_token: DUMMY_SP,
1482 arg: Box::new(Pat::Ident(quote_ident!("args").into())),
1483 type_ann: Default::default(),
1484 }),
1485 })]
1486 } else {
1487 Vec::new()
1488 },
1489 body: Some(BlockStmt {
1490 stmts: if has_super {
1491 vec![CallExpr {
1492 span: super_call_span,
1493 callee: Callee::Super(Super { span: DUMMY_SP }),
1494 args: vec![ExprOrSpread {
1495 spread: Some(DUMMY_SP),
1496 expr: Box::new(Expr::Ident(quote_ident!("args").into())),
1497 }],
1498 ..Default::default()
1499 }
1500 .into_stmt()]
1501 } else {
1502 Vec::new()
1503 },
1504 ..Default::default()
1505 }),
1506 ..Default::default()
1507 }
1508}
1509
1510pub fn is_rest_arguments(e: &ExprOrSpread) -> bool {
1512 if e.spread.is_none() {
1513 return false;
1514 }
1515
1516 e.expr.is_ident_ref_to("arguments")
1517}
1518
1519pub fn opt_chain_test(
1520 left: Box<Expr>,
1521 right: Box<Expr>,
1522 span: Span,
1523 no_document_all: bool,
1524) -> Expr {
1525 if no_document_all {
1526 BinExpr {
1527 span,
1528 left,
1529 op: op!("=="),
1530 right: Lit::Null(Null { span: DUMMY_SP }).into(),
1531 }
1532 .into()
1533 } else {
1534 BinExpr {
1535 span,
1536 left: BinExpr {
1537 span: DUMMY_SP,
1538 left,
1539 op: op!("==="),
1540 right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
1541 }
1542 .into(),
1543 op: op!("||"),
1544 right: BinExpr {
1545 span: DUMMY_SP,
1546 left: right,
1547 op: op!("==="),
1548 right: Expr::undefined(DUMMY_SP),
1549 }
1550 .into(),
1551 }
1552 .into()
1553 }
1554}
1555
1556#[inline]
1558pub fn prepend_stmt<T: StmtLike>(stmts: &mut Vec<T>, stmt: T) {
1559 stmts.prepend_stmt(stmt);
1560}
1561
1562pub fn is_maybe_branch_directive(stmt: &Stmt) -> bool {
1564 match stmt {
1565 Stmt::Expr(ExprStmt { ref expr, .. }) if matches!(&**expr, Expr::Lit(Lit::Str(..))) => true,
1566 _ => false,
1567 }
1568}
1569
1570#[inline]
1572pub fn prepend_stmts<T: StmtLike>(to: &mut Vec<T>, stmts: impl ExactSizeIterator<Item = T>) {
1573 to.prepend_stmts(stmts);
1574}
1575
1576pub trait IsDirective {
1577 fn as_ref(&self) -> Option<&Stmt>;
1578
1579 fn directive_continue(&self) -> bool {
1580 self.as_ref().is_some_and(Stmt::can_precede_directive)
1581 }
1582 fn is_use_strict(&self) -> bool {
1583 self.as_ref().is_some_and(Stmt::is_use_strict)
1584 }
1585}
1586
1587impl IsDirective for Stmt {
1588 fn as_ref(&self) -> Option<&Stmt> {
1589 Some(self)
1590 }
1591}
1592
1593impl IsDirective for ModuleItem {
1594 fn as_ref(&self) -> Option<&Stmt> {
1595 self.as_stmt()
1596 }
1597}
1598
1599impl IsDirective for &ModuleItem {
1600 fn as_ref(&self) -> Option<&Stmt> {
1601 self.as_stmt()
1602 }
1603}
1604
1605pub struct DestructuringFinder<I: IdentLike> {
1607 pub found: Vec<I>,
1608}
1609
1610pub fn find_pat_ids<T, I: IdentLike>(node: &T) -> Vec<I>
1614where
1615 T: VisitWith<DestructuringFinder<I>>,
1616{
1617 let mut v = DestructuringFinder { found: Vec::new() };
1618 node.visit_with(&mut v);
1619
1620 v.found
1621}
1622
1623impl<I: IdentLike> Visit for DestructuringFinder<I> {
1624 fn visit_expr(&mut self, _: &Expr) {}
1626
1627 fn visit_ident(&mut self, i: &Ident) {
1628 self.found.push(I::from_ident(i));
1629 }
1630
1631 fn visit_jsx_member_expr(&mut self, n: &JSXMemberExpr) {
1632 n.obj.visit_with(self);
1633 }
1634
1635 fn visit_prop_name(&mut self, _: &PropName) {}
1637
1638 fn visit_ts_type(&mut self, _: &TsType) {}
1639}
1640
1641pub struct BindingIdentifierVisitor<F>
1643where
1644 F: for<'a> FnMut(&'a BindingIdent),
1645{
1646 op: F,
1647}
1648
1649pub fn for_each_binding_ident<T, F>(node: &T, op: F)
1652where
1653 T: VisitWith<BindingIdentifierVisitor<F>>,
1654 F: for<'a> FnMut(&'a BindingIdent),
1655{
1656 let mut v = BindingIdentifierVisitor { op };
1657 node.visit_with(&mut v);
1658}
1659
1660impl<F> Visit for BindingIdentifierVisitor<F>
1661where
1662 F: for<'a> FnMut(&'a BindingIdent),
1663{
1664 noop_visit_type!();
1665
1666 fn visit_expr(&mut self, _: &Expr) {}
1668
1669 fn visit_binding_ident(&mut self, i: &BindingIdent) {
1670 (self.op)(i);
1671 }
1672}
1673
1674pub fn is_valid_ident(s: &Atom) -> bool {
1675 if s.is_empty() {
1676 return false;
1677 }
1678
1679 Ident::verify_symbol(s).is_ok()
1680}
1681
1682pub fn is_valid_prop_ident(s: &str) -> bool {
1683 s.starts_with(Ident::is_valid_start) && s.chars().all(Ident::is_valid_continue)
1684}
1685
1686pub fn drop_span<T>(mut t: T) -> T
1687where
1688 T: VisitMutWith<DropSpan>,
1689{
1690 t.visit_mut_with(&mut DropSpan {});
1691 t
1692}
1693
1694pub struct DropSpan;
1695
1696impl Pass for DropSpan {
1697 fn process(&mut self, program: &mut Program) {
1698 program.visit_mut_with(self);
1699 }
1700}
1701
1702impl VisitMut for DropSpan {
1703 fn visit_mut_span(&mut self, span: &mut Span) {
1704 *span = DUMMY_SP;
1705 }
1706}
1707
1708pub struct IdentUsageFinder<'a> {
1710 ident: &'a Ident,
1711 found: bool,
1712}
1713
1714impl Parallel for IdentUsageFinder<'_> {
1715 fn create(&self) -> Self {
1716 Self {
1717 ident: self.ident,
1718 found: self.found,
1719 }
1720 }
1721
1722 fn merge(&mut self, other: Self) {
1723 self.found = self.found || other.found;
1724 }
1725}
1726
1727impl Visit for IdentUsageFinder<'_> {
1728 noop_visit_type!();
1729
1730 visit_obj_and_computed!();
1731
1732 fn visit_ident(&mut self, i: &Ident) {
1733 if i.ctxt == self.ident.ctxt && i.sym == self.ident.sym {
1734 self.found = true;
1735 }
1736 }
1737
1738 fn visit_class_members(&mut self, n: &[ClassMember]) {
1739 self.maybe_par(*LIGHT_TASK_PARALLELS, n, |v, item| {
1740 item.visit_with(v);
1741 });
1742 }
1743
1744 fn visit_expr_or_spreads(&mut self, n: &[ExprOrSpread]) {
1745 self.maybe_par(*LIGHT_TASK_PARALLELS, n, |v, item| {
1746 item.visit_with(v);
1747 });
1748 }
1749
1750 fn visit_exprs(&mut self, exprs: &[Box<Expr>]) {
1751 self.maybe_par(*LIGHT_TASK_PARALLELS, exprs, |v, expr| {
1752 expr.visit_with(v);
1753 });
1754 }
1755
1756 fn visit_module_items(&mut self, n: &[ModuleItem]) {
1757 self.maybe_par(*LIGHT_TASK_PARALLELS, n, |v, item| {
1758 item.visit_with(v);
1759 });
1760 }
1761
1762 fn visit_opt_vec_expr_or_spreads(&mut self, n: &[Option<ExprOrSpread>]) {
1763 self.maybe_par(*LIGHT_TASK_PARALLELS, n, |v, item| {
1764 if let Some(e) = item {
1765 e.visit_with(v);
1766 }
1767 });
1768 }
1769
1770 fn visit_stmts(&mut self, stmts: &[Stmt]) {
1771 self.maybe_par(*LIGHT_TASK_PARALLELS, stmts, |v, stmt| {
1772 stmt.visit_with(v);
1773 });
1774 }
1775
1776 fn visit_var_declarators(&mut self, n: &[VarDeclarator]) {
1777 self.maybe_par(*LIGHT_TASK_PARALLELS, n, |v, item| {
1778 item.visit_with(v);
1779 });
1780 }
1781}
1782
1783impl<'a> IdentUsageFinder<'a> {
1784 pub fn find<N>(ident: &'a Ident, node: &N) -> bool
1785 where
1786 N: VisitWith<Self>,
1787 {
1788 let mut v = IdentUsageFinder {
1789 ident,
1790 found: false,
1791 };
1792 node.visit_with(&mut v);
1793 v.found
1794 }
1795}
1796
1797impl ExprCtx {
1798 pub fn consume_depth(self) -> Option<Self> {
1799 if self.remaining_depth == 0 {
1800 return None;
1801 }
1802
1803 Some(Self {
1804 remaining_depth: self.remaining_depth - 1,
1805 ..self
1806 })
1807 }
1808
1809 pub fn preserve_effects<I>(self, span: Span, val: Box<Expr>, exprs: I) -> Box<Expr>
1812 where
1813 I: IntoIterator<Item = Box<Expr>>,
1814 {
1815 let mut exprs = exprs.into_iter().fold(Vec::new(), |mut v, e| {
1816 self.extract_side_effects_to(&mut v, *e);
1817 v
1818 });
1819
1820 if exprs.is_empty() {
1821 val
1822 } else {
1823 exprs.push(val);
1824
1825 SeqExpr { exprs, span }.into()
1826 }
1827 }
1828
1829 #[allow(clippy::vec_box)]
1834 pub fn extract_side_effects_to(self, to: &mut Vec<Box<Expr>>, expr: Expr) {
1835 match expr {
1836 Expr::Lit(..)
1837 | Expr::This(..)
1838 | Expr::Fn(..)
1839 | Expr::Arrow(..)
1840 | Expr::PrivateName(..) => {}
1841
1842 Expr::Ident(..) => {
1843 if expr.may_have_side_effects(self) {
1844 to.push(Box::new(expr));
1845 }
1846 }
1847
1848 Expr::Update(_) | Expr::Assign(_) | Expr::Yield(_) | Expr::Await(_) => {
1850 to.push(Box::new(expr))
1851 }
1852
1853 Expr::MetaProp(_) => to.push(Box::new(expr)),
1855
1856 Expr::Call(_) => to.push(Box::new(expr)),
1857 Expr::New(e) => {
1858 if let Expr::Ident(Ident { ref sym, .. }) = *e.callee {
1860 if *sym == "Date" && e.args.is_empty() {
1861 return;
1862 }
1863 }
1864
1865 to.push(e.into())
1866 }
1867 Expr::Member(_) | Expr::SuperProp(_) => to.push(Box::new(expr)),
1868
1869 Expr::Cond(_) => to.push(Box::new(expr)),
1872
1873 Expr::Unary(UnaryExpr {
1874 op: op!("typeof"),
1875 arg,
1876 ..
1877 }) => {
1878 if arg.is_ident() {
1884 return;
1885 }
1886 self.extract_side_effects_to(to, *arg)
1887 }
1888
1889 Expr::Unary(UnaryExpr { arg, .. }) => self.extract_side_effects_to(to, *arg),
1890
1891 Expr::Bin(BinExpr { op, .. }) if op.may_short_circuit() => {
1892 to.push(Box::new(expr));
1893 }
1894 Expr::Bin(BinExpr { left, right, .. }) => {
1895 self.extract_side_effects_to(to, *left);
1896 self.extract_side_effects_to(to, *right);
1897 }
1898 Expr::Seq(SeqExpr { exprs, .. }) => exprs
1899 .into_iter()
1900 .for_each(|e| self.extract_side_effects_to(to, *e)),
1901
1902 Expr::Paren(e) => self.extract_side_effects_to(to, *e.expr),
1903
1904 Expr::Object(ObjectLit {
1905 span, mut props, ..
1906 }) => {
1907 let mut has_spread = false;
1909 props.retain(|node| match node {
1910 PropOrSpread::Prop(node) => match &**node {
1911 Prop::Shorthand(..) => false,
1912 Prop::KeyValue(KeyValueProp { key, value }) => {
1913 if let PropName::Computed(e) = key {
1914 if e.expr.may_have_side_effects(self) {
1915 return true;
1916 }
1917 }
1918
1919 value.may_have_side_effects(self)
1920 }
1921 Prop::Getter(GetterProp { key, .. })
1922 | Prop::Setter(SetterProp { key, .. })
1923 | Prop::Method(MethodProp { key, .. }) => {
1924 if let PropName::Computed(e) = key {
1925 e.expr.may_have_side_effects(self)
1926 } else {
1927 false
1928 }
1929 }
1930 Prop::Assign(..) => {
1931 unreachable!("assign property in object literal is not a valid syntax")
1932 }
1933 },
1934 PropOrSpread::Spread(SpreadElement { .. }) => {
1935 has_spread = true;
1936 true
1937 }
1938 });
1939
1940 if has_spread {
1941 to.push(ObjectLit { span, props }.into())
1942 } else {
1943 props.into_iter().for_each(|prop| match prop {
1944 PropOrSpread::Prop(node) => match *node {
1945 Prop::Shorthand(..) => {}
1946 Prop::KeyValue(KeyValueProp { key, value }) => {
1947 if let PropName::Computed(e) = key {
1948 self.extract_side_effects_to(to, *e.expr);
1949 }
1950
1951 self.extract_side_effects_to(to, *value)
1952 }
1953 Prop::Getter(GetterProp { key, .. })
1954 | Prop::Setter(SetterProp { key, .. })
1955 | Prop::Method(MethodProp { key, .. }) => {
1956 if let PropName::Computed(e) = key {
1957 self.extract_side_effects_to(to, *e.expr)
1958 }
1959 }
1960 Prop::Assign(..) => {
1961 unreachable!(
1962 "assign property in object literal is not a valid syntax"
1963 )
1964 }
1965 },
1966 _ => unreachable!(),
1967 })
1968 }
1969 }
1970
1971 Expr::Array(ArrayLit { elems, .. }) => {
1972 elems.into_iter().flatten().fold(to, |v, e| {
1973 self.extract_side_effects_to(v, *e.expr);
1974
1975 v
1976 });
1977 }
1978
1979 Expr::TaggedTpl(TaggedTpl { tag, tpl, .. }) => {
1980 self.extract_side_effects_to(to, *tag);
1981
1982 tpl.exprs
1983 .into_iter()
1984 .for_each(|e| self.extract_side_effects_to(to, *e));
1985 }
1986 Expr::Tpl(Tpl { exprs, .. }) => {
1987 exprs
1988 .into_iter()
1989 .for_each(|e| self.extract_side_effects_to(to, *e));
1990 }
1991 Expr::Class(ClassExpr { .. }) => unimplemented!("add_effects for class expression"),
1992
1993 Expr::JSXMember(..)
1994 | Expr::JSXNamespacedName(..)
1995 | Expr::JSXEmpty(..)
1996 | Expr::JSXElement(..)
1997 | Expr::JSXFragment(..) => to.push(Box::new(expr)),
1998
1999 Expr::TsTypeAssertion(TsTypeAssertion { expr, .. })
2000 | Expr::TsNonNull(TsNonNullExpr { expr, .. })
2001 | Expr::TsAs(TsAsExpr { expr, .. })
2002 | Expr::TsConstAssertion(TsConstAssertion { expr, .. })
2003 | Expr::TsInstantiation(TsInstantiation { expr, .. })
2004 | Expr::TsSatisfies(TsSatisfiesExpr { expr, .. }) => {
2005 self.extract_side_effects_to(to, *expr)
2006 }
2007 Expr::OptChain(..) => to.push(Box::new(expr)),
2008
2009 Expr::Invalid(..) => unreachable!(),
2010 }
2011 }
2012}
2013
2014pub fn prop_name_eq(p: &PropName, key: &str) -> bool {
2015 match p {
2016 PropName::Ident(i) => i.sym == *key,
2017 PropName::Str(s) => s.value == *key,
2018 PropName::Num(n) => n.value.to_string() == *key,
2019 PropName::BigInt(_) => false,
2020 PropName::Computed(e) => match &*e.expr {
2021 Expr::Lit(Lit::Str(Str { value, .. })) => *value == *key,
2022 _ => false,
2023 },
2024 }
2025}
2026
2027pub fn replace_ident<T>(node: &mut T, from: Id, to: &Ident)
2035where
2036 T: for<'any> VisitMutWith<IdentReplacer<'any>>,
2037{
2038 node.visit_mut_with(&mut IdentReplacer { from, to })
2039}
2040
2041pub struct IdentReplacer<'a> {
2042 from: Id,
2043 to: &'a Ident,
2044}
2045
2046impl VisitMut for IdentReplacer<'_> {
2047 noop_visit_mut_type!();
2048
2049 visit_mut_obj_and_computed!();
2050
2051 fn visit_mut_prop(&mut self, node: &mut Prop) {
2052 match node {
2053 Prop::Shorthand(i) => {
2054 let cloned = i.clone();
2055 i.visit_mut_with(self);
2056 if i.sym != cloned.sym || i.ctxt != cloned.ctxt {
2057 *node = Prop::KeyValue(KeyValueProp {
2058 key: PropName::Ident(IdentName::new(cloned.sym, cloned.span)),
2059 value: i.clone().into(),
2060 });
2061 }
2062 }
2063 _ => {
2064 node.visit_mut_children_with(self);
2065 }
2066 }
2067 }
2068
2069 fn visit_mut_ident(&mut self, node: &mut Ident) {
2070 if node.sym == self.from.0 && node.ctxt == self.from.1 {
2071 *node = self.to.clone();
2072 }
2073 }
2074}
2075
2076pub struct BindingCollector<I>
2077where
2078 I: IdentLike + Eq + Hash + Send + Sync,
2079{
2080 only: Option<SyntaxContext>,
2081 bindings: FxHashSet<I>,
2082 is_pat_decl: bool,
2083}
2084
2085impl<I> BindingCollector<I>
2086where
2087 I: IdentLike + Eq + Hash + Send + Sync,
2088{
2089 fn add(&mut self, i: &Ident) {
2090 if let Some(only) = self.only {
2091 if only != i.ctxt {
2092 return;
2093 }
2094 }
2095
2096 self.bindings.insert(I::from_ident(i));
2097 }
2098}
2099
2100impl<I> Visit for BindingCollector<I>
2101where
2102 I: IdentLike + Eq + Hash + Send + Sync,
2103{
2104 noop_visit_type!();
2105
2106 fn visit_arrow_expr(&mut self, n: &ArrowExpr) {
2107 let old = self.is_pat_decl;
2108
2109 for p in &n.params {
2110 self.is_pat_decl = true;
2111 p.visit_with(self);
2112 }
2113
2114 n.body.visit_with(self);
2115 self.is_pat_decl = old;
2116 }
2117
2118 fn visit_assign_pat_prop(&mut self, node: &AssignPatProp) {
2119 node.value.visit_with(self);
2120
2121 if self.is_pat_decl {
2122 self.add(&node.key.clone().into());
2123 }
2124 }
2125
2126 fn visit_class_decl(&mut self, node: &ClassDecl) {
2127 node.visit_children_with(self);
2128
2129 self.add(&node.ident);
2130 }
2131
2132 fn visit_expr(&mut self, node: &Expr) {
2133 let old = self.is_pat_decl;
2134 self.is_pat_decl = false;
2135 node.visit_children_with(self);
2136 self.is_pat_decl = old;
2137 }
2138
2139 fn visit_export_default_decl(&mut self, e: &ExportDefaultDecl) {
2140 match &e.decl {
2141 DefaultDecl::Class(ClassExpr {
2142 ident: Some(ident), ..
2143 }) => {
2144 self.add(ident);
2145 }
2146 DefaultDecl::Fn(FnExpr {
2147 ident: Some(ident),
2148 function: f,
2149 }) if f.body.is_some() => {
2150 self.add(ident);
2151 }
2152 _ => {}
2153 }
2154 e.visit_children_with(self);
2155 }
2156
2157 fn visit_fn_decl(&mut self, node: &FnDecl) {
2158 node.visit_children_with(self);
2159
2160 self.add(&node.ident);
2161 }
2162
2163 fn visit_import_default_specifier(&mut self, node: &ImportDefaultSpecifier) {
2164 self.add(&node.local);
2165 }
2166
2167 fn visit_import_named_specifier(&mut self, node: &ImportNamedSpecifier) {
2168 self.add(&node.local);
2169 }
2170
2171 fn visit_import_star_as_specifier(&mut self, node: &ImportStarAsSpecifier) {
2172 self.add(&node.local);
2173 }
2174
2175 fn visit_param(&mut self, node: &Param) {
2176 let old = self.is_pat_decl;
2177 self.is_pat_decl = true;
2178 node.visit_children_with(self);
2179 self.is_pat_decl = old;
2180 }
2181
2182 fn visit_pat(&mut self, node: &Pat) {
2183 node.visit_children_with(self);
2184
2185 if self.is_pat_decl {
2186 if let Pat::Ident(i) = node {
2187 self.add(&i.clone().into())
2188 }
2189 }
2190 }
2191
2192 fn visit_var_declarator(&mut self, node: &VarDeclarator) {
2193 let old = self.is_pat_decl;
2194 self.is_pat_decl = true;
2195 node.name.visit_with(self);
2196
2197 self.is_pat_decl = false;
2198 node.init.visit_with(self);
2199 self.is_pat_decl = old;
2200 }
2201}
2202
2203pub fn collect_decls<I, N>(n: &N) -> FxHashSet<I>
2205where
2206 I: IdentLike + Eq + Hash + Send + Sync,
2207 N: VisitWith<BindingCollector<I>>,
2208{
2209 let mut v = BindingCollector {
2210 only: None,
2211 bindings: Default::default(),
2212 is_pat_decl: false,
2213 };
2214 n.visit_with(&mut v);
2215 v.bindings
2216}
2217
2218pub fn collect_decls_with_ctxt<I, N>(n: &N, ctxt: SyntaxContext) -> FxHashSet<I>
2221where
2222 I: IdentLike + Eq + Hash + Send + Sync,
2223 N: VisitWith<BindingCollector<I>>,
2224{
2225 let mut v = BindingCollector {
2226 only: Some(ctxt),
2227 bindings: Default::default(),
2228 is_pat_decl: false,
2229 };
2230 n.visit_with(&mut v);
2231 v.bindings
2232}
2233
2234pub struct TopLevelAwait {
2235 found: bool,
2236}
2237
2238impl Visit for TopLevelAwait {
2239 noop_visit_type!();
2240
2241 fn visit_stmt(&mut self, n: &Stmt) {
2242 if !self.found {
2243 n.visit_children_with(self);
2244 }
2245 }
2246
2247 fn visit_param(&mut self, _: &Param) {}
2248
2249 fn visit_function(&mut self, _: &Function) {}
2250
2251 fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
2252
2253 fn visit_class_member(&mut self, prop: &ClassMember) {
2254 match prop {
2255 ClassMember::ClassProp(ClassProp {
2256 key: PropName::Computed(computed),
2257 ..
2258 })
2259 | ClassMember::Method(ClassMethod {
2260 key: PropName::Computed(computed),
2261 ..
2262 }) => computed.visit_children_with(self),
2263 _ => (),
2264 };
2265 }
2266
2267 fn visit_prop(&mut self, prop: &Prop) {
2268 match prop {
2269 Prop::KeyValue(KeyValueProp {
2270 key: PropName::Computed(computed),
2271 ..
2272 })
2273 | Prop::Getter(GetterProp {
2274 key: PropName::Computed(computed),
2275 ..
2276 })
2277 | Prop::Setter(SetterProp {
2278 key: PropName::Computed(computed),
2279 ..
2280 })
2281 | Prop::Method(MethodProp {
2282 key: PropName::Computed(computed),
2283 ..
2284 }) => computed.visit_children_with(self),
2285 _ => {}
2286 }
2287 }
2288
2289 fn visit_for_of_stmt(&mut self, for_of_stmt: &ForOfStmt) {
2290 if for_of_stmt.is_await {
2291 self.found = true;
2292 return;
2293 }
2294
2295 for_of_stmt.visit_children_with(self);
2296 }
2297
2298 fn visit_await_expr(&mut self, _: &AwaitExpr) {
2299 self.found = true;
2300 }
2301}
2302
2303pub fn contains_top_level_await<V: VisitWith<TopLevelAwait>>(t: &V) -> bool {
2304 let mut finder = TopLevelAwait { found: false };
2305
2306 t.visit_with(&mut finder);
2307
2308 finder.found
2309}
2310
2311pub struct Remapper<'a> {
2316 vars: &'a FxHashMap<Id, SyntaxContext>,
2317}
2318
2319impl<'a> Remapper<'a> {
2320 pub fn new(vars: &'a FxHashMap<Id, SyntaxContext>) -> Self {
2321 Self { vars }
2322 }
2323}
2324
2325impl VisitMut for Remapper<'_> {
2326 noop_visit_mut_type!(fail);
2327
2328 fn visit_mut_ident(&mut self, i: &mut Ident) {
2329 if let Some(new_ctxt) = self.vars.get(&i.to_id()).copied() {
2330 i.ctxt = new_ctxt;
2331 }
2332 }
2333}
2334
2335pub struct IdentRenamer<'a> {
2337 map: &'a FxHashMap<Id, Id>,
2338}
2339
2340impl<'a> IdentRenamer<'a> {
2341 pub fn new(map: &'a FxHashMap<Id, Id>) -> Self {
2342 Self { map }
2343 }
2344}
2345
2346impl VisitMut for IdentRenamer<'_> {
2347 noop_visit_mut_type!();
2348
2349 visit_mut_obj_and_computed!();
2350
2351 fn visit_mut_export_named_specifier(&mut self, node: &mut ExportNamedSpecifier) {
2352 if node.exported.is_some() {
2353 node.orig.visit_mut_children_with(self);
2354 return;
2355 }
2356
2357 match &mut node.orig {
2358 ModuleExportName::Ident(orig) => {
2359 if let Some(new) = self.map.get(&orig.to_id()) {
2360 node.exported = Some(ModuleExportName::Ident(orig.clone()));
2361
2362 orig.sym = new.0.clone();
2363 orig.ctxt = new.1;
2364 }
2365 }
2366 ModuleExportName::Str(_) => {}
2367 }
2368 }
2369
2370 fn visit_mut_ident(&mut self, node: &mut Ident) {
2371 if let Some(new) = self.map.get(&node.to_id()) {
2372 node.sym = new.0.clone();
2373 node.ctxt = new.1;
2374 }
2375 }
2376
2377 fn visit_mut_object_pat_prop(&mut self, i: &mut ObjectPatProp) {
2378 match i {
2379 ObjectPatProp::Assign(p) => {
2380 p.value.visit_mut_with(self);
2381
2382 let orig = p.key.clone();
2383 p.key.visit_mut_with(self);
2384
2385 if orig.ctxt == p.key.ctxt && orig.sym == p.key.sym {
2386 return;
2387 }
2388
2389 match p.value.take() {
2390 Some(default) => {
2391 *i = ObjectPatProp::KeyValue(KeyValuePatProp {
2392 key: PropName::Ident(orig.clone().into()),
2393 value: AssignPat {
2394 span: DUMMY_SP,
2395 left: p.key.clone().into(),
2396 right: default,
2397 }
2398 .into(),
2399 });
2400 }
2401 None => {
2402 *i = ObjectPatProp::KeyValue(KeyValuePatProp {
2403 key: PropName::Ident(orig.clone().into()),
2404 value: p.key.clone().into(),
2405 });
2406 }
2407 }
2408 }
2409
2410 _ => {
2411 i.visit_mut_children_with(self);
2412 }
2413 }
2414 }
2415
2416 fn visit_mut_prop(&mut self, node: &mut Prop) {
2417 match node {
2418 Prop::Shorthand(i) => {
2419 let cloned = i.clone();
2420 i.visit_mut_with(self);
2421 if i.sym != cloned.sym || i.ctxt != cloned.ctxt {
2422 *node = Prop::KeyValue(KeyValueProp {
2423 key: PropName::Ident(IdentName::new(cloned.sym, cloned.span)),
2424 value: i.clone().into(),
2425 });
2426 }
2427 }
2428 _ => {
2429 node.visit_mut_children_with(self);
2430 }
2431 }
2432 }
2433}
2434
2435pub trait QueryRef {
2436 fn query_ref(&self, _ident: &Ident) -> Option<Box<Expr>> {
2437 None
2438 }
2439 fn query_lhs(&self, _ident: &Ident) -> Option<Box<Expr>> {
2440 None
2441 }
2442
2443 fn query_jsx(&self, _ident: &Ident) -> Option<JSXElementName> {
2445 None
2446 }
2447
2448 fn should_fix_this(&self, _ident: &Ident) -> bool {
2451 false
2452 }
2453}
2454
2455pub struct RefRewriter<T>
2457where
2458 T: QueryRef,
2459{
2460 pub query: T,
2461}
2462
2463impl<T> RefRewriter<T>
2464where
2465 T: QueryRef,
2466{
2467 pub fn exit_prop(&mut self, n: &mut Prop) {
2468 if let Prop::Shorthand(shorthand) = n {
2469 if let Some(expr) = self.query.query_ref(shorthand) {
2470 *n = KeyValueProp {
2471 key: shorthand.take().into(),
2472 value: expr,
2473 }
2474 .into()
2475 }
2476 }
2477 }
2478
2479 pub fn exit_pat(&mut self, n: &mut Pat) {
2480 if let Pat::Ident(id) = n {
2481 if let Some(expr) = self.query.query_lhs(&id.clone().into()) {
2482 *n = expr.into();
2483 }
2484 }
2485 }
2486
2487 pub fn exit_expr(&mut self, n: &mut Expr) {
2488 if let Expr::Ident(ref_ident) = n {
2489 if let Some(expr) = self.query.query_ref(ref_ident) {
2490 *n = *expr;
2491 }
2492 };
2493 }
2494
2495 pub fn exit_simple_assign_target(&mut self, n: &mut SimpleAssignTarget) {
2496 if let SimpleAssignTarget::Ident(ref_ident) = n {
2497 if let Some(expr) = self.query.query_lhs(&ref_ident.clone().into()) {
2498 *n = expr.try_into().unwrap();
2499 }
2500 };
2501 }
2502
2503 pub fn exit_jsx_element_name(&mut self, n: &mut JSXElementName) {
2504 if let JSXElementName::Ident(ident) = n {
2505 if let Some(expr) = self.query.query_jsx(ident) {
2506 *n = expr;
2507 }
2508 }
2509 }
2510
2511 pub fn exit_jsx_object(&mut self, n: &mut JSXObject) {
2512 if let JSXObject::Ident(ident) = n {
2513 if let Some(expr) = self.query.query_jsx(ident) {
2514 *n = match expr {
2515 JSXElementName::Ident(ident) => ident.into(),
2516 JSXElementName::JSXMemberExpr(expr) => Box::new(expr).into(),
2517 JSXElementName::JSXNamespacedName(..) => unimplemented!(),
2518 }
2519 }
2520 }
2521 }
2522
2523 pub fn exit_object_pat_prop(&mut self, n: &mut ObjectPatProp) {
2524 if let ObjectPatProp::Assign(AssignPatProp { key, value, .. }) = n {
2525 if let Some(expr) = self.query.query_lhs(&key.id) {
2526 let value = value
2527 .take()
2528 .map(|default_value| {
2529 let left = expr.clone().try_into().unwrap();
2530 Box::new(default_value.make_assign_to(op!("="), left))
2531 })
2532 .unwrap_or(expr);
2533
2534 *n = ObjectPatProp::KeyValue(KeyValuePatProp {
2535 key: PropName::Ident(key.take().into()),
2536 value: value.into(),
2537 });
2538 }
2539 }
2540 }
2541}
2542
2543impl<T> VisitMut for RefRewriter<T>
2544where
2545 T: QueryRef,
2546{
2547 fn visit_mut_prop(&mut self, n: &mut Prop) {
2557 n.visit_mut_children_with(self);
2558 self.exit_prop(n);
2559 }
2560
2561 fn visit_mut_var_declarator(&mut self, n: &mut VarDeclarator) {
2562 if !n.name.is_ident() {
2563 n.name.visit_mut_with(self);
2564 }
2565
2566 n.init.visit_mut_with(self);
2568 }
2569
2570 fn visit_mut_pat(&mut self, n: &mut Pat) {
2571 n.visit_mut_children_with(self);
2572 self.exit_pat(n);
2573 }
2574
2575 fn visit_mut_expr(&mut self, n: &mut Expr) {
2576 n.visit_mut_children_with(self);
2577 self.exit_expr(n);
2578 }
2579
2580 fn visit_mut_simple_assign_target(&mut self, n: &mut SimpleAssignTarget) {
2581 n.visit_mut_children_with(self);
2582 self.exit_simple_assign_target(n);
2583 }
2584
2585 fn visit_mut_callee(&mut self, n: &mut Callee) {
2586 match n {
2587 Callee::Expr(e)
2588 if e.as_ident()
2589 .map(|ident| self.query.should_fix_this(ident))
2590 .unwrap_or_default() =>
2591 {
2592 e.visit_mut_with(self);
2593
2594 if e.is_member() {
2595 *n = n.take().into_indirect()
2596 }
2597 }
2598
2599 _ => n.visit_mut_children_with(self),
2600 }
2601 }
2602
2603 fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) {
2604 let should_fix_this = n
2605 .tag
2606 .as_ident()
2607 .map(|ident| self.query.should_fix_this(ident))
2608 .unwrap_or_default();
2609
2610 n.visit_mut_children_with(self);
2611
2612 if should_fix_this && n.tag.is_member() {
2613 *n = n.take().into_indirect()
2614 }
2615 }
2616
2617 fn visit_mut_jsx_element_name(&mut self, n: &mut JSXElementName) {
2618 n.visit_mut_children_with(self);
2619
2620 self.exit_jsx_element_name(n);
2621 }
2622
2623 fn visit_mut_jsx_object(&mut self, n: &mut JSXObject) {
2624 n.visit_mut_children_with(self);
2625
2626 self.exit_jsx_object(n);
2627 }
2628}
2629
2630fn is_immutable_value(expr: &Expr) -> bool {
2631 match *expr {
2640 Expr::Lit(Lit::Bool(..))
2641 | Expr::Lit(Lit::Str(..))
2642 | Expr::Lit(Lit::Num(..))
2643 | Expr::Lit(Lit::Null(..)) => true,
2644
2645 Expr::Unary(UnaryExpr {
2646 op: op!("!"),
2647 ref arg,
2648 ..
2649 })
2650 | Expr::Unary(UnaryExpr {
2651 op: op!("~"),
2652 ref arg,
2653 ..
2654 })
2655 | Expr::Unary(UnaryExpr {
2656 op: op!("void"),
2657 ref arg,
2658 ..
2659 }) => arg.is_immutable_value(),
2660
2661 Expr::Ident(ref i) => i.sym == "undefined" || i.sym == "Infinity" || i.sym == "NaN",
2662
2663 Expr::Tpl(Tpl { ref exprs, .. }) => exprs.iter().all(|e| e.is_immutable_value()),
2664
2665 _ => false,
2666 }
2667}
2668
2669fn is_number(expr: &Expr) -> bool {
2670 matches!(*expr, Expr::Lit(Lit::Num(..)))
2671}
2672
2673fn is_str(expr: &Expr) -> bool {
2674 match expr {
2675 Expr::Lit(Lit::Str(..)) | Expr::Tpl(_) => true,
2676 Expr::Unary(UnaryExpr {
2677 op: op!("typeof"), ..
2678 }) => true,
2679 Expr::Bin(BinExpr {
2680 op: op!(bin, "+"),
2681 left,
2682 right,
2683 ..
2684 }) => left.is_str() || right.is_str(),
2685 Expr::Assign(AssignExpr {
2686 op: op!("=") | op!("+="),
2687 right,
2688 ..
2689 }) => right.is_str(),
2690 Expr::Seq(s) => s.exprs.last().unwrap().is_str(),
2691 Expr::Cond(CondExpr { cons, alt, .. }) => cons.is_str() && alt.is_str(),
2692 _ => false,
2693 }
2694}
2695
2696fn is_array_lit(expr: &Expr) -> bool {
2697 matches!(*expr, Expr::Array(..))
2698}
2699
2700fn is_nan(expr: &Expr) -> bool {
2701 expr.is_ident_ref_to("NaN")
2703}
2704
2705fn is_undefined(expr: &Expr, ctx: ExprCtx) -> bool {
2706 expr.is_global_ref_to(ctx, "undefined")
2707}
2708
2709fn is_void(expr: &Expr) -> bool {
2710 matches!(
2711 *expr,
2712 Expr::Unary(UnaryExpr {
2713 op: op!("void"),
2714 ..
2715 })
2716 )
2717}
2718
2719fn is_global_ref_to(expr: &Expr, ctx: ExprCtx, id: &str) -> bool {
2720 match expr {
2721 Expr::Ident(i) => i.ctxt == ctx.unresolved_ctxt && &*i.sym == id,
2722 _ => false,
2723 }
2724}
2725
2726fn is_one_of_global_ref_to(expr: &Expr, ctx: ExprCtx, ids: &[&str]) -> bool {
2727 match expr {
2728 Expr::Ident(i) => i.ctxt == ctx.unresolved_ctxt && ids.contains(&&*i.sym),
2729 _ => false,
2730 }
2731}
2732
2733fn as_pure_bool(expr: &Expr, ctx: ExprCtx) -> BoolValue {
2734 match expr.cast_to_bool(ctx) {
2735 (Pure, Known(b)) => Known(b),
2736 _ => Unknown,
2737 }
2738}
2739
2740fn cast_to_bool(expr: &Expr, ctx: ExprCtx) -> (Purity, BoolValue) {
2741 let Some(ctx) = ctx.consume_depth() else {
2742 return (MayBeImpure, Unknown);
2743 };
2744
2745 if expr.is_global_ref_to(ctx, "undefined") {
2746 return (Pure, Known(false));
2747 }
2748 if expr.is_nan() {
2749 return (Pure, Known(false));
2750 }
2751
2752 let val = match expr {
2753 Expr::Paren(ref e) => return e.expr.cast_to_bool(ctx),
2754
2755 Expr::Assign(AssignExpr {
2756 ref right,
2757 op: op!("="),
2758 ..
2759 }) => {
2760 let (_, v) = right.cast_to_bool(ctx);
2761 return (MayBeImpure, v);
2762 }
2763
2764 Expr::Unary(UnaryExpr {
2765 op: op!(unary, "-"),
2766 arg,
2767 ..
2768 }) => {
2769 let v = arg.as_pure_number(ctx);
2770 match v {
2771 Known(n) => Known(!matches!(n.classify(), FpCategory::Nan | FpCategory::Zero)),
2772 Unknown => return (MayBeImpure, Unknown),
2773 }
2774 }
2775
2776 Expr::Unary(UnaryExpr {
2777 op: op!("!"),
2778 ref arg,
2779 ..
2780 }) => {
2781 let (p, v) = arg.cast_to_bool(ctx);
2782 return (p, !v);
2783 }
2784 Expr::Seq(SeqExpr { exprs, .. }) => exprs.last().unwrap().cast_to_bool(ctx).1,
2785
2786 Expr::Bin(BinExpr {
2787 left,
2788 op: op!(bin, "-"),
2789 right,
2790 ..
2791 }) => {
2792 let (lp, ln) = left.cast_to_number(ctx);
2793 let (rp, rn) = right.cast_to_number(ctx);
2794
2795 return (
2796 lp + rp,
2797 match (ln, rn) {
2798 (Known(ln), Known(rn)) => {
2799 if ln == rn {
2800 Known(false)
2801 } else {
2802 Known(true)
2803 }
2804 }
2805 _ => Unknown,
2806 },
2807 );
2808 }
2809
2810 Expr::Bin(BinExpr {
2811 left,
2812 op: op!("/"),
2813 right,
2814 ..
2815 }) => {
2816 let lv = left.as_pure_number(ctx);
2817 let rv = right.as_pure_number(ctx);
2818
2819 match (lv, rv) {
2820 (Known(lv), Known(rv)) => {
2821 if lv == 0.0 && rv == 0.0 {
2823 return (Pure, Known(false));
2824 }
2825 if rv == 0.0 {
2827 return (Pure, Known(true));
2828 }
2829 let v = lv / rv;
2830
2831 return (Pure, Known(v != 0.0));
2832 }
2833 _ => Unknown,
2834 }
2835 }
2836
2837 Expr::Bin(BinExpr {
2838 ref left,
2839 op: op @ op!("&"),
2840 ref right,
2841 ..
2842 })
2843 | Expr::Bin(BinExpr {
2844 ref left,
2845 op: op @ op!("|"),
2846 ref right,
2847 ..
2848 }) => {
2849 if left.get_type(ctx) != Known(BoolType) || right.get_type(ctx) != Known(BoolType) {
2850 return (MayBeImpure, Unknown);
2851 }
2852
2853 let (lp, lv) = left.cast_to_bool(ctx);
2856 let (rp, rv) = right.cast_to_bool(ctx);
2857
2858 let v = if *op == op!("&") {
2859 lv.and(rv)
2860 } else {
2861 lv.or(rv)
2862 };
2863
2864 if lp + rp == Pure {
2865 return (Pure, v);
2866 }
2867
2868 v
2869 }
2870
2871 Expr::Bin(BinExpr {
2872 ref left,
2873 op: op!("||"),
2874 ref right,
2875 ..
2876 }) => {
2877 let (lp, lv) = left.cast_to_bool(ctx);
2878 if let Known(true) = lv {
2879 return (lp, lv);
2880 }
2881
2882 let (rp, rv) = right.cast_to_bool(ctx);
2883 if let Known(true) = rv {
2884 return (lp + rp, rv);
2885 }
2886
2887 Unknown
2888 }
2889
2890 Expr::Bin(BinExpr {
2891 ref left,
2892 op: op!("&&"),
2893 ref right,
2894 ..
2895 }) => {
2896 let (lp, lv) = left.cast_to_bool(ctx);
2897 if let Known(false) = lv {
2898 return (lp, lv);
2899 }
2900
2901 let (rp, rv) = right.cast_to_bool(ctx);
2902 if let Known(false) = rv {
2903 return (lp + rp, rv);
2904 }
2905
2906 Unknown
2907 }
2908
2909 Expr::Bin(BinExpr {
2910 left,
2911 op: op!(bin, "+"),
2912 right,
2913 ..
2914 }) => {
2915 match &**left {
2916 Expr::Lit(Lit::Str(s)) if !s.value.is_empty() => return (MayBeImpure, Known(true)),
2917 _ => {}
2918 }
2919
2920 match &**right {
2921 Expr::Lit(Lit::Str(s)) if !s.value.is_empty() => return (MayBeImpure, Known(true)),
2922 _ => {}
2923 }
2924
2925 Unknown
2926 }
2927
2928 Expr::Fn(..) | Expr::Class(..) | Expr::New(..) | Expr::Array(..) | Expr::Object(..) => {
2929 Known(true)
2930 }
2931
2932 Expr::Unary(UnaryExpr {
2933 op: op!("void"), ..
2934 }) => Known(false),
2935
2936 Expr::Lit(ref lit) => {
2937 return (
2938 Pure,
2939 Known(match *lit {
2940 Lit::Num(Number { value: n, .. }) => {
2941 !matches!(n.classify(), FpCategory::Nan | FpCategory::Zero)
2942 }
2943 Lit::BigInt(ref v) => v
2944 .value
2945 .to_string()
2946 .contains(|c: char| matches!(c, '1'..='9')),
2947 Lit::Bool(b) => b.value,
2948 Lit::Str(Str { ref value, .. }) => !value.is_empty(),
2949 Lit::Null(..) => false,
2950 Lit::Regex(..) => true,
2951 Lit::JSXText(..) => unreachable!("as_bool() for JSXText"),
2952 }),
2953 );
2954 }
2955
2956 _ => Unknown,
2958 };
2959
2960 if expr.may_have_side_effects(ctx) {
2961 (MayBeImpure, val)
2962 } else {
2963 (Pure, val)
2964 }
2965}
2966
2967fn cast_to_number(expr: &Expr, ctx: ExprCtx) -> (Purity, Value<f64>) {
2968 let Some(ctx) = ctx.consume_depth() else {
2969 return (MayBeImpure, Unknown);
2970 };
2971
2972 let v = match expr {
2973 Expr::Lit(l) => match l {
2974 Lit::Bool(Bool { value: true, .. }) => 1.0,
2975 Lit::Bool(Bool { value: false, .. }) | Lit::Null(..) => 0.0,
2976 Lit::Num(Number { value: n, .. }) => *n,
2977 Lit::Str(Str { value, .. }) => return (Pure, num_from_str(value)),
2978 _ => return (Pure, Unknown),
2979 },
2980 Expr::Array(..) => {
2981 let Known(s) = expr.as_pure_string(ctx) else {
2982 return (Pure, Unknown);
2983 };
2984
2985 return (Pure, num_from_str(&s));
2986 }
2987 Expr::Ident(Ident { sym, ctxt, .. }) => match &**sym {
2988 "undefined" | "NaN" if *ctxt == ctx.unresolved_ctxt => f64::NAN,
2989 "Infinity" if *ctxt == ctx.unresolved_ctxt => f64::INFINITY,
2990 _ => return (Pure, Unknown),
2991 },
2992 Expr::Unary(UnaryExpr {
2993 op: op!(unary, "-"),
2994 arg,
2995 ..
2996 }) => match arg.cast_to_number(ctx) {
2997 (Pure, Known(v)) => -v,
2998 _ => return (MayBeImpure, Unknown),
2999 },
3000 Expr::Unary(UnaryExpr {
3001 op: op!("!"),
3002 ref arg,
3003 ..
3004 }) => match arg.cast_to_bool(ctx) {
3005 (Pure, Known(v)) => {
3006 if v {
3007 0.0
3008 } else {
3009 1.0
3010 }
3011 }
3012 _ => return (MayBeImpure, Unknown),
3013 },
3014 Expr::Unary(UnaryExpr {
3015 op: op!("void"),
3016 ref arg,
3017 ..
3018 }) => {
3019 if arg.may_have_side_effects(ctx) {
3020 return (MayBeImpure, Known(f64::NAN));
3021 } else {
3022 f64::NAN
3023 }
3024 }
3025
3026 Expr::Tpl(..) => {
3027 return (
3028 Pure,
3029 num_from_str(&match expr.as_pure_string(ctx) {
3030 Known(v) => v,
3031 Unknown => return (MayBeImpure, Unknown),
3032 }),
3033 );
3034 }
3035
3036 Expr::Seq(seq) => {
3037 if let Some(last) = seq.exprs.last() {
3038 let (_, v) = last.cast_to_number(ctx);
3039
3040 return (MayBeImpure, v);
3042 }
3043
3044 return (MayBeImpure, Unknown);
3045 }
3046
3047 _ => return (MayBeImpure, Unknown),
3048 };
3049
3050 (Purity::Pure, Known(v))
3051}
3052
3053fn as_pure_number(expr: &Expr, ctx: ExprCtx) -> Value<f64> {
3054 let (purity, v) = expr.cast_to_number(ctx);
3055 if !purity.is_pure() {
3056 return Unknown;
3057 }
3058
3059 v
3060}
3061
3062fn as_pure_string(expr: &Expr, ctx: ExprCtx) -> Value<Cow<'_, str>> {
3063 let Some(ctx) = ctx.consume_depth() else {
3064 return Unknown;
3065 };
3066
3067 match *expr {
3068 Expr::Lit(ref l) => match *l {
3069 Lit::Str(Str { ref value, .. }) => Known(Cow::Borrowed(value)),
3070 Lit::Num(ref n) => {
3071 if n.value == -0.0 {
3072 return Known(Cow::Borrowed("0"));
3073 }
3074
3075 Known(Cow::Owned(n.value.to_js_string()))
3076 }
3077 Lit::Bool(Bool { value: true, .. }) => Known(Cow::Borrowed("true")),
3078 Lit::Bool(Bool { value: false, .. }) => Known(Cow::Borrowed("false")),
3079 Lit::Null(..) => Known(Cow::Borrowed("null")),
3080 _ => Unknown,
3081 },
3082 Expr::Tpl(_) => {
3083 Value::Unknown
3084 }
3089 Expr::Ident(Ident { ref sym, ctxt, .. }) => match &**sym {
3090 "undefined" | "Infinity" | "NaN" if ctxt == ctx.unresolved_ctxt => {
3091 Known(Cow::Borrowed(&**sym))
3092 }
3093 _ => Unknown,
3094 },
3095 Expr::Unary(UnaryExpr {
3096 op: op!("void"), ..
3097 }) => Known(Cow::Borrowed("undefined")),
3098 Expr::Unary(UnaryExpr {
3099 op: op!("!"),
3100 ref arg,
3101 ..
3102 }) => Known(Cow::Borrowed(match arg.as_pure_bool(ctx) {
3103 Known(v) => {
3104 if v {
3105 "false"
3106 } else {
3107 "true"
3108 }
3109 }
3110 Unknown => return Value::Unknown,
3111 })),
3112 Expr::Array(ArrayLit { ref elems, .. }) => {
3113 let mut buf = String::new();
3114 let len = elems.len();
3115 for (idx, elem) in elems.iter().enumerate() {
3117 let last = idx == len - 1;
3118 let e = match *elem {
3119 Some(ref elem) => {
3120 let ExprOrSpread { ref expr, .. } = *elem;
3121 match &**expr {
3122 Expr::Lit(Lit::Null(..)) => Cow::Borrowed(""),
3123 Expr::Unary(UnaryExpr {
3124 op: op!("void"),
3125 arg,
3126 ..
3127 }) => {
3128 if arg.may_have_side_effects(ctx) {
3129 return Value::Unknown;
3130 }
3131 Cow::Borrowed("")
3132 }
3133 Expr::Ident(Ident { sym: undefined, .. })
3134 if &**undefined == "undefined" =>
3135 {
3136 Cow::Borrowed("")
3137 }
3138 _ => match expr.as_pure_string(ctx) {
3139 Known(v) => v,
3140 Unknown => return Value::Unknown,
3141 },
3142 }
3143 }
3144 None => Cow::Borrowed(""),
3145 };
3146 buf.push_str(&e);
3147
3148 if !last {
3149 buf.push(',');
3150 }
3151 }
3152 Known(buf.into())
3153 }
3154 _ => Unknown,
3155 }
3156}
3157
3158fn get_type(expr: &Expr, ctx: ExprCtx) -> Value<Type> {
3159 let Some(ctx) = ctx.consume_depth() else {
3160 return Unknown;
3161 };
3162
3163 match expr {
3164 Expr::Assign(AssignExpr {
3165 ref right,
3166 op: op!("="),
3167 ..
3168 }) => right.get_type(ctx),
3169
3170 Expr::Member(MemberExpr {
3171 obj,
3172 prop: MemberProp::Ident(IdentName { sym: length, .. }),
3173 ..
3174 }) if &**length == "length" => match &**obj {
3175 Expr::Array(ArrayLit { .. }) | Expr::Lit(Lit::Str(..)) => Known(Type::Num),
3176 Expr::Ident(Ident { sym: arguments, .. }) if &**arguments == "arguments" => {
3177 Known(Type::Num)
3178 }
3179 _ => Unknown,
3180 },
3181
3182 Expr::Seq(SeqExpr { ref exprs, .. }) => exprs
3183 .last()
3184 .expect("sequence expression should not be empty")
3185 .get_type(ctx),
3186
3187 Expr::Bin(BinExpr {
3188 ref left,
3189 op: op!("&&"),
3190 ref right,
3191 ..
3192 })
3193 | Expr::Bin(BinExpr {
3194 ref left,
3195 op: op!("||"),
3196 ref right,
3197 ..
3198 })
3199 | Expr::Cond(CondExpr {
3200 cons: ref left,
3201 alt: ref right,
3202 ..
3203 }) => and(left.get_type(ctx), right.get_type(ctx)),
3204
3205 Expr::Bin(BinExpr {
3206 ref left,
3207 op: op!(bin, "+"),
3208 ref right,
3209 ..
3210 }) => {
3211 let rt = right.get_type(ctx);
3212 if rt == Known(StringType) {
3213 return Known(StringType);
3214 }
3215
3216 let lt = left.get_type(ctx);
3217 if lt == Known(StringType) {
3218 return Known(StringType);
3219 }
3220
3221 if lt == Known(ObjectType) || rt == Known(ObjectType) {
3225 return Unknown;
3226 }
3227
3228 if !may_be_str(lt) && !may_be_str(rt) {
3229 return Known(NumberType);
3232 }
3233
3234 Unknown
3238 }
3239
3240 Expr::Assign(AssignExpr {
3241 op: op!("+="),
3242 ref right,
3243 ..
3244 }) => {
3245 if right.get_type(ctx) == Known(StringType) {
3246 return Known(StringType);
3247 }
3248 Unknown
3249 }
3250
3251 Expr::Ident(Ident { ref sym, .. }) => Known(match &**sym {
3252 "undefined" => UndefinedType,
3253 "NaN" | "Infinity" => NumberType,
3254 _ => return Unknown,
3255 }),
3256
3257 Expr::Lit(Lit::Num(..))
3258 | Expr::Assign(AssignExpr { op: op!("&="), .. })
3259 | Expr::Assign(AssignExpr { op: op!("^="), .. })
3260 | Expr::Assign(AssignExpr { op: op!("|="), .. })
3261 | Expr::Assign(AssignExpr { op: op!("<<="), .. })
3262 | Expr::Assign(AssignExpr { op: op!(">>="), .. })
3263 | Expr::Assign(AssignExpr {
3264 op: op!(">>>="), ..
3265 })
3266 | Expr::Assign(AssignExpr { op: op!("-="), .. })
3267 | Expr::Assign(AssignExpr { op: op!("*="), .. })
3268 | Expr::Assign(AssignExpr { op: op!("**="), .. })
3269 | Expr::Assign(AssignExpr { op: op!("/="), .. })
3270 | Expr::Assign(AssignExpr { op: op!("%="), .. })
3271 | Expr::Unary(UnaryExpr { op: op!("~"), .. })
3272 | Expr::Bin(BinExpr { op: op!("|"), .. })
3273 | Expr::Bin(BinExpr { op: op!("^"), .. })
3274 | Expr::Bin(BinExpr { op: op!("&"), .. })
3275 | Expr::Bin(BinExpr { op: op!("<<"), .. })
3276 | Expr::Bin(BinExpr { op: op!(">>"), .. })
3277 | Expr::Bin(BinExpr { op: op!(">>>"), .. })
3278 | Expr::Bin(BinExpr {
3279 op: op!(bin, "-"), ..
3280 })
3281 | Expr::Bin(BinExpr { op: op!("*"), .. })
3282 | Expr::Bin(BinExpr { op: op!("%"), .. })
3283 | Expr::Bin(BinExpr { op: op!("/"), .. })
3284 | Expr::Bin(BinExpr { op: op!("**"), .. })
3285 | Expr::Update(UpdateExpr { op: op!("++"), .. })
3286 | Expr::Update(UpdateExpr { op: op!("--"), .. })
3287 | Expr::Unary(UnaryExpr {
3288 op: op!(unary, "+"),
3289 ..
3290 })
3291 | Expr::Unary(UnaryExpr {
3292 op: op!(unary, "-"),
3293 ..
3294 }) => Known(NumberType),
3295
3296 Expr::Lit(Lit::Bool(..))
3298 | Expr::Bin(BinExpr { op: op!("=="), .. })
3299 | Expr::Bin(BinExpr { op: op!("!="), .. })
3300 | Expr::Bin(BinExpr { op: op!("==="), .. })
3301 | Expr::Bin(BinExpr { op: op!("!=="), .. })
3302 | Expr::Bin(BinExpr { op: op!("<"), .. })
3303 | Expr::Bin(BinExpr { op: op!("<="), .. })
3304 | Expr::Bin(BinExpr { op: op!(">"), .. })
3305 | Expr::Bin(BinExpr { op: op!(">="), .. })
3306 | Expr::Bin(BinExpr { op: op!("in"), .. })
3307 | Expr::Bin(BinExpr {
3308 op: op!("instanceof"),
3309 ..
3310 })
3311 | Expr::Unary(UnaryExpr { op: op!("!"), .. })
3312 | Expr::Unary(UnaryExpr {
3313 op: op!("delete"), ..
3314 }) => Known(BoolType),
3315
3316 Expr::Unary(UnaryExpr {
3317 op: op!("typeof"), ..
3318 })
3319 | Expr::Lit(Lit::Str { .. })
3320 | Expr::Tpl(..) => Known(StringType),
3321
3322 Expr::Lit(Lit::Null(..)) => Known(NullType),
3323
3324 Expr::Unary(UnaryExpr {
3325 op: op!("void"), ..
3326 }) => Known(UndefinedType),
3327
3328 Expr::Fn(..)
3329 | Expr::New(NewExpr { .. })
3330 | Expr::Array(ArrayLit { .. })
3331 | Expr::Object(ObjectLit { .. })
3332 | Expr::Lit(Lit::Regex(..)) => Known(ObjectType),
3333
3334 _ => Unknown,
3335 }
3336}
3337
3338fn is_pure_callee(expr: &Expr, ctx: ExprCtx) -> bool {
3339 if expr.is_global_ref_to(ctx, "Date") {
3340 return true;
3341 }
3342
3343 match expr {
3344 Expr::Member(MemberExpr {
3345 obj,
3346 prop: MemberProp::Ident(prop),
3347 ..
3348 }) => {
3349 fn is_pure_str_method(method: &str) -> bool {
3351 matches!(
3352 method,
3353 "charAt"
3354 | "charCodeAt"
3355 | "concat"
3356 | "endsWith"
3357 | "includes"
3358 | "indexOf"
3359 | "lastIndexOf"
3360 | "localeCompare"
3361 | "slice"
3362 | "split"
3363 | "startsWith"
3364 | "substr"
3365 | "substring"
3366 | "toLocaleLowerCase"
3367 | "toLocaleUpperCase"
3368 | "toLowerCase"
3369 | "toString"
3370 | "toUpperCase"
3371 | "trim"
3372 | "trimEnd"
3373 | "trimStart"
3374 )
3375 }
3376
3377 obj.is_global_ref_to(ctx, "Math")
3378 || match &**obj {
3379 Expr::Ident(Ident {
3381 ctxt, sym: math, ..
3382 }) => &**math == "Math" && *ctxt == SyntaxContext::empty(),
3383
3384 Expr::Lit(Lit::Str(..)) => is_pure_str_method(&prop.sym),
3385 Expr::Tpl(Tpl { exprs, .. }) if exprs.is_empty() => {
3386 is_pure_str_method(&prop.sym)
3387 }
3388
3389 _ => false,
3390 }
3391 }
3392
3393 Expr::Fn(FnExpr { function: f, .. })
3394 if f.params.iter().all(|p| p.pat.is_ident())
3395 && f.body.is_some()
3396 && f.body.as_ref().unwrap().stmts.is_empty() =>
3397 {
3398 true
3399 }
3400
3401 _ => false,
3402 }
3403}
3404
3405fn may_have_side_effects(expr: &Expr, ctx: ExprCtx) -> bool {
3406 let Some(ctx) = ctx.consume_depth() else {
3407 return true;
3408 };
3409
3410 if expr.is_pure_callee(ctx) {
3411 return false;
3412 }
3413
3414 match expr {
3415 Expr::Ident(i) => {
3416 if ctx.is_unresolved_ref_safe {
3417 return false;
3418 }
3419
3420 if i.ctxt == ctx.unresolved_ctxt {
3421 !matches!(
3422 &*i.sym,
3423 "Infinity"
3424 | "NaN"
3425 | "Math"
3426 | "undefined"
3427 | "Object"
3428 | "Array"
3429 | "Promise"
3430 | "Boolean"
3431 | "Number"
3432 | "String"
3433 | "BigInt"
3434 | "Error"
3435 | "RegExp"
3436 | "Function"
3437 | "document"
3438 )
3439 } else {
3440 false
3441 }
3442 }
3443
3444 Expr::Lit(..) | Expr::This(..) | Expr::PrivateName(..) | Expr::TsConstAssertion(..) => {
3445 false
3446 }
3447
3448 Expr::Paren(e) => e.expr.may_have_side_effects(ctx),
3449
3450 Expr::Fn(..) | Expr::Arrow(..) => false,
3452
3453 Expr::Class(c) => class_has_side_effect(ctx, &c.class),
3455 Expr::Array(ArrayLit { elems, .. }) => elems
3456 .iter()
3457 .filter_map(|e| e.as_ref())
3458 .any(|e| e.spread.is_some() || e.expr.may_have_side_effects(ctx)),
3459 Expr::Unary(UnaryExpr {
3460 op: op!("delete"), ..
3461 }) => true,
3462 Expr::Unary(UnaryExpr { arg, .. }) => arg.may_have_side_effects(ctx),
3463 Expr::Bin(BinExpr { left, right, .. }) => {
3464 left.may_have_side_effects(ctx) || right.may_have_side_effects(ctx)
3465 }
3466
3467 Expr::Member(MemberExpr { obj, prop, .. })
3468 if obj.is_object() || obj.is_fn_expr() || obj.is_arrow() || obj.is_class() =>
3469 {
3470 if obj.may_have_side_effects(ctx) {
3471 return true;
3472 }
3473 match &**obj {
3474 Expr::Class(c) => {
3475 let is_static_accessor = |member: &ClassMember| {
3476 if let ClassMember::Method(ClassMethod {
3477 kind: MethodKind::Getter | MethodKind::Setter,
3478 is_static: true,
3479 ..
3480 }) = member
3481 {
3482 true
3483 } else {
3484 false
3485 }
3486 };
3487 if c.class.body.iter().any(is_static_accessor) {
3488 return true;
3489 }
3490 }
3491 Expr::Object(obj) => {
3492 let can_have_side_effect = |prop: &PropOrSpread| match prop {
3493 PropOrSpread::Spread(_) => true,
3494 PropOrSpread::Prop(prop) => match prop.as_ref() {
3495 Prop::Getter(_) | Prop::Setter(_) | Prop::Method(_) => true,
3496 Prop::Shorthand(Ident { sym, .. })
3497 | Prop::KeyValue(KeyValueProp {
3498 key:
3499 PropName::Ident(IdentName { sym, .. })
3500 | PropName::Str(Str { value: sym, .. }),
3501 ..
3502 }) => &**sym == "__proto__",
3503 Prop::KeyValue(KeyValueProp {
3504 key: PropName::Computed(_),
3505 ..
3506 }) => true,
3507 _ => false,
3508 },
3509 };
3510 if obj.props.iter().any(can_have_side_effect) {
3511 return true;
3512 }
3513 }
3514 _ => {}
3515 };
3516
3517 match prop {
3518 MemberProp::Computed(c) => c.expr.may_have_side_effects(ctx),
3519 MemberProp::Ident(_) | MemberProp::PrivateName(_) => false,
3520 }
3521 }
3522
3523 Expr::Tpl(_) => true,
3525 Expr::TaggedTpl(_) => true,
3526 Expr::MetaProp(_) => true,
3527
3528 Expr::Await(_)
3529 | Expr::Yield(_)
3530 | Expr::Member(_)
3531 | Expr::SuperProp(_)
3532 | Expr::Update(_)
3533 | Expr::Assign(_) => true,
3534
3535 Expr::OptChain(OptChainExpr { base, .. }) if matches!(&**base, OptChainBase::Member(_)) => {
3536 true
3537 }
3538
3539 Expr::New(_) => true,
3541
3542 Expr::Call(CallExpr {
3543 callee: Callee::Expr(callee),
3544 ref args,
3545 ..
3546 }) if callee.is_pure_callee(ctx) => {
3547 args.iter().any(|arg| arg.expr.may_have_side_effects(ctx))
3548 }
3549 Expr::OptChain(OptChainExpr { base, .. })
3550 if matches!(&**base, OptChainBase::Call(..))
3551 && OptChainBase::as_call(base)
3552 .unwrap()
3553 .callee
3554 .is_pure_callee(ctx) =>
3555 {
3556 OptChainBase::as_call(base)
3557 .unwrap()
3558 .args
3559 .iter()
3560 .any(|arg| arg.expr.may_have_side_effects(ctx))
3561 }
3562
3563 Expr::Call(_) | Expr::OptChain(..) => true,
3564
3565 Expr::Seq(SeqExpr { exprs, .. }) => exprs.iter().any(|e| e.may_have_side_effects(ctx)),
3566
3567 Expr::Cond(CondExpr {
3568 test, cons, alt, ..
3569 }) => {
3570 test.may_have_side_effects(ctx)
3571 || cons.may_have_side_effects(ctx)
3572 || alt.may_have_side_effects(ctx)
3573 }
3574
3575 Expr::Object(ObjectLit { props, .. }) => props.iter().any(|node| match node {
3576 PropOrSpread::Prop(node) => match &**node {
3577 Prop::Shorthand(..) => false,
3578 Prop::KeyValue(KeyValueProp { key, value }) => {
3579 let k = match key {
3580 PropName::Computed(e) => e.expr.may_have_side_effects(ctx),
3581 _ => false,
3582 };
3583
3584 k || value.may_have_side_effects(ctx)
3585 }
3586 Prop::Getter(GetterProp { key, .. })
3587 | Prop::Setter(SetterProp { key, .. })
3588 | Prop::Method(MethodProp { key, .. }) => match key {
3589 PropName::Computed(e) => e.expr.may_have_side_effects(ctx),
3590 _ => false,
3591 },
3592 Prop::Assign(_) => true,
3593 },
3594 PropOrSpread::Spread(_) => true,
3596 }),
3597
3598 Expr::JSXMember(..)
3599 | Expr::JSXNamespacedName(..)
3600 | Expr::JSXEmpty(..)
3601 | Expr::JSXElement(..)
3602 | Expr::JSXFragment(..) => true,
3603
3604 Expr::TsAs(TsAsExpr { ref expr, .. })
3605 | Expr::TsNonNull(TsNonNullExpr { ref expr, .. })
3606 | Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. })
3607 | Expr::TsInstantiation(TsInstantiation { ref expr, .. })
3608 | Expr::TsSatisfies(TsSatisfiesExpr { ref expr, .. }) => expr.may_have_side_effects(ctx),
3609
3610 Expr::Invalid(..) => true,
3611 }
3612}
3613
3614#[cfg(test)]
3615mod tests {
3616 use swc_common::{input::StringInput, BytePos};
3617 use swc_ecma_parser::{Parser, Syntax};
3618
3619 use super::*;
3620
3621 #[test]
3622 fn test_collect_decls() {
3623 run_collect_decls(
3624 "const { a, b = 1, inner: { c }, ...d } = {};",
3625 &["a", "b", "c", "d"],
3626 );
3627 run_collect_decls("const [ a, b = 1, [c], ...d ] = [];", &["a", "b", "c", "d"]);
3628 }
3629
3630 #[test]
3631 fn test_collect_export_default_expr() {
3632 run_collect_decls("export default function foo(){}", &["foo"]);
3633 run_collect_decls("export default class Foo{}", &["Foo"]);
3634 }
3635
3636 fn run_collect_decls(text: &str, expected_names: &[&str]) {
3637 let module = parse_module(text);
3638 let decls: FxHashSet<Id> = collect_decls(&module);
3639 let mut names = decls.iter().map(|d| d.0.to_string()).collect::<Vec<_>>();
3640 names.sort();
3641 assert_eq!(names, expected_names);
3642 }
3643
3644 #[test]
3645 fn test_extract_var_ids() {
3646 run_extract_var_ids(
3647 "var { a, b = 1, inner: { c }, ...d } = {};",
3648 &["a", "b", "c", "d"],
3649 );
3650 run_extract_var_ids("var [ a, b = 1, [c], ...d ] = [];", &["a", "b", "c", "d"]);
3651 }
3652
3653 fn run_extract_var_ids(text: &str, expected_names: &[&str]) {
3654 let module = parse_module(text);
3655 let decls = extract_var_ids(&module);
3656 let mut names = decls.iter().map(|d| d.sym.to_string()).collect::<Vec<_>>();
3657 names.sort();
3658 assert_eq!(names, expected_names);
3659 }
3660
3661 fn parse_module(text: &str) -> Module {
3662 let syntax = Syntax::Es(Default::default());
3663 let mut p = Parser::new(
3664 syntax,
3665 StringInput::new(text, BytePos(0), BytePos(text.len() as u32)),
3666 None,
3667 );
3668 p.parse_module().unwrap()
3669 }
3670
3671 fn has_top_level_await(text: &str) -> bool {
3672 let module = parse_module(text);
3673 contains_top_level_await(&module)
3674 }
3675
3676 #[test]
3677 fn top_level_await_block() {
3678 assert!(has_top_level_await("if (maybe) { await test; }"))
3679 }
3680
3681 #[test]
3682 fn top_level_await_for_of() {
3683 assert!(has_top_level_await("for await (let iter of []){}"))
3684 }
3685
3686 #[test]
3687 fn top_level_export_await() {
3688 assert!(has_top_level_await("export const foo = await 1;"));
3689 assert!(has_top_level_await("export default await 1;"));
3690 }
3691}
3692
3693#[cfg(test)]
3694mod ident_usage_finder_parallel_tests {
3695 use swc_atoms::Atom;
3696 use swc_common::SyntaxContext;
3697 use swc_ecma_ast::*;
3698
3699 use super::*;
3700
3701 fn make_id(name: &str) -> Ident {
3702 Ident::new(Atom::from(name), Span::dummy(), SyntaxContext::empty())
3703 }
3704
3705 #[test]
3706 fn test_visit_class_members() {
3707 let id = make_id("foo");
3708 let member = ClassMember::ClassProp(ClassProp {
3709 key: PropName::Ident(quote_ident!("foo")),
3710 value: Some(Box::new(Expr::Ident(quote_ident!("foo").into()))),
3711 ..Default::default()
3712 });
3713 let found = IdentUsageFinder::find(&id, &vec![member.clone()]);
3714 assert!(found);
3715 let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![member]);
3716 assert!(!not_found);
3717 }
3718
3719 #[test]
3720 fn test_visit_expr_or_spreads() {
3721 let id = make_id("foo");
3722 let expr = ExprOrSpread {
3723 spread: None,
3724 expr: Box::new(Expr::Ident(quote_ident!("foo").into())),
3725 };
3726 let found = IdentUsageFinder::find(&id, &vec![expr.clone()]);
3727 assert!(found);
3728 let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![expr]);
3729 assert!(!not_found);
3730 }
3731
3732 #[test]
3733 fn test_visit_module_items() {
3734 let id = make_id("foo");
3735 let item = ModuleItem::Stmt(Stmt::Expr(ExprStmt {
3736 span: DUMMY_SP,
3737 expr: Box::new(Expr::Ident(quote_ident!("foo").into())),
3738 }));
3739 let found = IdentUsageFinder::find(&id, &vec![item.clone()]);
3740 assert!(found);
3741 let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![item]);
3742 assert!(!not_found);
3743 }
3744
3745 #[test]
3746 fn test_visit_stmts() {
3747 let id = make_id("foo");
3748 let stmt = Stmt::Expr(ExprStmt {
3749 span: DUMMY_SP,
3750 expr: Box::new(Expr::Ident(quote_ident!("foo").into())),
3751 });
3752 let found = IdentUsageFinder::find(&id, &vec![stmt.clone()]);
3753 assert!(found);
3754 let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![stmt]);
3755 assert!(!not_found);
3756 }
3757
3758 #[test]
3759 fn test_visit_opt_vec_expr_or_spreads() {
3760 let id = make_id("foo");
3761 let expr = Some(ExprOrSpread {
3762 spread: None,
3763 expr: Box::new(Expr::Ident(quote_ident!("foo").into())),
3764 });
3765 let found = IdentUsageFinder::find(&id, &vec![expr.clone()]);
3766 assert!(found);
3767 let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![expr]);
3768 assert!(!not_found);
3769 }
3770
3771 #[test]
3772 fn test_visit_var_declarators() {
3773 let id = make_id("foo");
3774 let decl = VarDeclarator {
3775 span: DUMMY_SP,
3776 name: Pat::Ident(quote_ident!("foo").into()),
3777 init: None,
3778 definite: false,
3779 };
3780 let found = IdentUsageFinder::find(&id, &vec![decl.clone()]);
3781 assert!(found);
3782 let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![decl]);
3783 assert!(!not_found);
3784 }
3785
3786 #[test]
3787 fn test_visit_exprs() {
3788 let id = make_id("foo");
3789 let expr = Box::new(Expr::Ident(quote_ident!("foo").into()));
3790 let found = IdentUsageFinder::find(&id, &vec![expr.clone()]);
3791 assert!(found);
3792 let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![expr]);
3793 assert!(!not_found);
3794 }
3795}