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