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