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                } else {
762                    self.mark_side_effect();
763                }
764            }
765            Expr::OptChain(opt_chain) => {
766                // Optional chaining can be pure if it's just member access
767                // But if it's an optional call, it has side effects
768                opt_chain.base.visit_with(self);
769            }
770            Expr::Seq(seq) => {
771                // Sequence expressions - check each expression
772                seq.exprs.visit_children_with(self);
773            }
774            Expr::SuperProp(super_prop) => {
775                // Super property access is pure (reading from parent class)
776                // Check if the property expression has side effects
777                super_prop.prop.visit_with(self);
778            }
779            Expr::MetaProp(_) => {
780                // Meta properties like import.meta and new.target are pure
781                // They just read metadata, don't cause side effects
782            }
783            Expr::JSXMember(_) | Expr::JSXNamespacedName(_) | Expr::JSXEmpty(_) => {
784                // JSX member expressions and names are pure (they're just identifiers)
785            }
786            Expr::JSXElement(_) | Expr::JSXFragment(_) => {
787                // JSX elements compile to function calls (React.createElement, etc.)
788                // These are side effect free but we don't technically know at this point that it is
789                // react (could be solid or qwik or millionjs).  In any case it doesn't matter too
790                // much since it is weird to construct jsx at the module scope.
791                self.mark_side_effect();
792            }
793            Expr::PrivateName(_) => {
794                // Private names are pure (just identifiers)
795            }
796
797            // Be conservative for other expression types and just assume they are effectful
798            _ => {
799                self.mark_side_effect();
800            }
801        }
802    }
803
804    fn visit_opt_chain_base(&mut self, base: &OptChainBase) {
805        check_side_effects!(self);
806
807        match base {
808            OptChainBase::Member(member) => {
809                member.visit_with(self);
810            }
811            OptChainBase::Call(_opt_call) => {
812                // Optional calls are still calls, so impure
813                // We could maybe support some of these `(foo_enabled? undefined :
814                // [])?.map(...)` but this seems pretty theoretical
815                self.mark_side_effect();
816            }
817        }
818    }
819
820    fn visit_prop_or_spread(&mut self, prop: &PropOrSpread) {
821        check_side_effects!(self);
822
823        match prop {
824            PropOrSpread::Spread(spread) => {
825                spread.expr.visit_with(self);
826            }
827            PropOrSpread::Prop(prop) => {
828                prop.visit_with(self);
829            }
830        }
831    }
832
833    fn visit_prop(&mut self, prop: &Prop) {
834        check_side_effects!(self);
835
836        match prop {
837            Prop::KeyValue(kv) => {
838                kv.key.visit_with(self);
839                kv.value.visit_with(self);
840            }
841            Prop::Getter(getter) => {
842                getter.key.visit_with(self);
843                // Body is not executed at definition time
844            }
845            Prop::Setter(setter) => {
846                setter.key.visit_with(self);
847                // Body is not executed at definition time
848            }
849            Prop::Method(method) => {
850                method.key.visit_with(self);
851                // Body is not executed at definition time
852            }
853            Prop::Shorthand(_) => {
854                // Shorthand properties are pure
855            }
856            Prop::Assign(_) => {
857                // Assignment properties (used in object rest/spread patterns)
858                // are side-effect free at definition
859            }
860        }
861    }
862
863    fn visit_prop_name(&mut self, prop_name: &PropName) {
864        check_side_effects!(self);
865
866        match prop_name {
867            PropName::Computed(computed) => {
868                // Computed property names need evaluation
869                computed.expr.visit_with(self);
870            }
871            _ => {
872                // Other property names are pure
873            }
874        }
875    }
876
877    fn visit_class(&mut self, class: &Class) {
878        check_side_effects!(self);
879
880        // Check decorators - they execute at definition time
881        for decorator in &class.decorators {
882            decorator.visit_with(self);
883        }
884
885        // Check the extends clause - this is evaluated at definition time
886        if let Some(super_class) = &class.super_class {
887            super_class.visit_with(self);
888        }
889
890        // Check class body for static members
891        for member in &class.body {
892            member.visit_with(self);
893        }
894    }
895
896    fn visit_class_member(&mut self, member: &ClassMember) {
897        check_side_effects!(self);
898
899        match member {
900            // Static blocks execute at class definition time
901            ClassMember::StaticBlock(block) => {
902                // Static blocks may have side effects because they execute immediately
903                // Check the statements in the block
904                for stmt in &block.body.stmts {
905                    stmt.visit_with(self);
906                }
907            }
908            // Check static properties - they execute at definition time
909            ClassMember::ClassProp(class_prop) if class_prop.is_static => {
910                // Check decorators - they execute at definition time
911                for decorator in &class_prop.decorators {
912                    decorator.visit_with(self);
913                }
914                // Check the property key (for computed properties)
915                class_prop.key.visit_with(self);
916                // Check the initializer - static property initializers execute at definition time
917                if let Some(value) = &class_prop.value {
918                    value.visit_with(self);
919                }
920            }
921            // Check computed property keys for all members
922            ClassMember::Method(method) => {
923                // Check decorators - they execute at definition time
924                for decorator in &method.function.decorators {
925                    decorator.visit_with(self);
926                }
927                method.key.visit_with(self);
928                // Method bodies don't execute at definition time
929            }
930            ClassMember::Constructor(constructor) => {
931                constructor.key.visit_with(self);
932                // Constructor body doesn't execute at definition time
933            }
934            ClassMember::PrivateMethod(private_method) => {
935                // Check decorators - they execute at definition time
936                for decorator in &private_method.function.decorators {
937                    decorator.visit_with(self);
938                }
939                private_method.key.visit_with(self);
940                // Method bodies don't execute at definition time
941            }
942            ClassMember::ClassProp(class_prop) => {
943                // Check decorators - they execute at definition time
944                for decorator in &class_prop.decorators {
945                    decorator.visit_with(self);
946                }
947                // For non-static properties, only check the key
948                class_prop.key.visit_with(self);
949                // Instance property initializers don't execute at definition time
950            }
951            ClassMember::PrivateProp(private_prop) => {
952                // Check decorators - they execute at definition time
953                for decorator in &private_prop.decorators {
954                    decorator.visit_with(self);
955                }
956                private_prop.key.visit_with(self);
957                // Instance property initializers don't execute at definition time
958            }
959            ClassMember::AutoAccessor(auto_accessor) if auto_accessor.is_static => {
960                // Check decorators - they execute at definition time
961                for decorator in &auto_accessor.decorators {
962                    decorator.visit_with(self);
963                }
964                // Static auto accessors execute at definition time
965                auto_accessor.key.visit_with(self);
966                if let Some(value) = &auto_accessor.value {
967                    value.visit_with(self);
968                }
969            }
970            ClassMember::AutoAccessor(auto_accessor) => {
971                // Check decorators - they execute at definition time
972                for decorator in &auto_accessor.decorators {
973                    decorator.visit_with(self);
974                }
975                // Non-static auto accessors only check the key
976                auto_accessor.key.visit_with(self);
977            }
978            ClassMember::Empty(_) => {
979                // Empty members are pure
980            }
981            ClassMember::TsIndexSignature(_) => {
982                // TypeScript index signatures are pure
983            }
984        }
985    }
986
987    fn visit_decorator(&mut self, _decorator: &Decorator) {
988        if self.has_side_effects {
989            return;
990        }
991
992        // Decorators always have side effects because they are function calls
993        // that execute at class/member definition time, even if they're just
994        // identifier references (e.g., @decorator is equivalent to calling decorator())
995        self.mark_side_effect();
996    }
997
998    fn visit_pat(&mut self, pat: &Pat) {
999        check_side_effects!(self);
1000
1001        match pat {
1002            // Object patterns with default values need checking
1003            Pat::Object(object_pat) => {
1004                for prop in &object_pat.props {
1005                    match prop {
1006                        ObjectPatProp::KeyValue(kv) => {
1007                            // Check the key (for computed properties)
1008                            kv.key.visit_with(self);
1009                            // Recursively check the value pattern
1010                            kv.value.visit_with(self);
1011                        }
1012                        ObjectPatProp::Assign(assign) => {
1013                            // Check the default value if present
1014                            if let Some(value) = &assign.value {
1015                                value.visit_with(self);
1016                            }
1017                        }
1018                        ObjectPatProp::Rest(rest) => {
1019                            // Rest patterns are pure, but check the nested pattern
1020                            rest.arg.visit_with(self);
1021                        }
1022                    }
1023                }
1024            }
1025            // Array patterns with default values need checking
1026            Pat::Array(array_pat) => {
1027                for elem in array_pat.elems.iter().flatten() {
1028                    elem.visit_with(self);
1029                }
1030            }
1031            // Assignment patterns (destructuring with defaults) need checking
1032            Pat::Assign(assign_pat) => {
1033                // Check the default value - this is evaluated if the value is undefined
1034                assign_pat.right.visit_with(self);
1035                // Also check the left side pattern
1036                assign_pat.left.visit_with(self);
1037            }
1038            // Other patterns are pure
1039            _ => {}
1040        }
1041    }
1042}
1043
1044#[cfg(test)]
1045mod tests {
1046    use swc_core::{
1047        common::{FileName, GLOBALS, Mark, SourceMap, comments::SingleThreadedComments, sync::Lrc},
1048        ecma::{
1049            ast::EsVersion,
1050            parser::{EsSyntax, Syntax, parse_file_as_program},
1051            transforms::base::resolver,
1052            visit::VisitMutWith,
1053        },
1054    };
1055
1056    use super::*;
1057
1058    /// Helper function to parse JavaScript code from a string and run the resolver
1059    fn parse_and_check_for_side_effects(code: &str, expected: ModuleSideEffects) {
1060        GLOBALS.set(&Default::default(), || {
1061            let cm = Lrc::new(SourceMap::default());
1062            let fm = cm.new_source_file(Lrc::new(FileName::Anon), code.to_string());
1063
1064            let comments = SingleThreadedComments::default();
1065            let mut errors = vec![];
1066
1067            let mut program = parse_file_as_program(
1068                &fm,
1069                Syntax::Es(EsSyntax {
1070                    jsx: true,
1071                    decorators: true,
1072                    ..Default::default()
1073                }),
1074                EsVersion::latest(),
1075                Some(&comments),
1076                &mut errors,
1077            )
1078            .expect("Failed to parse");
1079
1080            // Run the resolver to mark unresolved identifiers
1081            let unresolved_mark = Mark::new();
1082            let top_level_mark = Mark::new();
1083            program.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
1084
1085            let actual =
1086                compute_module_evaluation_side_effects(&program, &comments, unresolved_mark);
1087
1088            let msg = match expected {
1089                ModuleSideEffects::ModuleEvaluationIsSideEffectFree => {
1090                    "Expected code to have no local side effects"
1091                }
1092                ModuleSideEffects::SideEffectFree => "Expected code to be side effect free",
1093                ModuleSideEffects::SideEffectful => "Expected code to have side effects",
1094            };
1095            assert_eq!(actual, expected, "{}:\n{}", msg, code);
1096        })
1097    }
1098
1099    /// Generate a test that asserts the given code has the expected side effect status
1100    macro_rules! assert_side_effects {
1101        ($name:ident, $code:expr, $expected:expr) => {
1102            #[test]
1103            fn $name() {
1104                parse_and_check_for_side_effects($code, $expected);
1105            }
1106        };
1107    }
1108
1109    macro_rules! side_effects {
1110        ($name:ident, $code:expr) => {
1111            assert_side_effects!($name, $code, ModuleSideEffects::SideEffectful);
1112        };
1113    }
1114
1115    macro_rules! no_side_effects {
1116        ($name:ident, $code:expr) => {
1117            assert_side_effects!($name, $code, ModuleSideEffects::SideEffectFree);
1118        };
1119    }
1120    macro_rules! module_evaluation_is_side_effect_free {
1121        ($name:ident, $code:expr) => {
1122            assert_side_effects!(
1123                $name,
1124                $code,
1125                ModuleSideEffects::ModuleEvaluationIsSideEffectFree
1126            );
1127        };
1128    }
1129
1130    mod basic_tests {
1131        use super::*;
1132
1133        no_side_effects!(test_empty_program, "");
1134
1135        no_side_effects!(test_simple_const_declaration, "const x = 5;");
1136
1137        no_side_effects!(test_simple_let_declaration, "let y = 'string';");
1138
1139        no_side_effects!(test_array_literal, "const arr = [1, 2, 3];");
1140
1141        no_side_effects!(test_object_literal, "const obj = { a: 1, b: 2 };");
1142
1143        no_side_effects!(test_function_declaration, "function foo() { return 1; }");
1144
1145        no_side_effects!(
1146            test_function_expression,
1147            "const foo = function() { return 1; };"
1148        );
1149
1150        no_side_effects!(test_arrow_function, "const foo = () => 1;");
1151    }
1152
1153    mod side_effects_tests {
1154        use super::*;
1155
1156        side_effects!(test_console_log, "console.log('hello');");
1157
1158        side_effects!(test_function_call, "foo();");
1159
1160        side_effects!(test_method_call, "obj.method();");
1161
1162        side_effects!(test_assignment, "x = 5;");
1163
1164        side_effects!(test_member_assignment, "obj.prop = 5;");
1165
1166        side_effects!(test_constructor_call, "new SideEffect();");
1167
1168        side_effects!(test_update_expression, "x++;");
1169    }
1170
1171    mod pure_expressions_tests {
1172        use super::*;
1173
1174        no_side_effects!(test_binary_expression, "const x = 1 + 2;");
1175
1176        no_side_effects!(test_unary_expression, "const x = -5;");
1177
1178        no_side_effects!(test_conditional_expression, "const x = true ? 1 : 2;");
1179
1180        no_side_effects!(test_template_literal, "const x = `hello ${world}`;");
1181
1182        no_side_effects!(test_nested_object, "const obj = { a: { b: { c: 1 } } };");
1183
1184        no_side_effects!(test_nested_array, "const arr = [[1, 2], [3, 4]];");
1185    }
1186
1187    mod import_export_tests {
1188        use super::*;
1189
1190        module_evaluation_is_side_effect_free!(test_import_statement, "import x from 'y';");
1191        module_evaluation_is_side_effect_free!(test_require_statement, "const x = require('y');");
1192
1193        no_side_effects!(test_export_statement, "export default 5;");
1194
1195        no_side_effects!(test_export_const, "export const x = 5;");
1196
1197        side_effects!(
1198            test_export_const_with_side_effect,
1199            "export const x = foo();"
1200        );
1201    }
1202
1203    mod mixed_cases_tests {
1204        use super::*;
1205
1206        side_effects!(test_call_in_initializer, "const x = foo();");
1207
1208        side_effects!(test_call_in_array, "const arr = [1, foo(), 3];");
1209
1210        side_effects!(test_call_in_object, "const obj = { a: foo() };");
1211
1212        no_side_effects!(
1213            test_multiple_declarations_pure,
1214            "const x = 1;\nconst y = 2;\nconst z = 3;"
1215        );
1216
1217        side_effects!(
1218            test_multiple_declarations_with_side_effect,
1219            "const x = 1;\nfoo();\nconst z = 3;"
1220        );
1221
1222        no_side_effects!(test_class_declaration, "class Foo {}");
1223
1224        no_side_effects!(
1225            test_class_with_methods,
1226            "class Foo { method() { return 1; } }"
1227        );
1228    }
1229
1230    mod pure_annotations_tests {
1231        use super::*;
1232
1233        no_side_effects!(test_pure_annotation_function_call, "/*#__PURE__*/ foo();");
1234
1235        no_side_effects!(test_pure_annotation_with_at, "/*@__PURE__*/ foo();");
1236
1237        no_side_effects!(test_pure_annotation_constructor, "/*#__PURE__*/ new Foo();");
1238
1239        no_side_effects!(
1240            test_pure_annotation_in_variable,
1241            "const x = /*#__PURE__*/ foo();"
1242        );
1243
1244        no_side_effects!(
1245            test_pure_annotation_with_pure_args,
1246            "/*#__PURE__*/ foo(1, 2, 3);"
1247        );
1248
1249        // Even with PURE annotation, impure arguments make it impure
1250        side_effects!(
1251            test_pure_annotation_with_impure_args,
1252            "/*#__PURE__*/ foo(bar());"
1253        );
1254
1255        // Without annotation, calls are impure
1256        side_effects!(test_without_pure_annotation, "foo();");
1257
1258        no_side_effects!(
1259            test_pure_nested_in_object,
1260            "const obj = { x: /*#__PURE__*/ foo() };"
1261        );
1262
1263        no_side_effects!(test_pure_in_array, "const arr = [/*#__PURE__*/ foo()];");
1264
1265        no_side_effects!(
1266            test_multiple_pure_calls,
1267            "const x = /*#__PURE__*/ foo();\nconst y = /*#__PURE__*/ bar();"
1268        );
1269
1270        side_effects!(
1271            test_mixed_pure_and_impure,
1272            "const x = /*#__PURE__*/ foo();\nbar();\nconst z = /*#__PURE__*/ baz();"
1273        );
1274    }
1275
1276    mod known_pure_builtins_tests {
1277        use super::*;
1278
1279        no_side_effects!(test_math_abs, "const x = Math.abs(-5);");
1280
1281        no_side_effects!(test_math_floor, "const x = Math.floor(3.14);");
1282
1283        no_side_effects!(test_math_max, "const x = Math.max(1, 2, 3);");
1284
1285        no_side_effects!(test_object_keys, "const keys = Object.keys(obj);");
1286
1287        no_side_effects!(test_object_values, "const values = Object.values(obj);");
1288
1289        no_side_effects!(test_object_entries, "const entries = Object.entries(obj);");
1290
1291        no_side_effects!(test_array_is_array, "const result = Array.isArray([]);");
1292
1293        no_side_effects!(
1294            test_string_from_char_code,
1295            "const char = String.fromCharCode(65);"
1296        );
1297
1298        no_side_effects!(test_number_is_nan, "const result = Number.isNaN(x);");
1299
1300        no_side_effects!(
1301            test_multiple_math_calls,
1302            "const x = Math.abs(-5);\nconst y = Math.floor(3.14);\nconst z = Math.max(x, y);"
1303        );
1304
1305        // Even pure builtins become impure if arguments are impure
1306        side_effects!(
1307            test_pure_builtin_with_impure_arg,
1308            "const x = Math.abs(foo());"
1309        );
1310
1311        no_side_effects!(
1312            test_pure_builtin_in_expression,
1313            "const x = Math.abs(-5) + Math.floor(3.14);"
1314        );
1315
1316        side_effects!(
1317            test_mixed_builtin_and_impure,
1318            "const x = Math.abs(-5);\nfoo();\nconst z = Object.keys({});"
1319        );
1320
1321        // Accessing unknown Math properties is not in our list
1322        side_effects!(test_unknown_math_property, "const x = Math.random();");
1323
1324        // Object.assign is NOT pure (it mutates)
1325        side_effects!(test_object_assign, "Object.assign(target, source);");
1326
1327        no_side_effects!(test_array_from, "const arr = Array.from(iterable);");
1328
1329        no_side_effects!(test_global_is_nan, "const result = isNaN(value);");
1330
1331        no_side_effects!(test_global_is_finite, "const result = isFinite(value);");
1332
1333        no_side_effects!(test_global_parse_int, "const num = parseInt('42', 10);");
1334
1335        no_side_effects!(test_global_parse_float, "const num = parseFloat('3.14');");
1336
1337        no_side_effects!(
1338            test_global_decode_uri,
1339            "const decoded = decodeURI(encoded);"
1340        );
1341
1342        no_side_effects!(
1343            test_global_decode_uri_component,
1344            "const decoded = decodeURIComponent(encoded);"
1345        );
1346
1347        // String() as a function (not constructor) is pure
1348        no_side_effects!(
1349            test_global_string_constructor_as_function,
1350            "const str = String(123);"
1351        );
1352
1353        // Number() as a function (not constructor) is pure
1354        no_side_effects!(
1355            test_global_number_constructor_as_function,
1356            "const num = Number('123');"
1357        );
1358
1359        // Boolean() as a function (not constructor) is pure
1360        no_side_effects!(
1361            test_global_boolean_constructor_as_function,
1362            "const bool = Boolean(value);"
1363        );
1364
1365        // Symbol() as a function is pure
1366        no_side_effects!(
1367            test_global_symbol_constructor_as_function,
1368            "const sym = Symbol('description');"
1369        );
1370
1371        // Symbol.for() is pure
1372        no_side_effects!(test_symbol_for, "const sym = Symbol.for('description');");
1373
1374        // Symbol.keyFor() is pure
1375        no_side_effects!(
1376            test_symbol_key_for,
1377            "const description = Symbol.keyFor(sym);"
1378        );
1379
1380        // Global pure function with impure argument is impure
1381        side_effects!(
1382            test_global_pure_with_impure_arg,
1383            "const result = isNaN(foo());"
1384        );
1385
1386        // isNaN shadowed at top level
1387        side_effects!(
1388            test_shadowed_global_is_nan,
1389            r#"
1390            const isNaN = () => sideEffect();
1391            const result = isNaN(value);
1392            "#
1393        );
1394    }
1395
1396    mod edge_cases_tests {
1397        use super::*;
1398
1399        no_side_effects!(test_computed_property, "const obj = { [key]: value };");
1400
1401        side_effects!(
1402            test_computed_property_with_call,
1403            "const obj = { [foo()]: value };"
1404        );
1405
1406        no_side_effects!(test_spread_in_array, "const arr = [...other];");
1407
1408        no_side_effects!(test_spread_in_object, "const obj = { ...other };");
1409
1410        no_side_effects!(test_destructuring_assignment, "const { a, b } = obj;");
1411
1412        no_side_effects!(test_array_destructuring, "const [a, b] = arr;");
1413
1414        no_side_effects!(test_nested_ternary, "const x = a ? (b ? 1 : 2) : 3;");
1415
1416        no_side_effects!(test_logical_and, "const x = a && b;");
1417
1418        no_side_effects!(test_logical_or, "const x = a || b;");
1419
1420        no_side_effects!(test_nullish_coalescing, "const x = a ?? b;");
1421
1422        no_side_effects!(test_typeof_operator, "const x = typeof y;");
1423
1424        no_side_effects!(test_void_operator, "const x = void 0;");
1425
1426        // delete is impure (modifies object)
1427        side_effects!(test_delete_expression, "delete obj.prop;");
1428
1429        no_side_effects!(test_sequence_expression_pure, "const x = (1, 2, 3);");
1430
1431        side_effects!(test_sequence_expression_impure, "const x = (foo(), 2, 3);");
1432
1433        no_side_effects!(test_arrow_with_block, "const foo = () => { return 1; };");
1434
1435        no_side_effects!(
1436            test_class_with_constructor,
1437            "class Foo { constructor() { this.x = 1; } }"
1438        );
1439
1440        no_side_effects!(test_class_extends, "class Foo extends Bar {}");
1441
1442        no_side_effects!(test_async_function, "async function foo() { return 1; }");
1443
1444        no_side_effects!(test_generator_function, "function* foo() { yield 1; }");
1445
1446        // Tagged templates are function calls, so impure by default
1447        side_effects!(test_tagged_template, "const x = tag`hello`;");
1448
1449        // String.raw is known to be pure
1450        no_side_effects!(
1451            test_tagged_template_string_raw,
1452            "const x = String.raw`hello ${world}`;"
1453        );
1454
1455        no_side_effects!(test_regex_literal, "const re = /pattern/g;");
1456
1457        no_side_effects!(test_bigint_literal, "const big = 123n;");
1458
1459        no_side_effects!(test_optional_chaining_pure, "const x = obj?.prop;");
1460
1461        // Optional chaining with a call is still a call
1462        side_effects!(test_optional_chaining_call, "const x = obj?.method();");
1463
1464        no_side_effects!(
1465            test_multiple_exports_pure,
1466            "export const a = 1;\nexport const b = 2;\nexport const c = 3;"
1467        );
1468
1469        no_side_effects!(test_export_function, "export function foo() { return 1; }");
1470
1471        no_side_effects!(test_export_class, "export class Foo {}");
1472
1473        module_evaluation_is_side_effect_free!(test_reexport, "export { foo } from 'bar';");
1474
1475        // import() is a function-like expression, we allow it
1476        module_evaluation_is_side_effect_free!(
1477            test_dynamic_import,
1478            "const mod = import('./module');"
1479        );
1480
1481        module_evaluation_is_side_effect_free!(
1482            test_dynamic_import_with_await,
1483            "const mod = await import('./module');"
1484        );
1485
1486        no_side_effects!(test_export_default_expression, "export default 1 + 2;");
1487
1488        side_effects!(
1489            test_export_default_expression_with_side_effect,
1490            "export default foo();"
1491        );
1492
1493        no_side_effects!(
1494            test_export_default_function,
1495            "export default function() { return 1; }"
1496        );
1497
1498        no_side_effects!(test_export_default_class, "export default class Foo {}");
1499
1500        no_side_effects!(
1501            test_export_named_with_pure_builtin,
1502            "export const result = Math.abs(-5);"
1503        );
1504
1505        side_effects!(
1506            test_multiple_exports_mixed,
1507            "export const a = 1;\nexport const b = foo();\nexport const c = 3;"
1508        );
1509    }
1510
1511    mod pure_constructors_tests {
1512        use super::*;
1513
1514        no_side_effects!(test_new_set, "const s = new Set();");
1515
1516        no_side_effects!(test_new_map, "const m = new Map();");
1517
1518        no_side_effects!(test_new_weakset, "const ws = new WeakSet();");
1519
1520        no_side_effects!(test_new_weakmap, "const wm = new WeakMap();");
1521
1522        no_side_effects!(test_new_regexp, "const re = new RegExp('pattern');");
1523
1524        no_side_effects!(test_new_date, "const d = new Date();");
1525
1526        no_side_effects!(test_new_error, "const e = new Error('message');");
1527
1528        no_side_effects!(test_new_promise, "const p = new Promise(() => {});");
1529        side_effects!(
1530            test_new_promise_effectful,
1531            "const p = new Promise(() => {console.log('hello')});"
1532        );
1533
1534        no_side_effects!(test_new_array, "const arr = new Array(10);");
1535
1536        no_side_effects!(test_new_object, "const obj = new Object();");
1537
1538        no_side_effects!(test_new_typed_array, "const arr = new Uint8Array(10);");
1539
1540        no_side_effects!(test_new_url, "const url = new URL('https://example.com');");
1541
1542        no_side_effects!(
1543            test_new_url_search_params,
1544            "const params = new URLSearchParams();"
1545        );
1546
1547        // Pure constructor with impure arguments is impure
1548        side_effects!(
1549            test_pure_constructor_with_impure_args,
1550            "const s = new Set([foo()]);"
1551        );
1552
1553        no_side_effects!(
1554            test_multiple_pure_constructors,
1555            "const s = new Set();\nconst m = new Map();\nconst re = new RegExp('test');"
1556        );
1557
1558        // Unknown constructors are impure
1559        side_effects!(
1560            test_unknown_constructor,
1561            "const custom = new CustomClass();"
1562        );
1563
1564        side_effects!(
1565            test_mixed_constructors,
1566            "const s = new Set();\nconst custom = new CustomClass();\nconst m = new Map();"
1567        );
1568    }
1569
1570    mod shadowing_detection_tests {
1571        use super::*;
1572
1573        // Math is shadowed by a local variable, so Math.abs is not the built-in
1574        side_effects!(
1575            test_shadowed_math,
1576            r#"
1577            const Math = { abs: () => console.log('side effect') };
1578            const result = Math.abs(-5);
1579            "#
1580        );
1581
1582        // Object is shadowed at top level, so Object.keys is not the built-in
1583        side_effects!(
1584            test_shadowed_object,
1585            r#"
1586            const Object = { keys: () => sideEffect() };
1587            const result = Object.keys({});
1588            "#
1589        );
1590
1591        // Array is shadowed at top level by a local class
1592        side_effects!(
1593            test_shadowed_array_constructor,
1594            r#"
1595            const Array = class { constructor() { sideEffect(); } };
1596            const arr = new Array();
1597            "#
1598        );
1599
1600        // Set is shadowed at top level
1601        side_effects!(
1602            test_shadowed_set_constructor,
1603            r#"
1604            const Set = class { constructor() { sideEffect(); } };
1605            const s = new Set();
1606            "#
1607        );
1608
1609        // Map is shadowed in a block scope
1610        side_effects!(
1611            test_shadowed_map_constructor,
1612            r#"
1613            {
1614                const Map = class { constructor() { sideEffect(); } };
1615                const m = new Map();
1616            }
1617            "#
1618        );
1619
1620        // Math is NOT shadowed here, so Math.abs is the built-in
1621        no_side_effects!(
1622            test_global_math_not_shadowed,
1623            r#"
1624            const result = Math.abs(-5);
1625            "#
1626        );
1627
1628        // Object is NOT shadowed, so Object.keys is the built-in
1629        no_side_effects!(
1630            test_global_object_not_shadowed,
1631            r#"
1632            const keys = Object.keys({ a: 1, b: 2 });
1633            "#
1634        );
1635
1636        // Array is NOT shadowed, so new Array() is the built-in
1637        no_side_effects!(
1638            test_global_array_constructor_not_shadowed,
1639            r#"
1640            const arr = new Array(1, 2, 3);
1641            "#
1642        );
1643
1644        // If Math is imported (has a non-empty ctxt), it's not the global
1645        side_effects!(
1646            test_shadowed_by_import,
1647            r#"
1648            import { Math } from './custom-math';
1649            const result = Math.abs(-5);
1650            "#
1651        );
1652
1653        // Math is shadowed in a block scope at top level
1654        side_effects!(
1655            test_nested_scope_shadowing,
1656            r#"
1657            {
1658                const Math = { floor: () => sideEffect() };
1659                const result = Math.floor(4.5);
1660            }
1661            "#
1662        );
1663
1664        // This test shows that function declarations are pure at top level
1665        // even if they have shadowed parameters. The side effect only occurs
1666        // if the function is actually called.
1667        no_side_effects!(
1668            test_parameter_shadowing,
1669            r#"
1670            function test(RegExp) {
1671                return new RegExp('test');
1672            }
1673            "#
1674        );
1675
1676        // Number is shadowed by a var declaration
1677        side_effects!(
1678            test_shadowing_with_var,
1679            r#"
1680            var Number = { isNaN: () => sideEffect() };
1681            const check = Number.isNaN(123);
1682            "#
1683        );
1684
1685        // RegExp is NOT shadowed, constructor is pure
1686        no_side_effects!(
1687            test_global_regexp_not_shadowed,
1688            r#"
1689            const re = new RegExp('[a-z]+');
1690            "#
1691        );
1692    }
1693
1694    mod literal_receiver_methods_tests {
1695        use super::*;
1696
1697        // String literal methods
1698        no_side_effects!(
1699            test_string_literal_to_lower_case,
1700            r#"const result = "HELLO".toLowerCase();"#
1701        );
1702
1703        no_side_effects!(
1704            test_string_literal_to_upper_case,
1705            r#"const result = "hello".toUpperCase();"#
1706        );
1707
1708        no_side_effects!(
1709            test_string_literal_slice,
1710            r#"const result = "hello world".slice(0, 5);"#
1711        );
1712
1713        no_side_effects!(
1714            test_string_literal_split,
1715            r#"const result = "a,b,c".split(',');"#
1716        );
1717
1718        no_side_effects!(
1719            test_string_literal_trim,
1720            r#"const result = "  hello  ".trim();"#
1721        );
1722
1723        no_side_effects!(
1724            test_string_literal_replace,
1725            r#"const result = "hello".replace('h', 'H');"#
1726        );
1727
1728        no_side_effects!(
1729            test_string_literal_includes,
1730            r#"const result = "hello world".includes('world');"#
1731        );
1732
1733        // Array literal methods
1734        no_side_effects!(
1735            test_array_literal_map,
1736            r#"const result = [1, 2, 3].map(x => x * 2);"#
1737        );
1738        side_effects!(
1739            test_array_literal_map_with_effectful_callback,
1740            r#"const result = [1, 2, 3].map(x => {globalThis.something.push(x)});"#
1741        );
1742
1743        // Number literal methods - need parentheses for number literals
1744        no_side_effects!(
1745            test_number_literal_to_fixed,
1746            r#"const result = (3.14159).toFixed(2);"#
1747        );
1748
1749        no_side_effects!(
1750            test_number_literal_to_string,
1751            r#"const result = (42).toString();"#
1752        );
1753
1754        no_side_effects!(
1755            test_number_literal_to_exponential,
1756            r#"const result = (123.456).toExponential(2);"#
1757        );
1758
1759        // Boolean literal methods
1760        no_side_effects!(
1761            test_boolean_literal_to_string,
1762            r#"const result = true.toString();"#
1763        );
1764
1765        no_side_effects!(
1766            test_boolean_literal_value_of,
1767            r#"const result = false.valueOf();"#
1768        );
1769
1770        // RegExp literal methods
1771        no_side_effects!(
1772            test_regexp_literal_to_string,
1773            r#"const result = /[a-z]+/.toString();"#
1774        );
1775
1776        // Note: test() and exec() technically modify flags on the regex, but that is fine when
1777        // called on a literal.
1778        no_side_effects!(
1779            test_regexp_literal_test,
1780            r#"const result = /[a-z]+/g.test("hello");"#
1781        );
1782
1783        no_side_effects!(
1784            test_regexp_literal_exec,
1785            r#"const result = /(\d+)/g.exec("test123");"#
1786        );
1787
1788        // Array literal with impure elements - the array construction itself has side effects
1789        // because foo() is called when creating the array
1790        side_effects!(
1791            test_array_literal_with_impure_elements,
1792            r#"const result = [foo(), 2, 3].map(x => x * 2);"#
1793        );
1794
1795        // Array literal with callback that would have side effects when called
1796        // However, callbacks are just function definitions at module load time
1797        // They don't execute until runtime, so this is side-effect free at load time
1798        no_side_effects!(
1799            test_array_literal_map_with_callback,
1800            r#"const result = [1, 2, 3].map(x => x * 2);"#
1801        );
1802    }
1803
1804    mod class_expression_side_effects_tests {
1805        use super::*;
1806
1807        // Class with no extends and no static members is pure
1808        no_side_effects!(test_class_no_extends_no_static, "class Foo {}");
1809
1810        // Class with pure extends is pure
1811        no_side_effects!(test_class_pure_extends, "class Foo extends Bar {}");
1812
1813        // Class with function call in extends clause has side effects
1814        side_effects!(
1815            test_class_extends_with_call,
1816            "class Foo extends someMixinFunction() {}"
1817        );
1818
1819        // Class with complex expression in extends clause has side effects
1820        side_effects!(
1821            test_class_extends_with_complex_expr,
1822            "class Foo extends (Bar || Baz()) {}"
1823        );
1824
1825        // Class with static property initializer that calls function has side effects
1826        side_effects!(
1827            test_class_static_property_with_call,
1828            r#"
1829        class Foo {
1830            static foo = someFunction();
1831        }
1832        "#
1833        );
1834
1835        // Class with static property with pure initializer is pure
1836        no_side_effects!(
1837            test_class_static_property_pure,
1838            r#"
1839        class Foo {
1840            static foo = 42;
1841        }
1842        "#
1843        );
1844
1845        // Class with static property with array literal is pure
1846        no_side_effects!(
1847            test_class_static_property_array_literal,
1848            r#"
1849        class Foo {
1850            static foo = [1, 2, 3];
1851        }
1852        "#
1853        );
1854
1855        // Class with static block has side effects
1856        side_effects!(
1857            test_class_static_block,
1858            r#"
1859        class Foo {
1860            static {
1861                console.log("hello");
1862            }
1863        }
1864        "#
1865        );
1866
1867        no_side_effects!(
1868            test_class_static_block_empty,
1869            r#"
1870        class Foo {
1871            static {}
1872        }
1873        "#
1874        );
1875
1876        // Class with instance property is pure (doesn't execute at definition time)
1877        no_side_effects!(
1878            test_class_instance_property_with_call,
1879            r#"
1880        class Foo {
1881            foo = someFunction();
1882        }
1883        "#
1884        );
1885
1886        // Class with constructor is pure (doesn't execute at definition time)
1887        no_side_effects!(
1888            test_class_constructor_with_side_effects,
1889            r#"
1890        class Foo {
1891            constructor() {
1892                console.log("constructor");
1893            }
1894        }
1895        "#
1896        );
1897
1898        // Class with method is pure (doesn't execute at definition time)
1899        no_side_effects!(
1900            test_class_method,
1901            r#"
1902        class Foo {
1903            method() {
1904                console.log("method");
1905            }
1906        }
1907        "#
1908        );
1909
1910        // Class expression with side effects in extends
1911        side_effects!(
1912            test_class_expr_extends_with_call,
1913            "const Foo = class extends getMixin() {};"
1914        );
1915
1916        // Class expression with static property calling function
1917        side_effects!(
1918            test_class_expr_static_with_call,
1919            r#"
1920        const Foo = class {
1921            static prop = initValue();
1922        };
1923        "#
1924        );
1925
1926        // Class expression with pure static property
1927        no_side_effects!(
1928            test_class_expr_static_pure,
1929            r#"
1930        const Foo = class {
1931            static prop = "hello";
1932        };
1933        "#
1934        );
1935
1936        // Export class with side effects
1937        side_effects!(
1938            test_export_class_with_side_effects,
1939            r#"
1940        export class Foo extends getMixin() {
1941            static prop = init();
1942        }
1943        "#
1944        );
1945
1946        // Export default class with side effects
1947        side_effects!(
1948            test_export_default_class_with_side_effects,
1949            r#"
1950        export default class Foo {
1951            static { console.log("init"); }
1952        }
1953        "#
1954        );
1955
1956        // Export class without side effects
1957        no_side_effects!(
1958            test_export_class_no_side_effects,
1959            r#"
1960        export class Foo {
1961            method() {
1962                console.log("method");
1963            }
1964        }
1965        "#
1966        );
1967
1968        // Multiple static properties, some pure, some not
1969        side_effects!(
1970            test_class_mixed_static_properties,
1971            r#"
1972        class Foo {
1973            static a = 1;
1974            static b = impureCall();
1975            static c = 3;
1976        }
1977        "#
1978        );
1979
1980        // Class with pure static property using known pure built-in
1981        no_side_effects!(
1982            test_class_static_property_pure_builtin,
1983            r#"
1984        class Foo {
1985            static value = Math.abs(-5);
1986        }
1987        "#
1988        );
1989
1990        // Class with computed property name that has side effects
1991        side_effects!(
1992            test_class_computed_property_with_call,
1993            r#"
1994        class Foo {
1995            [computeName()]() {
1996                return 42;
1997            }
1998        }
1999        "#
2000        );
2001
2002        // Class with pure computed property name
2003        no_side_effects!(
2004            test_class_computed_property_pure,
2005            r#"
2006        class Foo {
2007            ['method']() {
2008                return 42;
2009            }
2010        }
2011        "#
2012        );
2013    }
2014
2015    mod complex_variable_declarations_tests {
2016        use super::*;
2017
2018        // Simple destructuring without defaults is pure
2019        no_side_effects!(test_destructure_simple, "const { foo } = obj;");
2020
2021        // Destructuring with function call in default value has side effects
2022        side_effects!(
2023            test_destructure_default_with_call,
2024            "const { foo = someFunction() } = obj;"
2025        );
2026
2027        // Destructuring with pure default value is pure
2028        no_side_effects!(test_destructure_default_pure, "const { foo = 42 } = obj;");
2029
2030        // Destructuring with array literal default is pure
2031        no_side_effects!(
2032            test_destructure_default_array_literal,
2033            "const { foo = ['hello'] } = obj;"
2034        );
2035
2036        // Destructuring with object literal default is pure
2037        no_side_effects!(
2038            test_destructure_default_object_literal,
2039            "const { foo = { bar: 'baz' } } = obj;"
2040        );
2041
2042        // Nested destructuring with default that has side effect
2043        side_effects!(
2044            test_destructure_nested_with_call,
2045            "const { a: { b = sideEffect() } } = obj;"
2046        );
2047
2048        // Array destructuring with default that has side effect
2049        side_effects!(
2050            test_array_destructure_default_with_call,
2051            "const [a, b = getDefault()] = arr;"
2052        );
2053
2054        // Array destructuring with pure default
2055        no_side_effects!(
2056            test_array_destructure_default_pure,
2057            "const [a, b = 10] = arr;"
2058        );
2059
2060        // Multiple variables, one with side effect in default
2061        side_effects!(
2062            test_multiple_destructure_mixed,
2063            "const { foo = 1, bar = compute() } = obj;"
2064        );
2065
2066        // Rest pattern is pure
2067        no_side_effects!(test_destructure_rest_pure, "const { foo, ...rest } = obj;");
2068
2069        // Complex destructuring with multiple levels
2070        side_effects!(
2071            test_destructure_complex_with_side_effect,
2072            r#"
2073        const {
2074            a,
2075            b: { c = sideEffect() },
2076            d = [1, 2, 3]
2077        } = obj;
2078        "#
2079        );
2080
2081        // Complex destructuring all pure
2082        no_side_effects!(
2083            test_destructure_complex_pure,
2084            r#"
2085        const {
2086            a,
2087            b: { c = 5 },
2088            d = [1, 2, 3]
2089        } = obj;
2090        "#
2091        );
2092
2093        // Destructuring in export with side effect
2094        side_effects!(
2095            test_export_destructure_with_side_effect,
2096            "export const { foo = init() } = obj;"
2097        );
2098
2099        // Destructuring in export without side effect
2100        no_side_effects!(
2101            test_export_destructure_pure,
2102            "export const { foo = 42 } = obj;"
2103        );
2104
2105        // Default value with known pure built-in
2106        no_side_effects!(
2107            test_destructure_default_pure_builtin,
2108            "const { foo = Math.abs(-5) } = obj;"
2109        );
2110
2111        // Default value with pure annotation
2112        no_side_effects!(
2113            test_destructure_default_pure_annotation,
2114            "const { foo = /*#__PURE__*/ compute() } = obj;"
2115        );
2116    }
2117
2118    mod decorator_side_effects_tests {
2119        use super::*;
2120
2121        // Class decorator has side effects (executes at definition time)
2122        side_effects!(
2123            test_class_decorator,
2124            r#"
2125        @decorator
2126        class Foo {}
2127        "#
2128        );
2129
2130        // Method decorator has side effects
2131        side_effects!(
2132            test_method_decorator,
2133            r#"
2134        class Foo {
2135            @decorator
2136            method() {}
2137        }
2138        "#
2139        );
2140
2141        // Property decorator has side effects
2142        side_effects!(
2143            test_property_decorator,
2144            r#"
2145        class Foo {
2146            @decorator
2147            prop = 1;
2148        }
2149        "#
2150        );
2151
2152        // Multiple decorators
2153        side_effects!(
2154            test_multiple_decorators,
2155            r#"
2156        @decorator1
2157        @decorator2
2158        class Foo {
2159            @propDecorator
2160            prop = 1;
2161
2162            @methodDecorator
2163            method() {}
2164        }
2165        "#
2166        );
2167
2168        // Decorator with arguments
2169        side_effects!(
2170            test_decorator_with_args,
2171            r#"
2172        @decorator(config())
2173        class Foo {}
2174        "#
2175        );
2176    }
2177
2178    mod additional_edge_cases_tests {
2179        use super::*;
2180
2181        // Super property access is pure
2182        no_side_effects!(
2183            test_super_property_pure,
2184            r#"
2185        class Foo extends Bar {
2186            method() {
2187                return super.parentMethod;
2188            }
2189        }
2190        "#
2191        );
2192
2193        // Super method call has side effects (but only when invoked, not at definition)
2194        no_side_effects!(
2195            test_super_call_in_method,
2196            r#"
2197        class Foo extends Bar {
2198            method() {
2199                return super.parentMethod();
2200            }
2201        }
2202        "#
2203        );
2204
2205        // import.meta is pure
2206        no_side_effects!(test_import_meta, "const url = import.meta.url;");
2207
2208        // new.target is pure (only valid inside functions/constructors)
2209        no_side_effects!(
2210            test_new_target,
2211            r#"
2212        function Foo() {
2213            console.log(new.target);
2214        }
2215        "#
2216        );
2217
2218        // JSX element has side effects (compiles to function calls)
2219        side_effects!(test_jsx_element, "const el = <div>Hello</div>;");
2220
2221        // JSX fragment has side effects
2222        side_effects!(test_jsx_fragment, "const el = <>Hello</>;");
2223
2224        // Private field access is pure
2225        no_side_effects!(
2226            test_private_field_access,
2227            r#"
2228        class Foo {
2229            #privateField = 42;
2230            method() {
2231                return this.#privateField;
2232            }
2233        }
2234        "#
2235        );
2236
2237        // Computed super property with side effect
2238        no_side_effects!(
2239            test_super_computed_property_pure,
2240            r#"
2241        class Foo extends Bar {
2242            method() {
2243                return super['prop'];
2244            }
2245        }
2246        "#
2247        );
2248
2249        // Static block with only pure statements is pure
2250        no_side_effects!(
2251            test_static_block_pure_content,
2252            r#"
2253        class Foo {
2254            static {
2255                const x = 1;
2256                const y = 2;
2257            }
2258        }
2259        "#
2260        );
2261
2262        // Static block with side effect
2263        side_effects!(
2264            test_static_block_with_side_effect_inside,
2265            r#"
2266        class Foo {
2267            static {
2268                sideEffect();
2269            }
2270        }
2271        "#
2272        );
2273
2274        // This binding is pure
2275        no_side_effects!(
2276            test_this_expression,
2277            r#"
2278        class Foo {
2279            method() {
2280                return this;
2281            }
2282        }
2283        "#
2284        );
2285
2286        // Spread in call arguments (with pure expression)
2287        no_side_effects!(
2288            test_spread_pure_in_call,
2289            "const result = Math.max(...[1, 2, 3]);"
2290        );
2291
2292        // Spread in call arguments (with side effect)
2293        side_effects!(
2294            test_spread_with_side_effect,
2295            "const result = Math.max(...getArray());"
2296        );
2297
2298        // Complex super expression
2299        no_side_effects!(
2300            test_super_complex_access,
2301            r#"
2302        class Foo extends Bar {
2303            static method() {
2304                return super.parentMethod;
2305            }
2306        }
2307        "#
2308        );
2309
2310        // Getter/setter definitions are pure
2311        no_side_effects!(
2312            test_getter_definition,
2313            r#"
2314        const obj = {
2315            get foo() {
2316                return this._foo;
2317            }
2318        };
2319        "#
2320        );
2321
2322        // Async function declaration is pure
2323        no_side_effects!(
2324            test_async_function_declaration,
2325            r#"
2326        async function foo() {
2327            return await something;
2328        }
2329        "#
2330        );
2331
2332        // Generator function declaration is pure
2333        no_side_effects!(
2334            test_generator_declaration,
2335            r#"
2336        function* foo() {
2337            yield 1;
2338            yield 2;
2339        }
2340        "#
2341        );
2342
2343        // Async generator is pure
2344        no_side_effects!(
2345            test_async_generator,
2346            r#"
2347        async function* foo() {
2348            yield await something;
2349        }
2350        "#
2351        );
2352
2353        // Using declaration (TC39 proposal) - if supported
2354        // This would need to be handled if the parser supports it
2355
2356        // Nullish coalescing with side effects in right operand
2357        side_effects!(
2358            test_nullish_coalescing_with_side_effect,
2359            "const x = a ?? sideEffect();"
2360        );
2361
2362        // Logical OR with side effects
2363        side_effects!(
2364            test_logical_or_with_side_effect,
2365            "const x = a || sideEffect();"
2366        );
2367
2368        // Logical AND with side effects
2369        side_effects!(
2370            test_logical_and_with_side_effect,
2371            "const x = a && sideEffect();"
2372        );
2373    }
2374
2375    mod common_js_modules_tests {
2376        use super::*;
2377
2378        side_effects!(test_common_js_exports, "exports.foo = 'a'");
2379        side_effects!(test_common_js_exports_module, "module.exports.foo = 'a'");
2380        side_effects!(test_common_js_exports_assignment, "module.exports = {}");
2381    }
2382}