Skip to main content

swc_ecma_utils/
lib.rs

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::{
22    atom,
23    wtf8::{Wtf8, Wtf8Buf},
24};
25use swc_common::{util::take::Take, Mark, Span, Spanned, SyntaxContext, DUMMY_SP};
26use swc_ecma_ast::*;
27use swc_ecma_visit::{
28    noop_visit_mut_type, noop_visit_type, visit_mut_obj_and_computed, visit_obj_and_computed,
29    Visit, VisitMut, VisitMutWith, VisitWith,
30};
31#[cfg(debug_assertions)]
32use tracing::trace;
33
34#[allow(deprecated)]
35pub use self::{
36    factory::{ExprFactory, FunctionFactory, IntoIndirectCall},
37    value::{
38        Merge,
39        Type::{
40            self, Bool as BoolType, Null as NullType, Num as NumberType, Obj as ObjectType,
41            Str as StringType, Symbol as SymbolType, Undefined as UndefinedType,
42        },
43        Value::{self, Known, Unknown},
44    },
45    Purity::{MayBeImpure, Pure},
46};
47use crate::ident::IdentLike;
48
49#[macro_use]
50mod macros;
51pub mod constructor;
52mod factory;
53pub mod function;
54pub mod ident;
55pub mod parallel;
56mod value;
57pub mod var;
58
59pub mod unicode;
60
61mod node_ignore_span;
62pub mod number;
63pub mod stack_size;
64pub mod str;
65pub use node_ignore_span::NodeIgnoringSpan;
66
67// TODO: remove
68pub struct ThisVisitor {
69    found: bool,
70}
71
72pub(crate) static CPU_COUNT: Lazy<usize> = Lazy::new(num_cpus::get);
73pub(crate) static LIGHT_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 100);
74
75impl Visit for ThisVisitor {
76    noop_visit_type!();
77
78    /// Don't recurse into constructor
79    fn visit_constructor(&mut self, _: &Constructor) {}
80
81    /// Don't recurse into fn
82    fn visit_fn_decl(&mut self, _: &FnDecl) {}
83
84    /// Don't recurse into fn
85    fn visit_fn_expr(&mut self, _: &FnExpr) {}
86
87    /// Don't recurse into fn
88    fn visit_function(&mut self, _: &Function) {}
89
90    fn visit_this_expr(&mut self, _: &ThisExpr) {
91        self.found = true;
92    }
93}
94
95/// This does not recurse into a function if `this` is changed by it.
96///
97/// e.g.
98///
99///   - The body of an arrow expression is visited.
100///   - The body of a function expression is **not** visited.
101pub fn contains_this_expr<N>(body: &N) -> bool
102where
103    N: VisitWith<ThisVisitor>,
104{
105    let mut visitor = ThisVisitor { found: false };
106    body.visit_with(&mut visitor);
107    visitor.found
108}
109
110pub fn contains_ident_ref<'a, N>(body: &N, ident: &'a Ident) -> bool
111where
112    N: VisitWith<IdentRefFinder<'a>>,
113{
114    let mut visitor = IdentRefFinder {
115        found: false,
116        ident,
117    };
118    body.visit_with(&mut visitor);
119    visitor.found
120}
121
122pub struct IdentRefFinder<'a> {
123    ident: &'a Ident,
124    found: bool,
125}
126
127impl Visit for IdentRefFinder<'_> {
128    noop_visit_type!();
129
130    fn visit_expr(&mut self, e: &Expr) {
131        e.visit_children_with(self);
132
133        match *e {
134            Expr::Ident(ref i) if i.ctxt == self.ident.ctxt && i.sym == self.ident.sym => {
135                self.found = true;
136            }
137            _ => {}
138        }
139    }
140}
141
142// TODO: remove
143pub fn contains_arguments<N>(body: &N) -> bool
144where
145    N: VisitWith<ArgumentsFinder>,
146{
147    let mut visitor = ArgumentsFinder { found: false };
148    body.visit_with(&mut visitor);
149    visitor.found
150}
151
152pub struct ArgumentsFinder {
153    found: bool,
154}
155
156impl Visit for ArgumentsFinder {
157    noop_visit_type!();
158
159    /// Don't recurse into constructor
160    fn visit_constructor(&mut self, _: &Constructor) {}
161
162    fn visit_expr(&mut self, e: &Expr) {
163        e.visit_children_with(self);
164
165        if e.is_ident_ref_to("arguments") {
166            self.found = true;
167        }
168    }
169
170    /// Don't recurse into fn
171    fn visit_function(&mut self, _: &Function) {}
172
173    fn visit_prop(&mut self, n: &Prop) {
174        n.visit_children_with(self);
175
176        if let Prop::Shorthand(i) = n {
177            if &*i.sym == "arguments" {
178                self.found = true;
179            }
180        }
181    }
182}
183
184pub trait StmtOrModuleItem: Send + Sync + Sized {
185    fn into_stmt(self) -> Result<Stmt, ModuleDecl>;
186
187    fn as_stmt(&self) -> Result<&Stmt, &ModuleDecl>;
188
189    fn as_stmt_mut(&mut self) -> Result<&mut Stmt, &mut ModuleDecl>;
190
191    fn from_stmt(stmt: Stmt) -> Self;
192
193    fn try_from_module_decl(decl: ModuleDecl) -> Result<Self, ModuleDecl>;
194}
195
196impl StmtOrModuleItem for Stmt {
197    #[inline]
198    fn into_stmt(self) -> Result<Stmt, ModuleDecl> {
199        Ok(self)
200    }
201
202    #[inline]
203    fn as_stmt(&self) -> Result<&Stmt, &ModuleDecl> {
204        Ok(self)
205    }
206
207    #[inline]
208    fn as_stmt_mut(&mut self) -> Result<&mut Stmt, &mut ModuleDecl> {
209        Ok(self)
210    }
211
212    #[inline]
213    fn from_stmt(stmt: Stmt) -> Self {
214        stmt
215    }
216
217    #[inline]
218    fn try_from_module_decl(decl: ModuleDecl) -> Result<Self, ModuleDecl> {
219        Err(decl)
220    }
221}
222
223impl StmtOrModuleItem for ModuleItem {
224    #[inline]
225    fn into_stmt(self) -> Result<Stmt, ModuleDecl> {
226        match self {
227            ModuleItem::ModuleDecl(v) => Err(v),
228            ModuleItem::Stmt(v) => Ok(v),
229            #[cfg(swc_ast_unknown)]
230            _ => panic!("unable to access unknown nodes"),
231        }
232    }
233
234    #[inline]
235    fn as_stmt(&self) -> Result<&Stmt, &ModuleDecl> {
236        match self {
237            ModuleItem::ModuleDecl(v) => Err(v),
238            ModuleItem::Stmt(v) => Ok(v),
239            #[cfg(swc_ast_unknown)]
240            _ => panic!("unable to access unknown nodes"),
241        }
242    }
243
244    #[inline]
245    fn as_stmt_mut(&mut self) -> Result<&mut Stmt, &mut ModuleDecl> {
246        match self {
247            ModuleItem::ModuleDecl(v) => Err(v),
248            ModuleItem::Stmt(v) => Ok(v),
249            #[cfg(swc_ast_unknown)]
250            _ => panic!("unable to access unknown nodes"),
251        }
252    }
253
254    #[inline]
255    fn from_stmt(stmt: Stmt) -> Self {
256        stmt.into()
257    }
258
259    #[inline]
260    fn try_from_module_decl(decl: ModuleDecl) -> Result<Self, ModuleDecl> {
261        Ok(decl.into())
262    }
263}
264
265pub trait ModuleItemLike: StmtLike {
266    fn try_into_module_decl(self) -> Result<ModuleDecl, Self> {
267        Err(self)
268    }
269    fn try_from_module_decl(decl: ModuleDecl) -> Result<Self, ModuleDecl> {
270        Err(decl)
271    }
272}
273
274pub trait StmtLike: Sized + 'static + Send + Sync + From<Stmt> {
275    fn try_into_stmt(self) -> Result<Stmt, Self>;
276    fn as_stmt(&self) -> Option<&Stmt>;
277    fn as_stmt_mut(&mut self) -> Option<&mut Stmt>;
278}
279
280impl ModuleItemLike for Stmt {}
281
282impl StmtLike for Stmt {
283    #[inline]
284    fn try_into_stmt(self) -> Result<Stmt, Self> {
285        Ok(self)
286    }
287
288    #[inline]
289    fn as_stmt(&self) -> Option<&Stmt> {
290        Some(self)
291    }
292
293    #[inline]
294    fn as_stmt_mut(&mut self) -> Option<&mut Stmt> {
295        Some(self)
296    }
297}
298
299impl ModuleItemLike for ModuleItem {
300    #[inline]
301    fn try_into_module_decl(self) -> Result<ModuleDecl, Self> {
302        match self {
303            ModuleItem::ModuleDecl(decl) => Ok(decl),
304            _ => Err(self),
305        }
306    }
307
308    #[inline]
309    fn try_from_module_decl(decl: ModuleDecl) -> Result<Self, ModuleDecl> {
310        Ok(decl.into())
311    }
312}
313impl StmtLike for ModuleItem {
314    #[inline]
315    fn try_into_stmt(self) -> Result<Stmt, Self> {
316        match self {
317            ModuleItem::Stmt(stmt) => Ok(stmt),
318            _ => Err(self),
319        }
320    }
321
322    #[inline]
323    fn as_stmt(&self) -> Option<&Stmt> {
324        match self {
325            ModuleItem::Stmt(stmt) => Some(stmt),
326            _ => None,
327        }
328    }
329
330    #[inline]
331    fn as_stmt_mut(&mut self) -> Option<&mut Stmt> {
332        match &mut *self {
333            ModuleItem::Stmt(stmt) => Some(stmt),
334            _ => None,
335        }
336    }
337}
338
339/// Prepends statements after directive statements.
340pub trait StmtLikeInjector<S>
341where
342    S: StmtLike,
343{
344    fn prepend_stmt(&mut self, insert_with: S);
345    fn prepend_stmts<I>(&mut self, insert_with: I)
346    where
347        I: IntoIterator<Item = S>;
348}
349
350impl<S> StmtLikeInjector<S> for Vec<S>
351where
352    S: StmtLike,
353{
354    /// Note: If there is no directive, use `insert` instead.
355    fn prepend_stmt(&mut self, insert_with: S) {
356        let directive_pos = self
357            .iter()
358            .position(|stmt| !stmt.as_stmt().is_some_and(is_maybe_branch_directive))
359            .unwrap_or(self.len());
360
361        self.insert(directive_pos, insert_with);
362    }
363
364    /// Note: If there is no directive, use `splice` instead.
365    fn prepend_stmts<I>(&mut self, insert_with: I)
366    where
367        I: IntoIterator<Item = S>,
368    {
369        let directive_pos = self
370            .iter()
371            .position(|stmt| !stmt.as_stmt().is_some_and(is_maybe_branch_directive))
372            .unwrap_or(self.len());
373
374        self.splice(directive_pos..directive_pos, insert_with);
375    }
376}
377
378pub type BoolValue = Value<bool>;
379
380pub trait IsEmpty {
381    fn is_empty(&self) -> bool;
382}
383
384impl IsEmpty for BlockStmt {
385    fn is_empty(&self) -> bool {
386        self.stmts.is_empty()
387    }
388}
389impl IsEmpty for FunctionBody {
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
433/// Extracts hoisted variables
434pub 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    /// Extracts hoisted variables
444    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    /// stmts contain top level return/break/continue/throw
470    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            // println!("2222 {in_switch} {allow_break} {:#?}", stmt);
491            Ok(match stmt {
492                Stmt::Break(_) => {
493                    if in_switch {
494                        // In case of `break` in switch, we should stop the analysis because the
495                        // statements after `if (foo) break;` may not execute.
496                        //
497                        // So the `return 1` in
498                        //
499                        // ```js
500                        // switch (foo) {
501                        //   case 1:
502                        //     if (bar) break;
503                        //     return 1;
504                        //   default:
505                        //     return 0;
506                        // }
507                        // ```
508                        //
509                        // may not execute and we should return `false`.
510                        return Err(());
511                    } else {
512                        allow_break
513                    }
514                }
515                Stmt::Throw(_) => allow_throw,
516                Stmt::Continue(_) | Stmt::Return(_) => true,
517                Stmt::Block(block) => {
518                    terminates_many(&block.stmts, in_switch, allow_break, allow_throw)?
519                }
520                Stmt::If(IfStmt { cons, alt, .. }) => {
521                    if let Some(alt) = alt {
522                        terminates(cons, in_switch, allow_break, allow_throw)?
523                            && terminates(alt, in_switch, allow_break, allow_throw)?
524                    } else {
525                        terminates(cons, in_switch, allow_break, allow_throw)?;
526
527                        false
528                    }
529                }
530                Stmt::Switch(s) => {
531                    let mut has_default = false;
532                    let mut has_non_empty_terminates = false;
533
534                    // last case empty or no case at all
535                    if s.cases
536                        .last()
537                        .map(|case| case.cons.is_empty())
538                        .unwrap_or(true)
539                    {
540                        return Ok(false);
541                    }
542
543                    for case in s.cases.iter().rev() {
544                        if case.test.is_none() {
545                            has_default = true
546                        }
547
548                        if !case.cons.is_empty() {
549                            let t = terminates_many(&case.cons, true, false, allow_throw)
550                                .unwrap_or(false);
551
552                            if t {
553                                has_non_empty_terminates = true
554                            } else {
555                                return Ok(false);
556                            }
557                        }
558                    }
559
560                    has_default && has_non_empty_terminates
561                }
562                Stmt::Try(t) => {
563                    if let Some(h) = &t.handler {
564                        terminates_many(&t.block.stmts, in_switch, allow_break, false)?
565                            && terminates_many(&h.body.stmts, in_switch, allow_break, allow_throw)?
566                    } else {
567                        terminates_many(&t.block.stmts, in_switch, allow_break, allow_throw)?
568                    }
569                }
570                _ => false,
571            })
572        }
573
574        terminates(self.as_stmt(), false, true, true) == Ok(true)
575    }
576
577    fn may_have_side_effects(&self, ctx: ExprCtx) -> bool {
578        fn may_have_side_effects(stmt: &Stmt, ctx: ExprCtx) -> bool {
579            match stmt {
580                Stmt::Block(block_stmt) => block_stmt
581                    .stmts
582                    .iter()
583                    .any(|stmt| stmt.may_have_side_effects(ctx)),
584                Stmt::Empty(_) => false,
585                Stmt::Labeled(labeled_stmt) => labeled_stmt.body.may_have_side_effects(ctx),
586                Stmt::If(if_stmt) => {
587                    if_stmt.test.may_have_side_effects(ctx)
588                        || if_stmt.cons.may_have_side_effects(ctx)
589                        || if_stmt
590                            .alt
591                            .as_ref()
592                            .is_some_and(|stmt| stmt.may_have_side_effects(ctx))
593                }
594                Stmt::Switch(switch_stmt) => {
595                    switch_stmt.discriminant.may_have_side_effects(ctx)
596                        || switch_stmt.cases.iter().any(|case| {
597                            case.test
598                                .as_ref()
599                                .is_some_and(|expr| expr.may_have_side_effects(ctx))
600                                || case.cons.iter().any(|con| con.may_have_side_effects(ctx))
601                        })
602                }
603                Stmt::Try(try_stmt) => {
604                    try_stmt
605                        .block
606                        .stmts
607                        .iter()
608                        .any(|stmt| stmt.may_have_side_effects(ctx))
609                        || try_stmt.handler.as_ref().is_some_and(|handler| {
610                            handler
611                                .body
612                                .stmts
613                                .iter()
614                                .any(|stmt| stmt.may_have_side_effects(ctx))
615                        })
616                        || try_stmt.finalizer.as_ref().is_some_and(|finalizer| {
617                            finalizer
618                                .stmts
619                                .iter()
620                                .any(|stmt| stmt.may_have_side_effects(ctx))
621                        })
622                }
623                Stmt::Decl(decl) => match decl {
624                    Decl::Class(class_decl) => class_has_side_effect(ctx, &class_decl.class),
625                    Decl::Fn(_) => !ctx.in_strict,
626                    Decl::Var(var_decl) => var_decl.kind == VarDeclKind::Var,
627                    _ => false,
628                },
629                Stmt::Expr(expr_stmt) => expr_stmt.expr.may_have_side_effects(ctx),
630                _ => true,
631            }
632        }
633
634        may_have_side_effects(self.as_stmt(), ctx)
635    }
636}
637
638impl StmtExt for Stmt {
639    fn as_stmt(&self) -> &Stmt {
640        self
641    }
642}
643
644impl StmtExt for Box<Stmt> {
645    fn as_stmt(&self) -> &Stmt {
646        self
647    }
648}
649
650pub struct Hoister {
651    vars: Vec<Ident>,
652}
653
654impl Visit for Hoister {
655    noop_visit_type!();
656
657    fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
658
659    fn visit_assign_expr(&mut self, node: &AssignExpr) {
660        node.right.visit_children_with(self);
661    }
662
663    fn visit_assign_pat_prop(&mut self, node: &AssignPatProp) {
664        node.value.visit_with(self);
665
666        self.vars.push(node.key.clone().into());
667    }
668
669    fn visit_constructor(&mut self, _: &Constructor) {}
670
671    fn visit_fn_decl(&mut self, f: &FnDecl) {
672        self.vars.push(f.ident.clone());
673    }
674
675    fn visit_function(&mut self, _: &Function) {}
676
677    fn visit_pat(&mut self, p: &Pat) {
678        p.visit_children_with(self);
679
680        if let Pat::Ident(ref i) = *p {
681            self.vars.push(i.clone().into())
682        }
683    }
684
685    fn visit_var_decl(&mut self, v: &VarDecl) {
686        if v.kind != VarDeclKind::Var {
687            return;
688        }
689
690        v.visit_children_with(self)
691    }
692}
693
694#[derive(Debug, Clone, Copy)]
695
696pub struct ExprCtx {
697    /// This [SyntaxContext] should be applied only to unresolved references.
698    ///
699    /// In other words, this should be applied to identifier references to
700    /// global objects like `Object` or `Math`, and when those are not shadowed
701    /// by a local declaration.
702    pub unresolved_ctxt: SyntaxContext,
703
704    /// True for argument of `typeof`.
705    pub is_unresolved_ref_safe: bool,
706
707    /// True if we are in the strict mode. This will be set to `true` for
708    /// statements **after** `'use strict'`
709    pub in_strict: bool,
710
711    /// Remaining depth of the current expression. If this is 0, it means the
712    /// function should not operate and return the safe value.
713    ///
714    /// Default value is `4`
715    pub remaining_depth: u8,
716}
717
718/// Extension methods for [Expr].
719pub trait ExprExt {
720    fn as_expr(&self) -> &Expr;
721
722    /// Returns true if this is an immutable value.
723    #[inline(always)]
724    fn is_immutable_value(&self) -> bool {
725        is_immutable_value(self.as_expr())
726    }
727
728    #[inline(always)]
729    fn is_number(&self) -> bool {
730        is_number(self.as_expr())
731    }
732
733    // TODO: remove this after a proper evaluator
734    #[inline(always)]
735    fn is_str(&self) -> bool {
736        is_str(self.as_expr())
737    }
738
739    #[inline(always)]
740    #[deprecated(note = "please use `is_array` in `Expr` directly")]
741    fn is_array_lit(&self) -> bool {
742        self.as_expr().is_array()
743    }
744
745    /// Checks if `self` is `NaN`.
746    #[inline(always)]
747    fn is_nan(&self) -> bool {
748        is_nan(self.as_expr())
749    }
750
751    #[inline(always)]
752    fn is_undefined(&self, ctx: ExprCtx) -> bool {
753        is_undefined(self.as_expr(), ctx)
754    }
755
756    #[inline(always)]
757    fn is_void(&self) -> bool {
758        is_void(self.as_expr())
759    }
760
761    /// Returns `true` if `id` references a global object.
762    #[inline(always)]
763    fn is_global_ref_to(&self, ctx: ExprCtx, id: &str) -> bool {
764        is_global_ref_to(self.as_expr(), ctx, id)
765    }
766
767    /// Returns `true` if `id` references a global object.
768    #[inline(always)]
769    fn is_one_of_global_ref_to(&self, ctx: ExprCtx, ids: &[&str]) -> bool {
770        is_one_of_global_ref_to(self.as_expr(), ctx, ids)
771    }
772
773    #[inline(always)]
774    fn is_pure(&self, ctx: ExprCtx) -> bool {
775        self.as_pure_bool(ctx).is_known()
776    }
777
778    /// Get bool value of `self` if it does not have any side effects.
779    #[inline(always)]
780    fn as_pure_bool(&self, ctx: ExprCtx) -> BoolValue {
781        as_pure_bool(self.as_expr(), ctx)
782    }
783
784    ///
785    /// This method emulates the `Boolean()` JavaScript cast function.
786    ///Note: unlike getPureBooleanValue this function does not return `None`
787    ///for expressions with side-effects.
788    #[inline(always)]
789    fn cast_to_bool(&self, ctx: ExprCtx) -> (Purity, BoolValue) {
790        cast_to_bool(self.as_expr(), ctx)
791    }
792
793    #[inline(always)]
794    fn cast_to_number(&self, ctx: ExprCtx) -> (Purity, Value<f64>) {
795        cast_to_number(self.as_expr(), ctx)
796    }
797
798    /// Emulates javascript Number() cast function.
799    ///
800    /// Note: This method returns [Known] only if it's pure.
801    #[inline(always)]
802    fn as_pure_number(&self, ctx: ExprCtx) -> Value<f64> {
803        as_pure_number(self.as_expr(), ctx)
804    }
805
806    /// Returns Known only if it's pure.
807    #[inline(always)]
808    fn as_pure_string(&self, ctx: ExprCtx) -> Value<Cow<'_, str>> {
809        as_pure_string(self.as_expr(), ctx)
810    }
811
812    /// Returns Known only if it's pure.
813    #[inline(always)]
814    fn as_pure_wtf8(&self, ctx: ExprCtx) -> Value<Cow<'_, Wtf8>> {
815        as_pure_wtf8(self.as_expr(), ctx)
816    }
817
818    /// Apply the supplied predicate against all possible result Nodes of the
819    /// expression.
820    #[inline(always)]
821    fn get_type(&self, ctx: ExprCtx) -> Value<Type> {
822        get_type(self.as_expr(), ctx)
823    }
824
825    #[inline(always)]
826    fn is_pure_callee(&self, ctx: ExprCtx) -> bool {
827        is_pure_callee(self.as_expr(), ctx)
828    }
829
830    #[inline(always)]
831    fn may_have_side_effects(&self, ctx: ExprCtx) -> bool {
832        may_have_side_effects(self.as_expr(), ctx)
833    }
834}
835
836pub fn class_has_side_effect(expr_ctx: ExprCtx, c: &Class) -> bool {
837    if let Some(e) = &c.super_class {
838        if e.may_have_side_effects(expr_ctx) {
839            return true;
840        }
841    }
842
843    for m in &c.body {
844        match m {
845            ClassMember::Method(p) => {
846                if let PropName::Computed(key) = &p.key {
847                    if key.expr.may_have_side_effects(expr_ctx) {
848                        return true;
849                    }
850                }
851            }
852
853            ClassMember::ClassProp(p) => {
854                if let PropName::Computed(key) = &p.key {
855                    if key.expr.may_have_side_effects(expr_ctx) {
856                        return true;
857                    }
858                }
859
860                if let Some(v) = &p.value {
861                    if p.is_static && v.may_have_side_effects(expr_ctx) {
862                        return true;
863                    }
864                }
865            }
866            ClassMember::PrivateProp(p) => {
867                if let Some(v) = &p.value {
868                    if p.is_static && v.may_have_side_effects(expr_ctx) {
869                        return true;
870                    }
871                }
872            }
873            ClassMember::StaticBlock(s)
874                if s.body
875                    .stmts
876                    .iter()
877                    .any(|stmt| stmt.may_have_side_effects(expr_ctx)) =>
878            {
879                return true;
880            }
881            _ => {}
882        }
883    }
884
885    false
886}
887
888fn and(lt: Value<Type>, rt: Value<Type>) -> Value<Type> {
889    if lt == rt {
890        return lt;
891    }
892    Unknown
893}
894
895/// Return if the node is possibly a string.
896fn may_be_str(ty: Value<Type>) -> bool {
897    match ty {
898        Known(BoolType) | Known(NullType) | Known(NumberType) | Known(UndefinedType) => false,
899        Known(ObjectType) | Known(StringType) | Unknown => true,
900        // TODO: Check if this is correct
901        Known(SymbolType) => true,
902    }
903}
904
905/// Converts the digits of a radix-prefixed numeric string (the part after
906/// `0x`/`0o`/`0b`) to a [`f64`], the way ECMAScript's `ToNumber` would.
907///
908/// `u64::from_str_radix` is used when the value fits, since `u64 as f64` is
909/// already a correctly-rounded conversion. When it doesn't (e.g.
910/// `"10000000000000000"` in hex, which is 2^64), the value is rounded to the
911/// nearest `f64` directly, in one step: `radix` is always a power of two here
912/// (2, 8, or 16, the only radixes ECMAScript's grammar uses for prefixed
913/// integer literals), so each digit maps to a fixed number of bits. That
914/// makes it possible to extract the top ~53 significant bits exactly and
915/// round them to nearest-even against a sticky bit for everything below,
916/// which is what a correctly-rounded conversion requires. Accumulating
917/// digit-by-digit into an `f64` (rounding after every multiply-add) is
918/// tempting but wrong: intermediate roundings can compound into a result a
919/// few ULPs off from rounding the exact integer once.
920fn radix_str_to_number(s: &str, radix: u32) -> f64 {
921    // Tried first, before validating anything: `u64::from_str_radix` already
922    // rejects invalid digits and empty input on its own, and this covers the
923    // overwhelming majority of real-world radix literals (short hex color
924    // codes, bit flags, hashes), so the explicit validity scan below only
925    // needs to run on the rare failure path, not on every call.
926    if let Ok(n) = u64::from_str_radix(s, radix) {
927        return n as f64;
928    }
929
930    if s.is_empty() || !s.bytes().all(|b| (b as char).is_digit(radix)) {
931        return f64::NAN;
932    }
933
934    let bits_per_digit = radix.trailing_zeros() as usize;
935    let bytes = s.as_bytes();
936
937    // `'0'` is the zero digit in every radix used here, so finding the first
938    // significant byte doesn't need to decode anything, and neither does the
939    // sticky-bit scan below — this whole function stays allocation-free even
940    // for a multi-megabyte numeric literal.
941    let Some(first) = bytes.iter().position(|&b| b != b'0') else {
942        return 0.0;
943    };
944    let significant = &bytes[first..];
945
946    // Even in the worst case (the leading digit contributes only 1 bit, e.g.
947    // a leading `1`), this many digits guarantee at least 7 bits beyond the
948    // 53-bit mantissa: a round bit plus headroom for the sticky check below.
949    let prefix_len = ((59usize).div_ceil(bits_per_digit) + 1).min(significant.len());
950    let prefix_value = significant[..prefix_len].iter().fold(0u64, |acc, &b| {
951        (acc << bits_per_digit) | (b as char).to_digit(radix).unwrap() as u64
952    });
953
954    let first_digit_bits = (32
955        - (significant[0] as char)
956            .to_digit(radix)
957            .unwrap()
958            .leading_zeros()) as usize;
959    let high_bits_count = (prefix_len - 1) * bits_per_digit + first_digit_bits;
960    let remaining_bits = (significant.len() - prefix_len) * bits_per_digit;
961    let remaining_sticky = significant[prefix_len..].iter().any(|&b| b != b'0');
962
963    let extra_bits = high_bits_count - 53;
964    let dropped = prefix_value & ((1u64 << extra_bits) - 1);
965    let mantissa = prefix_value >> extra_bits;
966    let round_bit = (dropped >> (extra_bits - 1)) & 1;
967    let lower_sticky = (dropped & ((1u64 << (extra_bits - 1)) - 1)) != 0;
968    let round_up = round_bit == 1 && (lower_sticky || remaining_sticky || mantissa & 1 == 1);
969    let mantissa = mantissa + round_up as u64;
970
971    let exponent = saturating_exponent(remaining_bits, extra_bits);
972
973    // Exact: multiplying by a power of two never loses precision in IEEE 754.
974    mantissa as f64 * 2f64.powi(exponent)
975}
976
977/// `(remaining_bits + extra_bits) as i32`, but saturating instead of
978/// wrapping when the sum exceeds `i32::MAX`.
979///
980/// An unchecked cast would wrap a large enough sum into a *negative* `i32`,
981/// turning what should be an astronomically large (legitimately `Infinity`
982/// once passed through `f64::powi`) exponent into a tiny or negative one
983/// instead — reachable only via a radix literal of hundreds of millions of
984/// digits, but `powi(i32::MAX)` already saturates to `Infinity` on its own,
985/// so clamping here is all that's needed to make that fall out correctly.
986fn saturating_exponent(remaining_bits: usize, extra_bits: usize) -> i32 {
987    (remaining_bits + extra_bits).min(i32::MAX as usize) as i32
988}
989
990pub fn num_from_str(s: &str) -> Value<f64> {
991    if s.contains('\u{000b}') {
992        return Unknown;
993    }
994
995    let s = s.trim();
996
997    if s.is_empty() {
998        return Known(0.0);
999    }
1000
1001    if s.len() >= 2 {
1002        match &s.as_bytes()[..2] {
1003            b"0x" | b"0X" => return Known(radix_str_to_number(&s[2..], 16)),
1004            b"0o" | b"0O" => return Known(radix_str_to_number(&s[2..], 8)),
1005            b"0b" | b"0B" => return Known(radix_str_to_number(&s[2..], 2)),
1006            _ => {}
1007        }
1008    }
1009
1010    if (s.starts_with('-') || s.starts_with('+'))
1011        && (s[1..].starts_with("0x") || s[1..].starts_with("0X"))
1012    {
1013        // hex numbers with explicit signs vary between browsers.
1014        return Unknown;
1015    }
1016
1017    // Firefox and IE treat the "Infinity" differently. Firefox is case
1018    // insensitive, but IE treats "infinity" as NaN.  So leave it alone.
1019    match s {
1020        "infinity" | "+infinity" | "-infinity" => return Unknown,
1021        _ => {}
1022    }
1023
1024    Known(s.parse().ok().unwrap_or(f64::NAN))
1025}
1026
1027impl ExprExt for Box<Expr> {
1028    #[inline(always)]
1029    fn as_expr(&self) -> &Expr {
1030        self
1031    }
1032}
1033
1034impl ExprExt for Expr {
1035    #[inline(always)]
1036    fn as_expr(&self) -> &Expr {
1037        self
1038    }
1039}
1040
1041#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
1042pub enum Purity {
1043    /// May have some side effects.
1044    MayBeImpure,
1045    /// Does not have any side effect.
1046    Pure,
1047}
1048impl Purity {
1049    /// Returns true if it's pure.
1050    pub fn is_pure(self) -> bool {
1051        self == Pure
1052    }
1053}
1054
1055impl Add for Purity {
1056    type Output = Self;
1057
1058    fn add(self, rhs: Self) -> Self {
1059        match (self, rhs) {
1060            (Pure, Pure) => Pure,
1061            _ => MayBeImpure,
1062        }
1063    }
1064}
1065
1066/// Cast to javascript's int32
1067pub fn to_int32(d: f64) -> i32 {
1068    let id = d as i32;
1069    if id as f64 == d {
1070        // This covers -0.0 as well
1071        return id;
1072    }
1073
1074    if d.is_nan() || d.is_infinite() {
1075        return 0;
1076    }
1077
1078    let d = if d >= 0.0 { d.floor() } else { d.ceil() };
1079
1080    const TWO32: f64 = 4_294_967_296.0;
1081    let d = d % TWO32;
1082    // (double)(long)d == d should hold here
1083
1084    let l = d as i64;
1085    // returning (int)d does not work as d can be outside int range
1086    // but the result must always be 32 lower bits of l
1087    l as i32
1088}
1089
1090// pub fn to_u32(_d: f64) -> u32 {
1091//     //   if (Double.isNaN(d) || Double.isInfinite(d) || d == 0) {
1092//     //   return 0;
1093//     // }
1094
1095//     // d = Math.signum(d) * Math.floor(Math.abs(d));
1096
1097//     // double two32 = 4294967296.0;
1098//     // // this ensures that d is positive
1099//     // d = ((d % two32) + two32) % two32;
1100//     // // (double)(long)d == d should hold here
1101
1102//     // long l = (long) d;
1103//     // // returning (int)d does not work as d can be outside int range
1104//     // // but the result must always be 32 lower bits of l
1105//     // return (int) l;
1106//     unimplemented!("to_u32")
1107// }
1108
1109pub fn has_rest_pat<T: VisitWith<RestPatVisitor>>(node: &T) -> bool {
1110    let mut v = RestPatVisitor { found: false };
1111    node.visit_with(&mut v);
1112    v.found
1113}
1114
1115pub struct RestPatVisitor {
1116    found: bool,
1117}
1118
1119impl Visit for RestPatVisitor {
1120    noop_visit_type!();
1121
1122    fn visit_rest_pat(&mut self, _: &RestPat) {
1123        self.found = true;
1124    }
1125}
1126
1127pub fn is_literal<T>(node: &T) -> bool
1128where
1129    T: VisitWith<LiteralVisitor>,
1130{
1131    let (v, _) = calc_literal_cost(node, true);
1132    v
1133}
1134
1135#[inline(never)]
1136pub fn calc_literal_cost<T>(e: &T, allow_non_json_value: bool) -> (bool, usize)
1137where
1138    T: VisitWith<LiteralVisitor>,
1139{
1140    let mut v = LiteralVisitor {
1141        is_lit: true,
1142        cost: 0,
1143        allow_non_json_value,
1144    };
1145    e.visit_with(&mut v);
1146
1147    (v.is_lit, v.cost)
1148}
1149
1150pub struct LiteralVisitor {
1151    is_lit: bool,
1152    cost: usize,
1153    allow_non_json_value: bool,
1154}
1155
1156impl Visit for LiteralVisitor {
1157    noop_visit_type!();
1158
1159    fn visit_array_lit(&mut self, e: &ArrayLit) {
1160        if !self.is_lit {
1161            return;
1162        }
1163
1164        self.cost += 2 + e.elems.len();
1165
1166        e.visit_children_with(self);
1167
1168        for elem in &e.elems {
1169            if !self.allow_non_json_value && elem.is_none() {
1170                self.is_lit = false;
1171            }
1172        }
1173    }
1174
1175    fn visit_arrow_expr(&mut self, _: &ArrowExpr) {
1176        self.is_lit = false
1177    }
1178
1179    fn visit_assign_expr(&mut self, _: &AssignExpr) {
1180        self.is_lit = false;
1181    }
1182
1183    fn visit_await_expr(&mut self, _: &AwaitExpr) {
1184        self.is_lit = false
1185    }
1186
1187    fn visit_bin_expr(&mut self, _: &BinExpr) {
1188        self.is_lit = false
1189    }
1190
1191    fn visit_call_expr(&mut self, _: &CallExpr) {
1192        self.is_lit = false
1193    }
1194
1195    fn visit_class_expr(&mut self, _: &ClassExpr) {
1196        self.is_lit = false
1197    }
1198
1199    fn visit_cond_expr(&mut self, _: &CondExpr) {
1200        self.is_lit = false;
1201    }
1202
1203    fn visit_expr(&mut self, e: &Expr) {
1204        if !self.is_lit {
1205            return;
1206        }
1207
1208        match *e {
1209            Expr::Ident(..) | Expr::Lit(Lit::Regex(..)) => self.is_lit = false,
1210            Expr::Tpl(ref tpl) if !tpl.exprs.is_empty() => self.is_lit = false,
1211            _ => e.visit_children_with(self),
1212        }
1213    }
1214
1215    fn visit_fn_expr(&mut self, _: &FnExpr) {
1216        self.is_lit = false;
1217    }
1218
1219    fn visit_invalid(&mut self, _: &Invalid) {
1220        self.is_lit = false;
1221    }
1222
1223    fn visit_member_expr(&mut self, m: &MemberExpr) {
1224        if m.obj.is_ident_ref_to("Symbol") {
1225            return;
1226        }
1227
1228        self.is_lit = false;
1229    }
1230
1231    fn visit_meta_prop_expr(&mut self, _: &MetaPropExpr) {
1232        self.is_lit = false
1233    }
1234
1235    fn visit_new_expr(&mut self, _: &NewExpr) {
1236        self.is_lit = false
1237    }
1238
1239    fn visit_number(&mut self, node: &Number) {
1240        // JSON number syntax can express infinities as overflowed values such
1241        // as `2e308`, but it has no representation that parses to `NaN`.
1242        if !self.allow_non_json_value && node.value.is_nan() {
1243            self.is_lit = false;
1244        }
1245    }
1246
1247    fn visit_opt_chain_expr(&mut self, _: &OptChainExpr) {
1248        self.is_lit = false
1249    }
1250
1251    fn visit_private_name(&mut self, _: &PrivateName) {
1252        self.is_lit = false
1253    }
1254
1255    fn visit_prop(&mut self, p: &Prop) {
1256        if !self.is_lit {
1257            return;
1258        }
1259
1260        p.visit_children_with(self);
1261
1262        match p {
1263            Prop::KeyValue(..) => {
1264                self.cost += 1;
1265            }
1266            _ => self.is_lit = false,
1267        }
1268    }
1269
1270    fn visit_prop_name(&mut self, node: &PropName) {
1271        if !self.is_lit {
1272            return;
1273        }
1274
1275        node.visit_children_with(self);
1276
1277        match node {
1278            PropName::Str(ref s) => self.cost += 2 + s.value.len(),
1279            PropName::Ident(ref id) => self.cost += 2 + id.sym.len(),
1280            PropName::Num(..) => {
1281                // TODO: Count digits
1282                self.cost += 5;
1283            }
1284            PropName::BigInt(_) => self.is_lit = false,
1285            PropName::Computed(..) => self.is_lit = false,
1286            #[cfg(swc_ast_unknown)]
1287            _ => (),
1288        }
1289    }
1290
1291    fn visit_seq_expr(&mut self, _: &SeqExpr) {
1292        self.is_lit = false
1293    }
1294
1295    fn visit_spread_element(&mut self, _: &SpreadElement) {
1296        self.is_lit = false;
1297    }
1298
1299    fn visit_tagged_tpl(&mut self, _: &TaggedTpl) {
1300        self.is_lit = false
1301    }
1302
1303    fn visit_this_expr(&mut self, _: &ThisExpr) {
1304        self.is_lit = false;
1305    }
1306
1307    fn visit_ts_const_assertion(&mut self, _: &TsConstAssertion) {
1308        self.is_lit = false
1309    }
1310
1311    fn visit_ts_non_null_expr(&mut self, _: &TsNonNullExpr) {
1312        self.is_lit = false
1313    }
1314
1315    fn visit_unary_expr(&mut self, _: &UnaryExpr) {
1316        self.is_lit = false;
1317    }
1318
1319    fn visit_update_expr(&mut self, _: &UpdateExpr) {
1320        self.is_lit = false;
1321    }
1322
1323    fn visit_yield_expr(&mut self, _: &YieldExpr) {
1324        self.is_lit = false
1325    }
1326}
1327
1328pub fn is_simple_pure_expr(expr: &Expr, pure_getters: bool) -> bool {
1329    match expr {
1330        Expr::Ident(..) | Expr::This(..) | Expr::Lit(..) => true,
1331        Expr::Unary(UnaryExpr {
1332            op: op!("void") | op!("!"),
1333            arg,
1334            ..
1335        }) => is_simple_pure_expr(arg, pure_getters),
1336        Expr::Member(m) if pure_getters => is_simple_pure_member_expr(m, pure_getters),
1337        _ => false,
1338    }
1339}
1340
1341pub fn is_simple_pure_member_expr(m: &MemberExpr, pure_getters: bool) -> bool {
1342    match &m.prop {
1343        MemberProp::Ident(..) | MemberProp::PrivateName(..) => {
1344            is_simple_pure_expr(&m.obj, pure_getters)
1345        }
1346        MemberProp::Computed(c) => {
1347            is_simple_pure_expr(&c.expr, pure_getters) && is_simple_pure_expr(&m.obj, pure_getters)
1348        }
1349        #[cfg(swc_ast_unknown)]
1350        _ => false,
1351    }
1352}
1353
1354fn sym_for_expr(expr: &Expr) -> Option<String> {
1355    match expr {
1356        Expr::Lit(Lit::Str(s)) => s.value.as_str().map(ToString::to_string),
1357        Expr::This(_) => Some("this".to_string()),
1358
1359        Expr::Ident(ident)
1360        | Expr::Fn(FnExpr {
1361            ident: Some(ident), ..
1362        })
1363        | Expr::Class(ClassExpr {
1364            ident: Some(ident), ..
1365        }) => Some(ident.sym.to_string()),
1366
1367        Expr::OptChain(OptChainExpr { base, .. }) => match &**base {
1368            OptChainBase::Call(OptCall { callee: expr, .. }) => sym_for_expr(expr),
1369            OptChainBase::Member(MemberExpr {
1370                prop: MemberProp::Ident(ident),
1371                obj,
1372                ..
1373            }) => Some(format!(
1374                "{}_{}",
1375                sym_for_expr(obj).unwrap_or_default(),
1376                ident.sym
1377            )),
1378
1379            OptChainBase::Member(MemberExpr {
1380                prop: MemberProp::Computed(ComputedPropName { expr, .. }),
1381                obj,
1382                ..
1383            }) => Some(format!(
1384                "{}_{}",
1385                sym_for_expr(obj).unwrap_or_default(),
1386                sym_for_expr(expr).unwrap_or_default()
1387            )),
1388            _ => None,
1389        },
1390        Expr::Call(CallExpr {
1391            callee: Callee::Expr(expr),
1392            ..
1393        }) => sym_for_expr(expr),
1394
1395        Expr::SuperProp(SuperPropExpr {
1396            prop: SuperProp::Ident(ident),
1397            ..
1398        }) => Some(format!("super_{}", ident.sym)),
1399
1400        Expr::SuperProp(SuperPropExpr {
1401            prop: SuperProp::Computed(ComputedPropName { expr, .. }),
1402            ..
1403        }) => Some(format!("super_{}", sym_for_expr(expr).unwrap_or_default())),
1404
1405        Expr::Member(MemberExpr {
1406            prop: MemberProp::Ident(ident),
1407            obj,
1408            ..
1409        }) => Some(format!(
1410            "{}_{}",
1411            sym_for_expr(obj).unwrap_or_default(),
1412            ident.sym
1413        )),
1414
1415        Expr::Member(MemberExpr {
1416            prop: MemberProp::Computed(ComputedPropName { expr, .. }),
1417            obj,
1418            ..
1419        }) => Some(format!(
1420            "{}_{}",
1421            sym_for_expr(obj).unwrap_or_default(),
1422            sym_for_expr(expr).unwrap_or_default()
1423        )),
1424
1425        _ => None,
1426    }
1427}
1428
1429/// Used to determine super_class_ident
1430pub fn alias_ident_for(expr: &Expr, default: &str) -> Ident {
1431    let ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root()));
1432    let span = expr.span();
1433
1434    let mut sym = sym_for_expr(expr).unwrap_or_else(|| default.to_string());
1435
1436    if let Err(s) = Ident::verify_symbol(&sym) {
1437        sym = s;
1438    }
1439
1440    if !sym.starts_with('_') {
1441        sym = format!("_{sym}")
1442    }
1443    quote_ident!(ctxt, span, sym)
1444}
1445
1446/// Used to determine super_class_ident
1447pub fn alias_ident_for_simple_assign_tatget(expr: &SimpleAssignTarget, default: &str) -> Ident {
1448    let ctxt = SyntaxContext::empty().apply_mark(Mark::fresh(Mark::root()));
1449
1450    let span = expr.span();
1451
1452    let mut sym = match expr {
1453        SimpleAssignTarget::Ident(i) => Some(i.sym.to_string()),
1454
1455        SimpleAssignTarget::SuperProp(SuperPropExpr {
1456            prop: SuperProp::Ident(ident),
1457            ..
1458        }) => Some(format!("super_{}", ident.sym)),
1459
1460        SimpleAssignTarget::SuperProp(SuperPropExpr {
1461            prop: SuperProp::Computed(ComputedPropName { expr, .. }),
1462            ..
1463        }) => Some(format!("super_{}", sym_for_expr(expr).unwrap_or_default())),
1464
1465        SimpleAssignTarget::Member(MemberExpr {
1466            prop: MemberProp::Ident(ident),
1467            obj,
1468            ..
1469        }) => Some(format!(
1470            "{}_{}",
1471            sym_for_expr(obj).unwrap_or_default(),
1472            ident.sym
1473        )),
1474
1475        SimpleAssignTarget::Member(MemberExpr {
1476            prop: MemberProp::Computed(ComputedPropName { expr, .. }),
1477            obj,
1478            ..
1479        }) => Some(format!(
1480            "{}_{}",
1481            sym_for_expr(obj).unwrap_or_default(),
1482            sym_for_expr(expr).unwrap_or_default()
1483        )),
1484        _ => None,
1485    }
1486    .unwrap_or_else(|| default.to_string());
1487
1488    if let Err(s) = Ident::verify_symbol(&sym) {
1489        sym = s;
1490    }
1491
1492    if !sym.starts_with('_') {
1493        sym = format!("_{sym}")
1494    }
1495    quote_ident!(ctxt, span, sym)
1496}
1497
1498/// Returns `(ident, aliased)`
1499pub fn alias_if_required(expr: &Expr, default: &str) -> (Ident, bool) {
1500    if let Expr::Ident(ref i) = *expr {
1501        return (Ident::new(i.sym.clone(), i.span, i.ctxt), false);
1502    }
1503
1504    (alias_ident_for(expr, default), true)
1505}
1506
1507pub fn prop_name_to_expr(p: PropName) -> Expr {
1508    match p {
1509        PropName::Ident(i) => i.into(),
1510        PropName::Str(s) => Lit::Str(s).into(),
1511        PropName::Num(n) => Lit::Num(n).into(),
1512        PropName::BigInt(b) => Lit::BigInt(b).into(),
1513        PropName::Computed(c) => *c.expr,
1514        #[cfg(swc_ast_unknown)]
1515        _ => panic!("unable to access unknown nodes"),
1516    }
1517}
1518/// Similar to `prop_name_to_expr`, but used for value position.
1519///
1520/// e.g. value from `{ key: value }`
1521pub fn prop_name_to_expr_value(p: PropName) -> Expr {
1522    match p {
1523        PropName::Ident(i) => Lit::Str(Str {
1524            span: i.span,
1525            raw: None,
1526            value: i.sym.into(),
1527        })
1528        .into(),
1529        PropName::Str(s) => Lit::Str(s).into(),
1530        PropName::Num(n) => Lit::Num(n).into(),
1531        PropName::BigInt(b) => Lit::BigInt(b).into(),
1532        PropName::Computed(c) => *c.expr,
1533        #[cfg(swc_ast_unknown)]
1534        _ => panic!("unable to access unknown nodes"),
1535    }
1536}
1537
1538pub fn prop_name_to_member_prop(prop_name: PropName) -> MemberProp {
1539    match prop_name {
1540        PropName::Ident(i) => MemberProp::Ident(i),
1541        PropName::Str(s) => MemberProp::Computed(ComputedPropName {
1542            span: DUMMY_SP,
1543            expr: s.into(),
1544        }),
1545        PropName::Num(n) => MemberProp::Computed(ComputedPropName {
1546            span: DUMMY_SP,
1547            expr: n.into(),
1548        }),
1549        PropName::Computed(c) => MemberProp::Computed(c),
1550        PropName::BigInt(b) => MemberProp::Computed(ComputedPropName {
1551            span: DUMMY_SP,
1552            expr: b.into(),
1553        }),
1554        #[cfg(swc_ast_unknown)]
1555        _ => panic!("unable to access unknown nodes"),
1556    }
1557}
1558
1559/// `super_call_span` should be the span of the class definition
1560/// Use value of [`Class::span`].
1561pub fn default_constructor_with_span(has_super: bool, super_call_span: Span) -> Constructor {
1562    #[cfg(debug_assertions)]
1563    trace!(has_super = has_super, "Creating a default constructor");
1564    let super_call_span = super_call_span.with_hi(super_call_span.lo);
1565
1566    Constructor {
1567        span: DUMMY_SP,
1568        key: PropName::Ident(atom!("constructor").into()),
1569        is_optional: false,
1570        params: if has_super {
1571            vec![ParamOrTsParamProp::Param(Param {
1572                span: DUMMY_SP,
1573                decorators: Vec::new(),
1574                pat: Pat::Rest(RestPat {
1575                    span: DUMMY_SP,
1576                    dot3_token: DUMMY_SP,
1577                    arg: Box::new(Pat::Ident(quote_ident!("args").into())),
1578                    type_ann: Default::default(),
1579                }),
1580            })]
1581        } else {
1582            Vec::new()
1583        },
1584        body: Some(FunctionBody {
1585            span: DUMMY_SP,
1586            stmts: if has_super {
1587                vec![CallExpr {
1588                    span: super_call_span,
1589                    callee: Callee::Super(Super { span: DUMMY_SP }),
1590                    args: vec![ExprOrSpread {
1591                        spread: Some(DUMMY_SP),
1592                        expr: Box::new(Expr::Ident(quote_ident!("args").into())),
1593                    }],
1594                    ..Default::default()
1595                }
1596                .into_stmt()]
1597            } else {
1598                Vec::new()
1599            },
1600        }),
1601        ..Default::default()
1602    }
1603}
1604
1605/// Check if `e` is `...arguments`
1606pub fn is_rest_arguments(e: &ExprOrSpread) -> bool {
1607    if e.spread.is_none() {
1608        return false;
1609    }
1610
1611    e.expr.is_ident_ref_to("arguments")
1612}
1613
1614pub fn opt_chain_test(
1615    left: Box<Expr>,
1616    right: Box<Expr>,
1617    span: Span,
1618    no_document_all: bool,
1619) -> Expr {
1620    if no_document_all {
1621        BinExpr {
1622            span,
1623            left,
1624            op: op!("=="),
1625            right: Lit::Null(Null { span: DUMMY_SP }).into(),
1626        }
1627        .into()
1628    } else {
1629        BinExpr {
1630            span,
1631            left: BinExpr {
1632                span: DUMMY_SP,
1633                left,
1634                op: op!("==="),
1635                right: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
1636            }
1637            .into(),
1638            op: op!("||"),
1639            right: BinExpr {
1640                span: DUMMY_SP,
1641                left: right,
1642                op: op!("==="),
1643                right: Expr::undefined(DUMMY_SP),
1644            }
1645            .into(),
1646        }
1647        .into()
1648    }
1649}
1650
1651/// inject `branch` after directives
1652#[inline]
1653pub fn prepend_stmt<T: StmtLike>(stmts: &mut Vec<T>, stmt: T) {
1654    stmts.prepend_stmt(stmt);
1655}
1656
1657/// If the stmt is maybe a directive like `"use strict";`
1658pub fn is_maybe_branch_directive(stmt: &Stmt) -> bool {
1659    match stmt {
1660        Stmt::Expr(ExprStmt { ref expr, .. }) if matches!(&**expr, Expr::Lit(Lit::Str(..))) => true,
1661        _ => false,
1662    }
1663}
1664
1665/// inject `stmts` after directives
1666#[inline]
1667pub fn prepend_stmts<T: StmtLike>(to: &mut Vec<T>, stmts: impl ExactSizeIterator<Item = T>) {
1668    to.prepend_stmts(stmts);
1669}
1670
1671pub trait IsDirective {
1672    fn as_ref(&self) -> Option<&Stmt>;
1673
1674    fn directive_continue(&self) -> bool {
1675        self.as_ref().is_some_and(Stmt::can_precede_directive)
1676    }
1677    fn is_use_strict(&self) -> bool {
1678        self.as_ref().is_some_and(Stmt::is_use_strict)
1679    }
1680}
1681
1682impl IsDirective for Stmt {
1683    fn as_ref(&self) -> Option<&Stmt> {
1684        Some(self)
1685    }
1686}
1687
1688impl IsDirective for ModuleItem {
1689    fn as_ref(&self) -> Option<&Stmt> {
1690        self.as_stmt()
1691    }
1692}
1693
1694impl IsDirective for &ModuleItem {
1695    fn as_ref(&self) -> Option<&Stmt> {
1696        self.as_stmt()
1697    }
1698}
1699
1700/// Finds all **binding** idents of variables.
1701pub struct DestructuringFinder<I: IdentLike> {
1702    pub found: Vec<I>,
1703}
1704
1705/// Finds all **binding** idents of `node`.
1706///
1707/// If you want to avoid allocation, use [`for_each_binding_ident`] instead.
1708pub fn find_pat_ids<T, I: IdentLike>(node: &T) -> Vec<I>
1709where
1710    T: VisitWith<DestructuringFinder<I>>,
1711{
1712    let mut v = DestructuringFinder { found: Vec::new() };
1713    node.visit_with(&mut v);
1714
1715    v.found
1716}
1717
1718impl<I: IdentLike> Visit for DestructuringFinder<I> {
1719    /// No-op (we don't care about expressions)
1720    fn visit_expr(&mut self, _: &Expr) {}
1721
1722    fn visit_ident(&mut self, i: &Ident) {
1723        self.found.push(I::from_ident(i));
1724    }
1725
1726    fn visit_jsx_member_expr(&mut self, n: &JSXMemberExpr) {
1727        n.obj.visit_with(self);
1728    }
1729
1730    /// No-op (we don't care about expressions)
1731    fn visit_prop_name(&mut self, _: &PropName) {}
1732
1733    fn visit_ts_type(&mut self, _: &TsType) {}
1734}
1735
1736/// Finds all **binding** idents of variables.
1737pub struct BindingIdentifierVisitor<F>
1738where
1739    F: for<'a> FnMut(&'a BindingIdent),
1740{
1741    op: F,
1742}
1743
1744/// Finds all **binding** idents of `node`. **Any nested identifiers in
1745/// expressions are ignored**.
1746pub fn for_each_binding_ident<T, F>(node: &T, op: F)
1747where
1748    T: VisitWith<BindingIdentifierVisitor<F>>,
1749    F: for<'a> FnMut(&'a BindingIdent),
1750{
1751    let mut v = BindingIdentifierVisitor { op };
1752    node.visit_with(&mut v);
1753}
1754
1755impl<F> Visit for BindingIdentifierVisitor<F>
1756where
1757    F: for<'a> FnMut(&'a BindingIdent),
1758{
1759    noop_visit_type!();
1760
1761    /// No-op (we don't care about expressions)
1762    fn visit_expr(&mut self, _: &Expr) {}
1763
1764    fn visit_binding_ident(&mut self, i: &BindingIdent) {
1765        (self.op)(i);
1766    }
1767}
1768
1769pub fn is_valid_ident(s: &str) -> bool {
1770    if s.is_empty() {
1771        return false;
1772    }
1773
1774    Ident::verify_symbol(s).is_ok()
1775}
1776
1777pub fn is_valid_prop_ident(s: &str) -> bool {
1778    s.starts_with(Ident::is_valid_start) && s.chars().all(Ident::is_valid_continue)
1779}
1780
1781pub fn drop_span<T>(mut t: T) -> T
1782where
1783    T: VisitMutWith<DropSpan>,
1784{
1785    t.visit_mut_with(&mut DropSpan {});
1786    t
1787}
1788
1789pub struct DropSpan;
1790
1791impl Pass for DropSpan {
1792    fn process(&mut self, program: &mut Program) {
1793        program.visit_mut_with(self);
1794    }
1795}
1796
1797impl VisitMut for DropSpan {
1798    fn visit_mut_span(&mut self, span: &mut Span) {
1799        *span = DUMMY_SP;
1800    }
1801}
1802
1803/// Finds usage of `ident`
1804pub struct IdentUsageFinder<'a> {
1805    ident: &'a Ident,
1806    found: bool,
1807}
1808
1809impl Parallel for IdentUsageFinder<'_> {
1810    fn create(&self) -> Self {
1811        Self {
1812            ident: self.ident,
1813            found: self.found,
1814        }
1815    }
1816
1817    fn merge(&mut self, other: Self) {
1818        self.found = self.found || other.found;
1819    }
1820}
1821
1822impl Visit for IdentUsageFinder<'_> {
1823    noop_visit_type!();
1824
1825    visit_obj_and_computed!();
1826
1827    fn visit_ident(&mut self, i: &Ident) {
1828        if i.ctxt == self.ident.ctxt && i.sym == self.ident.sym {
1829            self.found = true;
1830        }
1831    }
1832
1833    fn visit_class_members(&mut self, n: &[ClassMember]) {
1834        self.maybe_par(*LIGHT_TASK_PARALLELS, n, |v, item| {
1835            item.visit_with(v);
1836        });
1837    }
1838
1839    fn visit_expr_or_spreads(&mut self, n: &[ExprOrSpread]) {
1840        self.maybe_par(*LIGHT_TASK_PARALLELS, n, |v, item| {
1841            item.visit_with(v);
1842        });
1843    }
1844
1845    fn visit_exprs(&mut self, exprs: &[Box<Expr>]) {
1846        self.maybe_par(*LIGHT_TASK_PARALLELS, exprs, |v, expr| {
1847            expr.visit_with(v);
1848        });
1849    }
1850
1851    fn visit_module_items(&mut self, n: &[ModuleItem]) {
1852        self.maybe_par(*LIGHT_TASK_PARALLELS, n, |v, item| {
1853            item.visit_with(v);
1854        });
1855    }
1856
1857    fn visit_opt_vec_expr_or_spreads(&mut self, n: &[Option<ExprOrSpread>]) {
1858        self.maybe_par(*LIGHT_TASK_PARALLELS, n, |v, item| {
1859            if let Some(e) = item {
1860                e.visit_with(v);
1861            }
1862        });
1863    }
1864
1865    fn visit_stmts(&mut self, stmts: &[Stmt]) {
1866        self.maybe_par(*LIGHT_TASK_PARALLELS, stmts, |v, stmt| {
1867            stmt.visit_with(v);
1868        });
1869    }
1870
1871    fn visit_var_declarators(&mut self, n: &[VarDeclarator]) {
1872        self.maybe_par(*LIGHT_TASK_PARALLELS, n, |v, item| {
1873            item.visit_with(v);
1874        });
1875    }
1876}
1877
1878impl<'a> IdentUsageFinder<'a> {
1879    pub fn find<N>(ident: &'a Ident, node: &N) -> bool
1880    where
1881        N: VisitWith<Self>,
1882    {
1883        let mut v = IdentUsageFinder {
1884            ident,
1885            found: false,
1886        };
1887        node.visit_with(&mut v);
1888        v.found
1889    }
1890}
1891
1892impl ExprCtx {
1893    pub fn consume_depth(self) -> Option<Self> {
1894        if self.remaining_depth == 0 {
1895            return None;
1896        }
1897
1898        Some(Self {
1899            remaining_depth: self.remaining_depth - 1,
1900            ..self
1901        })
1902    }
1903
1904    /// make a new expression which evaluates `val` preserving side effects, if
1905    /// any.
1906    pub fn preserve_effects<I>(self, span: Span, val: Box<Expr>, exprs: I) -> Box<Expr>
1907    where
1908        I: IntoIterator<Item = Box<Expr>>,
1909    {
1910        let mut exprs = exprs.into_iter().fold(Vec::new(), |mut v, e| {
1911            self.extract_side_effects_to(&mut v, *e);
1912            v
1913        });
1914
1915        if exprs.is_empty() {
1916            val
1917        } else {
1918            exprs.push(val);
1919
1920            SeqExpr { exprs, span }.into()
1921        }
1922    }
1923
1924    /// Add side effects of `expr` to `to`.
1925    //
1926    /// This function preserves order and conditions. (think a() ? yield b() :
1927    /// c())
1928    #[allow(clippy::vec_box)]
1929    pub fn extract_side_effects_to(self, to: &mut Vec<Box<Expr>>, expr: Expr) {
1930        match expr {
1931            Expr::Lit(..)
1932            | Expr::This(..)
1933            | Expr::Fn(..)
1934            | Expr::Arrow(..)
1935            | Expr::PrivateName(..) => {}
1936
1937            Expr::Ident(..) => {
1938                if expr.may_have_side_effects(self) {
1939                    to.push(Box::new(expr));
1940                }
1941            }
1942
1943            // In most case, we can do nothing for this.
1944            Expr::Update(_) | Expr::Assign(_) | Expr::Yield(_) | Expr::Await(_) => {
1945                to.push(Box::new(expr))
1946            }
1947
1948            // TODO
1949            Expr::MetaProp(_) => to.push(Box::new(expr)),
1950
1951            Expr::Call(_) => to.push(Box::new(expr)),
1952            Expr::New(e) => {
1953                // Known constructors
1954                if let Expr::Ident(Ident { ref sym, .. }) = *e.callee {
1955                    if *sym == "Date" && e.args.is_empty() {
1956                        return;
1957                    }
1958                }
1959
1960                to.push(e.into())
1961            }
1962            Expr::Member(_) | Expr::SuperProp(_) => to.push(Box::new(expr)),
1963
1964            // We are at here because we could not determine value of test.
1965            //TODO: Drop values if it does not have side effects.
1966            Expr::Cond(_) => to.push(Box::new(expr)),
1967
1968            Expr::Unary(UnaryExpr {
1969                op: op!("typeof"),
1970                arg,
1971                ..
1972            }) => {
1973                // We should ignore side effect of `__dirname` in
1974                //
1975                // typeof __dirname != void 0
1976                //
1977                // https://github.com/swc-project/swc/pull/7763
1978                if arg.is_ident() {
1979                    return;
1980                }
1981                self.extract_side_effects_to(to, *arg)
1982            }
1983
1984            Expr::Unary(UnaryExpr { arg, .. }) => self.extract_side_effects_to(to, *arg),
1985
1986            Expr::Bin(BinExpr { op, .. }) if op.may_short_circuit() => {
1987                to.push(Box::new(expr));
1988            }
1989            Expr::Bin(BinExpr { left, right, .. }) => {
1990                self.extract_side_effects_to(to, *left);
1991                self.extract_side_effects_to(to, *right);
1992            }
1993            Expr::Seq(SeqExpr { exprs, .. }) => exprs
1994                .into_iter()
1995                .for_each(|e| self.extract_side_effects_to(to, *e)),
1996
1997            Expr::Paren(e) => self.extract_side_effects_to(to, *e.expr),
1998
1999            Expr::Object(ObjectLit {
2000                span, mut props, ..
2001            }) => {
2002                //
2003                let mut has_spread = false;
2004                props.retain(|node| match node {
2005                    PropOrSpread::Prop(node) => match &**node {
2006                        Prop::Shorthand(..) => false,
2007                        Prop::KeyValue(KeyValueProp { key, value }) => {
2008                            if let PropName::Computed(e) = key {
2009                                if e.expr.may_have_side_effects(self) {
2010                                    return true;
2011                                }
2012                            }
2013
2014                            value.may_have_side_effects(self)
2015                        }
2016                        Prop::Getter(GetterProp { key, .. })
2017                        | Prop::Setter(SetterProp { key, .. })
2018                        | Prop::Method(MethodProp { key, .. }) => {
2019                            if let PropName::Computed(e) = key {
2020                                e.expr.may_have_side_effects(self)
2021                            } else {
2022                                false
2023                            }
2024                        }
2025                        Prop::Assign(..) => {
2026                            unreachable!("assign property in object literal is not a valid syntax")
2027                        }
2028                        #[cfg(swc_ast_unknown)]
2029                        _ => true,
2030                    },
2031                    PropOrSpread::Spread(SpreadElement { .. }) => {
2032                        has_spread = true;
2033                        true
2034                    }
2035                    #[cfg(swc_ast_unknown)]
2036                    _ => true,
2037                });
2038
2039                if has_spread {
2040                    to.push(ObjectLit { span, props }.into())
2041                } else {
2042                    props.into_iter().for_each(|prop| match prop {
2043                        PropOrSpread::Prop(node) => match *node {
2044                            Prop::Shorthand(..) => {}
2045                            Prop::KeyValue(KeyValueProp { key, value }) => {
2046                                if let PropName::Computed(e) = key {
2047                                    self.extract_side_effects_to(to, *e.expr);
2048                                }
2049
2050                                self.extract_side_effects_to(to, *value)
2051                            }
2052                            Prop::Getter(GetterProp { key, .. })
2053                            | Prop::Setter(SetterProp { key, .. })
2054                            | Prop::Method(MethodProp { key, .. }) => {
2055                                if let PropName::Computed(e) = key {
2056                                    self.extract_side_effects_to(to, *e.expr)
2057                                }
2058                            }
2059                            Prop::Assign(..) => {
2060                                unreachable!(
2061                                    "assign property in object literal is not a valid syntax"
2062                                )
2063                            }
2064                            #[cfg(swc_ast_unknown)]
2065                            _ => panic!("unable to access unknown nodes"),
2066                        },
2067                        _ => unreachable!(),
2068                    })
2069                }
2070            }
2071
2072            Expr::Array(ArrayLit { elems, .. }) => {
2073                elems.into_iter().flatten().fold(to, |v, e| {
2074                    self.extract_side_effects_to(v, *e.expr);
2075
2076                    v
2077                });
2078            }
2079
2080            Expr::TaggedTpl(TaggedTpl { tag, tpl, .. }) => {
2081                self.extract_side_effects_to(to, *tag);
2082
2083                tpl.exprs
2084                    .into_iter()
2085                    .for_each(|e| self.extract_side_effects_to(to, *e));
2086            }
2087            Expr::Tpl(Tpl { exprs, .. }) => {
2088                exprs
2089                    .into_iter()
2090                    .for_each(|e| self.extract_side_effects_to(to, *e));
2091            }
2092            Expr::Class(ClassExpr { .. }) => unimplemented!("add_effects for class expression"),
2093
2094            Expr::JSXMember(..)
2095            | Expr::JSXNamespacedName(..)
2096            | Expr::JSXEmpty(..)
2097            | Expr::JSXElement(..)
2098            | Expr::JSXFragment(..) => to.push(Box::new(expr)),
2099
2100            Expr::TsTypeAssertion(TsTypeAssertion { expr, .. })
2101            | Expr::TsNonNull(TsNonNullExpr { expr, .. })
2102            | Expr::TsAs(TsAsExpr { expr, .. })
2103            | Expr::TsConstAssertion(TsConstAssertion { expr, .. })
2104            | Expr::TsInstantiation(TsInstantiation { expr, .. })
2105            | Expr::TsSatisfies(TsSatisfiesExpr { expr, .. }) => {
2106                self.extract_side_effects_to(to, *expr)
2107            }
2108            Expr::OptChain(..) => to.push(Box::new(expr)),
2109
2110            Expr::Invalid(..) => unreachable!(),
2111            #[cfg(swc_ast_unknown)]
2112            _ => to.push(Box::new(expr)),
2113        }
2114    }
2115}
2116
2117pub fn prop_name_eq(p: &PropName, key: &str) -> bool {
2118    match p {
2119        PropName::Ident(i) => i.sym == *key,
2120        PropName::Str(s) => s.value == *key,
2121        PropName::Num(n) => n.value.to_js_string() == *key,
2122        PropName::BigInt(_) => false,
2123        PropName::Computed(e) => match &*e.expr {
2124            Expr::Lit(Lit::Str(Str { value, .. })) => *value == *key,
2125            _ => false,
2126        },
2127        #[cfg(swc_ast_unknown)]
2128        _ => false,
2129    }
2130}
2131
2132/// Replace all `from` in `expr` with `to`.
2133///
2134/// # Usage
2135///
2136/// ```ignore
2137/// replace_ident(&mut dec.expr, cls_name.to_id(), alias);
2138/// ```
2139pub fn replace_ident<T>(node: &mut T, from: Id, to: &Ident)
2140where
2141    T: for<'any> VisitMutWith<IdentReplacer<'any>>,
2142{
2143    node.visit_mut_with(&mut IdentReplacer { from, to })
2144}
2145
2146pub struct IdentReplacer<'a> {
2147    from: Id,
2148    to: &'a Ident,
2149}
2150
2151impl VisitMut for IdentReplacer<'_> {
2152    noop_visit_mut_type!();
2153
2154    visit_mut_obj_and_computed!();
2155
2156    fn visit_mut_prop(&mut self, node: &mut Prop) {
2157        match node {
2158            Prop::Shorthand(i) => {
2159                let cloned = i.clone();
2160                i.visit_mut_with(self);
2161                if i.sym != cloned.sym || i.ctxt != cloned.ctxt {
2162                    *node = Prop::KeyValue(KeyValueProp {
2163                        key: PropName::Ident(IdentName::new(cloned.sym, cloned.span)),
2164                        value: i.clone().into(),
2165                    });
2166                }
2167            }
2168            _ => {
2169                node.visit_mut_children_with(self);
2170            }
2171        }
2172    }
2173
2174    fn visit_mut_ident(&mut self, node: &mut Ident) {
2175        if node.sym == self.from.0 && node.ctxt == self.from.1 {
2176            *node = self.to.clone();
2177        }
2178    }
2179}
2180
2181pub struct BindingCollector<I>
2182where
2183    I: IdentLike + Eq + Hash + Send + Sync,
2184{
2185    only: Option<SyntaxContext>,
2186    bindings: FxHashSet<I>,
2187    is_pat_decl: bool,
2188}
2189
2190impl<I> BindingCollector<I>
2191where
2192    I: IdentLike + Eq + Hash + Send + Sync,
2193{
2194    fn add(&mut self, i: &Ident) {
2195        if let Some(only) = self.only {
2196            if only != i.ctxt {
2197                return;
2198            }
2199        }
2200
2201        self.bindings.insert(I::from_ident(i));
2202    }
2203}
2204
2205impl<I> Visit for BindingCollector<I>
2206where
2207    I: IdentLike + Eq + Hash + Send + Sync,
2208{
2209    noop_visit_type!();
2210
2211    fn visit_arrow_expr(&mut self, n: &ArrowExpr) {
2212        let old = self.is_pat_decl;
2213
2214        for p in &n.params {
2215            self.is_pat_decl = true;
2216            p.visit_with(self);
2217        }
2218
2219        n.body.visit_with(self);
2220        self.is_pat_decl = old;
2221    }
2222
2223    fn visit_assign_pat_prop(&mut self, node: &AssignPatProp) {
2224        node.value.visit_with(self);
2225
2226        if self.is_pat_decl {
2227            self.add(&node.key.clone().into());
2228        }
2229    }
2230
2231    fn visit_class_decl(&mut self, node: &ClassDecl) {
2232        node.visit_children_with(self);
2233
2234        self.add(&node.ident);
2235    }
2236
2237    fn visit_expr(&mut self, node: &Expr) {
2238        let old = self.is_pat_decl;
2239        self.is_pat_decl = false;
2240        node.visit_children_with(self);
2241        self.is_pat_decl = old;
2242    }
2243
2244    fn visit_export_default_decl(&mut self, e: &ExportDefaultDecl) {
2245        match &e.decl {
2246            DefaultDecl::Class(ClassExpr {
2247                ident: Some(ident), ..
2248            }) => {
2249                self.add(ident);
2250            }
2251            DefaultDecl::Fn(FnExpr {
2252                ident: Some(ident),
2253                function: f,
2254            }) if f.body.is_some() => {
2255                self.add(ident);
2256            }
2257            _ => {}
2258        }
2259        e.visit_children_with(self);
2260    }
2261
2262    fn visit_fn_decl(&mut self, node: &FnDecl) {
2263        node.visit_children_with(self);
2264
2265        self.add(&node.ident);
2266    }
2267
2268    fn visit_import_default_specifier(&mut self, node: &ImportDefaultSpecifier) {
2269        self.add(&node.local);
2270    }
2271
2272    fn visit_import_named_specifier(&mut self, node: &ImportNamedSpecifier) {
2273        self.add(&node.local);
2274    }
2275
2276    fn visit_import_star_as_specifier(&mut self, node: &ImportStarAsSpecifier) {
2277        self.add(&node.local);
2278    }
2279
2280    fn visit_param(&mut self, node: &Param) {
2281        let old = self.is_pat_decl;
2282        self.is_pat_decl = true;
2283        node.visit_children_with(self);
2284        self.is_pat_decl = old;
2285    }
2286
2287    fn visit_pat(&mut self, node: &Pat) {
2288        node.visit_children_with(self);
2289
2290        if self.is_pat_decl {
2291            if let Pat::Ident(i) = node {
2292                self.add(&i.clone().into())
2293            }
2294        }
2295    }
2296
2297    fn visit_var_declarator(&mut self, node: &VarDeclarator) {
2298        let old = self.is_pat_decl;
2299        self.is_pat_decl = true;
2300        node.name.visit_with(self);
2301
2302        self.is_pat_decl = false;
2303        node.init.visit_with(self);
2304        self.is_pat_decl = old;
2305    }
2306}
2307
2308/// Collects binding identifiers.
2309pub fn collect_decls<I, N>(n: &N) -> FxHashSet<I>
2310where
2311    I: IdentLike + Eq + Hash + Send + Sync,
2312    N: VisitWith<BindingCollector<I>>,
2313{
2314    let mut v = BindingCollector {
2315        only: None,
2316        bindings: Default::default(),
2317        is_pat_decl: false,
2318    };
2319    n.visit_with(&mut v);
2320    v.bindings
2321}
2322
2323/// Collects binding identifiers, but only if it has a context which is
2324/// identical to `ctxt`.
2325pub fn collect_decls_with_ctxt<I, N>(n: &N, ctxt: SyntaxContext) -> FxHashSet<I>
2326where
2327    I: IdentLike + Eq + Hash + Send + Sync,
2328    N: VisitWith<BindingCollector<I>>,
2329{
2330    let mut v = BindingCollector {
2331        only: Some(ctxt),
2332        bindings: Default::default(),
2333        is_pat_decl: false,
2334    };
2335    n.visit_with(&mut v);
2336    v.bindings
2337}
2338
2339pub struct TopLevelAwait {
2340    found: bool,
2341}
2342
2343impl Visit for TopLevelAwait {
2344    noop_visit_type!();
2345
2346    fn visit_stmt(&mut self, n: &Stmt) {
2347        if !self.found {
2348            n.visit_children_with(self);
2349        }
2350    }
2351
2352    fn visit_param(&mut self, _: &Param) {}
2353
2354    fn visit_function(&mut self, _: &Function) {}
2355
2356    fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
2357
2358    fn visit_class_member(&mut self, prop: &ClassMember) {
2359        match prop {
2360            ClassMember::ClassProp(ClassProp {
2361                key: PropName::Computed(computed),
2362                ..
2363            })
2364            | ClassMember::Method(ClassMethod {
2365                key: PropName::Computed(computed),
2366                ..
2367            }) => computed.visit_children_with(self),
2368            ClassMember::AutoAccessor(AutoAccessor {
2369                key: Key::Public(PropName::Computed(computed)),
2370                ..
2371            }) => computed.visit_children_with(self),
2372            _ => (),
2373        };
2374    }
2375
2376    fn visit_prop(&mut self, prop: &Prop) {
2377        match prop {
2378            Prop::KeyValue(KeyValueProp {
2379                key: PropName::Computed(computed),
2380                value,
2381                ..
2382            }) => {
2383                computed.visit_children_with(self);
2384                value.visit_with(self);
2385            }
2386            Prop::KeyValue(KeyValueProp { value, .. }) | Prop::Assign(AssignProp { value, .. }) => {
2387                value.visit_with(self);
2388            }
2389            Prop::Getter(GetterProp {
2390                key: PropName::Computed(computed),
2391                ..
2392            })
2393            | Prop::Setter(SetterProp {
2394                key: PropName::Computed(computed),
2395                ..
2396            })
2397            | Prop::Method(MethodProp {
2398                key: PropName::Computed(computed),
2399                ..
2400            }) => computed.visit_children_with(self),
2401            _ => {}
2402        }
2403    }
2404
2405    fn visit_for_of_stmt(&mut self, for_of_stmt: &ForOfStmt) {
2406        if for_of_stmt.is_await {
2407            self.found = true;
2408            return;
2409        }
2410
2411        for_of_stmt.visit_children_with(self);
2412    }
2413
2414    fn visit_await_expr(&mut self, _: &AwaitExpr) {
2415        self.found = true;
2416    }
2417}
2418
2419pub fn contains_top_level_await<V: VisitWith<TopLevelAwait>>(t: &V) -> bool {
2420    let mut finder = TopLevelAwait { found: false };
2421
2422    t.visit_with(&mut finder);
2423
2424    finder.found
2425}
2426
2427/// Variable remapper
2428///
2429/// This visitor modifies [SyntaxContext] while preserving the symbol of
2430/// [Ident]s.
2431pub struct Remapper<'a> {
2432    vars: &'a FxHashMap<Id, SyntaxContext>,
2433}
2434
2435impl<'a> Remapper<'a> {
2436    pub fn new(vars: &'a FxHashMap<Id, SyntaxContext>) -> Self {
2437        Self { vars }
2438    }
2439}
2440
2441impl VisitMut for Remapper<'_> {
2442    noop_visit_mut_type!(fail);
2443
2444    fn visit_mut_ident(&mut self, i: &mut Ident) {
2445        if let Some(new_ctxt) = self.vars.get(&i.to_id()).copied() {
2446            i.ctxt = new_ctxt;
2447        }
2448    }
2449}
2450
2451/// Replacer for [Id] => ]Id]
2452pub struct IdentRenamer<'a> {
2453    map: &'a FxHashMap<Id, Id>,
2454}
2455
2456impl<'a> IdentRenamer<'a> {
2457    pub fn new(map: &'a FxHashMap<Id, Id>) -> Self {
2458        Self { map }
2459    }
2460}
2461
2462impl VisitMut for IdentRenamer<'_> {
2463    noop_visit_mut_type!();
2464
2465    visit_mut_obj_and_computed!();
2466
2467    fn visit_mut_export_named_specifier(&mut self, node: &mut ExportNamedSpecifier) {
2468        if node.exported.is_some() {
2469            node.orig.visit_mut_children_with(self);
2470            return;
2471        }
2472
2473        match &mut node.orig {
2474            ModuleExportName::Ident(orig) => {
2475                if let Some(new) = self.map.get(&orig.to_id()) {
2476                    node.exported = Some(ModuleExportName::Ident(orig.clone()));
2477
2478                    orig.sym = new.0.clone();
2479                    orig.ctxt = new.1;
2480                }
2481            }
2482            ModuleExportName::Str(_) => {}
2483            #[cfg(swc_ast_unknown)]
2484            _ => {}
2485        }
2486    }
2487
2488    fn visit_mut_ident(&mut self, node: &mut Ident) {
2489        if let Some(new) = self.map.get(&node.to_id()) {
2490            node.sym = new.0.clone();
2491            node.ctxt = new.1;
2492        }
2493    }
2494
2495    fn visit_mut_object_pat_prop(&mut self, i: &mut ObjectPatProp) {
2496        match i {
2497            ObjectPatProp::Assign(p) => {
2498                p.value.visit_mut_with(self);
2499
2500                let orig = p.key.clone();
2501                p.key.visit_mut_with(self);
2502
2503                if orig.ctxt == p.key.ctxt && orig.sym == p.key.sym {
2504                    return;
2505                }
2506
2507                match p.value.take() {
2508                    Some(default) => {
2509                        *i = ObjectPatProp::KeyValue(KeyValuePatProp {
2510                            key: PropName::Ident(orig.clone().into()),
2511                            value: AssignPat {
2512                                span: DUMMY_SP,
2513                                left: p.key.clone().into(),
2514                                right: default,
2515                            }
2516                            .into(),
2517                        });
2518                    }
2519                    None => {
2520                        *i = ObjectPatProp::KeyValue(KeyValuePatProp {
2521                            key: PropName::Ident(orig.clone().into()),
2522                            value: p.key.clone().into(),
2523                        });
2524                    }
2525                }
2526            }
2527
2528            _ => {
2529                i.visit_mut_children_with(self);
2530            }
2531        }
2532    }
2533
2534    fn visit_mut_prop(&mut self, node: &mut Prop) {
2535        match node {
2536            Prop::Shorthand(i) => {
2537                let cloned = i.clone();
2538                i.visit_mut_with(self);
2539                if i.sym != cloned.sym || i.ctxt != cloned.ctxt {
2540                    *node = Prop::KeyValue(KeyValueProp {
2541                        key: PropName::Ident(IdentName::new(cloned.sym, cloned.span)),
2542                        value: i.clone().into(),
2543                    });
2544                }
2545            }
2546            _ => {
2547                node.visit_mut_children_with(self);
2548            }
2549        }
2550    }
2551}
2552
2553pub trait QueryRef {
2554    fn query_ref(&self, _ident: &Ident) -> Option<Box<Expr>> {
2555        None
2556    }
2557    fn query_lhs(&self, _ident: &Ident) -> Option<Box<Expr>> {
2558        None
2559    }
2560
2561    /// ref used in JSX
2562    fn query_jsx(&self, _ident: &Ident) -> Option<JSXElementName> {
2563        None
2564    }
2565
2566    /// when `foo()` is replaced with `bar.baz()`,
2567    /// should `bar.baz` be indirect call?
2568    fn should_fix_this(&self, _ident: &Ident) -> bool {
2569        false
2570    }
2571}
2572
2573/// Replace `foo` with `bar` or `bar.baz`
2574pub struct RefRewriter<T>
2575where
2576    T: QueryRef,
2577{
2578    pub query: T,
2579}
2580
2581impl<T> RefRewriter<T>
2582where
2583    T: QueryRef,
2584{
2585    pub fn exit_prop(&mut self, n: &mut Prop) {
2586        if let Prop::Shorthand(shorthand) = n {
2587            if let Some(expr) = self.query.query_ref(shorthand) {
2588                *n = KeyValueProp {
2589                    key: shorthand.take().into(),
2590                    value: expr,
2591                }
2592                .into()
2593            }
2594        }
2595    }
2596
2597    pub fn exit_pat(&mut self, n: &mut Pat) {
2598        if let Pat::Ident(id) = n {
2599            if let Some(expr) = self.query.query_lhs(&id.clone().into()) {
2600                *n = expr.into();
2601            }
2602        }
2603    }
2604
2605    pub fn exit_expr(&mut self, n: &mut Expr) {
2606        if let Expr::Ident(ref_ident) = n {
2607            if let Some(expr) = self.query.query_ref(ref_ident) {
2608                *n = *expr;
2609            }
2610        };
2611    }
2612
2613    pub fn exit_simple_assign_target(&mut self, n: &mut SimpleAssignTarget) {
2614        if let SimpleAssignTarget::Ident(ref_ident) = n {
2615            if let Some(expr) = self.query.query_lhs(&ref_ident.clone().into()) {
2616                *n = expr.try_into().unwrap();
2617            }
2618        };
2619    }
2620
2621    pub fn exit_jsx_element_name(&mut self, n: &mut JSXElementName) {
2622        if let JSXElementName::Ident(ident) = n {
2623            if let Some(expr) = self.query.query_jsx(ident) {
2624                *n = expr;
2625            }
2626        }
2627    }
2628
2629    pub fn exit_jsx_object(&mut self, n: &mut JSXObject) {
2630        if let JSXObject::Ident(ident) = n {
2631            if let Some(expr) = self.query.query_jsx(ident) {
2632                *n = match expr {
2633                    JSXElementName::Ident(ident) => ident.into(),
2634                    JSXElementName::JSXMemberExpr(expr) => Box::new(expr).into(),
2635                    JSXElementName::JSXNamespacedName(..) => unimplemented!(),
2636                    #[cfg(swc_ast_unknown)]
2637                    _ => return,
2638                }
2639            }
2640        }
2641    }
2642
2643    pub fn exit_object_pat_prop(&mut self, n: &mut ObjectPatProp) {
2644        if let ObjectPatProp::Assign(AssignPatProp { key, value, .. }) = n {
2645            if let Some(expr) = self.query.query_lhs(&key.id) {
2646                let value = value
2647                    .take()
2648                    .map(|default_value| {
2649                        let left = expr.clone().try_into().unwrap();
2650                        Box::new(default_value.make_assign_to(op!("="), left))
2651                    })
2652                    .unwrap_or(expr);
2653
2654                *n = ObjectPatProp::KeyValue(KeyValuePatProp {
2655                    key: PropName::Ident(key.take().into()),
2656                    value: value.into(),
2657                });
2658            }
2659        }
2660    }
2661}
2662
2663impl<T> VisitMut for RefRewriter<T>
2664where
2665    T: QueryRef,
2666{
2667    /// replace bar in binding pattern
2668    /// input:
2669    /// ```JavaScript
2670    /// const foo = { bar }
2671    /// ```
2672    /// output:
2673    /// ```JavaScript
2674    /// cobst foo = { bar: baz }
2675    /// ```
2676    fn visit_mut_prop(&mut self, n: &mut Prop) {
2677        n.visit_mut_children_with(self);
2678        self.exit_prop(n);
2679    }
2680
2681    fn visit_mut_var_declarator(&mut self, n: &mut VarDeclarator) {
2682        if !n.name.is_ident() {
2683            n.name.visit_mut_with(self);
2684        }
2685
2686        // skip var declarator name
2687        n.init.visit_mut_with(self);
2688    }
2689
2690    fn visit_mut_pat(&mut self, n: &mut Pat) {
2691        n.visit_mut_children_with(self);
2692        self.exit_pat(n);
2693    }
2694
2695    fn visit_mut_expr(&mut self, n: &mut Expr) {
2696        n.visit_mut_children_with(self);
2697        self.exit_expr(n);
2698    }
2699
2700    fn visit_mut_simple_assign_target(&mut self, n: &mut SimpleAssignTarget) {
2701        n.visit_mut_children_with(self);
2702        self.exit_simple_assign_target(n);
2703    }
2704
2705    fn visit_mut_callee(&mut self, n: &mut Callee) {
2706        match n {
2707            Callee::Expr(e)
2708                if e.as_ident()
2709                    .map(|ident| self.query.should_fix_this(ident))
2710                    .unwrap_or_default() =>
2711            {
2712                e.visit_mut_with(self);
2713
2714                if e.is_member() {
2715                    *n = n.take().into_indirect()
2716                }
2717            }
2718
2719            _ => n.visit_mut_children_with(self),
2720        }
2721    }
2722
2723    fn visit_mut_tagged_tpl(&mut self, n: &mut TaggedTpl) {
2724        let should_fix_this = n
2725            .tag
2726            .as_ident()
2727            .map(|ident| self.query.should_fix_this(ident))
2728            .unwrap_or_default();
2729
2730        n.visit_mut_children_with(self);
2731
2732        if should_fix_this && n.tag.is_member() {
2733            *n = n.take().into_indirect()
2734        }
2735    }
2736
2737    fn visit_mut_jsx_element_name(&mut self, n: &mut JSXElementName) {
2738        n.visit_mut_children_with(self);
2739
2740        self.exit_jsx_element_name(n);
2741    }
2742
2743    fn visit_mut_jsx_object(&mut self, n: &mut JSXObject) {
2744        n.visit_mut_children_with(self);
2745
2746        self.exit_jsx_object(n);
2747    }
2748}
2749
2750fn is_immutable_value(expr: &Expr) -> bool {
2751    // TODO(johnlenz): rename this function.  It is currently being used
2752    // in two disjoint cases:
2753    // 1) We only care about the result of the expression (in which case NOT here
2754    //    should return true)
2755    // 2) We care that expression is a side-effect free and can't be side-effected
2756    //    by other expressions.
2757    // This should only be used to say the value is immutable and
2758    // hasSideEffects and canBeSideEffected should be used for the other case.
2759    match *expr {
2760        Expr::Lit(Lit::Bool(..))
2761        | Expr::Lit(Lit::Str(..))
2762        | Expr::Lit(Lit::Num(..))
2763        | Expr::Lit(Lit::Null(..)) => true,
2764
2765        Expr::Unary(UnaryExpr {
2766            op: op!("!"),
2767            ref arg,
2768            ..
2769        })
2770        | Expr::Unary(UnaryExpr {
2771            op: op!("~"),
2772            ref arg,
2773            ..
2774        })
2775        | Expr::Unary(UnaryExpr {
2776            op: op!("void"),
2777            ref arg,
2778            ..
2779        }) => arg.is_immutable_value(),
2780
2781        Expr::Ident(ref i) => i.sym == "undefined" || i.sym == "Infinity" || i.sym == "NaN",
2782
2783        Expr::Tpl(Tpl { ref exprs, .. }) => exprs.iter().all(|e| e.is_immutable_value()),
2784
2785        _ => false,
2786    }
2787}
2788
2789fn is_number(expr: &Expr) -> bool {
2790    matches!(*expr, Expr::Lit(Lit::Num(..)))
2791}
2792
2793fn is_str(expr: &Expr) -> bool {
2794    match expr {
2795        Expr::Lit(Lit::Str(..)) | Expr::Tpl(_) => true,
2796        Expr::Unary(UnaryExpr {
2797            op: op!("typeof"), ..
2798        }) => true,
2799        Expr::Bin(BinExpr {
2800            op: op!(bin, "+"),
2801            left,
2802            right,
2803            ..
2804        }) => left.is_str() || right.is_str(),
2805        Expr::Assign(AssignExpr {
2806            op: op!("=") | op!("+="),
2807            right,
2808            ..
2809        }) => right.is_str(),
2810        Expr::Seq(s) => s.exprs.last().unwrap().is_str(),
2811        Expr::Cond(CondExpr { cons, alt, .. }) => cons.is_str() && alt.is_str(),
2812        _ => false,
2813    }
2814}
2815
2816fn is_nan(expr: &Expr) -> bool {
2817    match expr {
2818        Expr::Lit(Lit::Num(number)) => number.value.is_nan(),
2819        _ => expr.is_ident_ref_to("NaN"),
2820    }
2821}
2822
2823fn is_undefined(expr: &Expr, ctx: ExprCtx) -> bool {
2824    expr.is_global_ref_to(ctx, "undefined")
2825}
2826
2827fn is_void(expr: &Expr) -> bool {
2828    matches!(
2829        *expr,
2830        Expr::Unary(UnaryExpr {
2831            op: op!("void"),
2832            ..
2833        })
2834    )
2835}
2836
2837fn is_global_ref_to(expr: &Expr, ctx: ExprCtx, id: &str) -> bool {
2838    match expr {
2839        Expr::Ident(i) => i.ctxt == ctx.unresolved_ctxt && &*i.sym == id,
2840        _ => false,
2841    }
2842}
2843
2844fn is_one_of_global_ref_to(expr: &Expr, ctx: ExprCtx, ids: &[&str]) -> bool {
2845    match expr {
2846        Expr::Ident(i) => i.ctxt == ctx.unresolved_ctxt && ids.contains(&&*i.sym),
2847        _ => false,
2848    }
2849}
2850
2851fn as_pure_bool(expr: &Expr, ctx: ExprCtx) -> BoolValue {
2852    match expr.cast_to_bool(ctx) {
2853        (Pure, Known(b)) => Known(b),
2854        _ => Unknown,
2855    }
2856}
2857
2858/// Returns whether a number is truthy according to ECMAScript semantics.
2859#[inline]
2860fn is_truthy_number(value: f64) -> bool {
2861    !matches!(value.classify(), FpCategory::Nan | FpCategory::Zero)
2862}
2863
2864fn cast_to_bool(expr: &Expr, ctx: ExprCtx) -> (Purity, BoolValue) {
2865    let Some(ctx) = ctx.consume_depth() else {
2866        return (MayBeImpure, Unknown);
2867    };
2868
2869    if let Expr::Ident(i) = expr {
2870        if i.ctxt != ctx.unresolved_ctxt {
2871            return (Pure, Unknown);
2872        }
2873
2874        if &*i.sym == "NaN" {
2875            return (Pure, Known(false));
2876        }
2877
2878        if &*i.sym == "undefined" {
2879            return (Pure, Known(false));
2880        }
2881
2882        return if ctx.is_unresolved_ref_safe
2883            || matches!(
2884                &*i.sym,
2885                "Infinity"
2886                    | "Math"
2887                    | "Object"
2888                    | "Array"
2889                    | "Date"
2890                    | "Promise"
2891                    | "Boolean"
2892                    | "Number"
2893                    | "String"
2894                    | "BigInt"
2895                    | "Error"
2896                    | "RegExp"
2897                    | "Function"
2898                    | "document"
2899            ) {
2900            (Pure, Unknown)
2901        } else {
2902            (MayBeImpure, Unknown)
2903        };
2904    }
2905
2906    if matches!(expr, Expr::This(..)) {
2907        return (Pure, Unknown);
2908    }
2909
2910    if matches!(expr, Expr::Fn(..)) {
2911        return (Pure, Known(true));
2912    }
2913
2914    let val = match expr {
2915        Expr::Paren(ref e) => return e.expr.cast_to_bool(ctx),
2916
2917        Expr::Assign(AssignExpr {
2918            ref right,
2919            op: op!("="),
2920            ..
2921        }) => {
2922            let (_, v) = right.cast_to_bool(ctx);
2923            return (MayBeImpure, v);
2924        }
2925
2926        Expr::Unary(UnaryExpr {
2927            op: op!(unary, "-"),
2928            arg,
2929            ..
2930        }) => {
2931            let v = arg.as_pure_number(ctx);
2932            match v {
2933                Known(n) => Known(is_truthy_number(-n)),
2934                Unknown => return (MayBeImpure, Unknown),
2935            }
2936        }
2937
2938        Expr::Unary(UnaryExpr {
2939            op: op!("!"),
2940            ref arg,
2941            ..
2942        }) => {
2943            let (p, v) = arg.cast_to_bool(ctx);
2944            return (p, !v);
2945        }
2946        Expr::Seq(SeqExpr { exprs, .. }) => exprs.last().unwrap().cast_to_bool(ctx).1,
2947
2948        Expr::Bin(BinExpr {
2949            left,
2950            op: op!(bin, "-"),
2951            right,
2952            ..
2953        }) => {
2954            let (lp, ln) = left.cast_to_number(ctx);
2955            let (rp, rn) = right.cast_to_number(ctx);
2956
2957            return (
2958                lp + rp,
2959                match (ln, rn) {
2960                    (Known(ln), Known(rn)) => Known(is_truthy_number(ln - rn)),
2961                    _ => Unknown,
2962                },
2963            );
2964        }
2965
2966        Expr::Bin(BinExpr {
2967            left,
2968            op: op!("/"),
2969            right,
2970            ..
2971        }) => {
2972            let lv = left.as_pure_number(ctx);
2973            let rv = right.as_pure_number(ctx);
2974
2975            match (lv, rv) {
2976                (Known(lv), Known(rv)) => {
2977                    return (Pure, Known(is_truthy_number(lv / rv)));
2978                }
2979                _ => Unknown,
2980            }
2981        }
2982
2983        Expr::Bin(BinExpr {
2984            ref left,
2985            op: op @ op!("&"),
2986            ref right,
2987            ..
2988        })
2989        | Expr::Bin(BinExpr {
2990            ref left,
2991            op: op @ op!("|"),
2992            ref right,
2993            ..
2994        }) => {
2995            if left.get_type(ctx) != Known(BoolType) || right.get_type(ctx) != Known(BoolType) {
2996                return (MayBeImpure, Unknown);
2997            }
2998
2999            // TODO: Ignore purity if value cannot be reached.
3000
3001            let (lp, lv) = left.cast_to_bool(ctx);
3002            let (rp, rv) = right.cast_to_bool(ctx);
3003
3004            let v = if *op == op!("&") {
3005                lv.and(rv)
3006            } else {
3007                lv.or(rv)
3008            };
3009
3010            if lp + rp == Pure {
3011                return (Pure, v);
3012            }
3013
3014            v
3015        }
3016
3017        Expr::Bin(BinExpr {
3018            ref left,
3019            op: op!("||"),
3020            ref right,
3021            ..
3022        }) => {
3023            let (lp, lv) = left.cast_to_bool(ctx);
3024            if let Known(true) = lv {
3025                return (lp, lv);
3026            }
3027
3028            let (rp, rv) = right.cast_to_bool(ctx);
3029            if let Known(true) = rv {
3030                return (lp + rp, rv);
3031            }
3032
3033            Unknown
3034        }
3035
3036        Expr::Bin(BinExpr {
3037            ref left,
3038            op: op!("&&"),
3039            ref right,
3040            ..
3041        }) => {
3042            let (lp, lv) = left.cast_to_bool(ctx);
3043            if let Known(false) = lv {
3044                return (lp, lv);
3045            }
3046
3047            let (rp, rv) = right.cast_to_bool(ctx);
3048            if let Known(false) = rv {
3049                return (lp + rp, rv);
3050            }
3051
3052            Unknown
3053        }
3054
3055        Expr::Bin(BinExpr {
3056            left,
3057            op: op!(bin, "+"),
3058            right,
3059            ..
3060        }) => {
3061            match &**left {
3062                Expr::Lit(Lit::Str(s)) if !s.value.is_empty() => return (MayBeImpure, Known(true)),
3063                _ => {}
3064            }
3065
3066            match &**right {
3067                Expr::Lit(Lit::Str(s)) if !s.value.is_empty() => return (MayBeImpure, Known(true)),
3068                _ => {}
3069            }
3070
3071            Unknown
3072        }
3073
3074        Expr::Fn(..) | Expr::Class(..) | Expr::New(..) | Expr::Array(..) | Expr::Object(..) => {
3075            Known(true)
3076        }
3077
3078        Expr::Unary(UnaryExpr {
3079            op: op!("void"), ..
3080        }) => Known(false),
3081
3082        Expr::Lit(ref lit) => {
3083            return (
3084                Pure,
3085                Known(match *lit {
3086                    Lit::Num(Number { value: n, .. }) => is_truthy_number(n),
3087                    Lit::BigInt(ref v) => v
3088                        .value
3089                        .to_string()
3090                        .contains(|c: char| matches!(c, '1'..='9')),
3091                    Lit::Bool(b) => b.value,
3092                    Lit::Str(Str { ref value, .. }) => !value.is_empty(),
3093                    Lit::Null(..) => false,
3094                    Lit::Regex(..) => true,
3095                    Lit::JSXText(..) => unreachable!("as_bool() for JSXText"),
3096                    #[cfg(swc_ast_unknown)]
3097                    _ => return (Pure, Unknown),
3098                }),
3099            );
3100        }
3101
3102        //TODO?
3103        _ => Unknown,
3104    };
3105
3106    if expr.may_have_side_effects(ctx) {
3107        (MayBeImpure, val)
3108    } else {
3109        (Pure, val)
3110    }
3111}
3112
3113fn cast_to_number(expr: &Expr, ctx: ExprCtx) -> (Purity, Value<f64>) {
3114    let Some(ctx) = ctx.consume_depth() else {
3115        return (MayBeImpure, Unknown);
3116    };
3117
3118    let v = match expr {
3119        Expr::Lit(l) => match l {
3120            Lit::Bool(Bool { value: true, .. }) => 1.0,
3121            Lit::Bool(Bool { value: false, .. }) | Lit::Null(..) => 0.0,
3122            Lit::Num(Number { value: n, .. }) => *n,
3123            Lit::Str(Str { value, .. }) => {
3124                if let Some(value) = value.as_str() {
3125                    return (Pure, num_from_str(value));
3126                }
3127                return (Pure, Unknown);
3128            }
3129            _ => return (Pure, Unknown),
3130        },
3131        Expr::Array(..) => {
3132            let Known(s) = expr.as_pure_string(ctx) else {
3133                return (Pure, Unknown);
3134            };
3135
3136            return (Pure, num_from_str(&s));
3137        }
3138        Expr::Ident(Ident { sym, ctxt, .. }) => match &**sym {
3139            "undefined" | "NaN" if *ctxt == ctx.unresolved_ctxt => f64::NAN,
3140            "Infinity" if *ctxt == ctx.unresolved_ctxt => f64::INFINITY,
3141            _ => return (Pure, Unknown),
3142        },
3143        Expr::Unary(UnaryExpr {
3144            op: op!(unary, "-"),
3145            arg,
3146            ..
3147        }) => match arg.cast_to_number(ctx) {
3148            (Pure, Known(v)) => -v,
3149            _ => return (MayBeImpure, Unknown),
3150        },
3151        Expr::Unary(UnaryExpr {
3152            op: op!("!"),
3153            ref arg,
3154            ..
3155        }) => match arg.cast_to_bool(ctx) {
3156            (Pure, Known(v)) => {
3157                if v {
3158                    0.0
3159                } else {
3160                    1.0
3161                }
3162            }
3163            _ => return (MayBeImpure, Unknown),
3164        },
3165        Expr::Unary(UnaryExpr {
3166            op: op!("void"),
3167            ref arg,
3168            ..
3169        }) => {
3170            if arg.may_have_side_effects(ctx) {
3171                return (MayBeImpure, Known(f64::NAN));
3172            } else {
3173                f64::NAN
3174            }
3175        }
3176
3177        Expr::Tpl(..) => {
3178            return (
3179                Pure,
3180                num_from_str(&match expr.as_pure_string(ctx) {
3181                    Known(v) => v,
3182                    Unknown => return (MayBeImpure, Unknown),
3183                }),
3184            );
3185        }
3186
3187        Expr::Seq(seq) => {
3188            if let Some(last) = seq.exprs.last() {
3189                let (_, v) = last.cast_to_number(ctx);
3190
3191                // TODO: Purity
3192                return (MayBeImpure, v);
3193            }
3194
3195            return (MayBeImpure, Unknown);
3196        }
3197
3198        _ => return (MayBeImpure, Unknown),
3199    };
3200
3201    (Purity::Pure, Known(v))
3202}
3203
3204fn as_pure_number(expr: &Expr, ctx: ExprCtx) -> Value<f64> {
3205    let (purity, v) = expr.cast_to_number(ctx);
3206    if !purity.is_pure() {
3207        return Unknown;
3208    }
3209
3210    v
3211}
3212
3213fn as_pure_string(expr: &Expr, ctx: ExprCtx) -> Value<Cow<'_, str>> {
3214    match as_pure_wtf8(expr, ctx) {
3215        Known(v) => match v {
3216            Cow::Borrowed(v) => {
3217                if let Some(v) = v.as_str() {
3218                    Known(Cow::Borrowed(v))
3219                } else {
3220                    Unknown
3221                }
3222            }
3223            Cow::Owned(v) => {
3224                if let Ok(v) = v.into_string() {
3225                    Known(Cow::Owned(v))
3226                } else {
3227                    Unknown
3228                }
3229            }
3230        },
3231        Unknown => Unknown,
3232    }
3233}
3234
3235fn as_pure_wtf8(expr: &Expr, ctx: ExprCtx) -> Value<Cow<'_, Wtf8>> {
3236    let Some(ctx) = ctx.consume_depth() else {
3237        return Unknown;
3238    };
3239
3240    match *expr {
3241        Expr::Lit(ref l) => match *l {
3242            Lit::Str(Str { ref value, .. }) => Known(Cow::Borrowed(&**value)),
3243            Lit::Num(ref n) => {
3244                if n.value == -0.0 {
3245                    return Known(Cow::Borrowed("0".into()));
3246                }
3247
3248                Known(Cow::Owned(Wtf8Buf::from_string(n.value.to_js_string())))
3249            }
3250            Lit::Bool(Bool { value: true, .. }) => Known(Cow::Borrowed("true".into())),
3251            Lit::Bool(Bool { value: false, .. }) => Known(Cow::Borrowed("false".into())),
3252            Lit::Null(..) => Known(Cow::Borrowed("null".into())),
3253            _ => Unknown,
3254        },
3255        Expr::Tpl(_) => {
3256            Value::Unknown
3257            // TODO:
3258            // Only convert a template literal if all its expressions
3259            // can be converted.
3260            // unimplemented!("TplLit. as_string()")
3261        }
3262        Expr::Ident(Ident { ref sym, ctxt, .. }) => match &**sym {
3263            "undefined" | "Infinity" | "NaN" if ctxt == ctx.unresolved_ctxt => {
3264                Known(Cow::Borrowed(Wtf8::from_str(sym)))
3265            }
3266            _ => Unknown,
3267        },
3268        Expr::Unary(UnaryExpr {
3269            op: op!("void"), ..
3270        }) => Known(Cow::Borrowed("undefined".into())),
3271        Expr::Unary(UnaryExpr {
3272            op: op!("!"),
3273            ref arg,
3274            ..
3275        }) => Known(Cow::Borrowed(match arg.as_pure_bool(ctx) {
3276            Known(v) => {
3277                if v {
3278                    "false".into()
3279                } else {
3280                    "true".into()
3281                }
3282            }
3283            Unknown => return Value::Unknown,
3284        })),
3285        Expr::Array(ArrayLit { ref elems, .. }) => {
3286            let mut buf = Wtf8Buf::new();
3287            let len = elems.len();
3288            // null, undefined is "" in array literal.
3289            for (idx, elem) in elems.iter().enumerate() {
3290                let last = idx == len - 1;
3291                let e = match *elem {
3292                    Some(ref elem) => {
3293                        let ExprOrSpread { ref expr, .. } = *elem;
3294                        match &**expr {
3295                            Expr::Lit(Lit::Null(..)) => Cow::Borrowed("".into()),
3296                            Expr::Unary(UnaryExpr {
3297                                op: op!("void"),
3298                                arg,
3299                                ..
3300                            }) => {
3301                                if arg.may_have_side_effects(ctx) {
3302                                    return Value::Unknown;
3303                                }
3304                                Cow::Borrowed("".into())
3305                            }
3306                            Expr::Ident(Ident { sym: undefined, .. })
3307                                if &**undefined == "undefined" =>
3308                            {
3309                                Cow::Borrowed("".into())
3310                            }
3311                            _ => match expr.as_pure_wtf8(ctx) {
3312                                Known(v) => v,
3313                                Unknown => return Value::Unknown,
3314                            },
3315                        }
3316                    }
3317                    None => Cow::Borrowed("".into()),
3318                };
3319                buf.push_wtf8(&e);
3320
3321                if !last {
3322                    buf.push_char(',');
3323                }
3324            }
3325            Known(buf.into())
3326        }
3327        _ => Unknown,
3328    }
3329}
3330
3331fn get_type(expr: &Expr, ctx: ExprCtx) -> Value<Type> {
3332    let Some(ctx) = ctx.consume_depth() else {
3333        return Unknown;
3334    };
3335
3336    match expr {
3337        Expr::Assign(AssignExpr {
3338            ref right,
3339            op: op!("="),
3340            ..
3341        }) => right.get_type(ctx),
3342
3343        Expr::Member(MemberExpr {
3344            obj,
3345            prop: MemberProp::Ident(IdentName { sym: length, .. }),
3346            ..
3347        }) if &**length == "length" => match &**obj {
3348            Expr::Array(ArrayLit { .. }) | Expr::Lit(Lit::Str(..)) => Known(Type::Num),
3349            Expr::Ident(Ident { sym: arguments, .. }) if &**arguments == "arguments" => {
3350                Known(Type::Num)
3351            }
3352            _ => Unknown,
3353        },
3354
3355        Expr::Seq(SeqExpr { ref exprs, .. }) => exprs
3356            .last()
3357            .expect("sequence expression should not be empty")
3358            .get_type(ctx),
3359
3360        Expr::Bin(BinExpr {
3361            ref left,
3362            op: op!("&&"),
3363            ref right,
3364            ..
3365        })
3366        | Expr::Bin(BinExpr {
3367            ref left,
3368            op: op!("||"),
3369            ref right,
3370            ..
3371        })
3372        | Expr::Cond(CondExpr {
3373            cons: ref left,
3374            alt: ref right,
3375            ..
3376        }) => and(left.get_type(ctx), right.get_type(ctx)),
3377
3378        Expr::Bin(BinExpr {
3379            ref left,
3380            op: op!(bin, "+"),
3381            ref right,
3382            ..
3383        }) => {
3384            let rt = right.get_type(ctx);
3385            if rt == Known(StringType) {
3386                return Known(StringType);
3387            }
3388
3389            let lt = left.get_type(ctx);
3390            if lt == Known(StringType) {
3391                return Known(StringType);
3392            }
3393
3394            // There are some pretty weird cases for object types:
3395            //   {} + [] === "0"
3396            //   [] + {} ==== "[object Object]"
3397            if lt == Known(ObjectType) || rt == Known(ObjectType) {
3398                return Unknown;
3399            }
3400
3401            if !may_be_str(lt) && !may_be_str(rt) {
3402                // ADD used with compilations of null, boolean and number always
3403                // result in numbers.
3404                return Known(NumberType);
3405            }
3406
3407            // There are some pretty weird cases for object types:
3408            //   {} + [] === "0"
3409            //   [] + {} ==== "[object Object]"
3410            Unknown
3411        }
3412
3413        Expr::Assign(AssignExpr {
3414            op: op!("+="),
3415            ref right,
3416            ..
3417        }) => {
3418            if right.get_type(ctx) == Known(StringType) {
3419                return Known(StringType);
3420            }
3421            Unknown
3422        }
3423
3424        Expr::Ident(Ident { ref sym, .. }) => Known(match &**sym {
3425            "undefined" => UndefinedType,
3426            "NaN" | "Infinity" => NumberType,
3427            _ => return Unknown,
3428        }),
3429
3430        Expr::Lit(Lit::Num(..))
3431        | Expr::Assign(AssignExpr { op: op!("&="), .. })
3432        | Expr::Assign(AssignExpr { op: op!("^="), .. })
3433        | Expr::Assign(AssignExpr { op: op!("|="), .. })
3434        | Expr::Assign(AssignExpr { op: op!("<<="), .. })
3435        | Expr::Assign(AssignExpr { op: op!(">>="), .. })
3436        | Expr::Assign(AssignExpr {
3437            op: op!(">>>="), ..
3438        })
3439        | Expr::Assign(AssignExpr { op: op!("-="), .. })
3440        | Expr::Assign(AssignExpr { op: op!("*="), .. })
3441        | Expr::Assign(AssignExpr { op: op!("**="), .. })
3442        | Expr::Assign(AssignExpr { op: op!("/="), .. })
3443        | Expr::Assign(AssignExpr { op: op!("%="), .. })
3444        | Expr::Unary(UnaryExpr { op: op!("~"), .. })
3445        | Expr::Bin(BinExpr { op: op!("|"), .. })
3446        | Expr::Bin(BinExpr { op: op!("^"), .. })
3447        | Expr::Bin(BinExpr { op: op!("&"), .. })
3448        | Expr::Bin(BinExpr { op: op!("<<"), .. })
3449        | Expr::Bin(BinExpr { op: op!(">>"), .. })
3450        | Expr::Bin(BinExpr { op: op!(">>>"), .. })
3451        | Expr::Bin(BinExpr {
3452            op: op!(bin, "-"), ..
3453        })
3454        | Expr::Bin(BinExpr { op: op!("*"), .. })
3455        | Expr::Bin(BinExpr { op: op!("%"), .. })
3456        | Expr::Bin(BinExpr { op: op!("/"), .. })
3457        | Expr::Bin(BinExpr { op: op!("**"), .. })
3458        | Expr::Update(UpdateExpr { op: op!("++"), .. })
3459        | Expr::Update(UpdateExpr { op: op!("--"), .. })
3460        | Expr::Unary(UnaryExpr {
3461            op: op!(unary, "+"),
3462            ..
3463        })
3464        | Expr::Unary(UnaryExpr {
3465            op: op!(unary, "-"),
3466            ..
3467        }) => Known(NumberType),
3468
3469        // Primitives
3470        Expr::Lit(Lit::Bool(..))
3471        | Expr::Bin(BinExpr { op: op!("=="), .. })
3472        | Expr::Bin(BinExpr { op: op!("!="), .. })
3473        | Expr::Bin(BinExpr { op: op!("==="), .. })
3474        | Expr::Bin(BinExpr { op: op!("!=="), .. })
3475        | Expr::Bin(BinExpr { op: op!("<"), .. })
3476        | Expr::Bin(BinExpr { op: op!("<="), .. })
3477        | Expr::Bin(BinExpr { op: op!(">"), .. })
3478        | Expr::Bin(BinExpr { op: op!(">="), .. })
3479        | Expr::Bin(BinExpr { op: op!("in"), .. })
3480        | Expr::Bin(BinExpr {
3481            op: op!("instanceof"),
3482            ..
3483        })
3484        | Expr::Unary(UnaryExpr { op: op!("!"), .. })
3485        | Expr::Unary(UnaryExpr {
3486            op: op!("delete"), ..
3487        }) => Known(BoolType),
3488
3489        Expr::Unary(UnaryExpr {
3490            op: op!("typeof"), ..
3491        })
3492        | Expr::Lit(Lit::Str { .. })
3493        | Expr::Tpl(..) => Known(StringType),
3494
3495        Expr::Lit(Lit::Null(..)) => Known(NullType),
3496
3497        Expr::Unary(UnaryExpr {
3498            op: op!("void"), ..
3499        }) => Known(UndefinedType),
3500
3501        Expr::Fn(..)
3502        | Expr::New(NewExpr { .. })
3503        | Expr::Array(ArrayLit { .. })
3504        | Expr::Object(ObjectLit { .. })
3505        | Expr::Lit(Lit::Regex(..)) => Known(ObjectType),
3506
3507        _ => Unknown,
3508    }
3509}
3510
3511#[inline(always)]
3512fn is_pure_str_method(method: &str) -> bool {
3513    matches!(
3514        method,
3515        "charAt"
3516            | "charCodeAt"
3517            | "concat"
3518            | "endsWith"
3519            | "includes"
3520            | "indexOf"
3521            | "lastIndexOf"
3522            | "localeCompare"
3523            | "slice"
3524            | "split"
3525            | "startsWith"
3526            | "substr"
3527            | "substring"
3528            | "toLocaleLowerCase"
3529            | "toLocaleUpperCase"
3530            | "toLowerCase"
3531            | "toString"
3532            | "toUpperCase"
3533            | "trim"
3534            | "trimEnd"
3535            | "trimStart"
3536    )
3537}
3538
3539#[inline(always)]
3540fn is_pure_member_callee(obj: &Expr, prop: &MemberProp, ctx: ExprCtx) -> bool {
3541    let MemberProp::Ident(prop) = prop else {
3542        return false;
3543    };
3544
3545    match obj {
3546        Expr::Ident(Ident {
3547            ctxt, sym: math, ..
3548        }) => {
3549            &**math == "Math" && (*ctxt == ctx.unresolved_ctxt || *ctxt == SyntaxContext::empty())
3550        }
3551
3552        Expr::Lit(Lit::Str(..)) => is_pure_str_method(&prop.sym),
3553        Expr::Tpl(Tpl { exprs, .. }) if exprs.is_empty() => is_pure_str_method(&prop.sym),
3554
3555        _ => false,
3556    }
3557}
3558
3559fn is_pure_callee(expr: &Expr, ctx: ExprCtx) -> bool {
3560    match expr {
3561        Expr::Ident(i) => i.ctxt == ctx.unresolved_ctxt && &*i.sym == "Date",
3562
3563        Expr::Member(MemberExpr { obj, prop, .. }) => is_pure_member_callee(obj, prop, ctx),
3564
3565        Expr::Fn(FnExpr { function: f, .. })
3566            if f.params.iter().all(|p| p.pat.is_ident())
3567                && f.body.is_some()
3568                && f.body.as_ref().unwrap().stmts.is_empty() =>
3569        {
3570            true
3571        }
3572
3573        _ => false,
3574    }
3575}
3576
3577/// Check if a class expression is pure when used with `new`.
3578/// This is different from `is_pure_callee` because:
3579/// - Calling a class as a function (`(class {})()`) throws TypeError
3580/// - But `new (class {})()` can be pure if the class has no side effects
3581fn is_pure_new_callee(expr: &Expr, ctx: ExprCtx) -> bool {
3582    match expr {
3583        // Only an empty non-async, non-generator function expression is pure for `new`.
3584        // Async and generator functions are not constructible, so `new` throws.
3585        Expr::Fn(FnExpr { function: f, .. })
3586            if !f.is_async
3587                && !f.is_generator
3588                && f.params.iter().all(|p| p.pat.is_ident())
3589                && f.body.is_some()
3590                && f.body.as_ref().unwrap().stmts.is_empty() =>
3591        {
3592            true
3593        }
3594
3595        // A class expression is pure for `new` if:
3596        // 1. It has no side effects from definition (computed keys, property initializers, static
3597        //    blocks)
3598        // 2. It has no super class (calling super() may have side effects)
3599        // 3. Either has no constructor, or constructor body is empty
3600        // 4. Has no instance properties (they are initialized in the constructor)
3601        Expr::Class(c) => {
3602            let class = &c.class;
3603
3604            // Check for super class - calling super() may have side effects
3605            if class.super_class.is_some() {
3606                return false;
3607            }
3608
3609            // Check for side effects from class definition
3610            if class_has_side_effect(ctx, class) {
3611                return false;
3612            }
3613
3614            // Check for instance properties (non-static) - they run during construction
3615            for member in &class.body {
3616                match member {
3617                    ClassMember::ClassProp(p) if !p.is_static => return false,
3618                    ClassMember::PrivateProp(p) if !p.is_static => return false,
3619                    // Auto-accessor initializers run when the instance is constructed,
3620                    // just like instance field initializers.
3621                    ClassMember::AutoAccessor(p) if !p.is_static => return false,
3622                    _ => {}
3623                }
3624            }
3625
3626            // Check constructor - must be empty or not present
3627            for member in &class.body {
3628                if let ClassMember::Constructor(ctor) = member {
3629                    if !ctor
3630                        .params
3631                        .iter()
3632                        .all(|param| param.as_param().is_some_and(|param| param.pat.is_ident()))
3633                    {
3634                        return false;
3635                    }
3636
3637                    if let Some(body) = &ctor.body {
3638                        if !body.stmts.is_empty() {
3639                            return false;
3640                        }
3641                    }
3642                }
3643            }
3644
3645            true
3646        }
3647
3648        _ => false,
3649    }
3650}
3651
3652fn may_have_side_effects(expr: &Expr, ctx: ExprCtx) -> bool {
3653    let Some(ctx) = ctx.consume_depth() else {
3654        return true;
3655    };
3656
3657    match expr {
3658        Expr::Ident(i) => {
3659            if ctx.is_unresolved_ref_safe {
3660                return false;
3661            }
3662
3663            if i.ctxt == ctx.unresolved_ctxt {
3664                !matches!(
3665                    &*i.sym,
3666                    "Infinity"
3667                        | "NaN"
3668                        | "Math"
3669                        | "undefined"
3670                        | "Object"
3671                        | "Array"
3672                        | "Date"
3673                        | "Promise"
3674                        | "Boolean"
3675                        | "Number"
3676                        | "String"
3677                        | "BigInt"
3678                        | "Error"
3679                        | "RegExp"
3680                        | "Function"
3681                        | "document"
3682                )
3683            } else {
3684                false
3685            }
3686        }
3687
3688        Expr::Lit(..) | Expr::This(..) | Expr::PrivateName(..) | Expr::TsConstAssertion(..) => {
3689            false
3690        }
3691
3692        Expr::Paren(e) => e.expr.may_have_side_effects(ctx),
3693
3694        // Function expression does not have any side effect if it's not used.
3695        Expr::Fn(..) | Expr::Arrow(..) => false,
3696
3697        // It's annoying to pass in_strict
3698        Expr::Class(c) => class_has_side_effect(ctx, &c.class),
3699        Expr::Array(ArrayLit { elems, .. }) => elems
3700            .iter()
3701            .filter_map(|e| e.as_ref())
3702            .any(|e| e.spread.is_some() || e.expr.may_have_side_effects(ctx)),
3703        Expr::Unary(UnaryExpr {
3704            op: op!("delete"), ..
3705        }) => true,
3706        Expr::Unary(UnaryExpr { arg, .. }) => arg.may_have_side_effects(ctx),
3707        Expr::Bin(BinExpr { left, right, .. }) => {
3708            left.may_have_side_effects(ctx) || right.may_have_side_effects(ctx)
3709        }
3710
3711        Expr::Member(MemberExpr { obj, prop, .. }) if is_pure_member_callee(obj, prop, ctx) => {
3712            false
3713        }
3714
3715        Expr::Member(MemberExpr { obj, prop, .. })
3716            if obj.is_object() || obj.is_fn_expr() || obj.is_arrow() || obj.is_class() =>
3717        {
3718            if obj.may_have_side_effects(ctx) {
3719                return true;
3720            }
3721            match &**obj {
3722                Expr::Class(c) => {
3723                    let is_static_accessor = |member: &ClassMember| {
3724                        if let ClassMember::Method(ClassMethod {
3725                            kind: MethodKind::Getter | MethodKind::Setter,
3726                            is_static: true,
3727                            ..
3728                        }) = member
3729                        {
3730                            true
3731                        } else {
3732                            false
3733                        }
3734                    };
3735                    if c.class.body.iter().any(is_static_accessor) {
3736                        return true;
3737                    }
3738                }
3739                Expr::Object(obj) => {
3740                    let can_have_side_effect = |prop: &PropOrSpread| match prop {
3741                        PropOrSpread::Spread(_) => true,
3742                        PropOrSpread::Prop(prop) => match prop.as_ref() {
3743                            Prop::Getter(_) | Prop::Setter(_) | Prop::Method(_) => true,
3744                            Prop::Shorthand(Ident { sym, .. })
3745                            | Prop::KeyValue(KeyValueProp {
3746                                key: PropName::Ident(IdentName { sym, .. }),
3747                                ..
3748                            }) => &**sym == "__proto__",
3749                            Prop::KeyValue(KeyValueProp {
3750                                key: PropName::Str(Str { value: sym, .. }),
3751                                ..
3752                            }) => &**sym == "__proto__",
3753                            Prop::KeyValue(KeyValueProp {
3754                                key: PropName::Computed(_),
3755                                ..
3756                            }) => true,
3757                            _ => false,
3758                        },
3759                        #[cfg(swc_ast_unknown)]
3760                        _ => true,
3761                    };
3762                    if obj.props.iter().any(can_have_side_effect) {
3763                        return true;
3764                    }
3765                }
3766                _ => {}
3767            };
3768
3769            match prop {
3770                MemberProp::Computed(c) => c.expr.may_have_side_effects(ctx),
3771                MemberProp::Ident(_) | MemberProp::PrivateName(_) => false,
3772                #[cfg(swc_ast_unknown)]
3773                _ => true,
3774            }
3775        }
3776
3777        //TODO
3778        Expr::Tpl(_) => true,
3779        Expr::TaggedTpl(_) => true,
3780        Expr::MetaProp(_) => true,
3781
3782        Expr::Await(_)
3783        | Expr::Yield(_)
3784        | Expr::Member(_)
3785        | Expr::SuperProp(_)
3786        | Expr::Update(_)
3787        | Expr::Assign(_) => true,
3788
3789        Expr::OptChain(OptChainExpr { base, .. }) if matches!(&**base, OptChainBase::Member(_)) => {
3790            true
3791        }
3792
3793        // A new expression is side-effect free if callee is pure for `new` and args are
3794        // side-effect free. Note: we use is_pure_new_callee instead of is_pure_callee because
3795        // class expressions are valid for `new` but calling them throws TypeError.
3796        Expr::New(NewExpr { callee, args, .. }) if is_pure_new_callee(callee, ctx) => args
3797            .iter()
3798            .flatten()
3799            .any(|arg| arg.expr.may_have_side_effects(ctx)),
3800
3801        Expr::New(_) => true,
3802
3803        Expr::Call(CallExpr {
3804            callee: Callee::Expr(callee),
3805            ref args,
3806            ..
3807        }) if callee.is_pure_callee(ctx) => {
3808            args.iter().any(|arg| arg.expr.may_have_side_effects(ctx))
3809        }
3810        Expr::OptChain(OptChainExpr { base, .. })
3811            if matches!(&**base, OptChainBase::Call(..))
3812                && OptChainBase::as_call(base)
3813                    .unwrap()
3814                    .callee
3815                    .is_pure_callee(ctx) =>
3816        {
3817            OptChainBase::as_call(base)
3818                .unwrap()
3819                .args
3820                .iter()
3821                .any(|arg| arg.expr.may_have_side_effects(ctx))
3822        }
3823
3824        Expr::Call(_) | Expr::OptChain(..) => true,
3825
3826        Expr::Seq(SeqExpr { exprs, .. }) => exprs.iter().any(|e| e.may_have_side_effects(ctx)),
3827
3828        Expr::Cond(CondExpr {
3829            test, cons, alt, ..
3830        }) => {
3831            test.may_have_side_effects(ctx)
3832                || cons.may_have_side_effects(ctx)
3833                || alt.may_have_side_effects(ctx)
3834        }
3835
3836        Expr::Object(ObjectLit { props, .. }) => props.iter().any(|node| match node {
3837            PropOrSpread::Prop(node) => match &**node {
3838                Prop::Shorthand(..) => false,
3839                Prop::KeyValue(KeyValueProp { key, value }) => {
3840                    let k = match key {
3841                        PropName::Computed(e) => e.expr.may_have_side_effects(ctx),
3842                        _ => false,
3843                    };
3844
3845                    k || value.may_have_side_effects(ctx)
3846                }
3847                Prop::Getter(GetterProp { key, .. })
3848                | Prop::Setter(SetterProp { key, .. })
3849                | Prop::Method(MethodProp { key, .. }) => match key {
3850                    PropName::Computed(e) => e.expr.may_have_side_effects(ctx),
3851                    _ => false,
3852                },
3853                Prop::Assign(_) => true,
3854                #[cfg(swc_ast_unknown)]
3855                _ => true,
3856            },
3857            // may trigger getter
3858            PropOrSpread::Spread(_) => true,
3859            #[cfg(swc_ast_unknown)]
3860            _ => true,
3861        }),
3862
3863        Expr::JSXMember(..)
3864        | Expr::JSXNamespacedName(..)
3865        | Expr::JSXEmpty(..)
3866        | Expr::JSXElement(..)
3867        | Expr::JSXFragment(..) => true,
3868
3869        Expr::TsAs(TsAsExpr { ref expr, .. })
3870        | Expr::TsNonNull(TsNonNullExpr { ref expr, .. })
3871        | Expr::TsTypeAssertion(TsTypeAssertion { ref expr, .. })
3872        | Expr::TsInstantiation(TsInstantiation { ref expr, .. })
3873        | Expr::TsSatisfies(TsSatisfiesExpr { ref expr, .. }) => expr.may_have_side_effects(ctx),
3874
3875        Expr::Invalid(..) => true,
3876        #[cfg(swc_ast_unknown)]
3877        _ => true,
3878    }
3879}
3880
3881/// Creates a PropName for a shorthand property, handling the special case of
3882/// `__proto__`. When the property name is `__proto__`, it must be converted to
3883/// a computed property to preserve JavaScript semantics.
3884pub fn prop_name_from_ident(ident: Ident) -> PropName {
3885    if ident.sym == "__proto__" {
3886        PropName::Computed(ComputedPropName {
3887            span: ident.span,
3888            expr: Box::new(Expr::Lit(Lit::Str(Str {
3889                span: ident.span,
3890                value: ident.sym.clone().into(),
3891                raw: None,
3892            }))),
3893        })
3894    } else {
3895        ident.into()
3896    }
3897}
3898
3899pub fn prop_name_from_str(span: Span, s: &str) -> PropName {
3900    if s == "__proto__" {
3901        PropName::Computed(ComputedPropName {
3902            span: DUMMY_SP,
3903            expr: Box::new(Expr::Lit(Lit::Str(Str {
3904                span,
3905                value: s.into(),
3906                raw: None,
3907            }))),
3908        })
3909    } else if is_valid_prop_ident(s) {
3910        PropName::Ident(IdentName {
3911            span,
3912            sym: s.into(),
3913        })
3914    } else {
3915        PropName::Str(quote_str!(span, s))
3916    }
3917}
3918
3919#[cfg(test)]
3920mod tests {
3921    use swc_common::{input::StringInput, BytePos, DUMMY_SP};
3922    use swc_ecma_parser::{Parser, Syntax};
3923
3924    use super::*;
3925
3926    #[test]
3927    fn number_literal_nan_is_recognized() {
3928        let expr = Expr::Lit(Lit::Num(Number {
3929            span: DUMMY_SP,
3930            value: f64::NAN,
3931            raw: None,
3932        }));
3933
3934        assert!(expr.is_nan());
3935    }
3936
3937    #[test]
3938    fn num_from_str_small_radix_values_are_exact() {
3939        assert_eq!(num_from_str("0xff"), Known(255.0));
3940        assert_eq!(num_from_str("0o17"), Known(15.0));
3941        assert_eq!(num_from_str("0b101"), Known(5.0));
3942    }
3943
3944    #[test]
3945    fn num_from_str_overflowing_radix_values_do_not_become_nan() {
3946        // 2^64, one hex digit past what `u64` can hold.
3947        assert_eq!(
3948            num_from_str("0x10000000000000000"),
3949            Known(18446744073709552000.0)
3950        );
3951        assert_eq!(
3952            num_from_str("0o2000000000000000000000"),
3953            Known(18446744073709552000.0)
3954        );
3955        assert_eq!(
3956            num_from_str("0b10000000000000000000000000000000000000000000000000000000000000000"),
3957            Known(18446744073709552000.0)
3958        );
3959    }
3960
3961    #[test]
3962    fn num_from_str_overflowing_radix_values_round_correctly() {
3963        // A leading digit worth only 1 bit (`1`) shrinks the margin between the
3964        // 53-bit mantissa and the digits sampled for rounding; this case only
3965        // rounds correctly if that margin is computed from the leading digit's
3966        // actual bit length rather than assumed to always be a full digit wide.
3967        // Reference: `u128::from_str_radix("123456789abcdef0123", 16) as f64`.
3968        assert_eq!(
3969            num_from_str("0x123456789abcdef0123"),
3970            Known(5.373003642731685e21)
3971        );
3972
3973        // Rounding after every digit (instead of once, on the exact value)
3974        // produces a result one ULP away from this.
3975        // Reference: `u128::from_str_radix("32a884c83f6f91624", 16) as f64`.
3976        assert_eq!(
3977            num_from_str("0x32a884c83f6f91624"),
3978            Known(5.840501589722223e19)
3979        );
3980    }
3981
3982    #[test]
3983    fn num_from_str_invalid_radix_digit_is_nan() {
3984        match num_from_str("0xg1") {
3985            Known(v) => assert!(v.is_nan()),
3986            Unknown => panic!("expected a known (NaN) value"),
3987        }
3988    }
3989
3990    #[test]
3991    fn num_from_str_overflow_with_later_invalid_digit_is_nan() {
3992        // The leading `f`s alone already overflow a `u64`, so
3993        // `u64::from_str_radix` can report the failure before ever examining
3994        // the trailing `g` — this must still come out as `NaN`, not as if
3995        // the invalid digit weren't there.
3996        match num_from_str("0xfffffffffffffffffg") {
3997            Known(v) => assert!(v.is_nan()),
3998            Unknown => panic!("expected a known (NaN) value"),
3999        }
4000    }
4001
4002    #[test]
4003    fn saturating_exponent_clamps_instead_of_wrapping() {
4004        // A plain, comfortably in-range sum is passed through unchanged.
4005        assert_eq!(saturating_exponent(100, 4), 104);
4006
4007        // Reachable only via a radix literal of hundreds of millions of
4008        // digits, but must not wrap into a negative exponent: an unchecked
4009        // `as i32` cast turns `i32::MAX as usize + 1` into `i32::MIN`.
4010        assert_eq!(saturating_exponent(i32::MAX as usize + 1, 0), i32::MAX);
4011        assert_eq!(saturating_exponent(i32::MAX as usize, 1), i32::MAX);
4012    }
4013
4014    #[test]
4015    fn test_collect_decls() {
4016        run_collect_decls(
4017            "const { a, b = 1, inner: { c }, ...d } = {};",
4018            &["a", "b", "c", "d"],
4019        );
4020        run_collect_decls("const [ a, b = 1, [c], ...d ] = [];", &["a", "b", "c", "d"]);
4021    }
4022
4023    #[test]
4024    fn test_collect_export_default_expr() {
4025        run_collect_decls("export default function foo(){}", &["foo"]);
4026        run_collect_decls("export default class Foo{}", &["Foo"]);
4027    }
4028
4029    fn run_collect_decls(text: &str, expected_names: &[&str]) {
4030        let module = parse_module(text);
4031        let decls: FxHashSet<Id> = collect_decls(&module);
4032        let mut names = decls.iter().map(|d| d.0.to_string()).collect::<Vec<_>>();
4033        names.sort();
4034        assert_eq!(names, expected_names);
4035    }
4036
4037    #[test]
4038    fn test_extract_var_ids() {
4039        run_extract_var_ids(
4040            "var { a, b = 1, inner: { c }, ...d } = {};",
4041            &["a", "b", "c", "d"],
4042        );
4043        run_extract_var_ids("var [ a, b = 1, [c], ...d ] = [];", &["a", "b", "c", "d"]);
4044    }
4045
4046    fn run_extract_var_ids(text: &str, expected_names: &[&str]) {
4047        let module = parse_module(text);
4048        let decls = extract_var_ids(&module);
4049        let mut names = decls.iter().map(|d| d.sym.to_string()).collect::<Vec<_>>();
4050        names.sort();
4051        assert_eq!(names, expected_names);
4052    }
4053
4054    fn parse_module(text: &str) -> Module {
4055        let syntax = Syntax::Es(Default::default());
4056        let mut p = Parser::new(
4057            syntax,
4058            StringInput::new(text, BytePos(0), BytePos(text.len() as u32)),
4059            None,
4060        );
4061        p.parse_module().unwrap()
4062    }
4063
4064    fn has_top_level_await(text: &str) -> bool {
4065        let module = parse_module(text);
4066        contains_top_level_await(&module)
4067    }
4068
4069    #[test]
4070    fn top_level_await_block() {
4071        assert!(has_top_level_await("if (maybe) { await test; }"))
4072    }
4073
4074    #[test]
4075    fn top_level_await_for_of() {
4076        assert!(has_top_level_await("for await (let iter of []){}"))
4077    }
4078
4079    #[test]
4080    fn top_level_await_object_property() {
4081        assert!(has_top_level_await("const obj = { value: await test };"));
4082        assert!(has_top_level_await("const obj = { [await key]: value };"));
4083        assert!(!has_top_level_await(
4084            "const obj = { async method() { await test; } };"
4085        ));
4086    }
4087
4088    #[test]
4089    fn top_level_await_class() {
4090        assert!(has_top_level_await("class C extends (await base) {}"));
4091        assert!(has_top_level_await("class C { [await key]() {} }"));
4092        assert!(!has_top_level_await(
4093            "class C { async method() { await test; } }"
4094        ));
4095    }
4096
4097    #[test]
4098    fn nested_await_is_not_top_level_await() {
4099        assert!(!has_top_level_await("const f = async () => await test;"));
4100    }
4101
4102    #[test]
4103    fn top_level_export_await() {
4104        assert!(has_top_level_await("export const foo = await 1;"));
4105        assert!(has_top_level_await("export default await 1;"));
4106    }
4107
4108    #[test]
4109    fn switch_default_before_empty_case_does_not_terminate() {
4110        assert!(!stmt_in_function_terminates(
4111            r#"
4112switch (foo) {
4113    default:
4114        return 1;
4115    case "0":
4116}
4117"#
4118        ));
4119    }
4120
4121    #[test]
4122    fn switch_empty_case_before_default_terminates() {
4123        assert!(stmt_in_function_terminates(
4124            r#"
4125switch (foo) {
4126    case "0":
4127    default:
4128        return 1;
4129}
4130"#
4131        ));
4132    }
4133
4134    #[test]
4135    fn switch_non_terminating_case_falls_through_to_terminating_case() {
4136        assert!(!stmt_in_function_terminates(
4137            r#"
4138switch (foo) {
4139    case "0":
4140        foo();
4141    default:
4142        return 1;
4143}
4144"#
4145        ));
4146    }
4147
4148    fn stmt_in_function_terminates(text: &str) -> bool {
4149        let module = parse_module(&format!("function f() {{ {text} }}"));
4150        let ModuleItem::Stmt(Stmt::Decl(Decl::Fn(f))) = &module.body[0] else {
4151            unreachable!("expected a function declaration")
4152        };
4153        let body = f.function.body.as_ref().unwrap();
4154        body.stmts[0].terminates()
4155    }
4156}
4157
4158#[cfg(test)]
4159mod ident_usage_finder_parallel_tests {
4160    use swc_atoms::Atom;
4161    use swc_common::SyntaxContext;
4162    use swc_ecma_ast::*;
4163
4164    use super::*;
4165
4166    fn make_id(name: &str) -> Ident {
4167        Ident::new(Atom::from(name), Span::dummy(), SyntaxContext::empty())
4168    }
4169
4170    #[test]
4171    fn test_visit_class_members() {
4172        let id = make_id("foo");
4173        let member = ClassMember::ClassProp(ClassProp {
4174            key: PropName::Ident(quote_ident!("foo")),
4175            value: Some(Box::new(Expr::Ident(quote_ident!("foo").into()))),
4176            ..Default::default()
4177        });
4178        let found = IdentUsageFinder::find(&id, &vec![member.clone()]);
4179        assert!(found);
4180        let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![member]);
4181        assert!(!not_found);
4182    }
4183
4184    #[test]
4185    fn test_visit_expr_or_spreads() {
4186        let id = make_id("foo");
4187        let expr = ExprOrSpread {
4188            spread: None,
4189            expr: Box::new(Expr::Ident(quote_ident!("foo").into())),
4190        };
4191        let found = IdentUsageFinder::find(&id, &vec![expr.clone()]);
4192        assert!(found);
4193        let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![expr]);
4194        assert!(!not_found);
4195    }
4196
4197    #[test]
4198    fn test_visit_module_items() {
4199        let id = make_id("foo");
4200        let item = ModuleItem::Stmt(Stmt::Expr(ExprStmt {
4201            span: DUMMY_SP,
4202            expr: Box::new(Expr::Ident(quote_ident!("foo").into())),
4203        }));
4204        let found = IdentUsageFinder::find(&id, &vec![item.clone()]);
4205        assert!(found);
4206        let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![item]);
4207        assert!(!not_found);
4208    }
4209
4210    #[test]
4211    fn test_visit_stmts() {
4212        let id = make_id("foo");
4213        let stmt = Stmt::Expr(ExprStmt {
4214            span: DUMMY_SP,
4215            expr: Box::new(Expr::Ident(quote_ident!("foo").into())),
4216        });
4217        let found = IdentUsageFinder::find(&id, &vec![stmt.clone()]);
4218        assert!(found);
4219        let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![stmt]);
4220        assert!(!not_found);
4221    }
4222
4223    #[test]
4224    fn test_visit_opt_vec_expr_or_spreads() {
4225        let id = make_id("foo");
4226        let expr = Some(ExprOrSpread {
4227            spread: None,
4228            expr: Box::new(Expr::Ident(quote_ident!("foo").into())),
4229        });
4230        let found = IdentUsageFinder::find(&id, &vec![expr.clone()]);
4231        assert!(found);
4232        let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![expr]);
4233        assert!(!not_found);
4234    }
4235
4236    #[test]
4237    fn test_visit_var_declarators() {
4238        let id = make_id("foo");
4239        let decl = VarDeclarator {
4240            span: DUMMY_SP,
4241            name: Pat::Ident(quote_ident!("foo").into()),
4242            init: None,
4243            definite: false,
4244        };
4245        let found = IdentUsageFinder::find(&id, &vec![decl.clone()]);
4246        assert!(found);
4247        let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![decl]);
4248        assert!(!not_found);
4249    }
4250
4251    #[test]
4252    fn test_visit_exprs() {
4253        let id = make_id("foo");
4254        let expr = Box::new(Expr::Ident(quote_ident!("foo").into()));
4255        let found = IdentUsageFinder::find(&id, &vec![expr.clone()]);
4256        assert!(found);
4257        let not_found = IdentUsageFinder::find(&make_id("bar"), &vec![expr]);
4258        assert!(!not_found);
4259    }
4260}