1use std::collections::HashSet;
40
41use phf::{phf_map, phf_set};
42use swc_core::{
43 common::{Mark, comments::Comments},
44 ecma::{
45 ast::*,
46 visit::{Visit, VisitWith, noop_visit_type},
47 },
48};
49use turbopack_core::module::ModuleSideEffects;
50
51use crate::utils::unparen;
52
53macro_rules! check_side_effects {
56 ($self:expr) => {
57 if $self.has_side_effects {
58 return;
59 }
60 };
61}
62
63static KNOWN_PURE_FUNCTIONS: phf::Map<&'static str, phf::Set<&'static str>> = phf_map! {
74 "Math" => phf_set! {
75 "abs", "acos", "acosh", "asin", "asinh", "atan", "atan2", "atanh", "cbrt", "ceil",
76 "clz32", "cos", "cosh", "exp", "expm1", "floor", "fround", "hypot", "imul", "log",
77 "log10", "log1p", "log2", "max", "min", "pow", "round", "sign", "sin", "sinh",
78 "sqrt", "tan", "tanh", "trunc",
79 },
80 "String" => phf_set! {
82 "fromCharCode", "fromCodePoint", "raw",
83 },
84 "Number" => phf_set! {
86 "isFinite", "isInteger", "isNaN", "isSafeInteger", "parseFloat", "parseInt",
87 },
88 "Object" => phf_set! {
90 "keys", "values", "entries", "hasOwn", "getOwnPropertyNames", "getOwnPropertySymbols",
91 "getOwnPropertyDescriptor", "getOwnPropertyDescriptors", "getPrototypeOf", "is",
92 "isExtensible", "isFrozen", "isSealed",
93 },
94 "Array" => phf_set! {
96 "isArray", "from", "of",
97 },
98 "Symbol" => phf_set! {
100 "for", "keyFor"
101 },
102};
103
104static KNOWN_PURE_GLOBAL_FUNCTIONS: phf::Set<&'static str> = phf_set! {
109 "String",
110 "Number",
111 "Symbol",
112 "Boolean",
113 "isNaN",
114 "isFinite",
115 "parseInt",
116 "parseFloat",
117 "decodeURI",
118 "decodeURIComponent",
119};
120
121static KNOWN_PURE_CONSTRUCTORS: phf::Set<&'static str> = phf_set! {
126 "Set",
128 "Map",
129 "WeakSet",
130 "WeakMap",
131 "RegExp",
133 "Array",
135 "Object",
136 "Int8Array",
138 "Uint8Array",
139 "Uint8ClampedArray",
140 "Int16Array",
141 "Uint16Array",
142 "Int32Array",
143 "Uint32Array",
144 "Float32Array",
145 "Float64Array",
146 "BigInt64Array",
147 "BigUint64Array",
148 "Date",
150 "Error",
151 "TypeError",
152 "RangeError",
153 "SyntaxError",
154 "ReferenceError",
155 "URIError",
156 "EvalError",
157 "Promise",
158 "ArrayBuffer",
159 "DataView",
160 "URL",
161 "URLSearchParams",
162 "String",
164 "Number",
165 "Symbol",
166 "Boolean",
167};
168
169static KNOWN_PURE_STRING_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
177 "toLowerCase",
179 "toUpperCase",
180 "toLocaleLowerCase",
181 "toLocaleUpperCase",
182 "charAt",
183 "charCodeAt",
184 "codePointAt",
185 "slice",
186 "substring",
187 "substr",
188 "indexOf",
189 "lastIndexOf",
190 "includes",
191 "startsWith",
192 "endsWith",
193 "search",
194 "match",
195 "matchAll",
196 "trim",
197 "trimStart",
198 "trimEnd",
199 "trimLeft",
200 "trimRight",
201 "repeat",
202 "padStart",
203 "padEnd",
204 "concat",
205 "split",
206 "replace",
207 "replaceAll",
208 "normalize",
209 "localeCompare",
210 "isWellFormed",
211 "toString",
212 "valueOf",
213};
214
215static KNOWN_PURE_ARRAY_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
217 "map",
219 "filter",
220 "reduce",
221 "reduceRight",
222 "find",
223 "findIndex",
224 "findLast",
225 "findLastIndex",
226 "some",
227 "every",
228 "flat",
229 "flatMap",
230 "at",
232 "slice",
233 "concat",
234 "includes",
235 "indexOf",
236 "lastIndexOf",
237 "join",
238 "toLocaleString",
240 "toReversed",
241 "toSorted",
242 "toSpliced",
243 "with",
244};
245
246static KNOWN_PURE_OBJECT_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
247 "hasOwnProperty",
248 "propertyIsEnumerable",
249 "toString",
250 "valueOf",
251};
252
253static KNOWN_PURE_NUMBER_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
255 "toExponential", "toFixed", "toPrecision", "toLocaleString",
256};
257
258static KNOWN_PURE_REGEXP_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
268 "test", "exec",
269};
270
271fn prop_is(prop: &MemberProp, name: &str) -> bool {
273 matches!(prop, MemberProp::Ident(i) if i.sym.as_ref() == name)
274}
275
276fn is_global(identifier: &Ident, name: &str, unresolved_mark: Mark) -> bool {
281 identifier.ctxt.outer() == unresolved_mark && identifier.sym.as_ref() == name
282}
283
284fn is_object_or_array_literal(expr: &Expr) -> bool {
286 matches!(unparen(expr), Expr::Object(_) | Expr::Array(_))
287}
288
289fn root_identifier(expr: &Expr) -> Option<&Ident> {
292 match unparen(expr) {
293 Expr::Ident(ident) => Some(ident),
294 Expr::Member(member) => root_identifier(&member.obj),
295 _ => None,
296 }
297}
298
299fn collect_safe_assignment_constant_ids(program: &Program) -> HashSet<Id> {
311 struct Collector {
315 ids: HashSet<Id>,
316 }
317 impl Visit for Collector {
318 noop_visit_type!();
319 fn visit_var_decl(&mut self, decl: &VarDecl) {
320 if decl.kind == VarDeclKind::Const {
321 for d in &decl.decls {
322 if let (Pat::Ident(binding), Some(init)) = (&d.name, d.init.as_deref())
323 && is_object_or_array_literal(init)
324 && !contains_getters_or_setters(init)
325 {
326 self.ids.insert(binding.id.to_id());
327 }
328 }
329 }
330 decl.visit_children_with(self);
331 }
332 fn visit_function(&mut self, _: &Function) {}
333 fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
334 }
335 let mut collector = Collector {
336 ids: HashSet::new(),
337 };
338 program.visit_with(&mut collector);
339 let mut ids = collector.ids;
340
341 for_each_top_level_assign(program, |assign| {
345 if assign.op == AssignOp::Assign
346 && let AssignTarget::Simple(SimpleAssignTarget::Member(member)) = &assign.left
347 && contains_getters_or_setters(&assign.right)
348 && let Some(root) = root_identifier(&member.obj)
349 {
350 ids.remove(&root.to_id());
351 }
352 });
353
354 ids
355}
356
357fn contains_getters_or_setters(expr: &Expr) -> bool {
364 match unparen(expr) {
365 Expr::Object(obj) => obj.props.iter().any(|prop| match prop {
366 PropOrSpread::Prop(prop) => match &**prop {
367 Prop::Getter(_) | Prop::Setter(_) => true,
368 Prop::KeyValue(kv) => contains_getters_or_setters(&kv.value),
369 Prop::Method(_) | Prop::Shorthand(_) | Prop::Assign(_) => false,
370 },
371 PropOrSpread::Spread(spread) => contains_getters_or_setters(&spread.expr),
372 }),
373 Expr::Array(arr) => arr
374 .elems
375 .iter()
376 .flatten()
377 .any(|elem| contains_getters_or_setters(&elem.expr)),
378 _ => false,
379 }
380}
381
382fn is_module_dot_exports(member: &MemberExpr, unresolved_mark: Mark) -> bool {
384 matches!(unparen(&member.obj), Expr::Ident(o) if is_global(o, "module", unresolved_mark))
385 && prop_is(&member.prop, "exports")
386}
387
388fn is_module_exports_chain(expr: &Expr, unresolved_mark: Mark) -> bool {
390 match unparen(expr) {
391 Expr::Member(m) => {
392 is_module_dot_exports(m, unresolved_mark)
393 || is_module_exports_chain(&m.obj, unresolved_mark)
394 }
395 _ => false,
396 }
397}
398
399fn is_cjs_export_member(member: &MemberExpr, unresolved_mark: Mark) -> bool {
402 match unparen(&member.obj) {
403 Expr::Ident(obj) => {
405 is_global(obj, "exports", unresolved_mark)
406 || is_module_dot_exports(member, unresolved_mark)
407 }
408 Expr::Member(inner) => is_cjs_export_member(inner, unresolved_mark),
410 _ => false,
411 }
412}
413
414fn for_each_top_level_assign(program: &Program, f: impl FnMut(&AssignExpr)) {
423 struct Collector<F> {
424 f: F,
425 }
426 impl<F: FnMut(&AssignExpr)> Visit for Collector<F> {
427 noop_visit_type!();
428 fn visit_assign_expr(&mut self, n: &AssignExpr) {
429 (self.f)(n);
430 n.visit_children_with(self);
431 }
432 fn visit_function(&mut self, _: &Function) {}
434 fn visit_arrow_expr(&mut self, _: &ArrowExpr) {}
435 fn visit_constructor(&mut self, _: &Constructor) {}
436 }
437 program.visit_with(&mut Collector { f });
438}
439
440fn module_exports_is_tainted(program: &Program, unresolved_mark: Mark) -> bool {
446 let mut tainted = false;
447 for_each_top_level_assign(program, |assign| {
448 if assign.op == AssignOp::Assign
449 && let AssignTarget::Simple(SimpleAssignTarget::Member(member)) = &assign.left
450 && is_module_dot_exports(member, unresolved_mark)
451 && !is_object_or_array_literal(&assign.right)
452 {
453 tainted = true;
454 }
455 });
456 tainted
457}
458
459fn module_exports_has_accessor(program: &Program, unresolved_mark: Mark) -> bool {
467 let mut found = false;
468 for_each_top_level_assign(program, |assign| {
469 if assign.op == AssignOp::Assign
470 && let AssignTarget::Simple(SimpleAssignTarget::Member(member)) = &assign.left
471 && is_cjs_export_member(member, unresolved_mark)
472 && contains_getters_or_setters(&assign.right)
473 {
474 found = true;
475 }
476 });
477 found
478}
479
480pub fn compute_module_evaluation_side_effects(
482 program: &Program,
483 comments: &dyn Comments,
484 unresolved_mark: Mark,
485) -> ModuleSideEffects {
486 let module_exports_tainted = module_exports_is_tainted(program, unresolved_mark);
487 let module_exports_has_accessor = module_exports_has_accessor(program, unresolved_mark);
488 let safe_assignment_constant_ids = collect_safe_assignment_constant_ids(program);
489 let mut visitor = SideEffectVisitor::new(
490 comments,
491 unresolved_mark,
492 module_exports_tainted,
493 module_exports_has_accessor,
494 safe_assignment_constant_ids,
495 );
496 program.visit_with(&mut visitor);
497 if visitor.has_side_effects {
498 ModuleSideEffects::SideEffectful
499 } else if visitor.has_imports {
500 ModuleSideEffects::ModuleEvaluationIsSideEffectFree
501 } else {
502 ModuleSideEffects::SideEffectFree
503 }
504}
505
506struct SideEffectVisitor<'a> {
507 comments: &'a dyn Comments,
508 unresolved_mark: Mark,
509 module_exports_tainted: bool,
512 module_exports_has_accessor: bool,
515 safe_assignment_constant_ids: HashSet<Id>,
518 has_side_effects: bool,
519 will_invoke_fn_exprs: bool,
520 has_imports: bool,
521}
522
523impl<'a> SideEffectVisitor<'a> {
524 fn new(
525 comments: &'a dyn Comments,
526 unresolved_mark: Mark,
527 module_exports_tainted: bool,
528 module_exports_has_accessor: bool,
529 safe_assignment_constant_ids: HashSet<Id>,
530 ) -> Self {
531 Self {
532 comments,
533 unresolved_mark,
534 module_exports_tainted,
535 module_exports_has_accessor,
536 safe_assignment_constant_ids,
537 has_side_effects: false,
538 will_invoke_fn_exprs: false,
539 has_imports: false,
540 }
541 }
542
543 fn mark_side_effect(&mut self) {
545 self.has_side_effects = true;
546 }
547
548 fn with_will_invoke_fn_exprs<F>(&mut self, value: bool, f: F)
554 where
555 F: FnOnce(&mut Self),
556 {
557 let old_value = self.will_invoke_fn_exprs;
558 self.will_invoke_fn_exprs = value;
559 f(self);
560 self.will_invoke_fn_exprs = old_value;
561 }
562
563 fn is_pure_annotated(&self, span: swc_core::common::Span) -> bool {
565 self.comments.has_flag(span.lo, "PURE")
566 }
567
568 fn is_known_pure_builtin(&self, callee: &Callee) -> bool {
572 match callee {
573 Callee::Expr(expr) => self.is_known_pure_builtin_function(expr),
574 _ => false,
575 }
576 }
577 fn is_require_or_import(&self, callee: &Callee) -> bool {
581 match callee {
582 Callee::Expr(expr) => {
583 let expr = unparen(expr);
584 if let Expr::Ident(ident) = expr {
585 is_global(ident, "require", self.unresolved_mark)
586 } else {
587 false
588 }
589 }
590
591 Callee::Import(_) => true,
592 _ => false,
593 }
594 }
595
596 fn assign_target_is_pure(&self, target: &AssignTarget) -> bool {
603 match target {
604 AssignTarget::Simple(SimpleAssignTarget::Member(member)) => {
605 self.member_target_is_pure(member)
606 }
607 _ => false,
608 }
609 }
610
611 fn member_target_is_pure(&self, member: &MemberExpr) -> bool {
614 if self.module_exports_tainted && is_module_exports_chain(&member.obj, self.unresolved_mark)
618 {
619 return false;
620 }
621 if is_cjs_export_member(member, self.unresolved_mark) {
625 return !self.module_exports_has_accessor;
626 }
627 let Some(root) = root_identifier(&member.obj) else {
630 return false;
631 };
632 self.safe_assignment_constant_ids.contains(&root.to_id())
633 }
634
635 fn is_known_pure_builtin_function(&self, expr: &Expr) -> bool {
645 match expr {
646 Expr::Member(member) => {
647 let receiver = unparen(&member.obj);
648 match (receiver, &member.prop) {
649 (Expr::Ident(obj), MemberProp::Ident(prop)) => {
651 if obj.ctxt.outer() != self.unresolved_mark {
655 return false;
657 }
658
659 KNOWN_PURE_FUNCTIONS
662 .get(obj.sym.as_ref())
663 .map(|methods| methods.contains(prop.sym.as_ref()))
664 .unwrap_or(false)
665 }
666 (Expr::Lit(lit), MemberProp::Ident(prop)) => {
669 let method_name = prop.sym.as_ref();
670 match lit {
671 Lit::Str(_) => {
672 KNOWN_PURE_STRING_PROTOTYPE_METHODS.contains(method_name)
673 || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
674 }
675 Lit::Num(_) => {
676 KNOWN_PURE_NUMBER_PROTOTYPE_METHODS.contains(method_name)
677 || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
678 }
679 Lit::Bool(_) => {
680 KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
681 }
682 Lit::Regex(_) => {
683 KNOWN_PURE_REGEXP_PROTOTYPE_METHODS.contains(method_name)
684 || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
685 }
686 _ => false,
687 }
688 }
689 (Expr::Array(_), MemberProp::Ident(prop)) => {
692 let method_name = prop.sym.as_ref();
693 KNOWN_PURE_ARRAY_PROTOTYPE_METHODS.contains(method_name)
694 || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
695 }
696 (Expr::Object(_), MemberProp::Ident(prop)) => {
697 KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(prop.sym.as_ref())
698 }
699 _ => false,
700 }
701 }
702 Expr::Ident(ident) => {
703 if ident.ctxt.outer() != self.unresolved_mark {
706 return false;
707 }
708
709 KNOWN_PURE_GLOBAL_FUNCTIONS.contains(ident.sym.as_ref())
711 }
712 _ => false,
713 }
714 }
715
716 fn is_known_pure_constructor(&self, expr: &Expr) -> bool {
722 match expr {
723 Expr::Ident(ident) => {
724 if ident.ctxt.outer() != self.unresolved_mark {
727 return false;
728 }
729
730 KNOWN_PURE_CONSTRUCTORS.contains(ident.sym.as_ref())
732 }
733 _ => false,
734 }
735 }
736}
737
738impl<'a> Visit for SideEffectVisitor<'a> {
739 noop_visit_type!();
740 fn visit_program(&mut self, program: &Program) {
742 check_side_effects!(self);
743 program.visit_children_with(self);
744 }
745
746 fn visit_module(&mut self, module: &Module) {
747 check_side_effects!(self);
748
749 for item in &module.body {
751 check_side_effects!(self);
752 item.visit_with(self);
753 }
754 }
755
756 fn visit_script(&mut self, script: &Script) {
757 check_side_effects!(self);
758
759 for stmt in &script.body {
761 check_side_effects!(self);
762 stmt.visit_with(self);
763 }
764 }
765
766 fn visit_module_decl(&mut self, decl: &ModuleDecl) {
768 check_side_effects!(self);
769
770 match decl {
771 ModuleDecl::Import(_) => {
775 self.has_imports = true;
776 }
777
778 ModuleDecl::ExportDecl(export_decl) => {
780 match &export_decl.decl {
782 Decl::Fn(_) => {
783 }
785 Decl::Class(class_decl) => {
786 class_decl.visit_with(self);
789 }
790 Decl::Var(var_decl) => {
791 var_decl.visit_with(self);
793 }
794 _ => {
795 export_decl.decl.visit_with(self);
797 }
798 }
799 }
800
801 ModuleDecl::ExportDefaultDecl(export_default_decl) => {
802 match &export_default_decl.decl {
804 DefaultDecl::Class(cls) => {
805 cls.visit_with(self);
808 }
809 DefaultDecl::Fn(_) => {
810 }
812 DefaultDecl::TsInterfaceDecl(_) => {
813 }
815 }
816 }
817
818 ModuleDecl::ExportDefaultExpr(export_default_expr) => {
819 export_default_expr.expr.visit_with(self);
821 }
822
823 ModuleDecl::ExportNamed(e) => {
825 if e.src.is_some() {
826 self.has_imports = true;
828 }
829 }
830 ModuleDecl::ExportAll(_) => {
831 self.has_imports = true;
833 }
834
835 ModuleDecl::TsExportAssignment(_) | ModuleDecl::TsNamespaceExport(_) => {}
837 ModuleDecl::TsImportEquals(e) => {
838 match &e.module_ref {
841 TsModuleRef::TsEntityName(_) => {}
842 TsModuleRef::TsExternalModuleRef(_) => {
843 self.has_imports = true
845 }
846 }
847 }
848 }
849 }
850
851 fn visit_stmt(&mut self, stmt: &Stmt) {
853 check_side_effects!(self);
854
855 match stmt {
856 Stmt::Expr(expr_stmt) => {
858 expr_stmt.visit_with(self);
859 }
860 Stmt::Decl(Decl::Var(var_decl)) => {
862 var_decl.visit_with(self);
863 }
864 Stmt::Decl(Decl::Fn(_)) => {
866 }
868 Stmt::Decl(Decl::Class(class_decl)) => {
870 class_decl.visit_with(self);
871 }
872 Stmt::Decl(decl) => {
874 decl.visit_with(self);
875 }
876 _ => {
878 self.mark_side_effect();
881 }
882 }
883 }
884
885 fn visit_var_declarator(&mut self, var_decl: &VarDeclarator) {
886 check_side_effects!(self);
887
888 var_decl.name.visit_with(self);
890
891 if let Some(init) = &var_decl.init {
893 init.visit_with(self);
894 }
895 }
896
897 fn visit_expr(&mut self, expr: &Expr) {
899 check_side_effects!(self);
900
901 match expr {
902 Expr::Lit(_) => {
904 }
906 Expr::Ident(_) => {
907 }
909 Expr::Arrow(_) | Expr::Fn(_) => {
910 if self.will_invoke_fn_exprs {
912 self.with_will_invoke_fn_exprs(false, |this| {
914 expr.visit_children_with(this);
915 });
916 }
917 }
918 Expr::Class(class_expr) => {
919 class_expr.class.visit_with(self);
921 }
922 Expr::Array(arr) => {
923 for elem in arr.elems.iter().flatten() {
925 elem.visit_with(self);
926 }
927 }
928 Expr::Object(obj) => {
929 for prop in &obj.props {
931 prop.visit_with(self);
932 }
933 }
934 Expr::Unary(unary) => {
935 if unary.op == UnaryOp::Delete {
937 self.mark_side_effect();
940 } else {
941 unary.arg.visit_with(self);
942 }
943 }
944 Expr::Bin(bin) => {
945 bin.left.visit_with(self);
947 bin.right.visit_with(self);
948 }
949 Expr::Cond(cond) => {
950 cond.test.visit_with(self);
952 cond.cons.visit_with(self);
953 cond.alt.visit_with(self);
954 }
955 Expr::Member(member) => {
956 member.obj.visit_with(self);
964 member.prop.visit_with(self);
965 }
966 Expr::Paren(paren) => {
967 paren.expr.visit_with(self);
969 }
970 Expr::Tpl(tpl) => {
971 for expr in &tpl.exprs {
973 expr.visit_with(self);
974 }
975 }
976
977 Expr::Call(call) => {
979 if self.is_pure_annotated(call.span) || self.is_known_pure_builtin(&call.callee) {
981 call.callee.visit_with(self);
987
988 self.with_will_invoke_fn_exprs(true, |this| {
991 call.args.visit_children_with(this);
992 });
993 } else if self.is_require_or_import(&call.callee) {
994 self.has_imports = true;
995 call.args.visit_children_with(self);
998 } else {
999 self.mark_side_effect();
1001 }
1002 }
1003 Expr::New(new) => {
1004 if self.is_pure_annotated(new.span) || self.is_known_pure_constructor(&new.callee) {
1006 self.with_will_invoke_fn_exprs(true, |this| {
1008 new.args.visit_children_with(this);
1009 });
1010 } else {
1011 self.mark_side_effect();
1013 }
1014 }
1015 Expr::Assign(assign) => {
1016 if assign.op == AssignOp::Assign && self.assign_target_is_pure(&assign.left) {
1025 assign.left.visit_with(self);
1028 assign.right.visit_with(self);
1029 } else {
1030 self.mark_side_effect();
1031 }
1032 }
1033 Expr::Update(_) => {
1034 self.mark_side_effect();
1037 }
1038 Expr::Await(e) => {
1039 e.arg.visit_with(self);
1040 }
1041 Expr::Yield(e) => {
1042 e.arg.visit_with(self);
1043 }
1044 Expr::TaggedTpl(tagged_tpl)
1045 if self.is_known_pure_builtin_function(&tagged_tpl.tag) => {
1048 for arg in &tagged_tpl.tpl.exprs {
1049 arg.visit_with(self);
1050 }
1051 }
1052 Expr::OptChain(opt_chain) => {
1053 opt_chain.base.visit_with(self);
1056 }
1057 Expr::Seq(seq) => {
1058 seq.exprs.visit_children_with(self);
1060 }
1061 Expr::SuperProp(super_prop) => {
1062 super_prop.prop.visit_with(self);
1065 }
1066 Expr::MetaProp(_) => {
1067 }
1070 Expr::JSXMember(_) | Expr::JSXNamespacedName(_) | Expr::JSXEmpty(_) => {
1071 }
1073 Expr::JSXElement(_) | Expr::JSXFragment(_) => {
1074 self.mark_side_effect();
1079 }
1080 Expr::PrivateName(_) => {
1081 }
1083
1084 _ => {
1086 self.mark_side_effect();
1087 }
1088 }
1089 }
1090
1091 fn visit_opt_chain_base(&mut self, base: &OptChainBase) {
1092 check_side_effects!(self);
1093
1094 match base {
1095 OptChainBase::Member(member) => {
1096 member.visit_with(self);
1097 }
1098 OptChainBase::Call(_opt_call) => {
1099 self.mark_side_effect();
1103 }
1104 }
1105 }
1106
1107 fn visit_prop_or_spread(&mut self, prop: &PropOrSpread) {
1108 check_side_effects!(self);
1109
1110 match prop {
1111 PropOrSpread::Spread(spread) => {
1112 spread.expr.visit_with(self);
1113 }
1114 PropOrSpread::Prop(prop) => {
1115 prop.visit_with(self);
1116 }
1117 }
1118 }
1119
1120 fn visit_prop(&mut self, prop: &Prop) {
1121 check_side_effects!(self);
1122
1123 match prop {
1124 Prop::KeyValue(kv) => {
1125 kv.key.visit_with(self);
1126 kv.value.visit_with(self);
1127 }
1128 Prop::Getter(getter) => {
1129 getter.key.visit_with(self);
1130 }
1132 Prop::Setter(setter) => {
1133 setter.key.visit_with(self);
1134 }
1136 Prop::Method(method) => {
1137 method.key.visit_with(self);
1138 }
1140 Prop::Shorthand(_) => {
1141 }
1143 Prop::Assign(_) => {
1144 }
1147 }
1148 }
1149
1150 fn visit_prop_name(&mut self, prop_name: &PropName) {
1151 check_side_effects!(self);
1152
1153 match prop_name {
1154 PropName::Computed(computed) => {
1155 computed.expr.visit_with(self);
1157 }
1158 _ => {
1159 }
1161 }
1162 }
1163
1164 fn visit_class(&mut self, class: &Class) {
1165 check_side_effects!(self);
1166
1167 for decorator in &class.decorators {
1169 decorator.visit_with(self);
1170 }
1171
1172 if let Some(super_class) = &class.super_class {
1174 super_class.visit_with(self);
1175 }
1176
1177 for member in &class.body {
1179 member.visit_with(self);
1180 }
1181 }
1182
1183 fn visit_class_member(&mut self, member: &ClassMember) {
1184 check_side_effects!(self);
1185
1186 match member {
1187 ClassMember::StaticBlock(block) => {
1189 for stmt in &block.body.stmts {
1192 stmt.visit_with(self);
1193 }
1194 }
1195 ClassMember::ClassProp(class_prop) if class_prop.is_static => {
1197 for decorator in &class_prop.decorators {
1199 decorator.visit_with(self);
1200 }
1201 class_prop.key.visit_with(self);
1203 if let Some(value) = &class_prop.value {
1205 value.visit_with(self);
1206 }
1207 }
1208 ClassMember::Method(method) => {
1210 for decorator in &method.function.decorators {
1212 decorator.visit_with(self);
1213 }
1214 method.key.visit_with(self);
1215 }
1217 ClassMember::Constructor(constructor) => {
1218 constructor.key.visit_with(self);
1219 }
1221 ClassMember::PrivateMethod(private_method) => {
1222 for decorator in &private_method.function.decorators {
1224 decorator.visit_with(self);
1225 }
1226 private_method.key.visit_with(self);
1227 }
1229 ClassMember::ClassProp(class_prop) => {
1230 for decorator in &class_prop.decorators {
1232 decorator.visit_with(self);
1233 }
1234 class_prop.key.visit_with(self);
1236 }
1238 ClassMember::PrivateProp(private_prop) => {
1239 for decorator in &private_prop.decorators {
1241 decorator.visit_with(self);
1242 }
1243 private_prop.key.visit_with(self);
1244 }
1246 ClassMember::AutoAccessor(auto_accessor) if auto_accessor.is_static => {
1247 for decorator in &auto_accessor.decorators {
1249 decorator.visit_with(self);
1250 }
1251 auto_accessor.key.visit_with(self);
1253 if let Some(value) = &auto_accessor.value {
1254 value.visit_with(self);
1255 }
1256 }
1257 ClassMember::AutoAccessor(auto_accessor) => {
1258 for decorator in &auto_accessor.decorators {
1260 decorator.visit_with(self);
1261 }
1262 auto_accessor.key.visit_with(self);
1264 }
1265 ClassMember::Empty(_) => {
1266 }
1268 ClassMember::TsIndexSignature(_) => {
1269 }
1271 }
1272 }
1273
1274 fn visit_decorator(&mut self, _decorator: &Decorator) {
1275 if self.has_side_effects {
1276 return;
1277 }
1278
1279 self.mark_side_effect();
1283 }
1284
1285 fn visit_pat(&mut self, pat: &Pat) {
1286 check_side_effects!(self);
1287
1288 match pat {
1289 Pat::Object(object_pat) => {
1291 for prop in &object_pat.props {
1292 match prop {
1293 ObjectPatProp::KeyValue(kv) => {
1294 kv.key.visit_with(self);
1296 kv.value.visit_with(self);
1298 }
1299 ObjectPatProp::Assign(assign) => {
1300 if let Some(value) = &assign.value {
1302 value.visit_with(self);
1303 }
1304 }
1305 ObjectPatProp::Rest(rest) => {
1306 rest.arg.visit_with(self);
1308 }
1309 }
1310 }
1311 }
1312 Pat::Array(array_pat) => {
1314 for elem in array_pat.elems.iter().flatten() {
1315 elem.visit_with(self);
1316 }
1317 }
1318 Pat::Assign(assign_pat) => {
1320 assign_pat.right.visit_with(self);
1322 assign_pat.left.visit_with(self);
1324 }
1325 _ => {}
1327 }
1328 }
1329}
1330
1331#[cfg(test)]
1332mod tests {
1333 use swc_core::{
1334 common::{FileName, GLOBALS, Mark, SourceMap, comments::SingleThreadedComments, sync::Lrc},
1335 ecma::{
1336 ast::EsVersion,
1337 parser::{EsSyntax, Syntax, parse_file_as_program},
1338 transforms::base::resolver,
1339 visit::VisitMutWith,
1340 },
1341 };
1342
1343 use super::*;
1344
1345 fn parse_and_check_for_side_effects(code: &str, expected: ModuleSideEffects) {
1347 GLOBALS.set(&Default::default(), || {
1348 let cm = Lrc::new(SourceMap::default());
1349 let fm = cm.new_source_file(Lrc::new(FileName::Anon), code.to_string());
1350
1351 let comments = SingleThreadedComments::default();
1352 let mut errors = vec![];
1353
1354 let mut program = parse_file_as_program(
1355 &fm,
1356 Syntax::Es(EsSyntax {
1357 jsx: true,
1358 decorators: true,
1359 ..Default::default()
1360 }),
1361 EsVersion::latest(),
1362 Some(&comments),
1363 &mut errors,
1364 )
1365 .expect("Failed to parse");
1366
1367 let unresolved_mark = Mark::new();
1369 let top_level_mark = Mark::new();
1370 program.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
1371
1372 let actual =
1373 compute_module_evaluation_side_effects(&program, &comments, unresolved_mark);
1374
1375 let msg = match expected {
1376 ModuleSideEffects::ModuleEvaluationIsSideEffectFree => {
1377 "Expected code to have no local side effects"
1378 }
1379 ModuleSideEffects::SideEffectFree => "Expected code to be side effect free",
1380 ModuleSideEffects::SideEffectful => "Expected code to have side effects",
1381 };
1382 assert_eq!(actual, expected, "{}:\n{}", msg, code);
1383 })
1384 }
1385
1386 macro_rules! assert_side_effects {
1388 ($name:ident, $code:expr, $expected:expr) => {
1389 #[test]
1390 fn $name() {
1391 parse_and_check_for_side_effects($code, $expected);
1392 }
1393 };
1394 }
1395
1396 macro_rules! side_effects {
1397 ($name:ident, $code:expr) => {
1398 assert_side_effects!($name, $code, ModuleSideEffects::SideEffectful);
1399 };
1400 }
1401
1402 macro_rules! no_side_effects {
1403 ($name:ident, $code:expr) => {
1404 assert_side_effects!($name, $code, ModuleSideEffects::SideEffectFree);
1405 };
1406 }
1407 macro_rules! module_evaluation_is_side_effect_free {
1408 ($name:ident, $code:expr) => {
1409 assert_side_effects!(
1410 $name,
1411 $code,
1412 ModuleSideEffects::ModuleEvaluationIsSideEffectFree
1413 );
1414 };
1415 }
1416
1417 mod basic_tests {
1418 use super::*;
1419
1420 no_side_effects!(test_empty_program, "");
1421
1422 no_side_effects!(test_simple_const_declaration, "const x = 5;");
1423
1424 no_side_effects!(test_simple_let_declaration, "let y = 'string';");
1425
1426 no_side_effects!(test_array_literal, "const arr = [1, 2, 3];");
1427
1428 no_side_effects!(test_object_literal, "const obj = { a: 1, b: 2 };");
1429
1430 no_side_effects!(test_function_declaration, "function foo() { return 1; }");
1431
1432 no_side_effects!(
1433 test_function_expression,
1434 "const foo = function() { return 1; };"
1435 );
1436
1437 no_side_effects!(test_arrow_function, "const foo = () => 1;");
1438 }
1439
1440 mod side_effects_tests {
1441 use super::*;
1442
1443 side_effects!(test_console_log, "console.log('hello');");
1444
1445 side_effects!(test_function_call, "foo();");
1446
1447 side_effects!(test_method_call, "obj.method();");
1448
1449 side_effects!(test_assignment, "x = 5;");
1450
1451 side_effects!(test_member_assignment, "obj.prop = 5;");
1452
1453 side_effects!(test_constructor_call, "new SideEffect();");
1454
1455 side_effects!(test_update_expression, "x++;");
1456 }
1457
1458 mod pure_expressions_tests {
1459 use super::*;
1460
1461 no_side_effects!(test_binary_expression, "const x = 1 + 2;");
1462
1463 no_side_effects!(test_unary_expression, "const x = -5;");
1464
1465 no_side_effects!(test_conditional_expression, "const x = true ? 1 : 2;");
1466
1467 no_side_effects!(test_template_literal, "const x = `hello ${world}`;");
1468
1469 no_side_effects!(test_nested_object, "const obj = { a: { b: { c: 1 } } };");
1470
1471 no_side_effects!(test_nested_array, "const arr = [[1, 2], [3, 4]];");
1472 }
1473
1474 mod import_export_tests {
1475 use super::*;
1476
1477 module_evaluation_is_side_effect_free!(test_import_statement, "import x from 'y';");
1478 module_evaluation_is_side_effect_free!(test_require_statement, "const x = require('y');");
1479
1480 no_side_effects!(test_export_statement, "export default 5;");
1481
1482 no_side_effects!(test_export_const, "export const x = 5;");
1483
1484 side_effects!(
1485 test_export_const_with_side_effect,
1486 "export const x = foo();"
1487 );
1488 }
1489
1490 mod mixed_cases_tests {
1491 use super::*;
1492
1493 side_effects!(test_call_in_initializer, "const x = foo();");
1494
1495 side_effects!(test_call_in_array, "const arr = [1, foo(), 3];");
1496
1497 side_effects!(test_call_in_object, "const obj = { a: foo() };");
1498
1499 no_side_effects!(
1500 test_multiple_declarations_pure,
1501 "const x = 1;\nconst y = 2;\nconst z = 3;"
1502 );
1503
1504 side_effects!(
1505 test_multiple_declarations_with_side_effect,
1506 "const x = 1;\nfoo();\nconst z = 3;"
1507 );
1508
1509 no_side_effects!(test_class_declaration, "class Foo {}");
1510
1511 no_side_effects!(
1512 test_class_with_methods,
1513 "class Foo { method() { return 1; } }"
1514 );
1515 }
1516
1517 mod pure_annotations_tests {
1518 use super::*;
1519
1520 no_side_effects!(test_pure_annotation_function_call, "/*#__PURE__*/ foo();");
1521
1522 no_side_effects!(test_pure_annotation_with_at, "/*@__PURE__*/ foo();");
1523
1524 no_side_effects!(test_pure_annotation_constructor, "/*#__PURE__*/ new Foo();");
1525
1526 no_side_effects!(
1527 test_pure_annotation_in_variable,
1528 "const x = /*#__PURE__*/ foo();"
1529 );
1530
1531 no_side_effects!(
1532 test_pure_annotation_with_pure_args,
1533 "/*#__PURE__*/ foo(1, 2, 3);"
1534 );
1535
1536 side_effects!(
1538 test_pure_annotation_with_impure_args,
1539 "/*#__PURE__*/ foo(bar());"
1540 );
1541
1542 side_effects!(test_without_pure_annotation, "foo();");
1544
1545 no_side_effects!(
1546 test_pure_nested_in_object,
1547 "const obj = { x: /*#__PURE__*/ foo() };"
1548 );
1549
1550 no_side_effects!(test_pure_in_array, "const arr = [/*#__PURE__*/ foo()];");
1551
1552 no_side_effects!(
1553 test_multiple_pure_calls,
1554 "const x = /*#__PURE__*/ foo();\nconst y = /*#__PURE__*/ bar();"
1555 );
1556
1557 side_effects!(
1558 test_mixed_pure_and_impure,
1559 "const x = /*#__PURE__*/ foo();\nbar();\nconst z = /*#__PURE__*/ baz();"
1560 );
1561 }
1562
1563 mod known_pure_builtins_tests {
1564 use super::*;
1565
1566 no_side_effects!(test_math_abs, "const x = Math.abs(-5);");
1567
1568 no_side_effects!(test_math_floor, "const x = Math.floor(3.14);");
1569
1570 no_side_effects!(test_math_max, "const x = Math.max(1, 2, 3);");
1571
1572 no_side_effects!(test_object_keys, "const keys = Object.keys(obj);");
1573
1574 no_side_effects!(test_object_values, "const values = Object.values(obj);");
1575
1576 no_side_effects!(test_object_entries, "const entries = Object.entries(obj);");
1577
1578 no_side_effects!(test_array_is_array, "const result = Array.isArray([]);");
1579
1580 no_side_effects!(
1581 test_string_from_char_code,
1582 "const char = String.fromCharCode(65);"
1583 );
1584
1585 no_side_effects!(test_number_is_nan, "const result = Number.isNaN(x);");
1586
1587 no_side_effects!(
1588 test_multiple_math_calls,
1589 "const x = Math.abs(-5);\nconst y = Math.floor(3.14);\nconst z = Math.max(x, y);"
1590 );
1591
1592 side_effects!(
1594 test_pure_builtin_with_impure_arg,
1595 "const x = Math.abs(foo());"
1596 );
1597
1598 no_side_effects!(
1599 test_pure_builtin_in_expression,
1600 "const x = Math.abs(-5) + Math.floor(3.14);"
1601 );
1602
1603 side_effects!(
1604 test_mixed_builtin_and_impure,
1605 "const x = Math.abs(-5);\nfoo();\nconst z = Object.keys({});"
1606 );
1607
1608 side_effects!(test_unknown_math_property, "const x = Math.random();");
1610
1611 side_effects!(test_object_assign, "Object.assign(target, source);");
1613
1614 no_side_effects!(test_array_from, "const arr = Array.from(iterable);");
1615
1616 no_side_effects!(test_global_is_nan, "const result = isNaN(value);");
1617
1618 no_side_effects!(test_global_is_finite, "const result = isFinite(value);");
1619
1620 no_side_effects!(test_global_parse_int, "const num = parseInt('42', 10);");
1621
1622 no_side_effects!(test_global_parse_float, "const num = parseFloat('3.14');");
1623
1624 no_side_effects!(
1625 test_global_decode_uri,
1626 "const decoded = decodeURI(encoded);"
1627 );
1628
1629 no_side_effects!(
1630 test_global_decode_uri_component,
1631 "const decoded = decodeURIComponent(encoded);"
1632 );
1633
1634 no_side_effects!(
1636 test_global_string_constructor_as_function,
1637 "const str = String(123);"
1638 );
1639
1640 no_side_effects!(
1642 test_global_number_constructor_as_function,
1643 "const num = Number('123');"
1644 );
1645
1646 no_side_effects!(
1648 test_global_boolean_constructor_as_function,
1649 "const bool = Boolean(value);"
1650 );
1651
1652 no_side_effects!(
1654 test_global_symbol_constructor_as_function,
1655 "const sym = Symbol('description');"
1656 );
1657
1658 no_side_effects!(test_symbol_for, "const sym = Symbol.for('description');");
1660
1661 no_side_effects!(
1663 test_symbol_key_for,
1664 "const description = Symbol.keyFor(sym);"
1665 );
1666
1667 side_effects!(
1669 test_global_pure_with_impure_arg,
1670 "const result = isNaN(foo());"
1671 );
1672
1673 side_effects!(
1675 test_shadowed_global_is_nan,
1676 r#"
1677 const isNaN = () => sideEffect();
1678 const result = isNaN(value);
1679 "#
1680 );
1681 }
1682
1683 mod edge_cases_tests {
1684 use super::*;
1685
1686 no_side_effects!(test_computed_property, "const obj = { [key]: value };");
1687
1688 side_effects!(
1689 test_computed_property_with_call,
1690 "const obj = { [foo()]: value };"
1691 );
1692
1693 no_side_effects!(test_spread_in_array, "const arr = [...other];");
1694
1695 no_side_effects!(test_spread_in_object, "const obj = { ...other };");
1696
1697 no_side_effects!(test_destructuring_assignment, "const { a, b } = obj;");
1698
1699 no_side_effects!(test_array_destructuring, "const [a, b] = arr;");
1700
1701 no_side_effects!(test_nested_ternary, "const x = a ? (b ? 1 : 2) : 3;");
1702
1703 no_side_effects!(test_logical_and, "const x = a && b;");
1704
1705 no_side_effects!(test_logical_or, "const x = a || b;");
1706
1707 no_side_effects!(test_nullish_coalescing, "const x = a ?? b;");
1708
1709 no_side_effects!(test_typeof_operator, "const x = typeof y;");
1710
1711 no_side_effects!(test_void_operator, "const x = void 0;");
1712
1713 side_effects!(test_delete_expression, "delete obj.prop;");
1715
1716 no_side_effects!(test_sequence_expression_pure, "const x = (1, 2, 3);");
1717
1718 side_effects!(test_sequence_expression_impure, "const x = (foo(), 2, 3);");
1719
1720 no_side_effects!(test_arrow_with_block, "const foo = () => { return 1; };");
1721
1722 no_side_effects!(
1723 test_class_with_constructor,
1724 "class Foo { constructor() { this.x = 1; } }"
1725 );
1726
1727 no_side_effects!(test_class_extends, "class Foo extends Bar {}");
1728
1729 no_side_effects!(test_async_function, "async function foo() { return 1; }");
1730
1731 no_side_effects!(test_generator_function, "function* foo() { yield 1; }");
1732
1733 side_effects!(test_tagged_template, "const x = tag`hello`;");
1735
1736 no_side_effects!(
1738 test_tagged_template_string_raw,
1739 "const x = String.raw`hello ${world}`;"
1740 );
1741
1742 no_side_effects!(test_regex_literal, "const re = /pattern/g;");
1743
1744 no_side_effects!(test_bigint_literal, "const big = 123n;");
1745
1746 no_side_effects!(test_optional_chaining_pure, "const x = obj?.prop;");
1747
1748 side_effects!(test_optional_chaining_call, "const x = obj?.method();");
1750
1751 no_side_effects!(
1752 test_multiple_exports_pure,
1753 "export const a = 1;\nexport const b = 2;\nexport const c = 3;"
1754 );
1755
1756 no_side_effects!(test_export_function, "export function foo() { return 1; }");
1757
1758 no_side_effects!(test_export_class, "export class Foo {}");
1759
1760 module_evaluation_is_side_effect_free!(test_reexport, "export { foo } from 'bar';");
1761
1762 module_evaluation_is_side_effect_free!(
1764 test_dynamic_import,
1765 "const mod = import('./module');"
1766 );
1767
1768 module_evaluation_is_side_effect_free!(
1769 test_dynamic_import_with_await,
1770 "const mod = await import('./module');"
1771 );
1772
1773 no_side_effects!(test_export_default_expression, "export default 1 + 2;");
1774
1775 side_effects!(
1776 test_export_default_expression_with_side_effect,
1777 "export default foo();"
1778 );
1779
1780 no_side_effects!(
1781 test_export_default_function,
1782 "export default function() { return 1; }"
1783 );
1784
1785 no_side_effects!(test_export_default_class, "export default class Foo {}");
1786
1787 no_side_effects!(
1788 test_export_named_with_pure_builtin,
1789 "export const result = Math.abs(-5);"
1790 );
1791
1792 side_effects!(
1793 test_multiple_exports_mixed,
1794 "export const a = 1;\nexport const b = foo();\nexport const c = 3;"
1795 );
1796 }
1797
1798 mod pure_constructors_tests {
1799 use super::*;
1800
1801 no_side_effects!(test_new_set, "const s = new Set();");
1802
1803 no_side_effects!(test_new_map, "const m = new Map();");
1804
1805 no_side_effects!(test_new_weakset, "const ws = new WeakSet();");
1806
1807 no_side_effects!(test_new_weakmap, "const wm = new WeakMap();");
1808
1809 no_side_effects!(test_new_regexp, "const re = new RegExp('pattern');");
1810
1811 no_side_effects!(test_new_date, "const d = new Date();");
1812
1813 no_side_effects!(test_new_error, "const e = new Error('message');");
1814
1815 no_side_effects!(test_new_promise, "const p = new Promise(() => {});");
1816 side_effects!(
1817 test_new_promise_effectful,
1818 "const p = new Promise(() => {console.log('hello')});"
1819 );
1820
1821 no_side_effects!(test_new_array, "const arr = new Array(10);");
1822
1823 no_side_effects!(test_new_object, "const obj = new Object();");
1824
1825 no_side_effects!(test_new_typed_array, "const arr = new Uint8Array(10);");
1826
1827 no_side_effects!(test_new_url, "const url = new URL('https://example.com');");
1828
1829 no_side_effects!(
1830 test_new_url_search_params,
1831 "const params = new URLSearchParams();"
1832 );
1833
1834 side_effects!(
1836 test_pure_constructor_with_impure_args,
1837 "const s = new Set([foo()]);"
1838 );
1839
1840 no_side_effects!(
1841 test_multiple_pure_constructors,
1842 "const s = new Set();\nconst m = new Map();\nconst re = new RegExp('test');"
1843 );
1844
1845 side_effects!(
1847 test_unknown_constructor,
1848 "const custom = new CustomClass();"
1849 );
1850
1851 side_effects!(
1852 test_mixed_constructors,
1853 "const s = new Set();\nconst custom = new CustomClass();\nconst m = new Map();"
1854 );
1855 }
1856
1857 mod shadowing_detection_tests {
1858 use super::*;
1859
1860 side_effects!(
1862 test_shadowed_math,
1863 r#"
1864 const Math = { abs: () => console.log('side effect') };
1865 const result = Math.abs(-5);
1866 "#
1867 );
1868
1869 side_effects!(
1871 test_shadowed_object,
1872 r#"
1873 const Object = { keys: () => sideEffect() };
1874 const result = Object.keys({});
1875 "#
1876 );
1877
1878 side_effects!(
1880 test_shadowed_array_constructor,
1881 r#"
1882 const Array = class { constructor() { sideEffect(); } };
1883 const arr = new Array();
1884 "#
1885 );
1886
1887 side_effects!(
1889 test_shadowed_set_constructor,
1890 r#"
1891 const Set = class { constructor() { sideEffect(); } };
1892 const s = new Set();
1893 "#
1894 );
1895
1896 side_effects!(
1898 test_shadowed_map_constructor,
1899 r#"
1900 {
1901 const Map = class { constructor() { sideEffect(); } };
1902 const m = new Map();
1903 }
1904 "#
1905 );
1906
1907 no_side_effects!(
1909 test_global_math_not_shadowed,
1910 r#"
1911 const result = Math.abs(-5);
1912 "#
1913 );
1914
1915 no_side_effects!(
1917 test_global_object_not_shadowed,
1918 r#"
1919 const keys = Object.keys({ a: 1, b: 2 });
1920 "#
1921 );
1922
1923 no_side_effects!(
1925 test_global_array_constructor_not_shadowed,
1926 r#"
1927 const arr = new Array(1, 2, 3);
1928 "#
1929 );
1930
1931 side_effects!(
1933 test_shadowed_by_import,
1934 r#"
1935 import { Math } from './custom-math';
1936 const result = Math.abs(-5);
1937 "#
1938 );
1939
1940 side_effects!(
1942 test_nested_scope_shadowing,
1943 r#"
1944 {
1945 const Math = { floor: () => sideEffect() };
1946 const result = Math.floor(4.5);
1947 }
1948 "#
1949 );
1950
1951 no_side_effects!(
1955 test_parameter_shadowing,
1956 r#"
1957 function test(RegExp) {
1958 return new RegExp('test');
1959 }
1960 "#
1961 );
1962
1963 side_effects!(
1965 test_shadowing_with_var,
1966 r#"
1967 var Number = { isNaN: () => sideEffect() };
1968 const check = Number.isNaN(123);
1969 "#
1970 );
1971
1972 no_side_effects!(
1974 test_global_regexp_not_shadowed,
1975 r#"
1976 const re = new RegExp('[a-z]+');
1977 "#
1978 );
1979 }
1980
1981 mod literal_receiver_methods_tests {
1982 use super::*;
1983
1984 no_side_effects!(
1986 test_string_literal_to_lower_case,
1987 r#"const result = "HELLO".toLowerCase();"#
1988 );
1989
1990 no_side_effects!(
1991 test_string_literal_to_upper_case,
1992 r#"const result = "hello".toUpperCase();"#
1993 );
1994
1995 no_side_effects!(
1996 test_string_literal_slice,
1997 r#"const result = "hello world".slice(0, 5);"#
1998 );
1999
2000 no_side_effects!(
2001 test_string_literal_split,
2002 r#"const result = "a,b,c".split(',');"#
2003 );
2004
2005 no_side_effects!(
2006 test_string_literal_trim,
2007 r#"const result = " hello ".trim();"#
2008 );
2009
2010 no_side_effects!(
2011 test_string_literal_replace,
2012 r#"const result = "hello".replace('h', 'H');"#
2013 );
2014
2015 no_side_effects!(
2016 test_string_literal_includes,
2017 r#"const result = "hello world".includes('world');"#
2018 );
2019
2020 no_side_effects!(
2022 test_array_literal_map,
2023 r#"const result = [1, 2, 3].map(x => x * 2);"#
2024 );
2025 side_effects!(
2026 test_array_literal_map_with_effectful_callback,
2027 r#"const result = [1, 2, 3].map(x => {globalThis.something.push(x)});"#
2028 );
2029
2030 no_side_effects!(
2032 test_number_literal_to_fixed,
2033 r#"const result = (3.14159).toFixed(2);"#
2034 );
2035
2036 no_side_effects!(
2037 test_number_literal_to_string,
2038 r#"const result = (42).toString();"#
2039 );
2040
2041 no_side_effects!(
2042 test_number_literal_to_exponential,
2043 r#"const result = (123.456).toExponential(2);"#
2044 );
2045
2046 no_side_effects!(
2048 test_boolean_literal_to_string,
2049 r#"const result = true.toString();"#
2050 );
2051
2052 no_side_effects!(
2053 test_boolean_literal_value_of,
2054 r#"const result = false.valueOf();"#
2055 );
2056
2057 no_side_effects!(
2059 test_regexp_literal_to_string,
2060 r#"const result = /[a-z]+/.toString();"#
2061 );
2062
2063 no_side_effects!(
2066 test_regexp_literal_test,
2067 r#"const result = /[a-z]+/g.test("hello");"#
2068 );
2069
2070 no_side_effects!(
2071 test_regexp_literal_exec,
2072 r#"const result = /(\d+)/g.exec("test123");"#
2073 );
2074
2075 side_effects!(
2078 test_array_literal_with_impure_elements,
2079 r#"const result = [foo(), 2, 3].map(x => x * 2);"#
2080 );
2081
2082 no_side_effects!(
2086 test_array_literal_map_with_callback,
2087 r#"const result = [1, 2, 3].map(x => x * 2);"#
2088 );
2089 }
2090
2091 mod class_expression_side_effects_tests {
2092 use super::*;
2093
2094 no_side_effects!(test_class_no_extends_no_static, "class Foo {}");
2096
2097 no_side_effects!(test_class_pure_extends, "class Foo extends Bar {}");
2099
2100 side_effects!(
2102 test_class_extends_with_call,
2103 "class Foo extends someMixinFunction() {}"
2104 );
2105
2106 side_effects!(
2108 test_class_extends_with_complex_expr,
2109 "class Foo extends (Bar || Baz()) {}"
2110 );
2111
2112 side_effects!(
2114 test_class_static_property_with_call,
2115 r#"
2116 class Foo {
2117 static foo = someFunction();
2118 }
2119 "#
2120 );
2121
2122 no_side_effects!(
2124 test_class_static_property_pure,
2125 r#"
2126 class Foo {
2127 static foo = 42;
2128 }
2129 "#
2130 );
2131
2132 no_side_effects!(
2134 test_class_static_property_array_literal,
2135 r#"
2136 class Foo {
2137 static foo = [1, 2, 3];
2138 }
2139 "#
2140 );
2141
2142 side_effects!(
2144 test_class_static_block,
2145 r#"
2146 class Foo {
2147 static {
2148 console.log("hello");
2149 }
2150 }
2151 "#
2152 );
2153
2154 no_side_effects!(
2155 test_class_static_block_empty,
2156 r#"
2157 class Foo {
2158 static {}
2159 }
2160 "#
2161 );
2162
2163 no_side_effects!(
2165 test_class_instance_property_with_call,
2166 r#"
2167 class Foo {
2168 foo = someFunction();
2169 }
2170 "#
2171 );
2172
2173 no_side_effects!(
2175 test_class_constructor_with_side_effects,
2176 r#"
2177 class Foo {
2178 constructor() {
2179 console.log("constructor");
2180 }
2181 }
2182 "#
2183 );
2184
2185 no_side_effects!(
2187 test_class_method,
2188 r#"
2189 class Foo {
2190 method() {
2191 console.log("method");
2192 }
2193 }
2194 "#
2195 );
2196
2197 side_effects!(
2199 test_class_expr_extends_with_call,
2200 "const Foo = class extends getMixin() {};"
2201 );
2202
2203 side_effects!(
2205 test_class_expr_static_with_call,
2206 r#"
2207 const Foo = class {
2208 static prop = initValue();
2209 };
2210 "#
2211 );
2212
2213 no_side_effects!(
2215 test_class_expr_static_pure,
2216 r#"
2217 const Foo = class {
2218 static prop = "hello";
2219 };
2220 "#
2221 );
2222
2223 side_effects!(
2225 test_export_class_with_side_effects,
2226 r#"
2227 export class Foo extends getMixin() {
2228 static prop = init();
2229 }
2230 "#
2231 );
2232
2233 side_effects!(
2235 test_export_default_class_with_side_effects,
2236 r#"
2237 export default class Foo {
2238 static { console.log("init"); }
2239 }
2240 "#
2241 );
2242
2243 no_side_effects!(
2245 test_export_class_no_side_effects,
2246 r#"
2247 export class Foo {
2248 method() {
2249 console.log("method");
2250 }
2251 }
2252 "#
2253 );
2254
2255 side_effects!(
2257 test_class_mixed_static_properties,
2258 r#"
2259 class Foo {
2260 static a = 1;
2261 static b = impureCall();
2262 static c = 3;
2263 }
2264 "#
2265 );
2266
2267 no_side_effects!(
2269 test_class_static_property_pure_builtin,
2270 r#"
2271 class Foo {
2272 static value = Math.abs(-5);
2273 }
2274 "#
2275 );
2276
2277 side_effects!(
2279 test_class_computed_property_with_call,
2280 r#"
2281 class Foo {
2282 [computeName()]() {
2283 return 42;
2284 }
2285 }
2286 "#
2287 );
2288
2289 no_side_effects!(
2291 test_class_computed_property_pure,
2292 r#"
2293 class Foo {
2294 ['method']() {
2295 return 42;
2296 }
2297 }
2298 "#
2299 );
2300 }
2301
2302 mod complex_variable_declarations_tests {
2303 use super::*;
2304
2305 no_side_effects!(test_destructure_simple, "const { foo } = obj;");
2307
2308 side_effects!(
2310 test_destructure_default_with_call,
2311 "const { foo = someFunction() } = obj;"
2312 );
2313
2314 no_side_effects!(test_destructure_default_pure, "const { foo = 42 } = obj;");
2316
2317 no_side_effects!(
2319 test_destructure_default_array_literal,
2320 "const { foo = ['hello'] } = obj;"
2321 );
2322
2323 no_side_effects!(
2325 test_destructure_default_object_literal,
2326 "const { foo = { bar: 'baz' } } = obj;"
2327 );
2328
2329 side_effects!(
2331 test_destructure_nested_with_call,
2332 "const { a: { b = sideEffect() } } = obj;"
2333 );
2334
2335 side_effects!(
2337 test_array_destructure_default_with_call,
2338 "const [a, b = getDefault()] = arr;"
2339 );
2340
2341 no_side_effects!(
2343 test_array_destructure_default_pure,
2344 "const [a, b = 10] = arr;"
2345 );
2346
2347 side_effects!(
2349 test_multiple_destructure_mixed,
2350 "const { foo = 1, bar = compute() } = obj;"
2351 );
2352
2353 no_side_effects!(test_destructure_rest_pure, "const { foo, ...rest } = obj;");
2355
2356 side_effects!(
2358 test_destructure_complex_with_side_effect,
2359 r#"
2360 const {
2361 a,
2362 b: { c = sideEffect() },
2363 d = [1, 2, 3]
2364 } = obj;
2365 "#
2366 );
2367
2368 no_side_effects!(
2370 test_destructure_complex_pure,
2371 r#"
2372 const {
2373 a,
2374 b: { c = 5 },
2375 d = [1, 2, 3]
2376 } = obj;
2377 "#
2378 );
2379
2380 side_effects!(
2382 test_export_destructure_with_side_effect,
2383 "export const { foo = init() } = obj;"
2384 );
2385
2386 no_side_effects!(
2388 test_export_destructure_pure,
2389 "export const { foo = 42 } = obj;"
2390 );
2391
2392 no_side_effects!(
2394 test_destructure_default_pure_builtin,
2395 "const { foo = Math.abs(-5) } = obj;"
2396 );
2397
2398 no_side_effects!(
2400 test_destructure_default_pure_annotation,
2401 "const { foo = /*#__PURE__*/ compute() } = obj;"
2402 );
2403 }
2404
2405 mod decorator_side_effects_tests {
2406 use super::*;
2407
2408 side_effects!(
2410 test_class_decorator,
2411 r#"
2412 @decorator
2413 class Foo {}
2414 "#
2415 );
2416
2417 side_effects!(
2419 test_method_decorator,
2420 r#"
2421 class Foo {
2422 @decorator
2423 method() {}
2424 }
2425 "#
2426 );
2427
2428 side_effects!(
2430 test_property_decorator,
2431 r#"
2432 class Foo {
2433 @decorator
2434 prop = 1;
2435 }
2436 "#
2437 );
2438
2439 side_effects!(
2441 test_multiple_decorators,
2442 r#"
2443 @decorator1
2444 @decorator2
2445 class Foo {
2446 @propDecorator
2447 prop = 1;
2448
2449 @methodDecorator
2450 method() {}
2451 }
2452 "#
2453 );
2454
2455 side_effects!(
2457 test_decorator_with_args,
2458 r#"
2459 @decorator(config())
2460 class Foo {}
2461 "#
2462 );
2463 }
2464
2465 mod additional_edge_cases_tests {
2466 use super::*;
2467
2468 no_side_effects!(
2470 test_super_property_pure,
2471 r#"
2472 class Foo extends Bar {
2473 method() {
2474 return super.parentMethod;
2475 }
2476 }
2477 "#
2478 );
2479
2480 no_side_effects!(
2482 test_super_call_in_method,
2483 r#"
2484 class Foo extends Bar {
2485 method() {
2486 return super.parentMethod();
2487 }
2488 }
2489 "#
2490 );
2491
2492 no_side_effects!(test_import_meta, "const url = import.meta.url;");
2494
2495 no_side_effects!(
2497 test_new_target,
2498 r#"
2499 function Foo() {
2500 console.log(new.target);
2501 }
2502 "#
2503 );
2504
2505 side_effects!(test_jsx_element, "const el = <div>Hello</div>;");
2507
2508 side_effects!(test_jsx_fragment, "const el = <>Hello</>;");
2510
2511 no_side_effects!(
2513 test_private_field_access,
2514 r#"
2515 class Foo {
2516 #privateField = 42;
2517 method() {
2518 return this.#privateField;
2519 }
2520 }
2521 "#
2522 );
2523
2524 no_side_effects!(
2526 test_super_computed_property_pure,
2527 r#"
2528 class Foo extends Bar {
2529 method() {
2530 return super['prop'];
2531 }
2532 }
2533 "#
2534 );
2535
2536 no_side_effects!(
2538 test_static_block_pure_content,
2539 r#"
2540 class Foo {
2541 static {
2542 const x = 1;
2543 const y = 2;
2544 }
2545 }
2546 "#
2547 );
2548
2549 side_effects!(
2551 test_static_block_with_side_effect_inside,
2552 r#"
2553 class Foo {
2554 static {
2555 sideEffect();
2556 }
2557 }
2558 "#
2559 );
2560
2561 no_side_effects!(
2563 test_this_expression,
2564 r#"
2565 class Foo {
2566 method() {
2567 return this;
2568 }
2569 }
2570 "#
2571 );
2572
2573 no_side_effects!(
2575 test_spread_pure_in_call,
2576 "const result = Math.max(...[1, 2, 3]);"
2577 );
2578
2579 side_effects!(
2581 test_spread_with_side_effect,
2582 "const result = Math.max(...getArray());"
2583 );
2584
2585 no_side_effects!(
2587 test_super_complex_access,
2588 r#"
2589 class Foo extends Bar {
2590 static method() {
2591 return super.parentMethod;
2592 }
2593 }
2594 "#
2595 );
2596
2597 no_side_effects!(
2599 test_getter_definition,
2600 r#"
2601 const obj = {
2602 get foo() {
2603 return this._foo;
2604 }
2605 };
2606 "#
2607 );
2608
2609 no_side_effects!(
2611 test_async_function_declaration,
2612 r#"
2613 async function foo() {
2614 return await something;
2615 }
2616 "#
2617 );
2618
2619 no_side_effects!(
2621 test_generator_declaration,
2622 r#"
2623 function* foo() {
2624 yield 1;
2625 yield 2;
2626 }
2627 "#
2628 );
2629
2630 no_side_effects!(
2632 test_async_generator,
2633 r#"
2634 async function* foo() {
2635 yield await something;
2636 }
2637 "#
2638 );
2639
2640 side_effects!(
2645 test_nullish_coalescing_with_side_effect,
2646 "const x = a ?? sideEffect();"
2647 );
2648
2649 side_effects!(
2651 test_logical_or_with_side_effect,
2652 "const x = a || sideEffect();"
2653 );
2654
2655 side_effects!(
2657 test_logical_and_with_side_effect,
2658 "const x = a && sideEffect();"
2659 );
2660 }
2661
2662 mod common_js_modules_tests {
2663 use super::*;
2664
2665 no_side_effects!(test_common_js_exports, "exports.foo = 'a'");
2668 no_side_effects!(test_common_js_exports_module, "module.exports.foo = 'a'");
2669 no_side_effects!(test_common_js_exports_assignment, "module.exports = {}");
2670 no_side_effects!(
2671 test_common_js_function_exports,
2672 "exports.foo = function () { return 1; }; exports.bar = 2;"
2673 );
2674
2675 module_evaluation_is_side_effect_free!(
2676 test_common_js_reexport,
2677 "module.exports = require('./other');"
2678 );
2679 module_evaluation_is_side_effect_free!(
2680 test_common_js_named_reexports,
2681 "exports.a = require('./a'); exports.b = require('./b');"
2682 );
2683
2684 side_effects!(
2686 test_common_js_export_impure_value,
2687 "exports.foo = sideEffect();"
2688 );
2689 side_effects!(
2691 test_common_js_export_computed_side_effect,
2692 "exports[sideEffect()] = 'a';"
2693 );
2694 side_effects!(test_module_non_export_assignment, "module.foo = 'a';");
2696 side_effects!(
2698 test_shadowed_exports_assignment,
2699 "let exports = {}; exports.foo = 'a';"
2700 );
2701
2702 side_effects!(
2706 test_cjs_export_setter_invoked,
2707 "module.exports = { set foo(v) { sideEffect() } }; module.exports.foo = 1;"
2708 );
2709
2710 no_side_effects!(
2713 test_cjs_export_fresh_then_write,
2714 "module.exports = {}; module.exports.foo = 1;"
2715 );
2716 side_effects!(
2720 test_cjs_export_reexport_then_write,
2721 "module.exports = require('./other'); module.exports.extra = 1;"
2722 );
2723 side_effects!(
2724 test_cjs_export_alias_then_write,
2725 "module.exports = other; module.exports.foo = 1;"
2726 );
2727 side_effects!(
2730 test_cjs_export_reexport_then_write_sequence,
2731 "module.exports = require('./other'), module.exports.extra = 1;"
2732 );
2733 side_effects!(
2737 test_cjs_export_reexport_in_logical_then_write,
2738 "x && (module.exports = require('./other')); module.exports.extra = 1;"
2739 );
2740 side_effects!(
2741 test_cjs_export_setter_attached_in_conditional,
2742 "x ? (module.exports = { set foo(v) { sideEffect() } }) : 0;"
2743 );
2744 no_side_effects!(
2747 test_cjs_export_reassign_in_function_body_is_pure,
2748 "function f() { module.exports = require('./other'); } module.exports.foo = 1;"
2749 );
2750
2751 side_effects!(
2756 test_cjs_export_setter_in_static_block,
2757 "class C { static { module.exports = { set foo(v) { sideEffect() } }; \
2758 module.exports.foo = 1; } }"
2759 );
2760 no_side_effects!(
2766 test_cjs_export_setter_in_constructor_is_pure,
2767 "class C { constructor() { module.exports = { set foo(v) { sideEffect() } }; } } \
2768 module.exports.foo = 1;"
2769 );
2770
2771 side_effects!(
2775 test_cjs_export_setter_in_static_property,
2776 "class C { static x = (module.exports = { set foo(v) { sideEffect() } }); } \
2777 module.exports.foo = 1;"
2778 );
2779 }
2780
2781 mod local_variable_mutation_tests {
2782 use super::*;
2783
2784 no_side_effects!(
2788 test_const_object_build,
2789 "const config = {}; config['a'] = 'a'; config['b'] = 'b'; export default config;"
2790 );
2791 no_side_effects!(test_const_member_assignment, "const o = {}; o.a = 1;");
2792 no_side_effects!(test_const_array_index, "const a = []; a[0] = 1;");
2793 no_side_effects!(test_const_nested_member, "const o = { a: {} }; o.a.b = 1;");
2794
2795 side_effects!(
2798 test_aliased_import_mutation,
2799 "import config from './config'; const c = config; c.enabled = true;"
2800 );
2801 side_effects!(
2803 test_aliased_global_mutation,
2804 "const g = globalThis; g.shared = 1;"
2805 );
2806 side_effects!(
2808 test_imported_binding_mutation,
2809 "import obj from 'x'; obj.foo = 1;"
2810 );
2811 side_effects!(
2813 test_non_safe_assignment_constant_init,
2814 "const o = makeObj(); o.a = 1;"
2815 );
2816 side_effects!(test_let_object_mutation, "let o = {}; o.a = 1;");
2818 side_effects!(test_global_assignment, "globalThis.shared = 1;");
2820 side_effects!(test_undeclared_assignment, "leaked = 1;");
2821 side_effects!(
2823 test_safe_assignment_constant_impure_value,
2824 "const o = {}; o.a = sideEffect();"
2825 );
2826 side_effects!(
2828 test_safe_assignment_constant_computed_key_side_effect,
2829 "const o = {}; o[sideEffect()] = 1;"
2830 );
2831 side_effects!(
2834 test_local_setter_invoked,
2835 "const o = { set x(v) { sideEffect() } }; o.x = 1;"
2836 );
2837 side_effects!(
2838 test_local_nested_setter_invoked,
2839 "const o = {}; o.a = { set y(v) { sideEffect() } }; o.a.y = 1;"
2840 );
2841 side_effects!(
2844 test_local_setter_attached_after_init,
2845 "const o = {}; o.x = { set y(v) { sideEffect() } };"
2846 );
2847 side_effects!(
2850 test_local_setter_via_define_property,
2851 "const o = {}; Object.defineProperty(o, 'b', { set(x) { this.a = x / 2 } }); o.b = 4;"
2852 );
2853 side_effects!(
2857 test_local_setter_attached_in_conditional,
2858 "const o = {}; x && (o.a = { set y(v) { sideEffect() } }); o.a.y = 1;"
2859 );
2860 }
2861}