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