Skip to main content

turbopack_ecmascript/analyzer/
side_effects.rs

1//! Side effect analysis for JavaScript/TypeScript programs.
2//!
3//! This module provides functionality to determine if a javascript script/module has side effects
4//! during module evaluation. This is useful for tree-shaking and dead code elimination.
5//!
6//! ## What are side effects?
7//!
8//! A side effect is any observable behavior that occurs when code is executed:
9//! - Function calls (unless marked with `/*#__PURE__*/` or otherwise known to be pure)
10//! - Constructor calls (unless marked with `/*#__PURE__*/`or otherwise known to be pure )
11//! - Assignments to variables or properties
12//! - Property mutations
13//! - Update expressions (`++`, `--`)
14//! - Delete expressions
15//!
16//! ## Conservative Analysis
17//!
18//! This analyzer is intentionally conservative. When in doubt, it assumes code
19//! has side effects. This is safe for tree-shaking purposes as it prevents
20//! incorrectly removing code that might be needed, and can simply be improved over time.
21//!
22//! ## Future Enhancement: Local Variable Mutation Tracking
23//!
24//! Currently, all assignments, updates, and property mutations are treated as side effects.
25//! However, mutations to locally-scoped variables that never escape the module evaluation scope
26//! could be considered side-effect free. This would handle common patterns like:
27//!
28//! ```javascript
29//! // Currently marked as having side effects, but could be pure:
30//! const config = {};
31//! config['a'] = 'a';
32//! config['b'] = 'b';
33//! export default config;
34//! ```
35//!
36//! A special case to consider would be CJS exports `module.exports ={}` and `export.foo = ` could
37//! be considered non-effecful just like `ESM` exports.  If we do that we should also consider
38//! changing how `require` is handled, currently it is considered to be effectful
39
40use phf::{phf_map, phf_set};
41use swc_core::{
42    common::{Mark, comments::Comments},
43    ecma::{
44        ast::*,
45        visit::{Visit, VisitWith, noop_visit_type},
46    },
47};
48use turbopack_core::module::ModuleSideEffects;
49
50use crate::utils::unparen;
51
52/// Macro to check if side effects have been detected and return early if so.
53/// This makes the early-return pattern more explicit and reduces boilerplate.
54macro_rules! check_side_effects {
55    ($self:expr) => {
56        if $self.has_side_effects {
57            return;
58        }
59    };
60}
61
62/// Known pure built-in functions organized by object (e.g., Math, Object, Array).
63///
64/// These are JavaScript built-in functions that are known to be side-effect free.
65/// This list is conservative and only includes functions that:
66/// 1. Don't modify global state
67/// 2. Don't perform I/O
68/// 3. Are deterministic (given the same inputs, produce the same outputs)
69///
70/// Note: Some of these can throw exceptions, but for tree-shaking purposes,
71/// we consider them pure as they don't have observable side effects beyond exceptions.
72static KNOWN_PURE_FUNCTIONS: phf::Map<&'static str, phf::Set<&'static str>> = phf_map! {
73    "Math" => phf_set! {
74        "abs", "acos", "acosh", "asin", "asinh", "atan", "atan2", "atanh", "cbrt", "ceil",
75        "clz32", "cos", "cosh", "exp", "expm1", "floor", "fround", "hypot", "imul", "log",
76        "log10", "log1p", "log2", "max", "min", "pow", "round", "sign", "sin", "sinh",
77        "sqrt", "tan", "tanh", "trunc",
78    },
79    // String static methods
80    "String" => phf_set! {
81        "fromCharCode", "fromCodePoint", "raw",
82    },
83    // Number static methods
84    "Number" => phf_set! {
85        "isFinite", "isInteger", "isNaN", "isSafeInteger", "parseFloat", "parseInt",
86    },
87    // Object static methods (read-only operations)
88    "Object" => phf_set! {
89        "keys", "values", "entries", "hasOwn", "getOwnPropertyNames", "getOwnPropertySymbols",
90        "getOwnPropertyDescriptor", "getOwnPropertyDescriptors", "getPrototypeOf", "is",
91        "isExtensible", "isFrozen", "isSealed",
92    },
93    // Array static methods
94    "Array" => phf_set! {
95        "isArray", "from", "of",
96    },
97    // Symbol static methods
98    "Symbol" => phf_set! {
99        "for", "keyFor"
100    },
101};
102
103/// Known pure global functions that can be called directly (not as methods).
104///
105/// These are global functions that are side-effect free when called.
106/// Structured as phf::Set for O(1) lookup.
107static KNOWN_PURE_GLOBAL_FUNCTIONS: phf::Set<&'static str> = phf_set! {
108    "String",
109    "Number",
110    "Symbol",
111    "Boolean",
112    "isNaN",
113    "isFinite",
114    "parseInt",
115    "parseFloat",
116    "decodeURI",
117    "decodeURIComponent",
118};
119
120/// Known pure constructors.
121///
122/// These constructors create new objects without side effects (no global state modification).
123/// They are safe to eliminate if their result is unused.
124static KNOWN_PURE_CONSTRUCTORS: phf::Set<&'static str> = phf_set! {
125    // Built-in collections
126    "Set",
127    "Map",
128    "WeakSet",
129    "WeakMap",
130    // Regular expressions
131    "RegExp",
132    // Data structures
133    "Array",
134    "Object",
135    // Typed arrays
136    "Int8Array",
137    "Uint8Array",
138    "Uint8ClampedArray",
139    "Int16Array",
140    "Uint16Array",
141    "Int32Array",
142    "Uint32Array",
143    "Float32Array",
144    "Float64Array",
145    "BigInt64Array",
146    "BigUint64Array",
147    // Other built-ins
148    "Date",
149    "Error",
150    "TypeError",
151    "RangeError",
152    "SyntaxError",
153    "ReferenceError",
154    "URIError",
155    "EvalError",
156    "Promise",
157    "ArrayBuffer",
158    "DataView",
159    "URL",
160    "URLSearchParams",
161    // Boxes
162    "String",
163    "Number",
164    "Symbol",
165    "Boolean",
166};
167
168// For prototype methods we are not saying that these functions are always side effect free but
169// rather that we can safely reason about their side effects when called on literal expressions.
170// We do however assume that these functions are not monkey patched.
171
172/// Known pure prototype methods for string literals.
173///
174/// These methods don't mutate the string (strings are immutable) and don't have side effects.
175static KNOWN_PURE_STRING_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
176    // Case conversion
177    "toLowerCase",
178    "toUpperCase",
179    "toLocaleLowerCase",
180    "toLocaleUpperCase",
181    "charAt",
182    "charCodeAt",
183    "codePointAt",
184    "slice",
185    "substring",
186    "substr",
187    "indexOf",
188    "lastIndexOf",
189    "includes",
190    "startsWith",
191    "endsWith",
192    "search",
193    "match",
194    "matchAll",
195    "trim",
196    "trimStart",
197    "trimEnd",
198    "trimLeft",
199    "trimRight",
200    "repeat",
201    "padStart",
202    "padEnd",
203    "concat",
204    "split",
205    "replace",
206    "replaceAll",
207    "normalize",
208    "localeCompare",
209    "isWellFormed",
210    "toString",
211    "valueOf",
212};
213
214/// Known pure prototype methods for array literals.
215static KNOWN_PURE_ARRAY_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
216    // Non-mutating iteration
217    "map",
218    "filter",
219    "reduce",
220    "reduceRight",
221    "find",
222    "findIndex",
223    "findLast",
224    "findLastIndex",
225    "some",
226    "every",
227    "flat",
228    "flatMap",
229    // Access methods
230    "at",
231    "slice",
232    "concat",
233    "includes",
234    "indexOf",
235    "lastIndexOf",
236    "join",
237    // Conversion
238    "toLocaleString",
239    "toReversed",
240    "toSorted",
241    "toSpliced",
242    "with",
243};
244
245static KNOWN_PURE_OBJECT_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
246    "hasOwnProperty",
247    "propertyIsEnumerable",
248    "toString",
249    "valueOf",
250};
251
252/// Known pure prototype methods for number literals.
253static KNOWN_PURE_NUMBER_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
254    "toExponential", "toFixed", "toPrecision", "toLocaleString",
255};
256
257/// Known pure prototype methods for RegExp literals.
258///
259/// Note: While `test()` and `exec()` mutate `lastIndex` on regexes with global/sticky flags,
260/// for literal regexes this is safe because:
261/// 1. Literals create fresh objects each time
262/// 2. The mutation is local to that object
263/// 3. The mutated state doesn't escape the expression
264///
265/// However, to be conservative for tree-shaking, we exclude these methods.
266static KNOWN_PURE_REGEXP_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
267    "test", "exec",
268};
269
270/// Analyzes a program to determine if it contains side effects at the top level.
271pub fn compute_module_evaluation_side_effects(
272    program: &Program,
273    comments: &dyn Comments,
274    unresolved_mark: Mark,
275) -> ModuleSideEffects {
276    let mut visitor = SideEffectVisitor::new(comments, unresolved_mark);
277    program.visit_with(&mut visitor);
278    if visitor.has_side_effects {
279        ModuleSideEffects::SideEffectful
280    } else if visitor.has_imports {
281        ModuleSideEffects::ModuleEvaluationIsSideEffectFree
282    } else {
283        ModuleSideEffects::SideEffectFree
284    }
285}
286
287struct SideEffectVisitor<'a> {
288    comments: &'a dyn Comments,
289    unresolved_mark: Mark,
290    has_side_effects: bool,
291    will_invoke_fn_exprs: bool,
292    has_imports: bool,
293}
294
295impl<'a> SideEffectVisitor<'a> {
296    fn new(comments: &'a dyn Comments, unresolved_mark: Mark) -> Self {
297        Self {
298            comments,
299            unresolved_mark,
300            has_side_effects: false,
301            will_invoke_fn_exprs: false,
302            has_imports: false,
303        }
304    }
305
306    /// Mark that we've found a side effect and stop further analysis.
307    fn mark_side_effect(&mut self) {
308        self.has_side_effects = true;
309    }
310
311    /// Temporarily set `will_invoke_fn_exprs` to the given value, execute the closure,
312    /// then restore the original value.
313    ///
314    /// This is useful when analyzing code that may invoke function expressions passed as
315    /// arguments (e.g., callbacks to pure functions like `array.map(fn)`).
316    fn with_will_invoke_fn_exprs<F>(&mut self, value: bool, f: F)
317    where
318        F: FnOnce(&mut Self),
319    {
320        let old_value = self.will_invoke_fn_exprs;
321        self.will_invoke_fn_exprs = value;
322        f(self);
323        self.will_invoke_fn_exprs = old_value;
324    }
325
326    /// Check if a span has a `/*#__PURE__*/` or `/*@__PURE__*/` annotation.
327    fn is_pure_annotated(&self, span: swc_core::common::Span) -> bool {
328        self.comments.has_flag(span.lo, "PURE")
329    }
330
331    /// Check if a callee expression is a known pure built-in function.
332    ///
333    /// This checks if the callee matches patterns like `Math.abs`, `Object.keys`, etc.
334    fn is_known_pure_builtin(&self, callee: &Callee) -> bool {
335        match callee {
336            Callee::Expr(expr) => self.is_known_pure_builtin_function(expr),
337            _ => false,
338        }
339    }
340    /// Returns true if this call is to `import()` or `require()`.
341    /// This is conservative since we don't resolve aliases and also because we don't support things
342    /// like `require.context` or `import.meta` apis
343    fn is_require_or_import(&self, callee: &Callee) -> bool {
344        match callee {
345            Callee::Expr(expr) => {
346                let expr = unparen(expr);
347                if let Expr::Ident(ident) = expr {
348                    ident.ctxt.outer() == self.unresolved_mark && ident.sym.as_ref() == "require"
349                } else {
350                    false
351                }
352            }
353
354            Callee::Import(_) => true,
355            _ => false,
356        }
357    }
358    /// Check if an expression is a known pure built-in function.
359    ///
360    /// This checks for:
361    /// - Member expressions like `Math.abs`, `Object.keys`, etc.
362    /// - Global function identifiers like `isNaN`, `parseInt`, etc.
363    /// - Literal receiver methods like `"hello".toLowerCase()`, `[1,2,3].map()`, etc.
364    ///
365    /// Only returns true if the base identifier is in the global scope (unresolved).
366    /// If it's shadowed by a local variable, we cannot assume it's the built-in.
367    fn is_known_pure_builtin_function(&self, expr: &Expr) -> bool {
368        match expr {
369            Expr::Member(member) => {
370                let receiver = unparen(&member.obj);
371                match (receiver, &member.prop) {
372                    // Handle global object methods like Math.abs, Object.keys, etc.
373                    (Expr::Ident(obj), MemberProp::Ident(prop)) => {
374                        // Only consider it pure if the base identifier is unresolved (global
375                        // scope). Check if the identifier's context matches
376                        // the unresolved mark.
377                        if obj.ctxt.outer() != self.unresolved_mark {
378                            // The identifier is in a local scope, might be shadowed
379                            return false;
380                        }
381
382                        // O(1) lookup: check if the object has the method in our known pure
383                        // functions
384                        KNOWN_PURE_FUNCTIONS
385                            .get(obj.sym.as_ref())
386                            .map(|methods| methods.contains(prop.sym.as_ref()))
387                            .unwrap_or(false)
388                    }
389                    // Handle literal receiver methods like "hello".toLowerCase(), [1,2,3].map(),
390                    // etc.
391                    (Expr::Lit(lit), MemberProp::Ident(prop)) => {
392                        let method_name = prop.sym.as_ref();
393                        match lit {
394                            Lit::Str(_) => {
395                                KNOWN_PURE_STRING_PROTOTYPE_METHODS.contains(method_name)
396                                    || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
397                            }
398                            Lit::Num(_) => {
399                                KNOWN_PURE_NUMBER_PROTOTYPE_METHODS.contains(method_name)
400                                    || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
401                            }
402                            Lit::Bool(_) => {
403                                KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
404                            }
405                            Lit::Regex(_) => {
406                                KNOWN_PURE_REGEXP_PROTOTYPE_METHODS.contains(method_name)
407                                    || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
408                            }
409                            _ => false,
410                        }
411                    }
412                    // Handle array literal methods like [1,2,3].map()
413                    // Note: We don't check array elements here - that's handled in visit_expr
414                    (Expr::Array(_), MemberProp::Ident(prop)) => {
415                        let method_name = prop.sym.as_ref();
416                        KNOWN_PURE_ARRAY_PROTOTYPE_METHODS.contains(method_name)
417                            || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
418                    }
419                    (Expr::Object(_), MemberProp::Ident(prop)) => {
420                        KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(prop.sym.as_ref())
421                    }
422                    _ => false,
423                }
424            }
425            Expr::Ident(ident) => {
426                // Check for global pure functions like isNaN, parseInt, etc.
427                // Only consider it pure if the identifier is unresolved (global scope).
428                if ident.ctxt.outer() != self.unresolved_mark {
429                    return false;
430                }
431
432                // O(1) lookup in the global functions set
433                KNOWN_PURE_GLOBAL_FUNCTIONS.contains(ident.sym.as_ref())
434            }
435            _ => false,
436        }
437    }
438
439    /// Check if an expression is a known pure constructor.
440    ///
441    /// These are built-in constructors that create new objects without side effects.
442    /// Only returns true if the identifier is in the global scope (unresolved).
443    /// If it's shadowed by a local variable, we cannot assume it's the built-in constructor.
444    fn is_known_pure_constructor(&self, expr: &Expr) -> bool {
445        match expr {
446            Expr::Ident(ident) => {
447                // Only consider it pure if the identifier is unresolved (global scope).
448                // Check if the identifier's context matches the unresolved mark.
449                if ident.ctxt.outer() != self.unresolved_mark {
450                    return false;
451                }
452
453                // O(1) lookup in the constructors set
454                KNOWN_PURE_CONSTRUCTORS.contains(ident.sym.as_ref())
455            }
456            _ => false,
457        }
458    }
459}
460
461impl<'a> Visit for SideEffectVisitor<'a> {
462    noop_visit_type!();
463    // If we've already found side effects, skip further visitation
464    fn visit_program(&mut self, program: &Program) {
465        check_side_effects!(self);
466        program.visit_children_with(self);
467    }
468
469    fn visit_module(&mut self, module: &Module) {
470        check_side_effects!(self);
471
472        // Only check top-level module items
473        for item in &module.body {
474            check_side_effects!(self);
475            item.visit_with(self);
476        }
477    }
478
479    fn visit_script(&mut self, script: &Script) {
480        check_side_effects!(self);
481
482        // Only check top-level statements
483        for stmt in &script.body {
484            check_side_effects!(self);
485            stmt.visit_with(self);
486        }
487    }
488
489    // Module declarations (imports/exports) need special handling
490    fn visit_module_decl(&mut self, decl: &ModuleDecl) {
491        check_side_effects!(self);
492
493        match decl {
494            // Import statements may have side effects, which could require full graph analysis
495            // Record that to decide if we can upgrade ModuleEvaluationIsSideEffectFree to
496            // SideEffectFree
497            ModuleDecl::Import(_) => {
498                self.has_imports = true;
499            }
500
501            // Export declarations need to check their contents
502            ModuleDecl::ExportDecl(export_decl) => {
503                // Check the declaration being exported
504                match &export_decl.decl {
505                    Decl::Fn(_) => {
506                        // function declarations are pure
507                    }
508                    Decl::Class(class_decl) => {
509                        // Class declarations can have side effects in static blocks, extends or
510                        // static property initializers.
511                        class_decl.visit_with(self);
512                    }
513                    Decl::Var(var_decl) => {
514                        // Variable declarations need their initializers checked
515                        var_decl.visit_with(self);
516                    }
517                    _ => {
518                        // Other declarations should be checked
519                        export_decl.decl.visit_with(self);
520                    }
521                }
522            }
523
524            ModuleDecl::ExportDefaultDecl(export_default_decl) => {
525                // Check the default export
526                match &export_default_decl.decl {
527                    DefaultDecl::Class(cls) => {
528                        // Class expressions can have side effects in extends clause and static
529                        // members
530                        cls.visit_with(self);
531                    }
532                    DefaultDecl::Fn(_) => {
533                        // function declarations are pure
534                    }
535                    DefaultDecl::TsInterfaceDecl(_) => {
536                        // TypeScript interface declarations are pure
537                    }
538                }
539            }
540
541            ModuleDecl::ExportDefaultExpr(export_default_expr) => {
542                // Check the expression being exported
543                export_default_expr.expr.visit_with(self);
544            }
545
546            // Re-exports have no side effects
547            ModuleDecl::ExportNamed(e) => {
548                if e.src.is_some() {
549                    // reexports are also imports
550                    self.has_imports = true;
551                }
552            }
553            ModuleDecl::ExportAll(_) => {
554                // reexports are also imports
555                self.has_imports = true;
556            }
557
558            // TypeScript-specific exports
559            ModuleDecl::TsExportAssignment(_) | ModuleDecl::TsNamespaceExport(_) => {}
560            ModuleDecl::TsImportEquals(e) => {
561                // The RHS of a ts import equals expression is typically an identifier but it might
562                // also be a require!
563                match &e.module_ref {
564                    TsModuleRef::TsEntityName(_) => {}
565                    TsModuleRef::TsExternalModuleRef(_) => {
566                        // This is a `import x = require('y')` call
567                        self.has_imports = true
568                    }
569                }
570            }
571        }
572    }
573
574    // Statement-level detection
575    fn visit_stmt(&mut self, stmt: &Stmt) {
576        check_side_effects!(self);
577
578        match stmt {
579            // Expression statements need checking
580            Stmt::Expr(expr_stmt) => {
581                expr_stmt.visit_with(self);
582            }
583            // Variable declarations need checking (initializers might have side effects)
584            Stmt::Decl(Decl::Var(var_decl)) => {
585                var_decl.visit_with(self);
586            }
587            // Function declarations are side-effect free
588            Stmt::Decl(Decl::Fn(_)) => {
589                // Function declarations don't execute, so no side effects
590            }
591            // Class declarations can have side effects in extends clause and static members
592            Stmt::Decl(Decl::Class(class_decl)) => {
593                class_decl.visit_with(self);
594            }
595            // Other declarations
596            Stmt::Decl(decl) => {
597                decl.visit_with(self);
598            }
599            // For other statement types, be conservative
600            _ => {
601                // Most other statement types (if, for, while, etc.) at top level
602                // would be unusual and potentially have side effects
603                self.mark_side_effect();
604            }
605        }
606    }
607
608    fn visit_var_declarator(&mut self, var_decl: &VarDeclarator) {
609        check_side_effects!(self);
610
611        // Check the pattern (for default values in destructuring)
612        var_decl.name.visit_with(self);
613
614        // Check the initializer
615        if let Some(init) = &var_decl.init {
616            init.visit_with(self);
617        }
618    }
619
620    // Expression-level detection
621    fn visit_expr(&mut self, expr: &Expr) {
622        check_side_effects!(self);
623
624        match expr {
625            // Pure expressions
626            Expr::Lit(_) => {
627                // Literals are always pure
628            }
629            Expr::Ident(_) => {
630                // Reading identifiers is pure
631            }
632            Expr::Arrow(_) | Expr::Fn(_) => {
633                // Function expressions are pure (don't execute until called)
634                if self.will_invoke_fn_exprs {
635                    // assume that any nested function expressions will not be invoked.
636                    self.with_will_invoke_fn_exprs(false, |this| {
637                        expr.visit_children_with(this);
638                    });
639                }
640            }
641            Expr::Class(class_expr) => {
642                // Class expressions can have side effects in extends clause and static members
643                class_expr.class.visit_with(self);
644            }
645            Expr::Array(arr) => {
646                // Arrays are pure if their elements are pure
647                for elem in arr.elems.iter().flatten() {
648                    elem.visit_with(self);
649                }
650            }
651            Expr::Object(obj) => {
652                // Objects are pure if their property names and initializers
653                for prop in &obj.props {
654                    prop.visit_with(self);
655                }
656            }
657            Expr::Unary(unary) => {
658                // Most unary operations are pure, but delete is not
659                if unary.op == UnaryOp::Delete {
660                    // TODO: allow deletes to module level variables or properties defined on module
661                    // level variables
662                    self.mark_side_effect();
663                } else {
664                    unary.arg.visit_with(self);
665                }
666            }
667            Expr::Bin(bin) => {
668                // Binary operations are pure if operands are pure
669                bin.left.visit_with(self);
670                bin.right.visit_with(self);
671            }
672            Expr::Cond(cond) => {
673                // Conditional is pure if all parts are pure
674                cond.test.visit_with(self);
675                cond.cons.visit_with(self);
676                cond.alt.visit_with(self);
677            }
678            Expr::Member(member) => {
679                // Member access is pure - just reading a property doesn't cause side effects.
680                // While getters *could* have side effects, in practice:
681                // 1. Most code doesn't use getters with side effects (rare pattern)
682                // 2. Webpack and rolldown treat member access as pure
683                // 3. Being too conservative here would mark too much code as impure
684                //
685                // We check the object and property for side effects (e.g., computed properties)
686                member.obj.visit_with(self);
687                member.prop.visit_with(self);
688            }
689            Expr::Paren(paren) => {
690                // Parenthesized expressions inherit purity from inner expr
691                paren.expr.visit_with(self);
692            }
693            Expr::Tpl(tpl) => {
694                // Template literals are pure if expressions are pure
695                for expr in &tpl.exprs {
696                    expr.visit_with(self);
697                }
698            }
699
700            // Impure expressions (conservative)
701            Expr::Call(call) => {
702                // Check for /*#__PURE__*/ annotation or for a well known function
703                if self.is_pure_annotated(call.span) || self.is_known_pure_builtin(&call.callee) {
704                    // For known pure builtins, we need to check both:
705                    // 1. The receiver (e.g., the array in [foo(), 2, 3].map(...))
706                    // 2. The arguments
707
708                    // Check the receiver
709                    call.callee.visit_with(self);
710
711                    // Check all arguments
712                    // Assume that any function expressions in the arguments will be invoked.
713                    self.with_will_invoke_fn_exprs(true, |this| {
714                        call.args.visit_children_with(this);
715                    });
716                } else if self.is_require_or_import(&call.callee) {
717                    self.has_imports = true;
718                    // It would be weird to have a side effect in a require(...) statement, but not
719                    // impossible.
720                    call.args.visit_children_with(self);
721                } else {
722                    // Unmarked calls are considered to have side effects
723                    self.mark_side_effect();
724                }
725            }
726            Expr::New(new) => {
727                // Check for /*#__PURE__*/ annotation or known pure constructor
728                if self.is_pure_annotated(new.span) || self.is_known_pure_constructor(&new.callee) {
729                    // Pure constructor, but still need to check arguments
730                    self.with_will_invoke_fn_exprs(true, |this| {
731                        new.args.visit_children_with(this);
732                    });
733                } else {
734                    // Unknown constructor calls are considered to have side effects
735                    self.mark_side_effect();
736                }
737            }
738            Expr::Assign(_) => {
739                // Assignments have side effects
740                // TODO: allow assignments to module level variables
741                self.mark_side_effect();
742            }
743            Expr::Update(_) => {
744                // Updates (++, --) have side effects
745                // TODO: allow updates to module level variables
746                self.mark_side_effect();
747            }
748            Expr::Await(e) => {
749                e.arg.visit_with(self);
750            }
751            Expr::Yield(e) => {
752                e.arg.visit_with(self);
753            }
754            Expr::TaggedTpl(tagged_tpl)
755                // Tagged template literals are function calls
756                // But some are known to be pure, like String.raw
757                if self.is_known_pure_builtin_function(&tagged_tpl.tag) => {
758                    for arg in &tagged_tpl.tpl.exprs {
759                        arg.visit_with(self);
760                    }
761                }
762            Expr::OptChain(opt_chain) => {
763                // Optional chaining can be pure if it's just member access
764                // But if it's an optional call, it has side effects
765                opt_chain.base.visit_with(self);
766            }
767            Expr::Seq(seq) => {
768                // Sequence expressions - check each expression
769                seq.exprs.visit_children_with(self);
770            }
771            Expr::SuperProp(super_prop) => {
772                // Super property access is pure (reading from parent class)
773                // Check if the property expression has side effects
774                super_prop.prop.visit_with(self);
775            }
776            Expr::MetaProp(_) => {
777                // Meta properties like import.meta and new.target are pure
778                // They just read metadata, don't cause side effects
779            }
780            Expr::JSXMember(_) | Expr::JSXNamespacedName(_) | Expr::JSXEmpty(_) => {
781                // JSX member expressions and names are pure (they're just identifiers)
782            }
783            Expr::JSXElement(_) | Expr::JSXFragment(_) => {
784                // JSX elements compile to function calls (React.createElement, etc.)
785                // These are side effect free but we don't technically know at this point that it is
786                // react (could be solid or qwik or millionjs).  In any case it doesn't matter too
787                // much since it is weird to construct jsx at the module scope.
788                self.mark_side_effect();
789            }
790            Expr::PrivateName(_) => {
791                // Private names are pure (just identifiers)
792            }
793
794            // Be conservative for other expression types and just assume they are effectful
795            _ => {
796                self.mark_side_effect();
797            }
798        }
799    }
800
801    fn visit_opt_chain_base(&mut self, base: &OptChainBase) {
802        check_side_effects!(self);
803
804        match base {
805            OptChainBase::Member(member) => {
806                member.visit_with(self);
807            }
808            OptChainBase::Call(_opt_call) => {
809                // Optional calls are still calls, so impure
810                // We could maybe support some of these `(foo_enabled? undefined :
811                // [])?.map(...)` but this seems pretty theoretical
812                self.mark_side_effect();
813            }
814        }
815    }
816
817    fn visit_prop_or_spread(&mut self, prop: &PropOrSpread) {
818        check_side_effects!(self);
819
820        match prop {
821            PropOrSpread::Spread(spread) => {
822                spread.expr.visit_with(self);
823            }
824            PropOrSpread::Prop(prop) => {
825                prop.visit_with(self);
826            }
827        }
828    }
829
830    fn visit_prop(&mut self, prop: &Prop) {
831        check_side_effects!(self);
832
833        match prop {
834            Prop::KeyValue(kv) => {
835                kv.key.visit_with(self);
836                kv.value.visit_with(self);
837            }
838            Prop::Getter(getter) => {
839                getter.key.visit_with(self);
840                // Body is not executed at definition time
841            }
842            Prop::Setter(setter) => {
843                setter.key.visit_with(self);
844                // Body is not executed at definition time
845            }
846            Prop::Method(method) => {
847                method.key.visit_with(self);
848                // Body is not executed at definition time
849            }
850            Prop::Shorthand(_) => {
851                // Shorthand properties are pure
852            }
853            Prop::Assign(_) => {
854                // Assignment properties (used in object rest/spread patterns)
855                // are side-effect free at definition
856            }
857        }
858    }
859
860    fn visit_prop_name(&mut self, prop_name: &PropName) {
861        check_side_effects!(self);
862
863        match prop_name {
864            PropName::Computed(computed) => {
865                // Computed property names need evaluation
866                computed.expr.visit_with(self);
867            }
868            _ => {
869                // Other property names are pure
870            }
871        }
872    }
873
874    fn visit_class(&mut self, class: &Class) {
875        check_side_effects!(self);
876
877        // Check decorators - they execute at definition time
878        for decorator in &class.decorators {
879            decorator.visit_with(self);
880        }
881
882        // Check the extends clause - this is evaluated at definition time
883        if let Some(super_class) = &class.super_class {
884            super_class.visit_with(self);
885        }
886
887        // Check class body for static members
888        for member in &class.body {
889            member.visit_with(self);
890        }
891    }
892
893    fn visit_class_member(&mut self, member: &ClassMember) {
894        check_side_effects!(self);
895
896        match member {
897            // Static blocks execute at class definition time
898            ClassMember::StaticBlock(block) => {
899                // Static blocks may have side effects because they execute immediately
900                // Check the statements in the block
901                for stmt in &block.body.stmts {
902                    stmt.visit_with(self);
903                }
904            }
905            // Check static properties - they execute at definition time
906            ClassMember::ClassProp(class_prop) if class_prop.is_static => {
907                // Check decorators - they execute at definition time
908                for decorator in &class_prop.decorators {
909                    decorator.visit_with(self);
910                }
911                // Check the property key (for computed properties)
912                class_prop.key.visit_with(self);
913                // Check the initializer - static property initializers execute at definition time
914                if let Some(value) = &class_prop.value {
915                    value.visit_with(self);
916                }
917            }
918            // Check computed property keys for all members
919            ClassMember::Method(method) => {
920                // Check decorators - they execute at definition time
921                for decorator in &method.function.decorators {
922                    decorator.visit_with(self);
923                }
924                method.key.visit_with(self);
925                // Method bodies don't execute at definition time
926            }
927            ClassMember::Constructor(constructor) => {
928                constructor.key.visit_with(self);
929                // Constructor body doesn't execute at definition time
930            }
931            ClassMember::PrivateMethod(private_method) => {
932                // Check decorators - they execute at definition time
933                for decorator in &private_method.function.decorators {
934                    decorator.visit_with(self);
935                }
936                private_method.key.visit_with(self);
937                // Method bodies don't execute at definition time
938            }
939            ClassMember::ClassProp(class_prop) => {
940                // Check decorators - they execute at definition time
941                for decorator in &class_prop.decorators {
942                    decorator.visit_with(self);
943                }
944                // For non-static properties, only check the key
945                class_prop.key.visit_with(self);
946                // Instance property initializers don't execute at definition time
947            }
948            ClassMember::PrivateProp(private_prop) => {
949                // Check decorators - they execute at definition time
950                for decorator in &private_prop.decorators {
951                    decorator.visit_with(self);
952                }
953                private_prop.key.visit_with(self);
954                // Instance property initializers don't execute at definition time
955            }
956            ClassMember::AutoAccessor(auto_accessor) if auto_accessor.is_static => {
957                // Check decorators - they execute at definition time
958                for decorator in &auto_accessor.decorators {
959                    decorator.visit_with(self);
960                }
961                // Static auto accessors execute at definition time
962                auto_accessor.key.visit_with(self);
963                if let Some(value) = &auto_accessor.value {
964                    value.visit_with(self);
965                }
966            }
967            ClassMember::AutoAccessor(auto_accessor) => {
968                // Check decorators - they execute at definition time
969                for decorator in &auto_accessor.decorators {
970                    decorator.visit_with(self);
971                }
972                // Non-static auto accessors only check the key
973                auto_accessor.key.visit_with(self);
974            }
975            ClassMember::Empty(_) => {
976                // Empty members are pure
977            }
978            ClassMember::TsIndexSignature(_) => {
979                // TypeScript index signatures are pure
980            }
981        }
982    }
983
984    fn visit_decorator(&mut self, _decorator: &Decorator) {
985        if self.has_side_effects {
986            return;
987        }
988
989        // Decorators always have side effects because they are function calls
990        // that execute at class/member definition time, even if they're just
991        // identifier references (e.g., @decorator is equivalent to calling decorator())
992        self.mark_side_effect();
993    }
994
995    fn visit_pat(&mut self, pat: &Pat) {
996        check_side_effects!(self);
997
998        match pat {
999            // Object patterns with default values need checking
1000            Pat::Object(object_pat) => {
1001                for prop in &object_pat.props {
1002                    match prop {
1003                        ObjectPatProp::KeyValue(kv) => {
1004                            // Check the key (for computed properties)
1005                            kv.key.visit_with(self);
1006                            // Recursively check the value pattern
1007                            kv.value.visit_with(self);
1008                        }
1009                        ObjectPatProp::Assign(assign) => {
1010                            // Check the default value if present
1011                            if let Some(value) = &assign.value {
1012                                value.visit_with(self);
1013                            }
1014                        }
1015                        ObjectPatProp::Rest(rest) => {
1016                            // Rest patterns are pure, but check the nested pattern
1017                            rest.arg.visit_with(self);
1018                        }
1019                    }
1020                }
1021            }
1022            // Array patterns with default values need checking
1023            Pat::Array(array_pat) => {
1024                for elem in array_pat.elems.iter().flatten() {
1025                    elem.visit_with(self);
1026                }
1027            }
1028            // Assignment patterns (destructuring with defaults) need checking
1029            Pat::Assign(assign_pat) => {
1030                // Check the default value - this is evaluated if the value is undefined
1031                assign_pat.right.visit_with(self);
1032                // Also check the left side pattern
1033                assign_pat.left.visit_with(self);
1034            }
1035            // Other patterns are pure
1036            _ => {}
1037        }
1038    }
1039}
1040
1041#[cfg(test)]
1042mod tests {
1043    use swc_core::{
1044        common::{FileName, GLOBALS, Mark, SourceMap, comments::SingleThreadedComments, sync::Lrc},
1045        ecma::{
1046            ast::EsVersion,
1047            parser::{EsSyntax, Syntax, parse_file_as_program},
1048            transforms::base::resolver,
1049            visit::VisitMutWith,
1050        },
1051    };
1052
1053    use super::*;
1054
1055    /// Helper function to parse JavaScript code from a string and run the resolver
1056    fn parse_and_check_for_side_effects(code: &str, expected: ModuleSideEffects) {
1057        GLOBALS.set(&Default::default(), || {
1058            let cm = Lrc::new(SourceMap::default());
1059            let fm = cm.new_source_file(Lrc::new(FileName::Anon), code.to_string());
1060
1061            let comments = SingleThreadedComments::default();
1062            let mut errors = vec![];
1063
1064            let mut program = parse_file_as_program(
1065                &fm,
1066                Syntax::Es(EsSyntax {
1067                    jsx: true,
1068                    decorators: true,
1069                    ..Default::default()
1070                }),
1071                EsVersion::latest(),
1072                Some(&comments),
1073                &mut errors,
1074            )
1075            .expect("Failed to parse");
1076
1077            // Run the resolver to mark unresolved identifiers
1078            let unresolved_mark = Mark::new();
1079            let top_level_mark = Mark::new();
1080            program.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
1081
1082            let actual =
1083                compute_module_evaluation_side_effects(&program, &comments, unresolved_mark);
1084
1085            let msg = match expected {
1086                ModuleSideEffects::ModuleEvaluationIsSideEffectFree => {
1087                    "Expected code to have no local side effects"
1088                }
1089                ModuleSideEffects::SideEffectFree => "Expected code to be side effect free",
1090                ModuleSideEffects::SideEffectful => "Expected code to have side effects",
1091            };
1092            assert_eq!(actual, expected, "{}:\n{}", msg, code);
1093        })
1094    }
1095
1096    /// Generate a test that asserts the given code has the expected side effect status
1097    macro_rules! assert_side_effects {
1098        ($name:ident, $code:expr, $expected:expr) => {
1099            #[test]
1100            fn $name() {
1101                parse_and_check_for_side_effects($code, $expected);
1102            }
1103        };
1104    }
1105
1106    macro_rules! side_effects {
1107        ($name:ident, $code:expr) => {
1108            assert_side_effects!($name, $code, ModuleSideEffects::SideEffectful);
1109        };
1110    }
1111
1112    macro_rules! no_side_effects {
1113        ($name:ident, $code:expr) => {
1114            assert_side_effects!($name, $code, ModuleSideEffects::SideEffectFree);
1115        };
1116    }
1117    macro_rules! module_evaluation_is_side_effect_free {
1118        ($name:ident, $code:expr) => {
1119            assert_side_effects!(
1120                $name,
1121                $code,
1122                ModuleSideEffects::ModuleEvaluationIsSideEffectFree
1123            );
1124        };
1125    }
1126
1127    mod basic_tests {
1128        use super::*;
1129
1130        no_side_effects!(test_empty_program, "");
1131
1132        no_side_effects!(test_simple_const_declaration, "const x = 5;");
1133
1134        no_side_effects!(test_simple_let_declaration, "let y = 'string';");
1135
1136        no_side_effects!(test_array_literal, "const arr = [1, 2, 3];");
1137
1138        no_side_effects!(test_object_literal, "const obj = { a: 1, b: 2 };");
1139
1140        no_side_effects!(test_function_declaration, "function foo() { return 1; }");
1141
1142        no_side_effects!(
1143            test_function_expression,
1144            "const foo = function() { return 1; };"
1145        );
1146
1147        no_side_effects!(test_arrow_function, "const foo = () => 1;");
1148    }
1149
1150    mod side_effects_tests {
1151        use super::*;
1152
1153        side_effects!(test_console_log, "console.log('hello');");
1154
1155        side_effects!(test_function_call, "foo();");
1156
1157        side_effects!(test_method_call, "obj.method();");
1158
1159        side_effects!(test_assignment, "x = 5;");
1160
1161        side_effects!(test_member_assignment, "obj.prop = 5;");
1162
1163        side_effects!(test_constructor_call, "new SideEffect();");
1164
1165        side_effects!(test_update_expression, "x++;");
1166    }
1167
1168    mod pure_expressions_tests {
1169        use super::*;
1170
1171        no_side_effects!(test_binary_expression, "const x = 1 + 2;");
1172
1173        no_side_effects!(test_unary_expression, "const x = -5;");
1174
1175        no_side_effects!(test_conditional_expression, "const x = true ? 1 : 2;");
1176
1177        no_side_effects!(test_template_literal, "const x = `hello ${world}`;");
1178
1179        no_side_effects!(test_nested_object, "const obj = { a: { b: { c: 1 } } };");
1180
1181        no_side_effects!(test_nested_array, "const arr = [[1, 2], [3, 4]];");
1182    }
1183
1184    mod import_export_tests {
1185        use super::*;
1186
1187        module_evaluation_is_side_effect_free!(test_import_statement, "import x from 'y';");
1188        module_evaluation_is_side_effect_free!(test_require_statement, "const x = require('y');");
1189
1190        no_side_effects!(test_export_statement, "export default 5;");
1191
1192        no_side_effects!(test_export_const, "export const x = 5;");
1193
1194        side_effects!(
1195            test_export_const_with_side_effect,
1196            "export const x = foo();"
1197        );
1198    }
1199
1200    mod mixed_cases_tests {
1201        use super::*;
1202
1203        side_effects!(test_call_in_initializer, "const x = foo();");
1204
1205        side_effects!(test_call_in_array, "const arr = [1, foo(), 3];");
1206
1207        side_effects!(test_call_in_object, "const obj = { a: foo() };");
1208
1209        no_side_effects!(
1210            test_multiple_declarations_pure,
1211            "const x = 1;\nconst y = 2;\nconst z = 3;"
1212        );
1213
1214        side_effects!(
1215            test_multiple_declarations_with_side_effect,
1216            "const x = 1;\nfoo();\nconst z = 3;"
1217        );
1218
1219        no_side_effects!(test_class_declaration, "class Foo {}");
1220
1221        no_side_effects!(
1222            test_class_with_methods,
1223            "class Foo { method() { return 1; } }"
1224        );
1225    }
1226
1227    mod pure_annotations_tests {
1228        use super::*;
1229
1230        no_side_effects!(test_pure_annotation_function_call, "/*#__PURE__*/ foo();");
1231
1232        no_side_effects!(test_pure_annotation_with_at, "/*@__PURE__*/ foo();");
1233
1234        no_side_effects!(test_pure_annotation_constructor, "/*#__PURE__*/ new Foo();");
1235
1236        no_side_effects!(
1237            test_pure_annotation_in_variable,
1238            "const x = /*#__PURE__*/ foo();"
1239        );
1240
1241        no_side_effects!(
1242            test_pure_annotation_with_pure_args,
1243            "/*#__PURE__*/ foo(1, 2, 3);"
1244        );
1245
1246        // Even with PURE annotation, impure arguments make it impure
1247        side_effects!(
1248            test_pure_annotation_with_impure_args,
1249            "/*#__PURE__*/ foo(bar());"
1250        );
1251
1252        // Without annotation, calls are impure
1253        side_effects!(test_without_pure_annotation, "foo();");
1254
1255        no_side_effects!(
1256            test_pure_nested_in_object,
1257            "const obj = { x: /*#__PURE__*/ foo() };"
1258        );
1259
1260        no_side_effects!(test_pure_in_array, "const arr = [/*#__PURE__*/ foo()];");
1261
1262        no_side_effects!(
1263            test_multiple_pure_calls,
1264            "const x = /*#__PURE__*/ foo();\nconst y = /*#__PURE__*/ bar();"
1265        );
1266
1267        side_effects!(
1268            test_mixed_pure_and_impure,
1269            "const x = /*#__PURE__*/ foo();\nbar();\nconst z = /*#__PURE__*/ baz();"
1270        );
1271    }
1272
1273    mod known_pure_builtins_tests {
1274        use super::*;
1275
1276        no_side_effects!(test_math_abs, "const x = Math.abs(-5);");
1277
1278        no_side_effects!(test_math_floor, "const x = Math.floor(3.14);");
1279
1280        no_side_effects!(test_math_max, "const x = Math.max(1, 2, 3);");
1281
1282        no_side_effects!(test_object_keys, "const keys = Object.keys(obj);");
1283
1284        no_side_effects!(test_object_values, "const values = Object.values(obj);");
1285
1286        no_side_effects!(test_object_entries, "const entries = Object.entries(obj);");
1287
1288        no_side_effects!(test_array_is_array, "const result = Array.isArray([]);");
1289
1290        no_side_effects!(
1291            test_string_from_char_code,
1292            "const char = String.fromCharCode(65);"
1293        );
1294
1295        no_side_effects!(test_number_is_nan, "const result = Number.isNaN(x);");
1296
1297        no_side_effects!(
1298            test_multiple_math_calls,
1299            "const x = Math.abs(-5);\nconst y = Math.floor(3.14);\nconst z = Math.max(x, y);"
1300        );
1301
1302        // Even pure builtins become impure if arguments are impure
1303        side_effects!(
1304            test_pure_builtin_with_impure_arg,
1305            "const x = Math.abs(foo());"
1306        );
1307
1308        no_side_effects!(
1309            test_pure_builtin_in_expression,
1310            "const x = Math.abs(-5) + Math.floor(3.14);"
1311        );
1312
1313        side_effects!(
1314            test_mixed_builtin_and_impure,
1315            "const x = Math.abs(-5);\nfoo();\nconst z = Object.keys({});"
1316        );
1317
1318        // Accessing unknown Math properties is not in our list
1319        side_effects!(test_unknown_math_property, "const x = Math.random();");
1320
1321        // Object.assign is NOT pure (it mutates)
1322        side_effects!(test_object_assign, "Object.assign(target, source);");
1323
1324        no_side_effects!(test_array_from, "const arr = Array.from(iterable);");
1325
1326        no_side_effects!(test_global_is_nan, "const result = isNaN(value);");
1327
1328        no_side_effects!(test_global_is_finite, "const result = isFinite(value);");
1329
1330        no_side_effects!(test_global_parse_int, "const num = parseInt('42', 10);");
1331
1332        no_side_effects!(test_global_parse_float, "const num = parseFloat('3.14');");
1333
1334        no_side_effects!(
1335            test_global_decode_uri,
1336            "const decoded = decodeURI(encoded);"
1337        );
1338
1339        no_side_effects!(
1340            test_global_decode_uri_component,
1341            "const decoded = decodeURIComponent(encoded);"
1342        );
1343
1344        // String() as a function (not constructor) is pure
1345        no_side_effects!(
1346            test_global_string_constructor_as_function,
1347            "const str = String(123);"
1348        );
1349
1350        // Number() as a function (not constructor) is pure
1351        no_side_effects!(
1352            test_global_number_constructor_as_function,
1353            "const num = Number('123');"
1354        );
1355
1356        // Boolean() as a function (not constructor) is pure
1357        no_side_effects!(
1358            test_global_boolean_constructor_as_function,
1359            "const bool = Boolean(value);"
1360        );
1361
1362        // Symbol() as a function is pure
1363        no_side_effects!(
1364            test_global_symbol_constructor_as_function,
1365            "const sym = Symbol('description');"
1366        );
1367
1368        // Symbol.for() is pure
1369        no_side_effects!(test_symbol_for, "const sym = Symbol.for('description');");
1370
1371        // Symbol.keyFor() is pure
1372        no_side_effects!(
1373            test_symbol_key_for,
1374            "const description = Symbol.keyFor(sym);"
1375        );
1376
1377        // Global pure function with impure argument is impure
1378        side_effects!(
1379            test_global_pure_with_impure_arg,
1380            "const result = isNaN(foo());"
1381        );
1382
1383        // isNaN shadowed at top level
1384        side_effects!(
1385            test_shadowed_global_is_nan,
1386            r#"
1387            const isNaN = () => sideEffect();
1388            const result = isNaN(value);
1389            "#
1390        );
1391    }
1392
1393    mod edge_cases_tests {
1394        use super::*;
1395
1396        no_side_effects!(test_computed_property, "const obj = { [key]: value };");
1397
1398        side_effects!(
1399            test_computed_property_with_call,
1400            "const obj = { [foo()]: value };"
1401        );
1402
1403        no_side_effects!(test_spread_in_array, "const arr = [...other];");
1404
1405        no_side_effects!(test_spread_in_object, "const obj = { ...other };");
1406
1407        no_side_effects!(test_destructuring_assignment, "const { a, b } = obj;");
1408
1409        no_side_effects!(test_array_destructuring, "const [a, b] = arr;");
1410
1411        no_side_effects!(test_nested_ternary, "const x = a ? (b ? 1 : 2) : 3;");
1412
1413        no_side_effects!(test_logical_and, "const x = a && b;");
1414
1415        no_side_effects!(test_logical_or, "const x = a || b;");
1416
1417        no_side_effects!(test_nullish_coalescing, "const x = a ?? b;");
1418
1419        no_side_effects!(test_typeof_operator, "const x = typeof y;");
1420
1421        no_side_effects!(test_void_operator, "const x = void 0;");
1422
1423        // delete is impure (modifies object)
1424        side_effects!(test_delete_expression, "delete obj.prop;");
1425
1426        no_side_effects!(test_sequence_expression_pure, "const x = (1, 2, 3);");
1427
1428        side_effects!(test_sequence_expression_impure, "const x = (foo(), 2, 3);");
1429
1430        no_side_effects!(test_arrow_with_block, "const foo = () => { return 1; };");
1431
1432        no_side_effects!(
1433            test_class_with_constructor,
1434            "class Foo { constructor() { this.x = 1; } }"
1435        );
1436
1437        no_side_effects!(test_class_extends, "class Foo extends Bar {}");
1438
1439        no_side_effects!(test_async_function, "async function foo() { return 1; }");
1440
1441        no_side_effects!(test_generator_function, "function* foo() { yield 1; }");
1442
1443        // Tagged templates are function calls, so impure by default
1444        side_effects!(test_tagged_template, "const x = tag`hello`;");
1445
1446        // String.raw is known to be pure
1447        no_side_effects!(
1448            test_tagged_template_string_raw,
1449            "const x = String.raw`hello ${world}`;"
1450        );
1451
1452        no_side_effects!(test_regex_literal, "const re = /pattern/g;");
1453
1454        no_side_effects!(test_bigint_literal, "const big = 123n;");
1455
1456        no_side_effects!(test_optional_chaining_pure, "const x = obj?.prop;");
1457
1458        // Optional chaining with a call is still a call
1459        side_effects!(test_optional_chaining_call, "const x = obj?.method();");
1460
1461        no_side_effects!(
1462            test_multiple_exports_pure,
1463            "export const a = 1;\nexport const b = 2;\nexport const c = 3;"
1464        );
1465
1466        no_side_effects!(test_export_function, "export function foo() { return 1; }");
1467
1468        no_side_effects!(test_export_class, "export class Foo {}");
1469
1470        module_evaluation_is_side_effect_free!(test_reexport, "export { foo } from 'bar';");
1471
1472        // import() is a function-like expression, we allow it
1473        module_evaluation_is_side_effect_free!(
1474            test_dynamic_import,
1475            "const mod = import('./module');"
1476        );
1477
1478        module_evaluation_is_side_effect_free!(
1479            test_dynamic_import_with_await,
1480            "const mod = await import('./module');"
1481        );
1482
1483        no_side_effects!(test_export_default_expression, "export default 1 + 2;");
1484
1485        side_effects!(
1486            test_export_default_expression_with_side_effect,
1487            "export default foo();"
1488        );
1489
1490        no_side_effects!(
1491            test_export_default_function,
1492            "export default function() { return 1; }"
1493        );
1494
1495        no_side_effects!(test_export_default_class, "export default class Foo {}");
1496
1497        no_side_effects!(
1498            test_export_named_with_pure_builtin,
1499            "export const result = Math.abs(-5);"
1500        );
1501
1502        side_effects!(
1503            test_multiple_exports_mixed,
1504            "export const a = 1;\nexport const b = foo();\nexport const c = 3;"
1505        );
1506    }
1507
1508    mod pure_constructors_tests {
1509        use super::*;
1510
1511        no_side_effects!(test_new_set, "const s = new Set();");
1512
1513        no_side_effects!(test_new_map, "const m = new Map();");
1514
1515        no_side_effects!(test_new_weakset, "const ws = new WeakSet();");
1516
1517        no_side_effects!(test_new_weakmap, "const wm = new WeakMap();");
1518
1519        no_side_effects!(test_new_regexp, "const re = new RegExp('pattern');");
1520
1521        no_side_effects!(test_new_date, "const d = new Date();");
1522
1523        no_side_effects!(test_new_error, "const e = new Error('message');");
1524
1525        no_side_effects!(test_new_promise, "const p = new Promise(() => {});");
1526        side_effects!(
1527            test_new_promise_effectful,
1528            "const p = new Promise(() => {console.log('hello')});"
1529        );
1530
1531        no_side_effects!(test_new_array, "const arr = new Array(10);");
1532
1533        no_side_effects!(test_new_object, "const obj = new Object();");
1534
1535        no_side_effects!(test_new_typed_array, "const arr = new Uint8Array(10);");
1536
1537        no_side_effects!(test_new_url, "const url = new URL('https://example.com');");
1538
1539        no_side_effects!(
1540            test_new_url_search_params,
1541            "const params = new URLSearchParams();"
1542        );
1543
1544        // Pure constructor with impure arguments is impure
1545        side_effects!(
1546            test_pure_constructor_with_impure_args,
1547            "const s = new Set([foo()]);"
1548        );
1549
1550        no_side_effects!(
1551            test_multiple_pure_constructors,
1552            "const s = new Set();\nconst m = new Map();\nconst re = new RegExp('test');"
1553        );
1554
1555        // Unknown constructors are impure
1556        side_effects!(
1557            test_unknown_constructor,
1558            "const custom = new CustomClass();"
1559        );
1560
1561        side_effects!(
1562            test_mixed_constructors,
1563            "const s = new Set();\nconst custom = new CustomClass();\nconst m = new Map();"
1564        );
1565    }
1566
1567    mod shadowing_detection_tests {
1568        use super::*;
1569
1570        // Math is shadowed by a local variable, so Math.abs is not the built-in
1571        side_effects!(
1572            test_shadowed_math,
1573            r#"
1574            const Math = { abs: () => console.log('side effect') };
1575            const result = Math.abs(-5);
1576            "#
1577        );
1578
1579        // Object is shadowed at top level, so Object.keys is not the built-in
1580        side_effects!(
1581            test_shadowed_object,
1582            r#"
1583            const Object = { keys: () => sideEffect() };
1584            const result = Object.keys({});
1585            "#
1586        );
1587
1588        // Array is shadowed at top level by a local class
1589        side_effects!(
1590            test_shadowed_array_constructor,
1591            r#"
1592            const Array = class { constructor() { sideEffect(); } };
1593            const arr = new Array();
1594            "#
1595        );
1596
1597        // Set is shadowed at top level
1598        side_effects!(
1599            test_shadowed_set_constructor,
1600            r#"
1601            const Set = class { constructor() { sideEffect(); } };
1602            const s = new Set();
1603            "#
1604        );
1605
1606        // Map is shadowed in a block scope
1607        side_effects!(
1608            test_shadowed_map_constructor,
1609            r#"
1610            {
1611                const Map = class { constructor() { sideEffect(); } };
1612                const m = new Map();
1613            }
1614            "#
1615        );
1616
1617        // Math is NOT shadowed here, so Math.abs is the built-in
1618        no_side_effects!(
1619            test_global_math_not_shadowed,
1620            r#"
1621            const result = Math.abs(-5);
1622            "#
1623        );
1624
1625        // Object is NOT shadowed, so Object.keys is the built-in
1626        no_side_effects!(
1627            test_global_object_not_shadowed,
1628            r#"
1629            const keys = Object.keys({ a: 1, b: 2 });
1630            "#
1631        );
1632
1633        // Array is NOT shadowed, so new Array() is the built-in
1634        no_side_effects!(
1635            test_global_array_constructor_not_shadowed,
1636            r#"
1637            const arr = new Array(1, 2, 3);
1638            "#
1639        );
1640
1641        // If Math is imported (has a non-empty ctxt), it's not the global
1642        side_effects!(
1643            test_shadowed_by_import,
1644            r#"
1645            import { Math } from './custom-math';
1646            const result = Math.abs(-5);
1647            "#
1648        );
1649
1650        // Math is shadowed in a block scope at top level
1651        side_effects!(
1652            test_nested_scope_shadowing,
1653            r#"
1654            {
1655                const Math = { floor: () => sideEffect() };
1656                const result = Math.floor(4.5);
1657            }
1658            "#
1659        );
1660
1661        // This test shows that function declarations are pure at top level
1662        // even if they have shadowed parameters. The side effect only occurs
1663        // if the function is actually called.
1664        no_side_effects!(
1665            test_parameter_shadowing,
1666            r#"
1667            function test(RegExp) {
1668                return new RegExp('test');
1669            }
1670            "#
1671        );
1672
1673        // Number is shadowed by a var declaration
1674        side_effects!(
1675            test_shadowing_with_var,
1676            r#"
1677            var Number = { isNaN: () => sideEffect() };
1678            const check = Number.isNaN(123);
1679            "#
1680        );
1681
1682        // RegExp is NOT shadowed, constructor is pure
1683        no_side_effects!(
1684            test_global_regexp_not_shadowed,
1685            r#"
1686            const re = new RegExp('[a-z]+');
1687            "#
1688        );
1689    }
1690
1691    mod literal_receiver_methods_tests {
1692        use super::*;
1693
1694        // String literal methods
1695        no_side_effects!(
1696            test_string_literal_to_lower_case,
1697            r#"const result = "HELLO".toLowerCase();"#
1698        );
1699
1700        no_side_effects!(
1701            test_string_literal_to_upper_case,
1702            r#"const result = "hello".toUpperCase();"#
1703        );
1704
1705        no_side_effects!(
1706            test_string_literal_slice,
1707            r#"const result = "hello world".slice(0, 5);"#
1708        );
1709
1710        no_side_effects!(
1711            test_string_literal_split,
1712            r#"const result = "a,b,c".split(',');"#
1713        );
1714
1715        no_side_effects!(
1716            test_string_literal_trim,
1717            r#"const result = "  hello  ".trim();"#
1718        );
1719
1720        no_side_effects!(
1721            test_string_literal_replace,
1722            r#"const result = "hello".replace('h', 'H');"#
1723        );
1724
1725        no_side_effects!(
1726            test_string_literal_includes,
1727            r#"const result = "hello world".includes('world');"#
1728        );
1729
1730        // Array literal methods
1731        no_side_effects!(
1732            test_array_literal_map,
1733            r#"const result = [1, 2, 3].map(x => x * 2);"#
1734        );
1735        side_effects!(
1736            test_array_literal_map_with_effectful_callback,
1737            r#"const result = [1, 2, 3].map(x => {globalThis.something.push(x)});"#
1738        );
1739
1740        // Number literal methods - need parentheses for number literals
1741        no_side_effects!(
1742            test_number_literal_to_fixed,
1743            r#"const result = (3.14159).toFixed(2);"#
1744        );
1745
1746        no_side_effects!(
1747            test_number_literal_to_string,
1748            r#"const result = (42).toString();"#
1749        );
1750
1751        no_side_effects!(
1752            test_number_literal_to_exponential,
1753            r#"const result = (123.456).toExponential(2);"#
1754        );
1755
1756        // Boolean literal methods
1757        no_side_effects!(
1758            test_boolean_literal_to_string,
1759            r#"const result = true.toString();"#
1760        );
1761
1762        no_side_effects!(
1763            test_boolean_literal_value_of,
1764            r#"const result = false.valueOf();"#
1765        );
1766
1767        // RegExp literal methods
1768        no_side_effects!(
1769            test_regexp_literal_to_string,
1770            r#"const result = /[a-z]+/.toString();"#
1771        );
1772
1773        // Note: test() and exec() technically modify flags on the regex, but that is fine when
1774        // called on a literal.
1775        no_side_effects!(
1776            test_regexp_literal_test,
1777            r#"const result = /[a-z]+/g.test("hello");"#
1778        );
1779
1780        no_side_effects!(
1781            test_regexp_literal_exec,
1782            r#"const result = /(\d+)/g.exec("test123");"#
1783        );
1784
1785        // Array literal with impure elements - the array construction itself has side effects
1786        // because foo() is called when creating the array
1787        side_effects!(
1788            test_array_literal_with_impure_elements,
1789            r#"const result = [foo(), 2, 3].map(x => x * 2);"#
1790        );
1791
1792        // Array literal with callback that would have side effects when called
1793        // However, callbacks are just function definitions at module load time
1794        // They don't execute until runtime, so this is side-effect free at load time
1795        no_side_effects!(
1796            test_array_literal_map_with_callback,
1797            r#"const result = [1, 2, 3].map(x => x * 2);"#
1798        );
1799    }
1800
1801    mod class_expression_side_effects_tests {
1802        use super::*;
1803
1804        // Class with no extends and no static members is pure
1805        no_side_effects!(test_class_no_extends_no_static, "class Foo {}");
1806
1807        // Class with pure extends is pure
1808        no_side_effects!(test_class_pure_extends, "class Foo extends Bar {}");
1809
1810        // Class with function call in extends clause has side effects
1811        side_effects!(
1812            test_class_extends_with_call,
1813            "class Foo extends someMixinFunction() {}"
1814        );
1815
1816        // Class with complex expression in extends clause has side effects
1817        side_effects!(
1818            test_class_extends_with_complex_expr,
1819            "class Foo extends (Bar || Baz()) {}"
1820        );
1821
1822        // Class with static property initializer that calls function has side effects
1823        side_effects!(
1824            test_class_static_property_with_call,
1825            r#"
1826        class Foo {
1827            static foo = someFunction();
1828        }
1829        "#
1830        );
1831
1832        // Class with static property with pure initializer is pure
1833        no_side_effects!(
1834            test_class_static_property_pure,
1835            r#"
1836        class Foo {
1837            static foo = 42;
1838        }
1839        "#
1840        );
1841
1842        // Class with static property with array literal is pure
1843        no_side_effects!(
1844            test_class_static_property_array_literal,
1845            r#"
1846        class Foo {
1847            static foo = [1, 2, 3];
1848        }
1849        "#
1850        );
1851
1852        // Class with static block has side effects
1853        side_effects!(
1854            test_class_static_block,
1855            r#"
1856        class Foo {
1857            static {
1858                console.log("hello");
1859            }
1860        }
1861        "#
1862        );
1863
1864        no_side_effects!(
1865            test_class_static_block_empty,
1866            r#"
1867        class Foo {
1868            static {}
1869        }
1870        "#
1871        );
1872
1873        // Class with instance property is pure (doesn't execute at definition time)
1874        no_side_effects!(
1875            test_class_instance_property_with_call,
1876            r#"
1877        class Foo {
1878            foo = someFunction();
1879        }
1880        "#
1881        );
1882
1883        // Class with constructor is pure (doesn't execute at definition time)
1884        no_side_effects!(
1885            test_class_constructor_with_side_effects,
1886            r#"
1887        class Foo {
1888            constructor() {
1889                console.log("constructor");
1890            }
1891        }
1892        "#
1893        );
1894
1895        // Class with method is pure (doesn't execute at definition time)
1896        no_side_effects!(
1897            test_class_method,
1898            r#"
1899        class Foo {
1900            method() {
1901                console.log("method");
1902            }
1903        }
1904        "#
1905        );
1906
1907        // Class expression with side effects in extends
1908        side_effects!(
1909            test_class_expr_extends_with_call,
1910            "const Foo = class extends getMixin() {};"
1911        );
1912
1913        // Class expression with static property calling function
1914        side_effects!(
1915            test_class_expr_static_with_call,
1916            r#"
1917        const Foo = class {
1918            static prop = initValue();
1919        };
1920        "#
1921        );
1922
1923        // Class expression with pure static property
1924        no_side_effects!(
1925            test_class_expr_static_pure,
1926            r#"
1927        const Foo = class {
1928            static prop = "hello";
1929        };
1930        "#
1931        );
1932
1933        // Export class with side effects
1934        side_effects!(
1935            test_export_class_with_side_effects,
1936            r#"
1937        export class Foo extends getMixin() {
1938            static prop = init();
1939        }
1940        "#
1941        );
1942
1943        // Export default class with side effects
1944        side_effects!(
1945            test_export_default_class_with_side_effects,
1946            r#"
1947        export default class Foo {
1948            static { console.log("init"); }
1949        }
1950        "#
1951        );
1952
1953        // Export class without side effects
1954        no_side_effects!(
1955            test_export_class_no_side_effects,
1956            r#"
1957        export class Foo {
1958            method() {
1959                console.log("method");
1960            }
1961        }
1962        "#
1963        );
1964
1965        // Multiple static properties, some pure, some not
1966        side_effects!(
1967            test_class_mixed_static_properties,
1968            r#"
1969        class Foo {
1970            static a = 1;
1971            static b = impureCall();
1972            static c = 3;
1973        }
1974        "#
1975        );
1976
1977        // Class with pure static property using known pure built-in
1978        no_side_effects!(
1979            test_class_static_property_pure_builtin,
1980            r#"
1981        class Foo {
1982            static value = Math.abs(-5);
1983        }
1984        "#
1985        );
1986
1987        // Class with computed property name that has side effects
1988        side_effects!(
1989            test_class_computed_property_with_call,
1990            r#"
1991        class Foo {
1992            [computeName()]() {
1993                return 42;
1994            }
1995        }
1996        "#
1997        );
1998
1999        // Class with pure computed property name
2000        no_side_effects!(
2001            test_class_computed_property_pure,
2002            r#"
2003        class Foo {
2004            ['method']() {
2005                return 42;
2006            }
2007        }
2008        "#
2009        );
2010    }
2011
2012    mod complex_variable_declarations_tests {
2013        use super::*;
2014
2015        // Simple destructuring without defaults is pure
2016        no_side_effects!(test_destructure_simple, "const { foo } = obj;");
2017
2018        // Destructuring with function call in default value has side effects
2019        side_effects!(
2020            test_destructure_default_with_call,
2021            "const { foo = someFunction() } = obj;"
2022        );
2023
2024        // Destructuring with pure default value is pure
2025        no_side_effects!(test_destructure_default_pure, "const { foo = 42 } = obj;");
2026
2027        // Destructuring with array literal default is pure
2028        no_side_effects!(
2029            test_destructure_default_array_literal,
2030            "const { foo = ['hello'] } = obj;"
2031        );
2032
2033        // Destructuring with object literal default is pure
2034        no_side_effects!(
2035            test_destructure_default_object_literal,
2036            "const { foo = { bar: 'baz' } } = obj;"
2037        );
2038
2039        // Nested destructuring with default that has side effect
2040        side_effects!(
2041            test_destructure_nested_with_call,
2042            "const { a: { b = sideEffect() } } = obj;"
2043        );
2044
2045        // Array destructuring with default that has side effect
2046        side_effects!(
2047            test_array_destructure_default_with_call,
2048            "const [a, b = getDefault()] = arr;"
2049        );
2050
2051        // Array destructuring with pure default
2052        no_side_effects!(
2053            test_array_destructure_default_pure,
2054            "const [a, b = 10] = arr;"
2055        );
2056
2057        // Multiple variables, one with side effect in default
2058        side_effects!(
2059            test_multiple_destructure_mixed,
2060            "const { foo = 1, bar = compute() } = obj;"
2061        );
2062
2063        // Rest pattern is pure
2064        no_side_effects!(test_destructure_rest_pure, "const { foo, ...rest } = obj;");
2065
2066        // Complex destructuring with multiple levels
2067        side_effects!(
2068            test_destructure_complex_with_side_effect,
2069            r#"
2070        const {
2071            a,
2072            b: { c = sideEffect() },
2073            d = [1, 2, 3]
2074        } = obj;
2075        "#
2076        );
2077
2078        // Complex destructuring all pure
2079        no_side_effects!(
2080            test_destructure_complex_pure,
2081            r#"
2082        const {
2083            a,
2084            b: { c = 5 },
2085            d = [1, 2, 3]
2086        } = obj;
2087        "#
2088        );
2089
2090        // Destructuring in export with side effect
2091        side_effects!(
2092            test_export_destructure_with_side_effect,
2093            "export const { foo = init() } = obj;"
2094        );
2095
2096        // Destructuring in export without side effect
2097        no_side_effects!(
2098            test_export_destructure_pure,
2099            "export const { foo = 42 } = obj;"
2100        );
2101
2102        // Default value with known pure built-in
2103        no_side_effects!(
2104            test_destructure_default_pure_builtin,
2105            "const { foo = Math.abs(-5) } = obj;"
2106        );
2107
2108        // Default value with pure annotation
2109        no_side_effects!(
2110            test_destructure_default_pure_annotation,
2111            "const { foo = /*#__PURE__*/ compute() } = obj;"
2112        );
2113    }
2114
2115    mod decorator_side_effects_tests {
2116        use super::*;
2117
2118        // Class decorator has side effects (executes at definition time)
2119        side_effects!(
2120            test_class_decorator,
2121            r#"
2122        @decorator
2123        class Foo {}
2124        "#
2125        );
2126
2127        // Method decorator has side effects
2128        side_effects!(
2129            test_method_decorator,
2130            r#"
2131        class Foo {
2132            @decorator
2133            method() {}
2134        }
2135        "#
2136        );
2137
2138        // Property decorator has side effects
2139        side_effects!(
2140            test_property_decorator,
2141            r#"
2142        class Foo {
2143            @decorator
2144            prop = 1;
2145        }
2146        "#
2147        );
2148
2149        // Multiple decorators
2150        side_effects!(
2151            test_multiple_decorators,
2152            r#"
2153        @decorator1
2154        @decorator2
2155        class Foo {
2156            @propDecorator
2157            prop = 1;
2158
2159            @methodDecorator
2160            method() {}
2161        }
2162        "#
2163        );
2164
2165        // Decorator with arguments
2166        side_effects!(
2167            test_decorator_with_args,
2168            r#"
2169        @decorator(config())
2170        class Foo {}
2171        "#
2172        );
2173    }
2174
2175    mod additional_edge_cases_tests {
2176        use super::*;
2177
2178        // Super property access is pure
2179        no_side_effects!(
2180            test_super_property_pure,
2181            r#"
2182        class Foo extends Bar {
2183            method() {
2184                return super.parentMethod;
2185            }
2186        }
2187        "#
2188        );
2189
2190        // Super method call has side effects (but only when invoked, not at definition)
2191        no_side_effects!(
2192            test_super_call_in_method,
2193            r#"
2194        class Foo extends Bar {
2195            method() {
2196                return super.parentMethod();
2197            }
2198        }
2199        "#
2200        );
2201
2202        // import.meta is pure
2203        no_side_effects!(test_import_meta, "const url = import.meta.url;");
2204
2205        // new.target is pure (only valid inside functions/constructors)
2206        no_side_effects!(
2207            test_new_target,
2208            r#"
2209        function Foo() {
2210            console.log(new.target);
2211        }
2212        "#
2213        );
2214
2215        // JSX element has side effects (compiles to function calls)
2216        side_effects!(test_jsx_element, "const el = <div>Hello</div>;");
2217
2218        // JSX fragment has side effects
2219        side_effects!(test_jsx_fragment, "const el = <>Hello</>;");
2220
2221        // Private field access is pure
2222        no_side_effects!(
2223            test_private_field_access,
2224            r#"
2225        class Foo {
2226            #privateField = 42;
2227            method() {
2228                return this.#privateField;
2229            }
2230        }
2231        "#
2232        );
2233
2234        // Computed super property with side effect
2235        no_side_effects!(
2236            test_super_computed_property_pure,
2237            r#"
2238        class Foo extends Bar {
2239            method() {
2240                return super['prop'];
2241            }
2242        }
2243        "#
2244        );
2245
2246        // Static block with only pure statements is pure
2247        no_side_effects!(
2248            test_static_block_pure_content,
2249            r#"
2250        class Foo {
2251            static {
2252                const x = 1;
2253                const y = 2;
2254            }
2255        }
2256        "#
2257        );
2258
2259        // Static block with side effect
2260        side_effects!(
2261            test_static_block_with_side_effect_inside,
2262            r#"
2263        class Foo {
2264            static {
2265                sideEffect();
2266            }
2267        }
2268        "#
2269        );
2270
2271        // This binding is pure
2272        no_side_effects!(
2273            test_this_expression,
2274            r#"
2275        class Foo {
2276            method() {
2277                return this;
2278            }
2279        }
2280        "#
2281        );
2282
2283        // Spread in call arguments (with pure expression)
2284        no_side_effects!(
2285            test_spread_pure_in_call,
2286            "const result = Math.max(...[1, 2, 3]);"
2287        );
2288
2289        // Spread in call arguments (with side effect)
2290        side_effects!(
2291            test_spread_with_side_effect,
2292            "const result = Math.max(...getArray());"
2293        );
2294
2295        // Complex super expression
2296        no_side_effects!(
2297            test_super_complex_access,
2298            r#"
2299        class Foo extends Bar {
2300            static method() {
2301                return super.parentMethod;
2302            }
2303        }
2304        "#
2305        );
2306
2307        // Getter/setter definitions are pure
2308        no_side_effects!(
2309            test_getter_definition,
2310            r#"
2311        const obj = {
2312            get foo() {
2313                return this._foo;
2314            }
2315        };
2316        "#
2317        );
2318
2319        // Async function declaration is pure
2320        no_side_effects!(
2321            test_async_function_declaration,
2322            r#"
2323        async function foo() {
2324            return await something;
2325        }
2326        "#
2327        );
2328
2329        // Generator function declaration is pure
2330        no_side_effects!(
2331            test_generator_declaration,
2332            r#"
2333        function* foo() {
2334            yield 1;
2335            yield 2;
2336        }
2337        "#
2338        );
2339
2340        // Async generator is pure
2341        no_side_effects!(
2342            test_async_generator,
2343            r#"
2344        async function* foo() {
2345            yield await something;
2346        }
2347        "#
2348        );
2349
2350        // Using declaration (TC39 proposal) - if supported
2351        // This would need to be handled if the parser supports it
2352
2353        // Nullish coalescing with side effects in right operand
2354        side_effects!(
2355            test_nullish_coalescing_with_side_effect,
2356            "const x = a ?? sideEffect();"
2357        );
2358
2359        // Logical OR with side effects
2360        side_effects!(
2361            test_logical_or_with_side_effect,
2362            "const x = a || sideEffect();"
2363        );
2364
2365        // Logical AND with side effects
2366        side_effects!(
2367            test_logical_and_with_side_effect,
2368            "const x = a && sideEffect();"
2369        );
2370    }
2371
2372    mod common_js_modules_tests {
2373        use super::*;
2374
2375        side_effects!(test_common_js_exports, "exports.foo = 'a'");
2376        side_effects!(test_common_js_exports_module, "module.exports.foo = 'a'");
2377        side_effects!(test_common_js_exports_assignment, "module.exports = {}");
2378    }
2379}