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};
98
99static KNOWN_PURE_GLOBAL_FUNCTIONS: phf::Set<&'static str> = phf_set! {
104 "String",
105 "Number",
106 "Symbol",
107 "Boolean",
108 "isNaN",
109 "isFinite",
110 "parseInt",
111 "parseFloat",
112 "decodeURI",
113 "decodeURIComponent",
114};
115
116static KNOWN_PURE_CONSTRUCTORS: phf::Set<&'static str> = phf_set! {
121 "Set",
123 "Map",
124 "WeakSet",
125 "WeakMap",
126 "RegExp",
128 "Array",
130 "Object",
131 "Int8Array",
133 "Uint8Array",
134 "Uint8ClampedArray",
135 "Int16Array",
136 "Uint16Array",
137 "Int32Array",
138 "Uint32Array",
139 "Float32Array",
140 "Float64Array",
141 "BigInt64Array",
142 "BigUint64Array",
143 "Date",
145 "Error",
146 "TypeError",
147 "RangeError",
148 "SyntaxError",
149 "ReferenceError",
150 "URIError",
151 "EvalError",
152 "Promise",
153 "ArrayBuffer",
154 "DataView",
155 "URL",
156 "URLSearchParams",
157 "String",
159 "Number",
160 "Symbol",
161 "Boolean",
162};
163
164static KNOWN_PURE_STRING_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
172 "toLowerCase",
174 "toUpperCase",
175 "toLocaleLowerCase",
176 "toLocaleUpperCase",
177 "charAt",
178 "charCodeAt",
179 "codePointAt",
180 "slice",
181 "substring",
182 "substr",
183 "indexOf",
184 "lastIndexOf",
185 "includes",
186 "startsWith",
187 "endsWith",
188 "search",
189 "match",
190 "matchAll",
191 "trim",
192 "trimStart",
193 "trimEnd",
194 "trimLeft",
195 "trimRight",
196 "repeat",
197 "padStart",
198 "padEnd",
199 "concat",
200 "split",
201 "replace",
202 "replaceAll",
203 "normalize",
204 "localeCompare",
205 "isWellFormed",
206 "toString",
207 "valueOf",
208};
209
210static KNOWN_PURE_ARRAY_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
212 "map",
214 "filter",
215 "reduce",
216 "reduceRight",
217 "find",
218 "findIndex",
219 "findLast",
220 "findLastIndex",
221 "some",
222 "every",
223 "flat",
224 "flatMap",
225 "at",
227 "slice",
228 "concat",
229 "includes",
230 "indexOf",
231 "lastIndexOf",
232 "join",
233 "toLocaleString",
235 "toReversed",
236 "toSorted",
237 "toSpliced",
238 "with",
239};
240
241static KNOWN_PURE_OBJECT_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
242 "hasOwnProperty",
243 "propertyIsEnumerable",
244 "toString",
245 "valueOf",
246};
247
248static KNOWN_PURE_NUMBER_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
250 "toExponential", "toFixed", "toPrecision", "toLocaleString",
251};
252
253static KNOWN_PURE_REGEXP_PROTOTYPE_METHODS: phf::Set<&'static str> = phf_set! {
263 "test", "exec",
264};
265
266pub fn compute_module_evaluation_side_effects(
268 program: &Program,
269 comments: &dyn Comments,
270 unresolved_mark: Mark,
271) -> ModuleSideEffects {
272 let mut visitor = SideEffectVisitor::new(comments, unresolved_mark);
273 program.visit_with(&mut visitor);
274 if visitor.has_side_effects {
275 ModuleSideEffects::SideEffectful
276 } else if visitor.has_imports {
277 ModuleSideEffects::ModuleEvaluationIsSideEffectFree
278 } else {
279 ModuleSideEffects::SideEffectFree
280 }
281}
282
283struct SideEffectVisitor<'a> {
284 comments: &'a dyn Comments,
285 unresolved_mark: Mark,
286 has_side_effects: bool,
287 will_invoke_fn_exprs: bool,
288 has_imports: bool,
289}
290
291impl<'a> SideEffectVisitor<'a> {
292 fn new(comments: &'a dyn Comments, unresolved_mark: Mark) -> Self {
293 Self {
294 comments,
295 unresolved_mark,
296 has_side_effects: false,
297 will_invoke_fn_exprs: false,
298 has_imports: false,
299 }
300 }
301
302 fn mark_side_effect(&mut self) {
304 self.has_side_effects = true;
305 }
306
307 fn with_will_invoke_fn_exprs<F>(&mut self, value: bool, f: F)
313 where
314 F: FnOnce(&mut Self),
315 {
316 let old_value = self.will_invoke_fn_exprs;
317 self.will_invoke_fn_exprs = value;
318 f(self);
319 self.will_invoke_fn_exprs = old_value;
320 }
321
322 fn is_pure_annotated(&self, span: swc_core::common::Span) -> bool {
324 self.comments.has_flag(span.lo, "PURE")
325 }
326
327 fn is_known_pure_builtin(&self, callee: &Callee) -> bool {
331 match callee {
332 Callee::Expr(expr) => self.is_known_pure_builtin_function(expr),
333 _ => false,
334 }
335 }
336 fn is_require_or_import(&self, callee: &Callee) -> bool {
340 match callee {
341 Callee::Expr(expr) => {
342 let expr = unparen(expr);
343 if let Expr::Ident(ident) = expr {
344 ident.ctxt.outer() == self.unresolved_mark && ident.sym.as_ref() == "require"
345 } else {
346 false
347 }
348 }
349
350 Callee::Import(_) => true,
351 _ => false,
352 }
353 }
354 fn is_known_pure_builtin_function(&self, expr: &Expr) -> bool {
364 match expr {
365 Expr::Member(member) => {
366 let receiver = unparen(&member.obj);
367 match (receiver, &member.prop) {
368 (Expr::Ident(obj), MemberProp::Ident(prop)) => {
370 if obj.ctxt.outer() != self.unresolved_mark {
374 return false;
376 }
377
378 KNOWN_PURE_FUNCTIONS
381 .get(obj.sym.as_ref())
382 .map(|methods| methods.contains(prop.sym.as_ref()))
383 .unwrap_or(false)
384 }
385 (Expr::Lit(lit), MemberProp::Ident(prop)) => {
388 let method_name = prop.sym.as_ref();
389 match lit {
390 Lit::Str(_) => {
391 KNOWN_PURE_STRING_PROTOTYPE_METHODS.contains(method_name)
392 || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
393 }
394 Lit::Num(_) => {
395 KNOWN_PURE_NUMBER_PROTOTYPE_METHODS.contains(method_name)
396 || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
397 }
398 Lit::Bool(_) => {
399 KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
400 }
401 Lit::Regex(_) => {
402 KNOWN_PURE_REGEXP_PROTOTYPE_METHODS.contains(method_name)
403 || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
404 }
405 _ => false,
406 }
407 }
408 (Expr::Array(_), MemberProp::Ident(prop)) => {
411 let method_name = prop.sym.as_ref();
412 KNOWN_PURE_ARRAY_PROTOTYPE_METHODS.contains(method_name)
413 || KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(method_name)
414 }
415 (Expr::Object(_), MemberProp::Ident(prop)) => {
416 KNOWN_PURE_OBJECT_PROTOTYPE_METHODS.contains(prop.sym.as_ref())
417 }
418 _ => false,
419 }
420 }
421 Expr::Ident(ident) => {
422 if ident.ctxt.outer() != self.unresolved_mark {
425 return false;
426 }
427
428 KNOWN_PURE_GLOBAL_FUNCTIONS.contains(ident.sym.as_ref())
430 }
431 _ => false,
432 }
433 }
434
435 fn is_known_pure_constructor(&self, expr: &Expr) -> bool {
441 match expr {
442 Expr::Ident(ident) => {
443 if ident.ctxt.outer() != self.unresolved_mark {
446 return false;
447 }
448
449 KNOWN_PURE_CONSTRUCTORS.contains(ident.sym.as_ref())
451 }
452 _ => false,
453 }
454 }
455}
456
457impl<'a> Visit for SideEffectVisitor<'a> {
458 noop_visit_type!();
459 fn visit_program(&mut self, program: &Program) {
461 check_side_effects!(self);
462 program.visit_children_with(self);
463 }
464
465 fn visit_module(&mut self, module: &Module) {
466 check_side_effects!(self);
467
468 for item in &module.body {
470 check_side_effects!(self);
471 item.visit_with(self);
472 }
473 }
474
475 fn visit_script(&mut self, script: &Script) {
476 check_side_effects!(self);
477
478 for stmt in &script.body {
480 check_side_effects!(self);
481 stmt.visit_with(self);
482 }
483 }
484
485 fn visit_module_decl(&mut self, decl: &ModuleDecl) {
487 check_side_effects!(self);
488
489 match decl {
490 ModuleDecl::Import(_) => {
494 self.has_imports = true;
495 }
496
497 ModuleDecl::ExportDecl(export_decl) => {
499 match &export_decl.decl {
501 Decl::Fn(_) => {
502 }
504 Decl::Class(class_decl) => {
505 class_decl.visit_with(self);
508 }
509 Decl::Var(var_decl) => {
510 var_decl.visit_with(self);
512 }
513 _ => {
514 export_decl.decl.visit_with(self);
516 }
517 }
518 }
519
520 ModuleDecl::ExportDefaultDecl(export_default_decl) => {
521 match &export_default_decl.decl {
523 DefaultDecl::Class(cls) => {
524 cls.visit_with(self);
527 }
528 DefaultDecl::Fn(_) => {
529 }
531 DefaultDecl::TsInterfaceDecl(_) => {
532 }
534 }
535 }
536
537 ModuleDecl::ExportDefaultExpr(export_default_expr) => {
538 export_default_expr.expr.visit_with(self);
540 }
541
542 ModuleDecl::ExportNamed(e) => {
544 if e.src.is_some() {
545 self.has_imports = true;
547 }
548 }
549 ModuleDecl::ExportAll(_) => {
550 self.has_imports = true;
552 }
553
554 ModuleDecl::TsExportAssignment(_) | ModuleDecl::TsNamespaceExport(_) => {}
556 ModuleDecl::TsImportEquals(e) => {
557 match &e.module_ref {
560 TsModuleRef::TsEntityName(_) => {}
561 TsModuleRef::TsExternalModuleRef(_) => {
562 self.has_imports = true
564 }
565 }
566 }
567 }
568 }
569
570 fn visit_stmt(&mut self, stmt: &Stmt) {
572 check_side_effects!(self);
573
574 match stmt {
575 Stmt::Expr(expr_stmt) => {
577 expr_stmt.visit_with(self);
578 }
579 Stmt::Decl(Decl::Var(var_decl)) => {
581 var_decl.visit_with(self);
582 }
583 Stmt::Decl(Decl::Fn(_)) => {
585 }
587 Stmt::Decl(Decl::Class(class_decl)) => {
589 class_decl.visit_with(self);
590 }
591 Stmt::Decl(decl) => {
593 decl.visit_with(self);
594 }
595 _ => {
597 self.mark_side_effect();
600 }
601 }
602 }
603
604 fn visit_var_declarator(&mut self, var_decl: &VarDeclarator) {
605 check_side_effects!(self);
606
607 var_decl.name.visit_with(self);
609
610 if let Some(init) = &var_decl.init {
612 init.visit_with(self);
613 }
614 }
615
616 fn visit_expr(&mut self, expr: &Expr) {
618 check_side_effects!(self);
619
620 match expr {
621 Expr::Lit(_) => {
623 }
625 Expr::Ident(_) => {
626 }
628 Expr::Arrow(_) | Expr::Fn(_) => {
629 if self.will_invoke_fn_exprs {
631 self.with_will_invoke_fn_exprs(false, |this| {
633 expr.visit_children_with(this);
634 });
635 }
636 }
637 Expr::Class(class_expr) => {
638 class_expr.class.visit_with(self);
640 }
641 Expr::Array(arr) => {
642 for elem in arr.elems.iter().flatten() {
644 elem.visit_with(self);
645 }
646 }
647 Expr::Object(obj) => {
648 for prop in &obj.props {
650 prop.visit_with(self);
651 }
652 }
653 Expr::Unary(unary) => {
654 if unary.op == UnaryOp::Delete {
656 self.mark_side_effect();
659 } else {
660 unary.arg.visit_with(self);
661 }
662 }
663 Expr::Bin(bin) => {
664 bin.left.visit_with(self);
666 bin.right.visit_with(self);
667 }
668 Expr::Cond(cond) => {
669 cond.test.visit_with(self);
671 cond.cons.visit_with(self);
672 cond.alt.visit_with(self);
673 }
674 Expr::Member(member) => {
675 member.obj.visit_with(self);
683 member.prop.visit_with(self);
684 }
685 Expr::Paren(paren) => {
686 paren.expr.visit_with(self);
688 }
689 Expr::Tpl(tpl) => {
690 for expr in &tpl.exprs {
692 expr.visit_with(self);
693 }
694 }
695
696 Expr::Call(call) => {
698 if self.is_pure_annotated(call.span) || self.is_known_pure_builtin(&call.callee) {
700 call.callee.visit_with(self);
706
707 self.with_will_invoke_fn_exprs(true, |this| {
710 call.args.visit_children_with(this);
711 });
712 } else if self.is_require_or_import(&call.callee) {
713 self.has_imports = true;
714 call.args.visit_children_with(self);
717 } else {
718 self.mark_side_effect();
720 }
721 }
722 Expr::New(new) => {
723 if self.is_pure_annotated(new.span) || self.is_known_pure_constructor(&new.callee) {
725 self.with_will_invoke_fn_exprs(true, |this| {
727 new.args.visit_children_with(this);
728 });
729 } else {
730 self.mark_side_effect();
732 }
733 }
734 Expr::Assign(_) => {
735 self.mark_side_effect();
738 }
739 Expr::Update(_) => {
740 self.mark_side_effect();
743 }
744 Expr::Await(e) => {
745 e.arg.visit_with(self);
746 }
747 Expr::Yield(e) => {
748 e.arg.visit_with(self);
749 }
750 Expr::TaggedTpl(tagged_tpl) => {
751 if self.is_known_pure_builtin_function(&tagged_tpl.tag) {
754 for arg in &tagged_tpl.tpl.exprs {
755 arg.visit_with(self);
756 }
757 } else {
758 self.mark_side_effect();
759 }
760 }
761 Expr::OptChain(opt_chain) => {
762 opt_chain.base.visit_with(self);
765 }
766 Expr::Seq(seq) => {
767 seq.exprs.visit_children_with(self);
769 }
770 Expr::SuperProp(super_prop) => {
771 super_prop.prop.visit_with(self);
774 }
775 Expr::MetaProp(_) => {
776 }
779 Expr::JSXMember(_) | Expr::JSXNamespacedName(_) | Expr::JSXEmpty(_) => {
780 }
782 Expr::JSXElement(_) | Expr::JSXFragment(_) => {
783 self.mark_side_effect();
788 }
789 Expr::PrivateName(_) => {
790 }
792
793 _ => {
795 self.mark_side_effect();
796 }
797 }
798 }
799
800 fn visit_opt_chain_base(&mut self, base: &OptChainBase) {
801 check_side_effects!(self);
802
803 match base {
804 OptChainBase::Member(member) => {
805 member.visit_with(self);
806 }
807 OptChainBase::Call(_opt_call) => {
808 self.mark_side_effect();
812 }
813 }
814 }
815
816 fn visit_prop_or_spread(&mut self, prop: &PropOrSpread) {
817 check_side_effects!(self);
818
819 match prop {
820 PropOrSpread::Spread(spread) => {
821 spread.expr.visit_with(self);
822 }
823 PropOrSpread::Prop(prop) => {
824 prop.visit_with(self);
825 }
826 }
827 }
828
829 fn visit_prop(&mut self, prop: &Prop) {
830 check_side_effects!(self);
831
832 match prop {
833 Prop::KeyValue(kv) => {
834 kv.key.visit_with(self);
835 kv.value.visit_with(self);
836 }
837 Prop::Getter(getter) => {
838 getter.key.visit_with(self);
839 }
841 Prop::Setter(setter) => {
842 setter.key.visit_with(self);
843 }
845 Prop::Method(method) => {
846 method.key.visit_with(self);
847 }
849 Prop::Shorthand(_) => {
850 }
852 Prop::Assign(_) => {
853 }
856 }
857 }
858
859 fn visit_prop_name(&mut self, prop_name: &PropName) {
860 check_side_effects!(self);
861
862 match prop_name {
863 PropName::Computed(computed) => {
864 computed.expr.visit_with(self);
866 }
867 _ => {
868 }
870 }
871 }
872
873 fn visit_class(&mut self, class: &Class) {
874 check_side_effects!(self);
875
876 for decorator in &class.decorators {
878 decorator.visit_with(self);
879 }
880
881 if let Some(super_class) = &class.super_class {
883 super_class.visit_with(self);
884 }
885
886 for member in &class.body {
888 member.visit_with(self);
889 }
890 }
891
892 fn visit_class_member(&mut self, member: &ClassMember) {
893 check_side_effects!(self);
894
895 match member {
896 ClassMember::StaticBlock(block) => {
898 for stmt in &block.body.stmts {
901 stmt.visit_with(self);
902 }
903 }
904 ClassMember::ClassProp(class_prop) if class_prop.is_static => {
906 for decorator in &class_prop.decorators {
908 decorator.visit_with(self);
909 }
910 class_prop.key.visit_with(self);
912 if let Some(value) = &class_prop.value {
914 value.visit_with(self);
915 }
916 }
917 ClassMember::Method(method) => {
919 for decorator in &method.function.decorators {
921 decorator.visit_with(self);
922 }
923 method.key.visit_with(self);
924 }
926 ClassMember::Constructor(constructor) => {
927 constructor.key.visit_with(self);
928 }
930 ClassMember::PrivateMethod(private_method) => {
931 for decorator in &private_method.function.decorators {
933 decorator.visit_with(self);
934 }
935 private_method.key.visit_with(self);
936 }
938 ClassMember::ClassProp(class_prop) => {
939 for decorator in &class_prop.decorators {
941 decorator.visit_with(self);
942 }
943 class_prop.key.visit_with(self);
945 }
947 ClassMember::PrivateProp(private_prop) => {
948 for decorator in &private_prop.decorators {
950 decorator.visit_with(self);
951 }
952 private_prop.key.visit_with(self);
953 }
955 ClassMember::AutoAccessor(auto_accessor) if auto_accessor.is_static => {
956 for decorator in &auto_accessor.decorators {
958 decorator.visit_with(self);
959 }
960 auto_accessor.key.visit_with(self);
962 if let Some(value) = &auto_accessor.value {
963 value.visit_with(self);
964 }
965 }
966 ClassMember::AutoAccessor(auto_accessor) => {
967 for decorator in &auto_accessor.decorators {
969 decorator.visit_with(self);
970 }
971 auto_accessor.key.visit_with(self);
973 }
974 ClassMember::Empty(_) => {
975 }
977 ClassMember::TsIndexSignature(_) => {
978 }
980 }
981 }
982
983 fn visit_decorator(&mut self, _decorator: &Decorator) {
984 if self.has_side_effects {
985 return;
986 }
987
988 self.mark_side_effect();
992 }
993
994 fn visit_pat(&mut self, pat: &Pat) {
995 check_side_effects!(self);
996
997 match pat {
998 Pat::Object(object_pat) => {
1000 for prop in &object_pat.props {
1001 match prop {
1002 ObjectPatProp::KeyValue(kv) => {
1003 kv.key.visit_with(self);
1005 kv.value.visit_with(self);
1007 }
1008 ObjectPatProp::Assign(assign) => {
1009 if let Some(value) = &assign.value {
1011 value.visit_with(self);
1012 }
1013 }
1014 ObjectPatProp::Rest(rest) => {
1015 rest.arg.visit_with(self);
1017 }
1018 }
1019 }
1020 }
1021 Pat::Array(array_pat) => {
1023 for elem in array_pat.elems.iter().flatten() {
1024 elem.visit_with(self);
1025 }
1026 }
1027 Pat::Assign(assign_pat) => {
1029 assign_pat.right.visit_with(self);
1031 assign_pat.left.visit_with(self);
1033 }
1034 _ => {}
1036 }
1037 }
1038}
1039
1040#[cfg(test)]
1041mod tests {
1042 use swc_core::{
1043 common::{FileName, GLOBALS, Mark, SourceMap, comments::SingleThreadedComments, sync::Lrc},
1044 ecma::{
1045 ast::EsVersion,
1046 parser::{EsSyntax, Syntax, parse_file_as_program},
1047 transforms::base::resolver,
1048 visit::VisitMutWith,
1049 },
1050 };
1051
1052 use super::*;
1053
1054 fn parse_and_check_for_side_effects(code: &str, expected: ModuleSideEffects) {
1056 GLOBALS.set(&Default::default(), || {
1057 let cm = Lrc::new(SourceMap::default());
1058 let fm = cm.new_source_file(Lrc::new(FileName::Anon), code.to_string());
1059
1060 let comments = SingleThreadedComments::default();
1061 let mut errors = vec![];
1062
1063 let mut program = parse_file_as_program(
1064 &fm,
1065 Syntax::Es(EsSyntax {
1066 jsx: true,
1067 decorators: true,
1068 ..Default::default()
1069 }),
1070 EsVersion::latest(),
1071 Some(&comments),
1072 &mut errors,
1073 )
1074 .expect("Failed to parse");
1075
1076 let unresolved_mark = Mark::new();
1078 let top_level_mark = Mark::new();
1079 program.visit_mut_with(&mut resolver(unresolved_mark, top_level_mark, false));
1080
1081 let actual =
1082 compute_module_evaluation_side_effects(&program, &comments, unresolved_mark);
1083
1084 let msg = match expected {
1085 ModuleSideEffects::ModuleEvaluationIsSideEffectFree => {
1086 "Expected code to have no local side effects"
1087 }
1088 ModuleSideEffects::SideEffectFree => "Expected code to be side effect free",
1089 ModuleSideEffects::SideEffectful => "Expected code to have side effects",
1090 };
1091 assert_eq!(actual, expected, "{}:\n{}", msg, code);
1092 })
1093 }
1094
1095 macro_rules! assert_side_effects {
1097 ($name:ident, $code:expr, $expected:expr) => {
1098 #[test]
1099 fn $name() {
1100 parse_and_check_for_side_effects($code, $expected);
1101 }
1102 };
1103 }
1104
1105 macro_rules! side_effects {
1106 ($name:ident, $code:expr) => {
1107 assert_side_effects!($name, $code, ModuleSideEffects::SideEffectful);
1108 };
1109 }
1110
1111 macro_rules! no_side_effects {
1112 ($name:ident, $code:expr) => {
1113 assert_side_effects!($name, $code, ModuleSideEffects::SideEffectFree);
1114 };
1115 }
1116 macro_rules! module_evaluation_is_side_effect_free {
1117 ($name:ident, $code:expr) => {
1118 assert_side_effects!(
1119 $name,
1120 $code,
1121 ModuleSideEffects::ModuleEvaluationIsSideEffectFree
1122 );
1123 };
1124 }
1125
1126 mod basic_tests {
1127 use super::*;
1128
1129 no_side_effects!(test_empty_program, "");
1130
1131 no_side_effects!(test_simple_const_declaration, "const x = 5;");
1132
1133 no_side_effects!(test_simple_let_declaration, "let y = 'string';");
1134
1135 no_side_effects!(test_array_literal, "const arr = [1, 2, 3];");
1136
1137 no_side_effects!(test_object_literal, "const obj = { a: 1, b: 2 };");
1138
1139 no_side_effects!(test_function_declaration, "function foo() { return 1; }");
1140
1141 no_side_effects!(
1142 test_function_expression,
1143 "const foo = function() { return 1; };"
1144 );
1145
1146 no_side_effects!(test_arrow_function, "const foo = () => 1;");
1147 }
1148
1149 mod side_effects_tests {
1150 use super::*;
1151
1152 side_effects!(test_console_log, "console.log('hello');");
1153
1154 side_effects!(test_function_call, "foo();");
1155
1156 side_effects!(test_method_call, "obj.method();");
1157
1158 side_effects!(test_assignment, "x = 5;");
1159
1160 side_effects!(test_member_assignment, "obj.prop = 5;");
1161
1162 side_effects!(test_constructor_call, "new SideEffect();");
1163
1164 side_effects!(test_update_expression, "x++;");
1165 }
1166
1167 mod pure_expressions_tests {
1168 use super::*;
1169
1170 no_side_effects!(test_binary_expression, "const x = 1 + 2;");
1171
1172 no_side_effects!(test_unary_expression, "const x = -5;");
1173
1174 no_side_effects!(test_conditional_expression, "const x = true ? 1 : 2;");
1175
1176 no_side_effects!(test_template_literal, "const x = `hello ${world}`;");
1177
1178 no_side_effects!(test_nested_object, "const obj = { a: { b: { c: 1 } } };");
1179
1180 no_side_effects!(test_nested_array, "const arr = [[1, 2], [3, 4]];");
1181 }
1182
1183 mod import_export_tests {
1184 use super::*;
1185
1186 module_evaluation_is_side_effect_free!(test_import_statement, "import x from 'y';");
1187 module_evaluation_is_side_effect_free!(test_require_statement, "const x = require('y');");
1188
1189 no_side_effects!(test_export_statement, "export default 5;");
1190
1191 no_side_effects!(test_export_const, "export const x = 5;");
1192
1193 side_effects!(
1194 test_export_const_with_side_effect,
1195 "export const x = foo();"
1196 );
1197 }
1198
1199 mod mixed_cases_tests {
1200 use super::*;
1201
1202 side_effects!(test_call_in_initializer, "const x = foo();");
1203
1204 side_effects!(test_call_in_array, "const arr = [1, foo(), 3];");
1205
1206 side_effects!(test_call_in_object, "const obj = { a: foo() };");
1207
1208 no_side_effects!(
1209 test_multiple_declarations_pure,
1210 "const x = 1;\nconst y = 2;\nconst z = 3;"
1211 );
1212
1213 side_effects!(
1214 test_multiple_declarations_with_side_effect,
1215 "const x = 1;\nfoo();\nconst z = 3;"
1216 );
1217
1218 no_side_effects!(test_class_declaration, "class Foo {}");
1219
1220 no_side_effects!(
1221 test_class_with_methods,
1222 "class Foo { method() { return 1; } }"
1223 );
1224 }
1225
1226 mod pure_annotations_tests {
1227 use super::*;
1228
1229 no_side_effects!(test_pure_annotation_function_call, "/*#__PURE__*/ foo();");
1230
1231 no_side_effects!(test_pure_annotation_with_at, "/*@__PURE__*/ foo();");
1232
1233 no_side_effects!(test_pure_annotation_constructor, "/*#__PURE__*/ new Foo();");
1234
1235 no_side_effects!(
1236 test_pure_annotation_in_variable,
1237 "const x = /*#__PURE__*/ foo();"
1238 );
1239
1240 no_side_effects!(
1241 test_pure_annotation_with_pure_args,
1242 "/*#__PURE__*/ foo(1, 2, 3);"
1243 );
1244
1245 side_effects!(
1247 test_pure_annotation_with_impure_args,
1248 "/*#__PURE__*/ foo(bar());"
1249 );
1250
1251 side_effects!(test_without_pure_annotation, "foo();");
1253
1254 no_side_effects!(
1255 test_pure_nested_in_object,
1256 "const obj = { x: /*#__PURE__*/ foo() };"
1257 );
1258
1259 no_side_effects!(test_pure_in_array, "const arr = [/*#__PURE__*/ foo()];");
1260
1261 no_side_effects!(
1262 test_multiple_pure_calls,
1263 "const x = /*#__PURE__*/ foo();\nconst y = /*#__PURE__*/ bar();"
1264 );
1265
1266 side_effects!(
1267 test_mixed_pure_and_impure,
1268 "const x = /*#__PURE__*/ foo();\nbar();\nconst z = /*#__PURE__*/ baz();"
1269 );
1270 }
1271
1272 mod known_pure_builtins_tests {
1273 use super::*;
1274
1275 no_side_effects!(test_math_abs, "const x = Math.abs(-5);");
1276
1277 no_side_effects!(test_math_floor, "const x = Math.floor(3.14);");
1278
1279 no_side_effects!(test_math_max, "const x = Math.max(1, 2, 3);");
1280
1281 no_side_effects!(test_object_keys, "const keys = Object.keys(obj);");
1282
1283 no_side_effects!(test_object_values, "const values = Object.values(obj);");
1284
1285 no_side_effects!(test_object_entries, "const entries = Object.entries(obj);");
1286
1287 no_side_effects!(test_array_is_array, "const result = Array.isArray([]);");
1288
1289 no_side_effects!(
1290 test_string_from_char_code,
1291 "const char = String.fromCharCode(65);"
1292 );
1293
1294 no_side_effects!(test_number_is_nan, "const result = Number.isNaN(x);");
1295
1296 no_side_effects!(
1297 test_multiple_math_calls,
1298 "const x = Math.abs(-5);\nconst y = Math.floor(3.14);\nconst z = Math.max(x, y);"
1299 );
1300
1301 side_effects!(
1303 test_pure_builtin_with_impure_arg,
1304 "const x = Math.abs(foo());"
1305 );
1306
1307 no_side_effects!(
1308 test_pure_builtin_in_expression,
1309 "const x = Math.abs(-5) + Math.floor(3.14);"
1310 );
1311
1312 side_effects!(
1313 test_mixed_builtin_and_impure,
1314 "const x = Math.abs(-5);\nfoo();\nconst z = Object.keys({});"
1315 );
1316
1317 side_effects!(test_unknown_math_property, "const x = Math.random();");
1319
1320 side_effects!(test_object_assign, "Object.assign(target, source);");
1322
1323 no_side_effects!(test_array_from, "const arr = Array.from(iterable);");
1324
1325 no_side_effects!(test_global_is_nan, "const result = isNaN(value);");
1326
1327 no_side_effects!(test_global_is_finite, "const result = isFinite(value);");
1328
1329 no_side_effects!(test_global_parse_int, "const num = parseInt('42', 10);");
1330
1331 no_side_effects!(test_global_parse_float, "const num = parseFloat('3.14');");
1332
1333 no_side_effects!(
1334 test_global_decode_uri,
1335 "const decoded = decodeURI(encoded);"
1336 );
1337
1338 no_side_effects!(
1339 test_global_decode_uri_component,
1340 "const decoded = decodeURIComponent(encoded);"
1341 );
1342
1343 no_side_effects!(
1345 test_global_string_constructor_as_function,
1346 "const str = String(123);"
1347 );
1348
1349 no_side_effects!(
1351 test_global_number_constructor_as_function,
1352 "const num = Number('123');"
1353 );
1354
1355 no_side_effects!(
1357 test_global_boolean_constructor_as_function,
1358 "const bool = Boolean(value);"
1359 );
1360
1361 no_side_effects!(
1363 test_global_symbol_constructor_as_function,
1364 "const sym = Symbol('description');"
1365 );
1366
1367 side_effects!(
1369 test_global_pure_with_impure_arg,
1370 "const result = isNaN(foo());"
1371 );
1372
1373 side_effects!(
1375 test_shadowed_global_is_nan,
1376 r#"
1377 const isNaN = () => sideEffect();
1378 const result = isNaN(value);
1379 "#
1380 );
1381 }
1382
1383 mod edge_cases_tests {
1384 use super::*;
1385
1386 no_side_effects!(test_computed_property, "const obj = { [key]: value };");
1387
1388 side_effects!(
1389 test_computed_property_with_call,
1390 "const obj = { [foo()]: value };"
1391 );
1392
1393 no_side_effects!(test_spread_in_array, "const arr = [...other];");
1394
1395 no_side_effects!(test_spread_in_object, "const obj = { ...other };");
1396
1397 no_side_effects!(test_destructuring_assignment, "const { a, b } = obj;");
1398
1399 no_side_effects!(test_array_destructuring, "const [a, b] = arr;");
1400
1401 no_side_effects!(test_nested_ternary, "const x = a ? (b ? 1 : 2) : 3;");
1402
1403 no_side_effects!(test_logical_and, "const x = a && b;");
1404
1405 no_side_effects!(test_logical_or, "const x = a || b;");
1406
1407 no_side_effects!(test_nullish_coalescing, "const x = a ?? b;");
1408
1409 no_side_effects!(test_typeof_operator, "const x = typeof y;");
1410
1411 no_side_effects!(test_void_operator, "const x = void 0;");
1412
1413 side_effects!(test_delete_expression, "delete obj.prop;");
1415
1416 no_side_effects!(test_sequence_expression_pure, "const x = (1, 2, 3);");
1417
1418 side_effects!(test_sequence_expression_impure, "const x = (foo(), 2, 3);");
1419
1420 no_side_effects!(test_arrow_with_block, "const foo = () => { return 1; };");
1421
1422 no_side_effects!(
1423 test_class_with_constructor,
1424 "class Foo { constructor() { this.x = 1; } }"
1425 );
1426
1427 no_side_effects!(test_class_extends, "class Foo extends Bar {}");
1428
1429 no_side_effects!(test_async_function, "async function foo() { return 1; }");
1430
1431 no_side_effects!(test_generator_function, "function* foo() { yield 1; }");
1432
1433 side_effects!(test_tagged_template, "const x = tag`hello`;");
1435
1436 no_side_effects!(
1438 test_tagged_template_string_raw,
1439 "const x = String.raw`hello ${world}`;"
1440 );
1441
1442 no_side_effects!(test_regex_literal, "const re = /pattern/g;");
1443
1444 no_side_effects!(test_bigint_literal, "const big = 123n;");
1445
1446 no_side_effects!(test_optional_chaining_pure, "const x = obj?.prop;");
1447
1448 side_effects!(test_optional_chaining_call, "const x = obj?.method();");
1450
1451 no_side_effects!(
1452 test_multiple_exports_pure,
1453 "export const a = 1;\nexport const b = 2;\nexport const c = 3;"
1454 );
1455
1456 no_side_effects!(test_export_function, "export function foo() { return 1; }");
1457
1458 no_side_effects!(test_export_class, "export class Foo {}");
1459
1460 module_evaluation_is_side_effect_free!(test_reexport, "export { foo } from 'bar';");
1461
1462 module_evaluation_is_side_effect_free!(
1464 test_dynamic_import,
1465 "const mod = import('./module');"
1466 );
1467
1468 module_evaluation_is_side_effect_free!(
1469 test_dynamic_import_with_await,
1470 "const mod = await import('./module');"
1471 );
1472
1473 no_side_effects!(test_export_default_expression, "export default 1 + 2;");
1474
1475 side_effects!(
1476 test_export_default_expression_with_side_effect,
1477 "export default foo();"
1478 );
1479
1480 no_side_effects!(
1481 test_export_default_function,
1482 "export default function() { return 1; }"
1483 );
1484
1485 no_side_effects!(test_export_default_class, "export default class Foo {}");
1486
1487 no_side_effects!(
1488 test_export_named_with_pure_builtin,
1489 "export const result = Math.abs(-5);"
1490 );
1491
1492 side_effects!(
1493 test_multiple_exports_mixed,
1494 "export const a = 1;\nexport const b = foo();\nexport const c = 3;"
1495 );
1496 }
1497
1498 mod pure_constructors_tests {
1499 use super::*;
1500
1501 no_side_effects!(test_new_set, "const s = new Set();");
1502
1503 no_side_effects!(test_new_map, "const m = new Map();");
1504
1505 no_side_effects!(test_new_weakset, "const ws = new WeakSet();");
1506
1507 no_side_effects!(test_new_weakmap, "const wm = new WeakMap();");
1508
1509 no_side_effects!(test_new_regexp, "const re = new RegExp('pattern');");
1510
1511 no_side_effects!(test_new_date, "const d = new Date();");
1512
1513 no_side_effects!(test_new_error, "const e = new Error('message');");
1514
1515 no_side_effects!(test_new_promise, "const p = new Promise(() => {});");
1516 side_effects!(
1517 test_new_promise_effectful,
1518 "const p = new Promise(() => {console.log('hello')});"
1519 );
1520
1521 no_side_effects!(test_new_array, "const arr = new Array(10);");
1522
1523 no_side_effects!(test_new_object, "const obj = new Object();");
1524
1525 no_side_effects!(test_new_typed_array, "const arr = new Uint8Array(10);");
1526
1527 no_side_effects!(test_new_url, "const url = new URL('https://example.com');");
1528
1529 no_side_effects!(
1530 test_new_url_search_params,
1531 "const params = new URLSearchParams();"
1532 );
1533
1534 side_effects!(
1536 test_pure_constructor_with_impure_args,
1537 "const s = new Set([foo()]);"
1538 );
1539
1540 no_side_effects!(
1541 test_multiple_pure_constructors,
1542 "const s = new Set();\nconst m = new Map();\nconst re = new RegExp('test');"
1543 );
1544
1545 side_effects!(
1547 test_unknown_constructor,
1548 "const custom = new CustomClass();"
1549 );
1550
1551 side_effects!(
1552 test_mixed_constructors,
1553 "const s = new Set();\nconst custom = new CustomClass();\nconst m = new Map();"
1554 );
1555 }
1556
1557 mod shadowing_detection_tests {
1558 use super::*;
1559
1560 side_effects!(
1562 test_shadowed_math,
1563 r#"
1564 const Math = { abs: () => console.log('side effect') };
1565 const result = Math.abs(-5);
1566 "#
1567 );
1568
1569 side_effects!(
1571 test_shadowed_object,
1572 r#"
1573 const Object = { keys: () => sideEffect() };
1574 const result = Object.keys({});
1575 "#
1576 );
1577
1578 side_effects!(
1580 test_shadowed_array_constructor,
1581 r#"
1582 const Array = class { constructor() { sideEffect(); } };
1583 const arr = new Array();
1584 "#
1585 );
1586
1587 side_effects!(
1589 test_shadowed_set_constructor,
1590 r#"
1591 const Set = class { constructor() { sideEffect(); } };
1592 const s = new Set();
1593 "#
1594 );
1595
1596 side_effects!(
1598 test_shadowed_map_constructor,
1599 r#"
1600 {
1601 const Map = class { constructor() { sideEffect(); } };
1602 const m = new Map();
1603 }
1604 "#
1605 );
1606
1607 no_side_effects!(
1609 test_global_math_not_shadowed,
1610 r#"
1611 const result = Math.abs(-5);
1612 "#
1613 );
1614
1615 no_side_effects!(
1617 test_global_object_not_shadowed,
1618 r#"
1619 const keys = Object.keys({ a: 1, b: 2 });
1620 "#
1621 );
1622
1623 no_side_effects!(
1625 test_global_array_constructor_not_shadowed,
1626 r#"
1627 const arr = new Array(1, 2, 3);
1628 "#
1629 );
1630
1631 side_effects!(
1633 test_shadowed_by_import,
1634 r#"
1635 import { Math } from './custom-math';
1636 const result = Math.abs(-5);
1637 "#
1638 );
1639
1640 side_effects!(
1642 test_nested_scope_shadowing,
1643 r#"
1644 {
1645 const Math = { floor: () => sideEffect() };
1646 const result = Math.floor(4.5);
1647 }
1648 "#
1649 );
1650
1651 no_side_effects!(
1655 test_parameter_shadowing,
1656 r#"
1657 function test(RegExp) {
1658 return new RegExp('test');
1659 }
1660 "#
1661 );
1662
1663 side_effects!(
1665 test_shadowing_with_var,
1666 r#"
1667 var Number = { isNaN: () => sideEffect() };
1668 const check = Number.isNaN(123);
1669 "#
1670 );
1671
1672 no_side_effects!(
1674 test_global_regexp_not_shadowed,
1675 r#"
1676 const re = new RegExp('[a-z]+');
1677 "#
1678 );
1679 }
1680
1681 mod literal_receiver_methods_tests {
1682 use super::*;
1683
1684 no_side_effects!(
1686 test_string_literal_to_lower_case,
1687 r#"const result = "HELLO".toLowerCase();"#
1688 );
1689
1690 no_side_effects!(
1691 test_string_literal_to_upper_case,
1692 r#"const result = "hello".toUpperCase();"#
1693 );
1694
1695 no_side_effects!(
1696 test_string_literal_slice,
1697 r#"const result = "hello world".slice(0, 5);"#
1698 );
1699
1700 no_side_effects!(
1701 test_string_literal_split,
1702 r#"const result = "a,b,c".split(',');"#
1703 );
1704
1705 no_side_effects!(
1706 test_string_literal_trim,
1707 r#"const result = " hello ".trim();"#
1708 );
1709
1710 no_side_effects!(
1711 test_string_literal_replace,
1712 r#"const result = "hello".replace('h', 'H');"#
1713 );
1714
1715 no_side_effects!(
1716 test_string_literal_includes,
1717 r#"const result = "hello world".includes('world');"#
1718 );
1719
1720 no_side_effects!(
1722 test_array_literal_map,
1723 r#"const result = [1, 2, 3].map(x => x * 2);"#
1724 );
1725 side_effects!(
1726 test_array_literal_map_with_effectful_callback,
1727 r#"const result = [1, 2, 3].map(x => {globalThis.something.push(x)});"#
1728 );
1729
1730 no_side_effects!(
1732 test_number_literal_to_fixed,
1733 r#"const result = (3.14159).toFixed(2);"#
1734 );
1735
1736 no_side_effects!(
1737 test_number_literal_to_string,
1738 r#"const result = (42).toString();"#
1739 );
1740
1741 no_side_effects!(
1742 test_number_literal_to_exponential,
1743 r#"const result = (123.456).toExponential(2);"#
1744 );
1745
1746 no_side_effects!(
1748 test_boolean_literal_to_string,
1749 r#"const result = true.toString();"#
1750 );
1751
1752 no_side_effects!(
1753 test_boolean_literal_value_of,
1754 r#"const result = false.valueOf();"#
1755 );
1756
1757 no_side_effects!(
1759 test_regexp_literal_to_string,
1760 r#"const result = /[a-z]+/.toString();"#
1761 );
1762
1763 no_side_effects!(
1766 test_regexp_literal_test,
1767 r#"const result = /[a-z]+/g.test("hello");"#
1768 );
1769
1770 no_side_effects!(
1771 test_regexp_literal_exec,
1772 r#"const result = /(\d+)/g.exec("test123");"#
1773 );
1774
1775 side_effects!(
1778 test_array_literal_with_impure_elements,
1779 r#"const result = [foo(), 2, 3].map(x => x * 2);"#
1780 );
1781
1782 no_side_effects!(
1786 test_array_literal_map_with_callback,
1787 r#"const result = [1, 2, 3].map(x => x * 2);"#
1788 );
1789 }
1790
1791 mod class_expression_side_effects_tests {
1792 use super::*;
1793
1794 no_side_effects!(test_class_no_extends_no_static, "class Foo {}");
1796
1797 no_side_effects!(test_class_pure_extends, "class Foo extends Bar {}");
1799
1800 side_effects!(
1802 test_class_extends_with_call,
1803 "class Foo extends someMixinFunction() {}"
1804 );
1805
1806 side_effects!(
1808 test_class_extends_with_complex_expr,
1809 "class Foo extends (Bar || Baz()) {}"
1810 );
1811
1812 side_effects!(
1814 test_class_static_property_with_call,
1815 r#"
1816 class Foo {
1817 static foo = someFunction();
1818 }
1819 "#
1820 );
1821
1822 no_side_effects!(
1824 test_class_static_property_pure,
1825 r#"
1826 class Foo {
1827 static foo = 42;
1828 }
1829 "#
1830 );
1831
1832 no_side_effects!(
1834 test_class_static_property_array_literal,
1835 r#"
1836 class Foo {
1837 static foo = [1, 2, 3];
1838 }
1839 "#
1840 );
1841
1842 side_effects!(
1844 test_class_static_block,
1845 r#"
1846 class Foo {
1847 static {
1848 console.log("hello");
1849 }
1850 }
1851 "#
1852 );
1853
1854 no_side_effects!(
1855 test_class_static_block_empty,
1856 r#"
1857 class Foo {
1858 static {}
1859 }
1860 "#
1861 );
1862
1863 no_side_effects!(
1865 test_class_instance_property_with_call,
1866 r#"
1867 class Foo {
1868 foo = someFunction();
1869 }
1870 "#
1871 );
1872
1873 no_side_effects!(
1875 test_class_constructor_with_side_effects,
1876 r#"
1877 class Foo {
1878 constructor() {
1879 console.log("constructor");
1880 }
1881 }
1882 "#
1883 );
1884
1885 no_side_effects!(
1887 test_class_method,
1888 r#"
1889 class Foo {
1890 method() {
1891 console.log("method");
1892 }
1893 }
1894 "#
1895 );
1896
1897 side_effects!(
1899 test_class_expr_extends_with_call,
1900 "const Foo = class extends getMixin() {};"
1901 );
1902
1903 side_effects!(
1905 test_class_expr_static_with_call,
1906 r#"
1907 const Foo = class {
1908 static prop = initValue();
1909 };
1910 "#
1911 );
1912
1913 no_side_effects!(
1915 test_class_expr_static_pure,
1916 r#"
1917 const Foo = class {
1918 static prop = "hello";
1919 };
1920 "#
1921 );
1922
1923 side_effects!(
1925 test_export_class_with_side_effects,
1926 r#"
1927 export class Foo extends getMixin() {
1928 static prop = init();
1929 }
1930 "#
1931 );
1932
1933 side_effects!(
1935 test_export_default_class_with_side_effects,
1936 r#"
1937 export default class Foo {
1938 static { console.log("init"); }
1939 }
1940 "#
1941 );
1942
1943 no_side_effects!(
1945 test_export_class_no_side_effects,
1946 r#"
1947 export class Foo {
1948 method() {
1949 console.log("method");
1950 }
1951 }
1952 "#
1953 );
1954
1955 side_effects!(
1957 test_class_mixed_static_properties,
1958 r#"
1959 class Foo {
1960 static a = 1;
1961 static b = impureCall();
1962 static c = 3;
1963 }
1964 "#
1965 );
1966
1967 no_side_effects!(
1969 test_class_static_property_pure_builtin,
1970 r#"
1971 class Foo {
1972 static value = Math.abs(-5);
1973 }
1974 "#
1975 );
1976
1977 side_effects!(
1979 test_class_computed_property_with_call,
1980 r#"
1981 class Foo {
1982 [computeName()]() {
1983 return 42;
1984 }
1985 }
1986 "#
1987 );
1988
1989 no_side_effects!(
1991 test_class_computed_property_pure,
1992 r#"
1993 class Foo {
1994 ['method']() {
1995 return 42;
1996 }
1997 }
1998 "#
1999 );
2000 }
2001
2002 mod complex_variable_declarations_tests {
2003 use super::*;
2004
2005 no_side_effects!(test_destructure_simple, "const { foo } = obj;");
2007
2008 side_effects!(
2010 test_destructure_default_with_call,
2011 "const { foo = someFunction() } = obj;"
2012 );
2013
2014 no_side_effects!(test_destructure_default_pure, "const { foo = 42 } = obj;");
2016
2017 no_side_effects!(
2019 test_destructure_default_array_literal,
2020 "const { foo = ['hello'] } = obj;"
2021 );
2022
2023 no_side_effects!(
2025 test_destructure_default_object_literal,
2026 "const { foo = { bar: 'baz' } } = obj;"
2027 );
2028
2029 side_effects!(
2031 test_destructure_nested_with_call,
2032 "const { a: { b = sideEffect() } } = obj;"
2033 );
2034
2035 side_effects!(
2037 test_array_destructure_default_with_call,
2038 "const [a, b = getDefault()] = arr;"
2039 );
2040
2041 no_side_effects!(
2043 test_array_destructure_default_pure,
2044 "const [a, b = 10] = arr;"
2045 );
2046
2047 side_effects!(
2049 test_multiple_destructure_mixed,
2050 "const { foo = 1, bar = compute() } = obj;"
2051 );
2052
2053 no_side_effects!(test_destructure_rest_pure, "const { foo, ...rest } = obj;");
2055
2056 side_effects!(
2058 test_destructure_complex_with_side_effect,
2059 r#"
2060 const {
2061 a,
2062 b: { c = sideEffect() },
2063 d = [1, 2, 3]
2064 } = obj;
2065 "#
2066 );
2067
2068 no_side_effects!(
2070 test_destructure_complex_pure,
2071 r#"
2072 const {
2073 a,
2074 b: { c = 5 },
2075 d = [1, 2, 3]
2076 } = obj;
2077 "#
2078 );
2079
2080 side_effects!(
2082 test_export_destructure_with_side_effect,
2083 "export const { foo = init() } = obj;"
2084 );
2085
2086 no_side_effects!(
2088 test_export_destructure_pure,
2089 "export const { foo = 42 } = obj;"
2090 );
2091
2092 no_side_effects!(
2094 test_destructure_default_pure_builtin,
2095 "const { foo = Math.abs(-5) } = obj;"
2096 );
2097
2098 no_side_effects!(
2100 test_destructure_default_pure_annotation,
2101 "const { foo = /*#__PURE__*/ compute() } = obj;"
2102 );
2103 }
2104
2105 mod decorator_side_effects_tests {
2106 use super::*;
2107
2108 side_effects!(
2110 test_class_decorator,
2111 r#"
2112 @decorator
2113 class Foo {}
2114 "#
2115 );
2116
2117 side_effects!(
2119 test_method_decorator,
2120 r#"
2121 class Foo {
2122 @decorator
2123 method() {}
2124 }
2125 "#
2126 );
2127
2128 side_effects!(
2130 test_property_decorator,
2131 r#"
2132 class Foo {
2133 @decorator
2134 prop = 1;
2135 }
2136 "#
2137 );
2138
2139 side_effects!(
2141 test_multiple_decorators,
2142 r#"
2143 @decorator1
2144 @decorator2
2145 class Foo {
2146 @propDecorator
2147 prop = 1;
2148
2149 @methodDecorator
2150 method() {}
2151 }
2152 "#
2153 );
2154
2155 side_effects!(
2157 test_decorator_with_args,
2158 r#"
2159 @decorator(config())
2160 class Foo {}
2161 "#
2162 );
2163 }
2164
2165 mod additional_edge_cases_tests {
2166 use super::*;
2167
2168 no_side_effects!(
2170 test_super_property_pure,
2171 r#"
2172 class Foo extends Bar {
2173 method() {
2174 return super.parentMethod;
2175 }
2176 }
2177 "#
2178 );
2179
2180 no_side_effects!(
2182 test_super_call_in_method,
2183 r#"
2184 class Foo extends Bar {
2185 method() {
2186 return super.parentMethod();
2187 }
2188 }
2189 "#
2190 );
2191
2192 no_side_effects!(test_import_meta, "const url = import.meta.url;");
2194
2195 no_side_effects!(
2197 test_new_target,
2198 r#"
2199 function Foo() {
2200 console.log(new.target);
2201 }
2202 "#
2203 );
2204
2205 side_effects!(test_jsx_element, "const el = <div>Hello</div>;");
2207
2208 side_effects!(test_jsx_fragment, "const el = <>Hello</>;");
2210
2211 no_side_effects!(
2213 test_private_field_access,
2214 r#"
2215 class Foo {
2216 #privateField = 42;
2217 method() {
2218 return this.#privateField;
2219 }
2220 }
2221 "#
2222 );
2223
2224 no_side_effects!(
2226 test_super_computed_property_pure,
2227 r#"
2228 class Foo extends Bar {
2229 method() {
2230 return super['prop'];
2231 }
2232 }
2233 "#
2234 );
2235
2236 no_side_effects!(
2238 test_static_block_pure_content,
2239 r#"
2240 class Foo {
2241 static {
2242 const x = 1;
2243 const y = 2;
2244 }
2245 }
2246 "#
2247 );
2248
2249 side_effects!(
2251 test_static_block_with_side_effect_inside,
2252 r#"
2253 class Foo {
2254 static {
2255 sideEffect();
2256 }
2257 }
2258 "#
2259 );
2260
2261 no_side_effects!(
2263 test_this_expression,
2264 r#"
2265 class Foo {
2266 method() {
2267 return this;
2268 }
2269 }
2270 "#
2271 );
2272
2273 no_side_effects!(
2275 test_spread_pure_in_call,
2276 "const result = Math.max(...[1, 2, 3]);"
2277 );
2278
2279 side_effects!(
2281 test_spread_with_side_effect,
2282 "const result = Math.max(...getArray());"
2283 );
2284
2285 no_side_effects!(
2287 test_super_complex_access,
2288 r#"
2289 class Foo extends Bar {
2290 static method() {
2291 return super.parentMethod;
2292 }
2293 }
2294 "#
2295 );
2296
2297 no_side_effects!(
2299 test_getter_definition,
2300 r#"
2301 const obj = {
2302 get foo() {
2303 return this._foo;
2304 }
2305 };
2306 "#
2307 );
2308
2309 no_side_effects!(
2311 test_async_function_declaration,
2312 r#"
2313 async function foo() {
2314 return await something;
2315 }
2316 "#
2317 );
2318
2319 no_side_effects!(
2321 test_generator_declaration,
2322 r#"
2323 function* foo() {
2324 yield 1;
2325 yield 2;
2326 }
2327 "#
2328 );
2329
2330 no_side_effects!(
2332 test_async_generator,
2333 r#"
2334 async function* foo() {
2335 yield await something;
2336 }
2337 "#
2338 );
2339
2340 side_effects!(
2345 test_nullish_coalescing_with_side_effect,
2346 "const x = a ?? sideEffect();"
2347 );
2348
2349 side_effects!(
2351 test_logical_or_with_side_effect,
2352 "const x = a || sideEffect();"
2353 );
2354
2355 side_effects!(
2357 test_logical_and_with_side_effect,
2358 "const x = a && sideEffect();"
2359 );
2360 }
2361
2362 mod common_js_modules_tests {
2363 use super::*;
2364
2365 side_effects!(test_common_js_exports, "exports.foo = 'a'");
2366 side_effects!(test_common_js_exports_module, "module.exports.foo = 'a'");
2367 side_effects!(test_common_js_exports_assignment, "module.exports = {}");
2368 }
2369}