1use 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
52macro_rules! check_side_effects {
55 ($self:expr) => {
56 if $self.has_side_effects {
57 return;
58 }
59 };
60}
61
62static 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" => phf_set! {
81 "fromCharCode", "fromCodePoint", "raw",
82 },
83 "Number" => phf_set! {
85 "isFinite", "isInteger", "isNaN", "isSafeInteger", "parseFloat", "parseInt",
86 },
87 "Object" => phf_set! {
89 "keys", "values", "entries", "hasOwn", "getOwnPropertyNames", "getOwnPropertySymbols",
90 "getOwnPropertyDescriptor", "getOwnPropertyDescriptors", "getPrototypeOf", "is",
91 "isExtensible", "isFrozen", "isSealed",
92 },
93 "Array" => phf_set! {
95 "isArray", "from", "of",
96 },
97 "Symbol" => phf_set! {
99 "for", "keyFor"
100 },
101};
102
103static 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
120static KNOWN_PURE_CONSTRUCTORS: phf::Set<&'static str> = phf_set! {
125 "Set",
127 "Map",
128 "WeakSet",
129 "WeakMap",
130 "RegExp",
132 "Array",
134 "Object",
135 "Int8Array",
137 "Uint8Array",
138 "Uint8ClampedArray",
139 "Int16Array",
140 "Uint16Array",
141 "Int32Array",
142 "Uint32Array",
143 "Float32Array",
144 "Float64Array",
145 "BigInt64Array",
146 "BigUint64Array",
147 "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 "String",
163 "Number",
164 "Symbol",
165 "Boolean",
166};
167
168static KNOWN_PURE_STRING_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
176 "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
214static KNOWN_PURE_ARRAY_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
216 "map",
218 "filter",
219 "reduce",
220 "reduceRight",
221 "find",
222 "findIndex",
223 "findLast",
224 "findLastIndex",
225 "some",
226 "every",
227 "flat",
228 "flatMap",
229 "at",
231 "slice",
232 "concat",
233 "includes",
234 "indexOf",
235 "lastIndexOf",
236 "join",
237 "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
252static KNOWN_PURE_NUMBER_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
254 "toExponential", "toFixed", "toPrecision", "toLocaleString",
255};
256
257static KNOWN_PURE_REGEXP_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
267 "test", "exec",
268};
269
270pub 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 fn mark_side_effect(&mut self) {
308 self.has_side_effects = true;
309 }
310
311 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 fn is_pure_annotated(&self, span: swc_core::common::Span) -> bool {
328 self.comments.has_flag(span.lo, "PURE")
329 }
330
331 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 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 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 (Expr::Ident(obj), MemberProp::Ident(prop)) => {
374 if obj.ctxt.outer() != self.unresolved_mark {
378 return false;
380 }
381
382 KNOWN_PURE_FUNCTIONS
385 .get(obj.sym.as_ref())
386 .map(|methods| methods.contains(prop.sym.as_ref()))
387 .unwrap_or(false)
388 }
389 (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 (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 if ident.ctxt.outer() != self.unresolved_mark {
429 return false;
430 }
431
432 KNOWN_PURE_GLOBAL_FUNCTIONS.contains(ident.sym.as_ref())
434 }
435 _ => false,
436 }
437 }
438
439 fn is_known_pure_constructor(&self, expr: &Expr) -> bool {
445 match expr {
446 Expr::Ident(ident) => {
447 if ident.ctxt.outer() != self.unresolved_mark {
450 return false;
451 }
452
453 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 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 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 for stmt in &script.body {
484 check_side_effects!(self);
485 stmt.visit_with(self);
486 }
487 }
488
489 fn visit_module_decl(&mut self, decl: &ModuleDecl) {
491 check_side_effects!(self);
492
493 match decl {
494 ModuleDecl::Import(_) => {
498 self.has_imports = true;
499 }
500
501 ModuleDecl::ExportDecl(export_decl) => {
503 match &export_decl.decl {
505 Decl::Fn(_) => {
506 }
508 Decl::Class(class_decl) => {
509 class_decl.visit_with(self);
512 }
513 Decl::Var(var_decl) => {
514 var_decl.visit_with(self);
516 }
517 _ => {
518 export_decl.decl.visit_with(self);
520 }
521 }
522 }
523
524 ModuleDecl::ExportDefaultDecl(export_default_decl) => {
525 match &export_default_decl.decl {
527 DefaultDecl::Class(cls) => {
528 cls.visit_with(self);
531 }
532 DefaultDecl::Fn(_) => {
533 }
535 DefaultDecl::TsInterfaceDecl(_) => {
536 }
538 }
539 }
540
541 ModuleDecl::ExportDefaultExpr(export_default_expr) => {
542 export_default_expr.expr.visit_with(self);
544 }
545
546 ModuleDecl::ExportNamed(e) => {
548 if e.src.is_some() {
549 self.has_imports = true;
551 }
552 }
553 ModuleDecl::ExportAll(_) => {
554 self.has_imports = true;
556 }
557
558 ModuleDecl::TsExportAssignment(_) | ModuleDecl::TsNamespaceExport(_) => {}
560 ModuleDecl::TsImportEquals(e) => {
561 match &e.module_ref {
564 TsModuleRef::TsEntityName(_) => {}
565 TsModuleRef::TsExternalModuleRef(_) => {
566 self.has_imports = true
568 }
569 }
570 }
571 }
572 }
573
574 fn visit_stmt(&mut self, stmt: &Stmt) {
576 check_side_effects!(self);
577
578 match stmt {
579 Stmt::Expr(expr_stmt) => {
581 expr_stmt.visit_with(self);
582 }
583 Stmt::Decl(Decl::Var(var_decl)) => {
585 var_decl.visit_with(self);
586 }
587 Stmt::Decl(Decl::Fn(_)) => {
589 }
591 Stmt::Decl(Decl::Class(class_decl)) => {
593 class_decl.visit_with(self);
594 }
595 Stmt::Decl(decl) => {
597 decl.visit_with(self);
598 }
599 _ => {
601 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 var_decl.name.visit_with(self);
613
614 if let Some(init) = &var_decl.init {
616 init.visit_with(self);
617 }
618 }
619
620 fn visit_expr(&mut self, expr: &Expr) {
622 check_side_effects!(self);
623
624 match expr {
625 Expr::Lit(_) => {
627 }
629 Expr::Ident(_) => {
630 }
632 Expr::Arrow(_) | Expr::Fn(_) => {
633 if self.will_invoke_fn_exprs {
635 self.with_will_invoke_fn_exprs(false, |this| {
637 expr.visit_children_with(this);
638 });
639 }
640 }
641 Expr::Class(class_expr) => {
642 class_expr.class.visit_with(self);
644 }
645 Expr::Array(arr) => {
646 for elem in arr.elems.iter().flatten() {
648 elem.visit_with(self);
649 }
650 }
651 Expr::Object(obj) => {
652 for prop in &obj.props {
654 prop.visit_with(self);
655 }
656 }
657 Expr::Unary(unary) => {
658 if unary.op == UnaryOp::Delete {
660 self.mark_side_effect();
663 } else {
664 unary.arg.visit_with(self);
665 }
666 }
667 Expr::Bin(bin) => {
668 bin.left.visit_with(self);
670 bin.right.visit_with(self);
671 }
672 Expr::Cond(cond) => {
673 cond.test.visit_with(self);
675 cond.cons.visit_with(self);
676 cond.alt.visit_with(self);
677 }
678 Expr::Member(member) => {
679 member.obj.visit_with(self);
687 member.prop.visit_with(self);
688 }
689 Expr::Paren(paren) => {
690 paren.expr.visit_with(self);
692 }
693 Expr::Tpl(tpl) => {
694 for expr in &tpl.exprs {
696 expr.visit_with(self);
697 }
698 }
699
700 Expr::Call(call) => {
702 if self.is_pure_annotated(call.span) || self.is_known_pure_builtin(&call.callee) {
704 call.callee.visit_with(self);
710
711 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 call.args.visit_children_with(self);
721 } else {
722 self.mark_side_effect();
724 }
725 }
726 Expr::New(new) => {
727 if self.is_pure_annotated(new.span) || self.is_known_pure_constructor(&new.callee) {
729 self.with_will_invoke_fn_exprs(true, |this| {
731 new.args.visit_children_with(this);
732 });
733 } else {
734 self.mark_side_effect();
736 }
737 }
738 Expr::Assign(_) => {
739 self.mark_side_effect();
742 }
743 Expr::Update(_) => {
744 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 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 opt_chain.base.visit_with(self);
769 }
770 Expr::Seq(seq) => {
771 seq.exprs.visit_children_with(self);
773 }
774 Expr::SuperProp(super_prop) => {
775 super_prop.prop.visit_with(self);
778 }
779 Expr::MetaProp(_) => {
780 }
783 Expr::JSXMember(_) | Expr::JSXNamespacedName(_) | Expr::JSXEmpty(_) => {
784 }
786 Expr::JSXElement(_) | Expr::JSXFragment(_) => {
787 self.mark_side_effect();
792 }
793 Expr::PrivateName(_) => {
794 }
796
797 _ => {
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 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 }
845 Prop::Setter(setter) => {
846 setter.key.visit_with(self);
847 }
849 Prop::Method(method) => {
850 method.key.visit_with(self);
851 }
853 Prop::Shorthand(_) => {
854 }
856 Prop::Assign(_) => {
857 }
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.expr.visit_with(self);
870 }
871 _ => {
872 }
874 }
875 }
876
877 fn visit_class(&mut self, class: &Class) {
878 check_side_effects!(self);
879
880 for decorator in &class.decorators {
882 decorator.visit_with(self);
883 }
884
885 if let Some(super_class) = &class.super_class {
887 super_class.visit_with(self);
888 }
889
890 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 ClassMember::StaticBlock(block) => {
902 for stmt in &block.body.stmts {
905 stmt.visit_with(self);
906 }
907 }
908 ClassMember::ClassProp(class_prop) if class_prop.is_static => {
910 for decorator in &class_prop.decorators {
912 decorator.visit_with(self);
913 }
914 class_prop.key.visit_with(self);
916 if let Some(value) = &class_prop.value {
918 value.visit_with(self);
919 }
920 }
921 ClassMember::Method(method) => {
923 for decorator in &method.function.decorators {
925 decorator.visit_with(self);
926 }
927 method.key.visit_with(self);
928 }
930 ClassMember::Constructor(constructor) => {
931 constructor.key.visit_with(self);
932 }
934 ClassMember::PrivateMethod(private_method) => {
935 for decorator in &private_method.function.decorators {
937 decorator.visit_with(self);
938 }
939 private_method.key.visit_with(self);
940 }
942 ClassMember::ClassProp(class_prop) => {
943 for decorator in &class_prop.decorators {
945 decorator.visit_with(self);
946 }
947 class_prop.key.visit_with(self);
949 }
951 ClassMember::PrivateProp(private_prop) => {
952 for decorator in &private_prop.decorators {
954 decorator.visit_with(self);
955 }
956 private_prop.key.visit_with(self);
957 }
959 ClassMember::AutoAccessor(auto_accessor) if auto_accessor.is_static => {
960 for decorator in &auto_accessor.decorators {
962 decorator.visit_with(self);
963 }
964 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 for decorator in &auto_accessor.decorators {
973 decorator.visit_with(self);
974 }
975 auto_accessor.key.visit_with(self);
977 }
978 ClassMember::Empty(_) => {
979 }
981 ClassMember::TsIndexSignature(_) => {
982 }
984 }
985 }
986
987 fn visit_decorator(&mut self, _decorator: &Decorator) {
988 if self.has_side_effects {
989 return;
990 }
991
992 self.mark_side_effect();
996 }
997
998 fn visit_pat(&mut self, pat: &Pat) {
999 check_side_effects!(self);
1000
1001 match pat {
1002 Pat::Object(object_pat) => {
1004 for prop in &object_pat.props {
1005 match prop {
1006 ObjectPatProp::KeyValue(kv) => {
1007 kv.key.visit_with(self);
1009 kv.value.visit_with(self);
1011 }
1012 ObjectPatProp::Assign(assign) => {
1013 if let Some(value) = &assign.value {
1015 value.visit_with(self);
1016 }
1017 }
1018 ObjectPatProp::Rest(rest) => {
1019 rest.arg.visit_with(self);
1021 }
1022 }
1023 }
1024 }
1025 Pat::Array(array_pat) => {
1027 for elem in array_pat.elems.iter().flatten() {
1028 elem.visit_with(self);
1029 }
1030 }
1031 Pat::Assign(assign_pat) => {
1033 assign_pat.right.visit_with(self);
1035 assign_pat.left.visit_with(self);
1037 }
1038 _ => {}
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 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 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 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 side_effects!(
1251 test_pure_annotation_with_impure_args,
1252 "/*#__PURE__*/ foo(bar());"
1253 );
1254
1255 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 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 side_effects!(test_unknown_math_property, "const x = Math.random();");
1323
1324 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 no_side_effects!(
1349 test_global_string_constructor_as_function,
1350 "const str = String(123);"
1351 );
1352
1353 no_side_effects!(
1355 test_global_number_constructor_as_function,
1356 "const num = Number('123');"
1357 );
1358
1359 no_side_effects!(
1361 test_global_boolean_constructor_as_function,
1362 "const bool = Boolean(value);"
1363 );
1364
1365 no_side_effects!(
1367 test_global_symbol_constructor_as_function,
1368 "const sym = Symbol('description');"
1369 );
1370
1371 no_side_effects!(test_symbol_for, "const sym = Symbol.for('description');");
1373
1374 no_side_effects!(
1376 test_symbol_key_for,
1377 "const description = Symbol.keyFor(sym);"
1378 );
1379
1380 side_effects!(
1382 test_global_pure_with_impure_arg,
1383 "const result = isNaN(foo());"
1384 );
1385
1386 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 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 side_effects!(test_tagged_template, "const x = tag`hello`;");
1448
1449 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 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 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 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 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 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 side_effects!(
1584 test_shadowed_object,
1585 r#"
1586 const Object = { keys: () => sideEffect() };
1587 const result = Object.keys({});
1588 "#
1589 );
1590
1591 side_effects!(
1593 test_shadowed_array_constructor,
1594 r#"
1595 const Array = class { constructor() { sideEffect(); } };
1596 const arr = new Array();
1597 "#
1598 );
1599
1600 side_effects!(
1602 test_shadowed_set_constructor,
1603 r#"
1604 const Set = class { constructor() { sideEffect(); } };
1605 const s = new Set();
1606 "#
1607 );
1608
1609 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 no_side_effects!(
1622 test_global_math_not_shadowed,
1623 r#"
1624 const result = Math.abs(-5);
1625 "#
1626 );
1627
1628 no_side_effects!(
1630 test_global_object_not_shadowed,
1631 r#"
1632 const keys = Object.keys({ a: 1, b: 2 });
1633 "#
1634 );
1635
1636 no_side_effects!(
1638 test_global_array_constructor_not_shadowed,
1639 r#"
1640 const arr = new Array(1, 2, 3);
1641 "#
1642 );
1643
1644 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 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 no_side_effects!(
1668 test_parameter_shadowing,
1669 r#"
1670 function test(RegExp) {
1671 return new RegExp('test');
1672 }
1673 "#
1674 );
1675
1676 side_effects!(
1678 test_shadowing_with_var,
1679 r#"
1680 var Number = { isNaN: () => sideEffect() };
1681 const check = Number.isNaN(123);
1682 "#
1683 );
1684
1685 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 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 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 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 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 no_side_effects!(
1772 test_regexp_literal_to_string,
1773 r#"const result = /[a-z]+/.toString();"#
1774 );
1775
1776 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 side_effects!(
1791 test_array_literal_with_impure_elements,
1792 r#"const result = [foo(), 2, 3].map(x => x * 2);"#
1793 );
1794
1795 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 no_side_effects!(test_class_no_extends_no_static, "class Foo {}");
1809
1810 no_side_effects!(test_class_pure_extends, "class Foo extends Bar {}");
1812
1813 side_effects!(
1815 test_class_extends_with_call,
1816 "class Foo extends someMixinFunction() {}"
1817 );
1818
1819 side_effects!(
1821 test_class_extends_with_complex_expr,
1822 "class Foo extends (Bar || Baz()) {}"
1823 );
1824
1825 side_effects!(
1827 test_class_static_property_with_call,
1828 r#"
1829 class Foo {
1830 static foo = someFunction();
1831 }
1832 "#
1833 );
1834
1835 no_side_effects!(
1837 test_class_static_property_pure,
1838 r#"
1839 class Foo {
1840 static foo = 42;
1841 }
1842 "#
1843 );
1844
1845 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 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 no_side_effects!(
1878 test_class_instance_property_with_call,
1879 r#"
1880 class Foo {
1881 foo = someFunction();
1882 }
1883 "#
1884 );
1885
1886 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 no_side_effects!(
1900 test_class_method,
1901 r#"
1902 class Foo {
1903 method() {
1904 console.log("method");
1905 }
1906 }
1907 "#
1908 );
1909
1910 side_effects!(
1912 test_class_expr_extends_with_call,
1913 "const Foo = class extends getMixin() {};"
1914 );
1915
1916 side_effects!(
1918 test_class_expr_static_with_call,
1919 r#"
1920 const Foo = class {
1921 static prop = initValue();
1922 };
1923 "#
1924 );
1925
1926 no_side_effects!(
1928 test_class_expr_static_pure,
1929 r#"
1930 const Foo = class {
1931 static prop = "hello";
1932 };
1933 "#
1934 );
1935
1936 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 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 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 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 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 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 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 no_side_effects!(test_destructure_simple, "const { foo } = obj;");
2020
2021 side_effects!(
2023 test_destructure_default_with_call,
2024 "const { foo = someFunction() } = obj;"
2025 );
2026
2027 no_side_effects!(test_destructure_default_pure, "const { foo = 42 } = obj;");
2029
2030 no_side_effects!(
2032 test_destructure_default_array_literal,
2033 "const { foo = ['hello'] } = obj;"
2034 );
2035
2036 no_side_effects!(
2038 test_destructure_default_object_literal,
2039 "const { foo = { bar: 'baz' } } = obj;"
2040 );
2041
2042 side_effects!(
2044 test_destructure_nested_with_call,
2045 "const { a: { b = sideEffect() } } = obj;"
2046 );
2047
2048 side_effects!(
2050 test_array_destructure_default_with_call,
2051 "const [a, b = getDefault()] = arr;"
2052 );
2053
2054 no_side_effects!(
2056 test_array_destructure_default_pure,
2057 "const [a, b = 10] = arr;"
2058 );
2059
2060 side_effects!(
2062 test_multiple_destructure_mixed,
2063 "const { foo = 1, bar = compute() } = obj;"
2064 );
2065
2066 no_side_effects!(test_destructure_rest_pure, "const { foo, ...rest } = obj;");
2068
2069 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 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 side_effects!(
2095 test_export_destructure_with_side_effect,
2096 "export const { foo = init() } = obj;"
2097 );
2098
2099 no_side_effects!(
2101 test_export_destructure_pure,
2102 "export const { foo = 42 } = obj;"
2103 );
2104
2105 no_side_effects!(
2107 test_destructure_default_pure_builtin,
2108 "const { foo = Math.abs(-5) } = obj;"
2109 );
2110
2111 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 side_effects!(
2123 test_class_decorator,
2124 r#"
2125 @decorator
2126 class Foo {}
2127 "#
2128 );
2129
2130 side_effects!(
2132 test_method_decorator,
2133 r#"
2134 class Foo {
2135 @decorator
2136 method() {}
2137 }
2138 "#
2139 );
2140
2141 side_effects!(
2143 test_property_decorator,
2144 r#"
2145 class Foo {
2146 @decorator
2147 prop = 1;
2148 }
2149 "#
2150 );
2151
2152 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 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 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 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 no_side_effects!(test_import_meta, "const url = import.meta.url;");
2207
2208 no_side_effects!(
2210 test_new_target,
2211 r#"
2212 function Foo() {
2213 console.log(new.target);
2214 }
2215 "#
2216 );
2217
2218 side_effects!(test_jsx_element, "const el = <div>Hello</div>;");
2220
2221 side_effects!(test_jsx_fragment, "const el = <>Hello</>;");
2223
2224 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 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 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 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 no_side_effects!(
2276 test_this_expression,
2277 r#"
2278 class Foo {
2279 method() {
2280 return this;
2281 }
2282 }
2283 "#
2284 );
2285
2286 no_side_effects!(
2288 test_spread_pure_in_call,
2289 "const result = Math.max(...[1, 2, 3]);"
2290 );
2291
2292 side_effects!(
2294 test_spread_with_side_effect,
2295 "const result = Math.max(...getArray());"
2296 );
2297
2298 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 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 no_side_effects!(
2324 test_async_function_declaration,
2325 r#"
2326 async function foo() {
2327 return await something;
2328 }
2329 "#
2330 );
2331
2332 no_side_effects!(
2334 test_generator_declaration,
2335 r#"
2336 function* foo() {
2337 yield 1;
2338 yield 2;
2339 }
2340 "#
2341 );
2342
2343 no_side_effects!(
2345 test_async_generator,
2346 r#"
2347 async function* foo() {
2348 yield await something;
2349 }
2350 "#
2351 );
2352
2353 side_effects!(
2358 test_nullish_coalescing_with_side_effect,
2359 "const x = a ?? sideEffect();"
2360 );
2361
2362 side_effects!(
2364 test_logical_or_with_side_effect,
2365 "const x = a || sideEffect();"
2366 );
2367
2368 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}