1use std::sync::LazyLock;
2
3use rspack_core::{
4 DeferredPureCheck, Dependency, DependencyRange, ModuleDependency, SideEffectsBailoutItemWithSpan,
5};
6use rspack_util::SpanExt;
7use rustc_hash::FxHashSet;
8use swc_atoms::Atom;
9use swc_experimental_allocator::{CloneIn, atom::Atom as AstAtom};
10use swc_experimental_ecma_ast::{
11 ArrayLit, ArrowExpr, BlockStmt, BlockStmtOrExpr, CallExpr, Class, ClassMember, CommentKind,
12 Comments, Decl, DefaultDecl, ExportSpecifier, Expr, ExprOrSpread, Function, GetSpan,
13 ImportSpecifier, ModuleDecl, ModuleExportName, ModuleItem, ObjectPatProp, Pat, Program, PropName,
14 Span, Span as AstSpan, Stmt, VarDecl, VarDeclKind, VarDeclOrExpr, Visit, VisitWith,
15};
16use swc_experimental_ecma_utils::{ExprCtx, ExprExt};
17
18use crate::{
19 ClassExt, JavascriptParserPlugin,
20 dependency::ESMImportSideEffectDependency,
21 parser_plugin::esm_import_dependency_parser_plugin::{ESM_SPECIFIER_TAG, ESMSpecifierData},
22 visitors::{JavascriptParser, Statement, TagInfoData, VariableDeclaration},
23};
24
25static PURE_COMMENTS: LazyLock<regex::Regex> = LazyLock::new(|| {
26 regex::Regex::new("(?s)^\\s*(#|@)__PURE__(?:\\s|$)").expect("Should create the regex")
27});
28pub struct SideEffectsParserPlugin {
29 analyze_side_effects_free: bool,
30}
31
32impl SideEffectsParserPlugin {
33 pub fn new(analyze_side_effects_free: bool) -> Self {
34 Self {
35 analyze_side_effects_free,
36 }
37 }
38}
39
40struct PureAnnotation<'a> {
41 side_effects_free: FxHashSet<Atom>,
42 parser: &'a JavascriptParser<'a>,
43}
44
45fn compat_atom(atom: &AstAtom<'_>) -> Atom {
46 Atom::from(atom.as_str())
47}
48
49fn has_no_side_effects_notation(comments: &Comments<'_>, span: AstSpan) -> bool {
50 comments.has_flag(span.start, "NO_SIDE_EFFECTS")
51}
52
53fn expr_ctx<'a>(parser: &'a JavascriptParser<'_>, is_unresolved_ref_safe: bool) -> ExprCtx<'a> {
54 ExprCtx {
55 semantic: parser.ast.semantic,
56 is_unresolved_ref_safe,
57 in_strict: false,
58 remaining_depth: 4,
59 }
60}
61
62impl<'a> Visit<'a> for PureAnnotation<'a> {
63 fn visit_module_decl(&mut self, node: &ModuleDecl<'a>) {
64 match &node {
65 ModuleDecl::ExportDefaultExpr(default_expr) => {
66 if let Some(fn_expr) = default_expr.expr.as_fn()
67 && (has_no_side_effects_notation(self.parser.ast.comments, default_expr.span())
68 || has_no_side_effects_notation(self.parser.ast.comments, fn_expr.span()))
69 {
70 if let Some(ident) = &fn_expr.ident {
71 self.side_effects_free.insert(compat_atom(&ident.sym));
72 }
73 self.side_effects_free.insert(Atom::from("default"));
74 } else if let Some(arrow_expr) = default_expr.expr.as_arrow()
75 && (has_no_side_effects_notation(self.parser.ast.comments, default_expr.span())
76 || has_no_side_effects_notation(self.parser.ast.comments, arrow_expr.span()))
77 {
78 self.side_effects_free.insert(Atom::from("default"));
79 }
80 }
81 ModuleDecl::ExportDefaultDecl(default_decl) => {
82 if let Some(fn_expr) = default_decl.decl.as_fn()
83 && (has_no_side_effects_notation(self.parser.ast.comments, default_decl.span())
84 || has_no_side_effects_notation(self.parser.ast.comments, fn_expr.span()))
85 {
86 if let Some(ident) = &fn_expr.ident {
87 self.side_effects_free.insert(compat_atom(&ident.sym));
88 }
89 self.side_effects_free.insert(Atom::from("default"));
90 }
91 }
92 ModuleDecl::ExportDecl(export_decl) => {
93 if let Some(fn_decl) = export_decl.decl.as_fn()
94 && (has_no_side_effects_notation(self.parser.ast.comments, export_decl.span())
95 || has_no_side_effects_notation(self.parser.ast.comments, fn_decl.span()))
96 {
97 self
98 .side_effects_free
99 .insert(compat_atom(&fn_decl.ident.sym));
100 } else if let Some(var_decl) = export_decl.decl.as_var()
101 && matches!(var_decl.kind, VarDeclKind::Const)
102 && var_decl.decls.len() == 1
103 {
104 let const_decl = &var_decl.decls[0];
105 if let Some(ident) = const_decl.name.as_ident()
106 && let Some(Expr::Fn(fn_expr)) = const_decl.init.as_ref()
107 && (has_no_side_effects_notation(self.parser.ast.comments, var_decl.span())
108 || has_no_side_effects_notation(self.parser.ast.comments, fn_expr.span())
109 || has_no_side_effects_notation(self.parser.ast.comments, export_decl.span()))
110 {
111 self.side_effects_free.insert(compat_atom(&ident.id.sym));
112 } else if let Some(ident) = const_decl.name.as_ident()
113 && let Some(Expr::Arrow(fn_expr)) = const_decl.init.as_ref()
114 && (has_no_side_effects_notation(self.parser.ast.comments, var_decl.span())
115 || has_no_side_effects_notation(self.parser.ast.comments, fn_expr.span())
116 || has_no_side_effects_notation(self.parser.ast.comments, export_decl.span()))
117 {
118 self.side_effects_free.insert(compat_atom(&ident.id.sym));
119 }
120 }
121 }
122 _ => {}
123 }
124 }
125
126 fn visit_stmt(&mut self, node: &Stmt<'a>) {
127 if let Stmt::Decl(decl) = node {
128 #[allow(clippy::collapsible_match)]
129 match &**decl {
130 Decl::Fn(fn_decl) => {
131 if has_no_side_effects_notation(self.parser.ast.comments, fn_decl.span()) {
132 self
133 .side_effects_free
134 .insert(compat_atom(&fn_decl.ident.sym));
135 }
136 }
137 Decl::Var(var_decl) => {
138 if matches!(var_decl.kind, VarDeclKind::Const) && var_decl.decls.len() == 1 {
146 let const_decl = &var_decl.decls[0];
147
148 if let Some(ident) = const_decl.name.as_ident()
149 && let Some(Expr::Fn(fn_expr)) = const_decl.init.as_ref()
150 && (has_no_side_effects_notation(self.parser.ast.comments, var_decl.span())
151 || has_no_side_effects_notation(self.parser.ast.comments, fn_expr.span()))
152 {
153 self.side_effects_free.insert(compat_atom(&ident.id.sym));
154 } else if let Some(ident) = const_decl.name.as_ident()
155 && let Some(Expr::Arrow(fn_expr)) = const_decl.init.as_ref()
156 && (has_no_side_effects_notation(self.parser.ast.comments, var_decl.span())
157 || has_no_side_effects_notation(self.parser.ast.comments, fn_expr.span()))
158 {
159 self.side_effects_free.insert(compat_atom(&ident.id.sym));
160 }
161 }
162 }
163 _ => {}
164 }
165 }
166 }
167}
168
169fn collect_pure_function_acceptable_names(program: &Program) -> FxHashSet<Atom> {
170 let mut names = FxHashSet::default();
177 let mut insert = |name: Atom| {
178 names.insert(name);
179 };
180
181 match program {
182 Program::Module(module) => {
183 for item in &module.body {
185 match item {
186 ModuleItem::Stmt(stmt) => {
187 if let Stmt::Decl(decl) = &**stmt {
188 visit_decl_binding_names(decl, &mut insert)
189 }
190 }
191 ModuleItem::ModuleDecl(decl) => {
192 visit_module_decl_defined_binding_names(decl, &mut insert)
193 }
194 }
195 }
196
197 let local_bindings = names.clone();
199 for item in &module.body {
200 let ModuleItem::ModuleDecl(decl) = item else {
201 continue;
202 };
203 match &**decl {
204 ModuleDecl::ExportNamed(named_export) if named_export.src.is_none() => {
205 for specifier in &named_export.specifiers {
206 let ExportSpecifier::Named(named) = specifier else {
207 continue;
208 };
209 let orig_atom = match &named.orig {
210 ModuleExportName::Ident(ident) => compat_atom(&ident.sym),
211 ModuleExportName::Str(_) => continue,
212 };
213 if !local_bindings.contains(&orig_atom) {
214 continue;
215 }
216 match named.exported.as_ref().unwrap_or(&named.orig) {
217 ModuleExportName::Ident(ident) => {
218 names.insert(compat_atom(&ident.sym));
219 }
220 ModuleExportName::Str(s) => {
221 names.insert(Atom::from(s.value.to_string_lossy().as_ref()));
222 }
223 }
224 }
225 }
226 ModuleDecl::ExportDefaultDecl(default_decl)
227 if matches!(default_decl.decl, DefaultDecl::Fn(_)) =>
228 {
229 names.insert(Atom::from("default"));
230 }
231 ModuleDecl::ExportDefaultExpr(default_expr)
232 if default_expr.expr.is_fn() || default_expr.expr.is_arrow() =>
233 {
234 names.insert(Atom::from("default"));
235 }
236 _ => {}
237 }
238 }
239 }
240 Program::Script(script) => {
241 for stmt in &script.body {
242 if let Stmt::Decl(decl) = stmt {
243 visit_decl_binding_names(decl, &mut insert);
244 }
245 }
246 }
247 }
248
249 names
250}
251
252fn collect_defined_configured_side_effects_free(
253 program: &Program,
254 configured_side_effects_free: &[String],
255) -> FxHashSet<Atom> {
256 let acceptable = collect_pure_function_acceptable_names(program);
257
258 configured_side_effects_free
259 .iter()
260 .filter_map(|name| {
261 let atom = Atom::from(name.clone());
262 acceptable.contains(&atom).then_some(atom)
263 })
264 .collect()
265}
266
267fn visit_pat_binding_names(pat: &Pat, f: &mut impl FnMut(Atom)) {
268 match pat {
269 Pat::Ident(ident) => f(compat_atom(&ident.id.sym)),
270 Pat::Array(array) => {
271 for elem in array.elems.iter().flatten() {
272 visit_pat_binding_names(elem, f);
273 }
274 }
275 Pat::Object(object) => {
276 for prop in &object.props {
277 match prop {
278 ObjectPatProp::KeyValue(prop) => {
279 visit_pat_binding_names(&prop.value, f);
280 }
281 ObjectPatProp::Assign(prop) => f(compat_atom(&prop.key.id.sym)),
282 ObjectPatProp::Rest(prop) => visit_pat_binding_names(&prop.arg, f),
283 }
284 }
285 }
286 Pat::Assign(assign) => visit_pat_binding_names(&assign.left, f),
287 Pat::Rest(rest) => visit_pat_binding_names(&rest.arg, f),
288 Pat::Expr(_) | Pat::Invalid(_) => {}
289 }
290}
291
292fn visit_decl_binding_names(decl: &Decl, f: &mut impl FnMut(Atom)) {
293 match decl {
294 Decl::Fn(fn_decl) => f(compat_atom(&fn_decl.ident.sym)),
295 Decl::Class(class_decl) => f(compat_atom(&class_decl.ident.sym)),
296 Decl::Var(var_decl) => {
297 for declarator in &var_decl.decls {
298 visit_pat_binding_names(&declarator.name, f);
299 }
300 }
301 _ => {}
302 }
303}
304
305fn visit_module_decl_defined_binding_names(decl: &ModuleDecl, f: &mut impl FnMut(Atom)) {
306 match decl {
307 ModuleDecl::Import(import_decl) => {
308 for specifier in &import_decl.specifiers {
309 match specifier {
310 ImportSpecifier::Named(named) => f(compat_atom(&named.local.sym)),
311 ImportSpecifier::Default(default) => f(compat_atom(&default.local.sym)),
312 ImportSpecifier::Namespace(namespace) => f(compat_atom(&namespace.local.sym)),
313 }
314 }
315 }
316 ModuleDecl::ExportDecl(export_decl) => visit_decl_binding_names(&export_decl.decl, f),
317 ModuleDecl::ExportDefaultDecl(default_decl) => match &default_decl.decl {
318 DefaultDecl::Fn(fn_expr) => {
319 if let Some(ident) = &fn_expr.ident {
320 f(compat_atom(&ident.sym));
321 }
322 }
323 DefaultDecl::Class(class_expr) => {
324 if let Some(ident) = &class_expr.ident {
325 f(compat_atom(&ident.sym));
326 }
327 }
328 },
329 _ => {}
330 }
331}
332
333fn collect_duplicate_top_level_names(program: &Program) -> FxHashSet<Atom> {
334 let mut counts = rustc_hash::FxHashMap::<Atom, usize>::default();
335 let mut count_name = |name: Atom| {
336 *counts.entry(name).or_default() += 1;
337 };
338
339 match program {
340 Program::Module(module) => {
341 for item in &module.body {
342 match item {
343 ModuleItem::Stmt(stmt) => {
344 if let Stmt::Decl(decl) = &**stmt {
345 visit_decl_binding_names(decl, &mut count_name)
346 }
347 }
348 ModuleItem::ModuleDecl(decl) => {
349 visit_module_decl_defined_binding_names(decl, &mut count_name)
350 }
351 }
352 }
353 }
354 Program::Script(script) => {
355 for stmt in &script.body {
356 if let Stmt::Decl(decl) = stmt {
357 visit_decl_binding_names(decl, &mut count_name);
358 }
359 }
360 }
361 }
362
363 counts
364 .into_iter()
365 .filter_map(|(name, count)| (count > 1).then_some(name))
366 .collect()
367}
368
369fn mark_side_effects_free(parser: &mut JavascriptParser, name: &Atom, export_name: Option<&Atom>) {
370 let side_effects_free = parser.build_info.side_effects_free.get_or_insert_default();
371 side_effects_free.insert(name.clone());
372 if let Some(export_name) = export_name {
373 side_effects_free.insert(export_name.clone());
374 }
375}
376
377fn try_mark_auto_side_effects_free_var_decl(
378 parser: &mut JavascriptParser,
379 analyze_side_effects_free: bool,
380 var_decl: &VarDecl,
381 export_name: Option<&Atom>,
382 comments: &Comments<'_>,
383 duplicate_names: &FxHashSet<Atom>,
384) {
385 if !matches!(var_decl.kind, VarDeclKind::Const) {
386 return;
387 }
388
389 for declarator in &var_decl.decls {
390 let Some(ident) = declarator.name.as_ident() else {
391 continue;
392 };
393 let ident = compat_atom(&ident.id.sym);
394
395 if parser
396 .build_info
397 .side_effects_free
398 .as_ref()
399 .is_some_and(|side_effects_free| side_effects_free.contains(&ident))
400 {
401 continue;
402 }
403
404 if duplicate_names.contains(&ident) {
405 continue;
406 }
407
408 let is_side_effects_free = match declarator.init.as_ref() {
409 Some(Expr::Fn(fn_expr)) => is_side_effects_free_function_body(
410 parser,
411 analyze_side_effects_free,
412 &fn_expr.function,
413 comments,
414 ),
415 Some(Expr::Arrow(arrow_expr)) => {
416 is_side_effects_free_arrow_body(parser, analyze_side_effects_free, arrow_expr, comments)
417 }
418 _ => false,
419 };
420
421 if is_side_effects_free {
422 mark_side_effects_free(parser, &ident, export_name);
423 }
424 }
425}
426
427fn try_mark_auto_side_effects_free_stmt(
428 parser: &mut JavascriptParser,
429 analyze_side_effects_free: bool,
430 stmt: &Stmt,
431 comments: &Comments<'_>,
432 duplicate_names: &FxHashSet<Atom>,
433) {
434 if let Stmt::Decl(decl) = stmt {
435 match &**decl {
436 Decl::Fn(fn_decl) => {
437 let ident = compat_atom(&fn_decl.ident.sym);
438 if parser
439 .build_info
440 .side_effects_free
441 .as_ref()
442 .is_some_and(|side_effects_free| side_effects_free.contains(&ident))
443 || duplicate_names.contains(&ident)
444 {
445 return;
446 }
447
448 if is_side_effects_free_function_body(
449 parser,
450 analyze_side_effects_free,
451 &fn_decl.function,
452 comments,
453 ) {
454 mark_side_effects_free(parser, &ident, None);
455 }
456 }
457 Decl::Var(var_decl) => try_mark_auto_side_effects_free_var_decl(
458 parser,
459 analyze_side_effects_free,
460 var_decl,
461 None,
462 comments,
463 duplicate_names,
464 ),
465 _ => {}
466 }
467 }
468}
469
470fn try_mark_auto_side_effects_free_module_decl(
471 parser: &mut JavascriptParser,
472 analyze_side_effects_free: bool,
473 decl: &ModuleDecl,
474 comments: &Comments<'_>,
475 duplicate_names: &FxHashSet<Atom>,
476) {
477 match decl {
478 ModuleDecl::ExportDefaultExpr(default_expr) => {
479 let Some(fn_expr) = default_expr.expr.as_fn() else {
480 return;
481 };
482 let Some(ident) = &fn_expr.ident else {
483 return;
484 };
485 let ident = compat_atom(&ident.sym);
486 let export_name = Atom::from("default");
487 if parser
488 .build_info
489 .side_effects_free
490 .as_ref()
491 .is_some_and(|side_effects_free| side_effects_free.contains(&ident))
492 || duplicate_names.contains(&ident)
493 {
494 return;
495 }
496 if is_side_effects_free_function_body(
497 parser,
498 analyze_side_effects_free,
499 &fn_expr.function,
500 comments,
501 ) {
502 mark_side_effects_free(parser, &ident, Some(&export_name));
503 }
504 }
505 ModuleDecl::ExportDefaultDecl(default_decl) => {
506 let Some(fn_expr) = default_decl.decl.as_fn() else {
507 return;
508 };
509 let Some(ident) = &fn_expr.ident else {
510 return;
511 };
512 let ident = compat_atom(&ident.sym);
513 let export_name = Atom::from("default");
514 if parser
515 .build_info
516 .side_effects_free
517 .as_ref()
518 .is_some_and(|side_effects_free| side_effects_free.contains(&ident))
519 || duplicate_names.contains(&ident)
520 {
521 return;
522 }
523 if is_side_effects_free_function_body(
524 parser,
525 analyze_side_effects_free,
526 &fn_expr.function,
527 comments,
528 ) {
529 mark_side_effects_free(parser, &ident, Some(&export_name));
530 }
531 }
532 ModuleDecl::ExportDecl(export_decl) => match &export_decl.decl {
533 Decl::Fn(fn_decl) => {
534 if parser
535 .build_info
536 .side_effects_free
537 .as_ref()
538 .is_some_and(|side_effects_free| {
539 side_effects_free.contains(&compat_atom(&fn_decl.ident.sym))
540 })
541 || duplicate_names.contains(&compat_atom(&fn_decl.ident.sym))
542 {
543 return;
544 }
545
546 if is_side_effects_free_function_body(
547 parser,
548 analyze_side_effects_free,
549 &fn_decl.function,
550 comments,
551 ) {
552 mark_side_effects_free(parser, &compat_atom(&fn_decl.ident.sym), None);
553 }
554 }
555 Decl::Var(var_decl) => try_mark_auto_side_effects_free_var_decl(
556 parser,
557 analyze_side_effects_free,
558 var_decl,
559 None,
560 comments,
561 duplicate_names,
562 ),
563 _ => {}
564 },
565 _ => {}
566 }
567}
568
569fn mark_auto_side_effects_free_program(
570 parser: &mut JavascriptParser,
571 analyze_side_effects_free: bool,
572 program: &Program,
573 comments: &Comments<'_>,
574 duplicate_names: &FxHashSet<Atom>,
575) {
576 match program {
577 Program::Module(module) => {
578 for item in &module.body {
579 match item {
580 ModuleItem::Stmt(stmt) => try_mark_auto_side_effects_free_stmt(
581 parser,
582 analyze_side_effects_free,
583 stmt,
584 comments,
585 duplicate_names,
586 ),
587 ModuleItem::ModuleDecl(decl) => try_mark_auto_side_effects_free_module_decl(
588 parser,
589 analyze_side_effects_free,
590 decl,
591 comments,
592 duplicate_names,
593 ),
594 }
595 }
596 }
597 Program::Script(script) => {
598 for stmt in &script.body {
599 try_mark_auto_side_effects_free_stmt(
600 parser,
601 analyze_side_effects_free,
602 stmt,
603 comments,
604 duplicate_names,
605 );
606 }
607 }
608 }
609}
610
611#[rspack_macros::implemented_javascript_parser_hooks]
612impl<'p, 'a> JavascriptParserPlugin<'p, 'a> for SideEffectsParserPlugin {
613 fn program(&self, parser: &mut JavascriptParser<'p>, ast: &Program) -> Option<bool> {
614 parser.build_info.side_effects_free = None;
615 parser.build_info.deferred_pure_checks.clear();
616
617 if self.analyze_side_effects_free {
620 let mut pure_annotation = PureAnnotation {
627 side_effects_free: FxHashSet::default(),
628 parser,
629 };
630 ast.visit_with(&mut pure_annotation);
631 let detected_side_effects_free = pure_annotation.side_effects_free;
632 if !detected_side_effects_free.is_empty() {
633 let side_effects_free = parser.build_info.side_effects_free.get_or_insert_default();
634 side_effects_free.extend(detected_side_effects_free);
635 }
636
637 if let Some(flagged_side_effects_free) = &parser.javascript_options.side_effects_free {
638 let defined_side_effects_free =
639 collect_defined_configured_side_effects_free(ast, flagged_side_effects_free);
640 if !defined_side_effects_free.is_empty() {
641 let side_effects_free = parser.build_info.side_effects_free.get_or_insert_default();
642 side_effects_free.extend(defined_side_effects_free);
643 }
644 }
645
646 let duplicate_names = collect_duplicate_top_level_names(ast);
647 loop {
648 let prev_len = parser
649 .build_info
650 .side_effects_free
651 .as_ref()
652 .map_or(0, FxHashSet::len);
653 mark_auto_side_effects_free_program(
654 parser,
655 self.analyze_side_effects_free,
656 ast,
657 parser.ast.comments,
658 &duplicate_names,
659 );
660 let next_len = parser
661 .build_info
662 .side_effects_free
663 .as_ref()
664 .map_or(0, FxHashSet::len);
665 if next_len == prev_len {
666 break;
667 }
668 }
669 }
670
671 None
672 }
673
674 fn module_declaration(
675 &self,
676 parser: &mut JavascriptParser<'p>,
677 decl: &ModuleDecl,
678 ) -> Option<bool> {
679 match decl {
680 ModuleDecl::ExportDefaultExpr(expr) => {
681 let mut callees = vec![];
682 if !is_pure_expression(
683 parser,
684 self.analyze_side_effects_free,
685 &expr.expr,
686 parser.ast.comments,
687 Some(&mut callees),
688 ) {
689 let range = DependencyRange::from(expr.span);
690 let loc = parser.to_dependency_location(range);
691 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
692 range,
693 loc,
694 String::from("ExportDefaultExpr"),
695 ));
696 } else {
697 for (callee, span) in callees {
699 if let Some(deferred_check) = try_extract_deferred_check(parser, callee, span) {
700 parser
701 .build_info
702 .deferred_pure_checks
703 .insert(deferred_check);
704 } else {
705 let range = DependencyRange::from(span);
706 let loc = parser.to_dependency_location(range);
707 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
708 range,
709 loc,
710 String::from("ExportDefaultExpr"),
711 ));
712 break;
713 }
714 }
715 }
716 }
717 ModuleDecl::ExportDecl(decl) => {
718 let mut callees = vec![];
719 if !is_pure_decl(
720 parser,
721 self.analyze_side_effects_free,
722 &decl.decl,
723 parser.ast.comments,
724 Some(&mut callees),
725 ) {
726 let range = DependencyRange::from(decl.decl.span());
727 let loc = parser.to_dependency_location(range);
728 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
729 range,
730 loc,
731 String::from("Decl"),
732 ));
733 }
734 for (callee, span) in callees {
735 if let Some(deferred_check) = try_extract_deferred_check(parser, callee, span) {
736 parser
737 .build_info
738 .deferred_pure_checks
739 .insert(deferred_check);
740 } else {
741 let range = DependencyRange::from(span);
742 let loc = parser.to_dependency_location(range);
743 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
744 range,
745 loc,
746 String::from("Decl"),
747 ));
748 break;
749 }
750 }
751 }
752 _ => {}
753 };
754 None
755 }
756 fn statement(&self, parser: &mut JavascriptParser<'p>, stmt: Statement) -> Option<bool> {
757 if !parser.is_top_level_scope() {
758 return None;
759 }
760 self.analyze_stmt_side_effects(&stmt, parser);
761 None
762 }
763
764 fn finish(&self, parser: &mut JavascriptParser<'p>) -> Option<bool> {
765 if self.analyze_side_effects_free {
766 let mut not_defined = Vec::new();
767 if let Some(side_effects_free) = &parser.javascript_options.side_effects_free {
769 let mut side_effects_free = side_effects_free.iter().collect::<Vec<_>>();
770 side_effects_free.sort();
771 let defined_side_effects_free = parser.build_info.side_effects_free.as_ref();
772 for atom in side_effects_free {
773 if !defined_side_effects_free.is_some_and(|configured_side_effects_free| {
774 configured_side_effects_free.contains(&Atom::from(atom.clone()))
775 }) {
776 not_defined.push(Atom::from(atom.clone()));
777 }
778 }
779 }
780
781 if !not_defined.is_empty() {
782 if let Some(side_effects_free) = parser.build_info.side_effects_free.as_mut() {
783 for atom in ¬_defined {
784 side_effects_free.remove(atom);
785 }
786 }
787
788 let resource = parser.resource_data.resource();
789 parser.add_warning(
790 rspack_error::Diagnostic::warn("PURE_FUNCTION_NOT_FOUND".into(), format!("Following pure functions are not found in {resource}:\n[{}]\nRemove it from `module.rules[*].parser.pureFunctions`", not_defined.iter().map(|atom| format!("`{atom}`")).collect::<Vec<_>>().join(", ")))
791 );
792 }
793 }
794
795 None
796 }
797}
798
799#[inline(never)]
800fn is_pure_call_expr(
801 parser: &mut JavascriptParser,
802 analyze_side_effects_free: bool,
803 expr: &Expr,
804 comments: &Comments<'_>,
805 callees: Option<&mut Vec<(Atom, Span)>>,
806) -> bool {
807 let Expr::Call(call_expr) = expr else {
808 unreachable!();
809 };
810 let pure_flag = has_pure_comment(comments, expr.span().start)
811 || has_pure_comment(comments, call_expr.callee.span().start);
812 let callee = &call_expr.callee;
813
814 if pure_flag {
815 return is_pure_call_args(
816 parser,
817 analyze_side_effects_free,
818 call_expr,
819 comments,
820 callees,
821 );
822 } else if analyze_side_effects_free && let Some(Expr::Ident(ident)) = callee.as_expr() {
823 match resolve_explicit_side_effects_free_callee(
824 parser,
825 &compat_atom(&ident.sym),
826 callee.span(),
827 callees.is_none(),
828 ) {
829 ExplicitSideEffectsFreeCallee::Direct => {
830 return is_pure_call_args(
831 parser,
832 analyze_side_effects_free,
833 call_expr,
834 comments,
835 callees,
836 );
837 }
838 ExplicitSideEffectsFreeCallee::Deferred => {
839 let Some(callees) = callees else {
840 return false;
841 };
842 callees.push((compat_atom(&ident.sym), callee.span()));
843 return is_pure_call_args(
844 parser,
845 analyze_side_effects_free,
846 call_expr,
847 comments,
848 Some(callees),
849 );
850 }
851 ExplicitSideEffectsFreeCallee::Invalid => return false,
852 ExplicitSideEffectsFreeCallee::NotMarked => {}
853 }
854
855 if let Some(callees) = callees {
856 callees.push((compat_atom(&ident.sym), callee.span()));
857 return is_pure_call_args(
858 parser,
859 analyze_side_effects_free,
860 call_expr,
861 comments,
862 Some(callees),
863 );
864 }
865 }
866
867 !expr.may_have_side_effects(expr_ctx(parser, false))
868}
869
870#[inline(never)]
871fn is_pure_call_args(
872 parser: &mut JavascriptParser,
873 analyze_side_effects_free: bool,
874 call_expr: &CallExpr,
875 comments: &Comments<'_>,
876 mut callees: Option<&mut Vec<(Atom, Span)>>,
877) -> bool {
878 for arg in &call_expr.args {
879 if arg.spread.is_some() {
880 return false;
881 }
882 if !is_pure_expression(
883 parser,
884 analyze_side_effects_free,
885 &arg.expr,
886 comments,
887 callees.as_deref_mut(),
888 ) {
889 return false;
890 }
891 }
892 true
893}
894
895fn is_pure_array_lit<'a>(
896 parser: &mut JavascriptParser,
897 analyze_side_effects_free: bool,
898 array_lit: &'a ArrayLit,
899 comments: &'a Comments<'a>,
900 mut callees: Option<&mut Vec<(Atom, Span)>>,
901) -> bool {
902 for elem in array_lit.elems.iter().flatten() {
903 if elem.spread.is_some()
904 || !is_pure_expression(
905 parser,
906 analyze_side_effects_free,
907 &elem.expr,
908 comments,
909 callees.as_deref_mut(),
910 )
911 {
912 return false;
913 }
914 }
915 true
916}
917
918enum ExplicitSideEffectsFreeCallee {
919 Direct,
920 Deferred,
921 Invalid,
922 NotMarked,
923}
924
925fn resolve_explicit_side_effects_free_callee(
926 parser: &mut JavascriptParser,
927 ident: &Atom,
928 span: Span,
929 allow_unresolved_marked: bool,
930) -> ExplicitSideEffectsFreeCallee {
931 let is_marked = parser
932 .build_info
933 .side_effects_free
934 .as_ref()
935 .is_some_and(|side_effects_free| side_effects_free.contains(ident));
936
937 if !is_marked {
938 return ExplicitSideEffectsFreeCallee::NotMarked;
939 }
940
941 if try_extract_deferred_check(parser, ident.clone(), span).is_some() {
944 let is_user_configured = parser
949 .javascript_options
950 .side_effects_free
951 .as_ref()
952 .is_some_and(|names| names.iter().any(|name| name.as_str() == ident.as_str()));
953 if !is_user_configured {
954 return ExplicitSideEffectsFreeCallee::Deferred;
955 }
956 }
957
958 if let Some((declared_scope, is_free)) = parser
959 .get_variable_info(ident)
960 .map(|info| (info.declared_scope, info.is_free()))
961 {
962 if !is_free && declared_scope == parser.definitions {
963 return ExplicitSideEffectsFreeCallee::Direct;
964 }
965 return ExplicitSideEffectsFreeCallee::Invalid;
966 }
967
968 if allow_unresolved_marked {
969 return ExplicitSideEffectsFreeCallee::Direct;
970 }
971
972 ExplicitSideEffectsFreeCallee::Invalid
973}
974
975fn try_extract_deferred_check(
976 parser: &mut JavascriptParser,
977 ident: Atom,
978 span: Span,
979) -> Option<DeferredPureCheck> {
980 let info = parser.get_variable_info(&ident)?;
981
982 let tag_info_id = info.tag_info?;
983 let tag_info = parser.definitions_db.expect_get_tag_info(tag_info_id);
984
985 if tag_info.tag != ESM_SPECIFIER_TAG {
986 return None;
987 }
988
989 let data = ESMSpecifierData::downcast(tag_info.data.clone()?);
990
991 parser
992 .get_dependencies()
993 .iter()
994 .find(|dep| {
995 let Some(dep) = dep.downcast_ref::<ESMImportSideEffectDependency>() else {
996 return false;
997 };
998
999 let request_eq = dep.request() == &data.source;
1000 let attributes: Option<&rspack_core::ImportAttributes> = data.attributes.as_ref();
1001 let attributes_eq = attributes == dep.get_attributes();
1002 request_eq && attributes_eq
1003 })
1004 .map(|dep| DeferredPureCheck {
1005 atom: data
1006 .ids
1007 .first()
1008 .cloned()
1009 .unwrap_or_else(|| data.name.clone()),
1010 dep_id: *dep.id(),
1011 start: span.real_lo(),
1012 end: span.real_hi(),
1013 })
1014}
1015
1016fn is_pure_new_expr(
1017 parser: &mut JavascriptParser,
1018 analyze_side_effects_free: bool,
1019 expr: &Expr,
1020 comments: &Comments<'_>,
1021) -> bool {
1022 let Expr::New(new_expr) = expr else {
1023 unreachable!();
1024 };
1025 let pure_flag = has_pure_comment(comments, expr.span().start);
1026 if !pure_flag {
1027 !expr.may_have_side_effects(expr_ctx(parser, false))
1028 } else {
1029 are_pure_args(
1030 parser,
1031 analyze_side_effects_free,
1032 new_expr.args.as_deref().unwrap_or(&[]),
1033 comments,
1034 )
1035 }
1036}
1037
1038fn has_pure_comment(comments: &Comments<'_>, pos: u32) -> bool {
1039 comments.leading.get(&pos).is_some_and(|comment_list| {
1040 comment_list
1041 .iter()
1042 .any(|comment| comment.kind == CommentKind::Block && PURE_COMMENTS.is_match(&comment.text))
1043 })
1044}
1045
1046fn are_pure_args<'a>(
1047 parser: &mut JavascriptParser,
1048 analyze_side_effects_free: bool,
1049 args: &'a [ExprOrSpread],
1050 comments: &'a Comments<'a>,
1051) -> bool {
1052 args.iter().all(|arg| {
1053 if arg.spread.is_some() {
1054 false
1055 } else {
1056 is_pure_expression(parser, analyze_side_effects_free, &arg.expr, comments, None)
1057 }
1058 })
1059}
1060
1061impl SideEffectsParserPlugin {
1062 fn analyze_stmt_side_effects(&self, stmt: &Statement, parser: &mut JavascriptParser) {
1063 if parser.side_effects_item.is_some() {
1064 return;
1065 }
1066 let mut callees = vec![];
1067 match stmt {
1068 Statement::If(if_stmt) => {
1069 if !is_pure_expression(
1070 parser,
1071 self.analyze_side_effects_free,
1072 &if_stmt.test,
1073 parser.ast.comments,
1074 Some(&mut callees),
1075 ) {
1076 let range = DependencyRange::from(if_stmt.span());
1077 let loc = parser.to_dependency_location(range);
1078 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1079 range,
1080 loc,
1081 String::from("Statement"),
1082 ));
1083 }
1084 }
1085 Statement::While(while_stmt) => {
1086 if !is_pure_expression(
1087 parser,
1088 self.analyze_side_effects_free,
1089 &while_stmt.test,
1090 parser.ast.comments,
1091 Some(&mut callees),
1092 ) {
1093 let range = DependencyRange::from(while_stmt.span());
1094 let loc = parser.to_dependency_location(range);
1095 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1096 range,
1097 loc,
1098 String::from("Statement"),
1099 ));
1100 }
1101 }
1102 Statement::DoWhile(do_while_stmt) => {
1103 if !is_pure_expression(
1104 parser,
1105 self.analyze_side_effects_free,
1106 &do_while_stmt.test,
1107 parser.ast.comments,
1108 Some(&mut callees),
1109 ) {
1110 let range = DependencyRange::from(do_while_stmt.span());
1111 let loc = parser.to_dependency_location(range);
1112 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1113 range,
1114 loc,
1115 String::from("Statement"),
1116 ));
1117 }
1118 }
1119 Statement::For(for_stmt) => {
1120 let pure_init = match for_stmt.init {
1121 Some(ref init) => match init {
1122 VarDeclOrExpr::VarDecl(decl) => is_pure_var_decl(
1123 parser,
1124 self.analyze_side_effects_free,
1125 decl,
1126 parser.ast.comments,
1127 Some(&mut callees),
1128 ),
1129 VarDeclOrExpr::Expr(expr) => is_pure_expression(
1130 parser,
1131 self.analyze_side_effects_free,
1132 expr,
1133 parser.ast.comments,
1134 Some(&mut callees),
1135 ),
1136 },
1137 None => true,
1138 };
1139
1140 if !pure_init {
1141 let range = DependencyRange::from(for_stmt.span());
1142 let loc = parser.to_dependency_location(range);
1143 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1144 range,
1145 loc,
1146 String::from("Statement"),
1147 ));
1148 return;
1149 }
1150
1151 let pure_test = match &for_stmt.test {
1152 Some(test) => is_pure_expression(
1153 parser,
1154 self.analyze_side_effects_free,
1155 test,
1156 parser.ast.comments,
1157 Some(&mut callees),
1158 ),
1159 None => true,
1160 };
1161
1162 if !pure_test {
1163 let range = DependencyRange::from(for_stmt.span());
1164 let loc = parser.to_dependency_location(range);
1165 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1166 range,
1167 loc,
1168 String::from("Statement"),
1169 ));
1170 return;
1171 }
1172
1173 let pure_update = match for_stmt.update {
1174 Some(ref expr) => is_pure_expression(
1175 parser,
1176 self.analyze_side_effects_free,
1177 expr,
1178 parser.ast.comments,
1179 Some(&mut callees),
1180 ),
1181 None => true,
1182 };
1183
1184 if !pure_update {
1185 let range = DependencyRange::from(for_stmt.span());
1186 let loc = parser.to_dependency_location(range);
1187 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1188 range,
1189 loc,
1190 String::from("Statement"),
1191 ));
1192 }
1193 }
1194 Statement::Expr(expr_stmt) => {
1195 if !is_pure_expression(
1196 parser,
1197 self.analyze_side_effects_free,
1198 &expr_stmt.expr,
1199 parser.ast.comments,
1200 Some(&mut callees),
1201 ) {
1202 let range = DependencyRange::from(expr_stmt.span());
1203 let loc = parser.to_dependency_location(range);
1204 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1205 range,
1206 loc,
1207 String::from("Statement"),
1208 ));
1209 }
1210 }
1211 Statement::Switch(switch_stmt) => {
1212 if !is_pure_expression(
1213 parser,
1214 self.analyze_side_effects_free,
1215 &switch_stmt.discriminant,
1216 parser.ast.comments,
1217 Some(&mut callees),
1218 ) {
1219 let range = DependencyRange::from(switch_stmt.span());
1220 let loc = parser.to_dependency_location(range);
1221 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1222 range,
1223 loc,
1224 String::from("Statement"),
1225 ));
1226 }
1227 }
1228 Statement::Class(class_stmt) => {
1229 if !is_pure_class(
1230 parser,
1231 self.analyze_side_effects_free,
1232 class_stmt.class(),
1233 parser.ast.comments,
1234 Some(&mut callees),
1235 ) {
1236 let range = DependencyRange::from(stmt.span());
1237 let loc = parser.to_dependency_location(range);
1238 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1239 range,
1240 loc,
1241 String::from("Statement"),
1242 ));
1243 }
1244 }
1245 Statement::Var(var_stmt) => match var_stmt {
1246 VariableDeclaration::VarDecl(var_decl) => {
1247 if !is_pure_var_decl(
1248 parser,
1249 self.analyze_side_effects_free,
1250 var_decl,
1251 parser.ast.comments,
1252 Some(&mut callees),
1253 ) {
1254 let range = DependencyRange::from(var_stmt.span());
1255 let loc = parser.to_dependency_location(range);
1256 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1257 range,
1258 loc,
1259 String::from("Statement"),
1260 ));
1261 }
1262 }
1263 VariableDeclaration::UsingDecl(_) => {
1264 let range = DependencyRange::from(var_stmt.span());
1265 let loc = parser.to_dependency_location(range);
1266 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1267 range,
1268 loc,
1269 String::from("Statement"),
1270 ));
1271 }
1272 },
1273 Statement::Empty(_) => {}
1274 Statement::Labeled(_) => {}
1275 Statement::Block(_) => {}
1276 Statement::Fn(_) => {}
1277 _ => {
1278 let range = DependencyRange::from(stmt.span());
1279 let loc = parser.to_dependency_location(range);
1280 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1281 range,
1282 loc,
1283 String::from("Statement"),
1284 ))
1285 }
1286 };
1287
1288 if parser.side_effects_item.is_none() {
1289 for (callee, span) in callees {
1290 if let Some(deferred_check) = try_extract_deferred_check(parser, callee, span) {
1291 parser
1292 .build_info
1293 .deferred_pure_checks
1294 .insert(deferred_check);
1295 } else {
1296 let range = DependencyRange::from(span);
1297 let loc = parser.to_dependency_location(range);
1298 parser.side_effects_item = Some(SideEffectsBailoutItemWithSpan::new(
1299 range,
1300 loc,
1301 String::from("Statement"),
1302 ));
1303 break;
1304 }
1305 }
1306 }
1307 }
1308}
1309
1310pub fn is_pure_pat<'a>(
1311 parser: &mut JavascriptParser,
1312 analyze_side_effects_free: bool,
1313 pat: &'a Pat,
1314 comments: &'a Comments<'a>,
1315 mut callees: Option<&mut Vec<(Atom, Span)>>,
1316) -> bool {
1317 match pat {
1318 Pat::Ident(_) => true,
1319 Pat::Array(array_pat) => {
1320 for pat in array_pat.elems.iter().flatten() {
1321 if !is_pure_pat(
1322 parser,
1323 analyze_side_effects_free,
1324 pat,
1325 comments,
1326 callees.as_deref_mut(),
1327 ) {
1328 return false;
1329 }
1330 }
1331 true
1332 }
1333 Pat::Rest(_) => true,
1334 Pat::Invalid(_) | Pat::Assign(_) | Pat::Object(_) => false,
1335 Pat::Expr(expr) => {
1336 is_pure_expression(parser, analyze_side_effects_free, expr, comments, callees)
1337 }
1338 }
1339}
1340
1341fn is_side_effects_free_param(pat: &Pat) -> bool {
1342 matches!(pat, Pat::Ident(_))
1343}
1344
1345#[inline(never)]
1346fn is_side_effects_free_var_decl(
1347 parser: &mut JavascriptParser,
1348 analyze_side_effects_free: bool,
1349 var_decl: &VarDecl,
1350 comments: &Comments<'_>,
1351) -> bool {
1352 for declarator in &var_decl.decls {
1353 if declarator.name.as_ident().is_none() {
1354 return false;
1355 }
1356
1357 if let Some(init) = declarator.init.as_ref()
1358 && !is_pure_expression(parser, analyze_side_effects_free, init, comments, None)
1359 {
1360 return false;
1361 }
1362 }
1363
1364 true
1365}
1366
1367fn stmt_may_have_side_effects(parser: &JavascriptParser, stmt: &Stmt) -> bool {
1368 let expr_ctx = expr_ctx(parser, true);
1369
1370 match stmt {
1371 Stmt::Empty(_) => false,
1372 Stmt::Expr(expr_stmt) => expr_stmt.expr.may_have_side_effects(expr_ctx),
1373 Stmt::Return(return_stmt) => return_stmt
1374 .arg
1375 .as_ref()
1376 .is_some_and(|arg| arg.may_have_side_effects(expr_ctx)),
1377 Stmt::Decl(decl) => match &**decl {
1378 Decl::Var(var_decl) => var_decl.decls.iter().any(|declarator| {
1379 declarator.name.as_ident().is_none()
1380 || declarator
1381 .init
1382 .as_ref()
1383 .is_some_and(|init| init.may_have_side_effects(expr_ctx))
1384 }),
1385 _ => true,
1386 },
1387 _ => true,
1388 }
1389}
1390
1391fn is_side_effects_free_stmt(
1392 parser: &mut JavascriptParser,
1393 analyze_side_effects_free: bool,
1394 stmt: &Stmt,
1395 comments: &Comments<'_>,
1396) -> bool {
1397 if !stmt_may_have_side_effects(parser, stmt) {
1398 return true;
1399 }
1400
1401 match stmt {
1402 Stmt::Expr(expr_stmt) => is_pure_expression(
1403 parser,
1404 analyze_side_effects_free,
1405 &expr_stmt.expr,
1406 comments,
1407 None,
1408 ),
1409 Stmt::Return(return_stmt) => return_stmt
1410 .arg
1411 .as_ref()
1412 .is_none_or(|arg| is_pure_expression(parser, analyze_side_effects_free, arg, comments, None)),
1413 Stmt::Decl(decl) => match &**decl {
1414 Decl::Var(var_decl) => {
1415 is_side_effects_free_var_decl(parser, analyze_side_effects_free, var_decl, comments)
1416 }
1417 _ => false,
1418 },
1419 _ => false,
1420 }
1421}
1422
1423#[inline(never)]
1424fn is_side_effects_free_block_stmt(
1425 parser: &mut JavascriptParser,
1426 analyze_side_effects_free: bool,
1427 block_stmt: &BlockStmt,
1428 comments: &Comments<'_>,
1429) -> bool {
1430 for stmt in &block_stmt.stmts {
1431 if !is_side_effects_free_stmt(parser, analyze_side_effects_free, stmt, comments) {
1432 return false;
1433 }
1434 }
1435
1436 true
1437}
1438
1439#[inline(never)]
1440fn is_side_effects_free_function_body(
1441 parser: &mut JavascriptParser,
1442 analyze_side_effects_free: bool,
1443 function: &Function,
1444 comments: &Comments<'_>,
1445) -> bool {
1446 if !function
1447 .params
1448 .iter()
1449 .all(|param| is_side_effects_free_param(¶m.pat))
1450 {
1451 return false;
1452 }
1453
1454 function.body.as_ref().is_none_or(|body| {
1455 is_side_effects_free_block_stmt(parser, analyze_side_effects_free, body, comments)
1456 })
1457}
1458
1459#[inline(never)]
1460fn is_side_effects_free_arrow_body(
1461 parser: &mut JavascriptParser,
1462 analyze_side_effects_free: bool,
1463 arrow_expr: &ArrowExpr,
1464 comments: &Comments<'_>,
1465) -> bool {
1466 if !arrow_expr.params.iter().all(is_side_effects_free_param) {
1467 return false;
1468 }
1469
1470 match &arrow_expr.body {
1471 BlockStmtOrExpr::BlockStmt(block_stmt) => {
1472 is_side_effects_free_block_stmt(parser, analyze_side_effects_free, block_stmt, comments)
1473 }
1474 BlockStmtOrExpr::Expr(expr) => {
1475 is_pure_expression(parser, analyze_side_effects_free, expr, comments, None)
1476 }
1477 }
1478}
1479
1480pub fn is_pure_function<'a>(
1481 parser: &mut JavascriptParser,
1482 analyze_side_effects_free: bool,
1483 function: &'a Function,
1484 comments: &'a Comments<'a>,
1485 mut callees: Option<&mut Vec<(Atom, Span)>>,
1486) -> bool {
1487 for param in &function.params {
1488 if !is_pure_pat(
1489 parser,
1490 analyze_side_effects_free,
1491 ¶m.pat,
1492 comments,
1493 callees.as_deref_mut(),
1494 ) {
1495 return false;
1496 }
1497 }
1498 true
1499}
1500
1501#[inline(never)]
1502pub fn is_pure_expression<'a>(
1503 parser: &mut JavascriptParser,
1504 analyze_side_effects_free: bool,
1505 expr: &'a Expr,
1506 comments: &'a Comments<'a>,
1507 callees: Option<&mut Vec<(Atom, Span)>>,
1508) -> bool {
1509 pub fn _is_pure_expression<'a>(
1510 parser: &mut JavascriptParser,
1511 analyze_side_effects_free: bool,
1512 expr: &'a Expr,
1513 comments: &'a Comments<'a>,
1514 mut callees: Option<&mut Vec<(Atom, Span)>>,
1515 ) -> bool {
1516 if let Some(res) = parser.plugin_drive.clone().is_pure(parser, expr) {
1517 return res;
1518 }
1519
1520 match expr {
1521 Expr::Array(array_lit) => is_pure_array_lit(
1522 parser,
1523 analyze_side_effects_free,
1524 array_lit,
1525 comments,
1526 callees.as_deref_mut(),
1527 ),
1528 Expr::Call(_) => {
1529 is_pure_call_expr(parser, analyze_side_effects_free, expr, comments, callees)
1530 }
1531 Expr::New(_) => is_pure_new_expr(parser, analyze_side_effects_free, expr, comments),
1532 Expr::Paren(_) => unreachable!(),
1533 Expr::Seq(seq_expr) => {
1534 for expr in &seq_expr.exprs {
1535 if !is_pure_expression(
1536 parser,
1537 analyze_side_effects_free,
1538 expr,
1539 comments,
1540 callees.as_deref_mut(),
1541 ) {
1542 return false;
1543 }
1544 }
1545 true
1546 }
1547 _ => {
1548 if !expr.may_have_side_effects(expr_ctx(parser, true)) {
1549 return true;
1550 }
1551 let evaluated = parser.evaluate_expression(expr);
1553 !evaluated.could_have_side_effects()
1554 }
1555 }
1556 }
1557 _is_pure_expression(parser, analyze_side_effects_free, expr, comments, callees)
1558}
1559
1560#[inline(never)]
1561pub fn is_pure_class_member<'a>(
1562 parser: &mut JavascriptParser,
1563 analyze_side_effects_free: bool,
1564 member: &'a ClassMember,
1565 comments: &'a Comments<'a>,
1566 mut callees: Option<&mut Vec<(Atom, Span)>>,
1567) -> bool {
1568 let is_key_pure = match member.class_key() {
1569 Some(PropName::Ident(_ident)) => true,
1570 Some(PropName::Str(_)) => true,
1571 Some(PropName::Num(_)) => true,
1572 Some(PropName::Computed(computed)) => is_pure_expression(
1573 parser,
1574 analyze_side_effects_free,
1575 &computed.expr,
1576 comments,
1577 callees.as_deref_mut(),
1578 ),
1579 Some(PropName::BigInt(_)) => true,
1580 None => true,
1581 };
1582 if !is_key_pure {
1583 return false;
1584 }
1585 let is_static = member.is_static();
1586 let is_value_pure = match member {
1587 ClassMember::Constructor(_) => true,
1588 ClassMember::Method(_) => true,
1589 ClassMember::PrivateMethod(_) => true,
1590 ClassMember::ClassProp(prop) => {
1591 if let Some(ref value) = prop.value {
1592 is_pure_expression(
1593 parser,
1594 analyze_side_effects_free,
1595 value,
1596 comments,
1597 callees.as_deref_mut(),
1598 )
1599 } else {
1600 true
1601 }
1602 }
1603 ClassMember::PrivateProp(prop) => {
1604 if let Some(ref value) = prop.value {
1605 is_pure_expression(parser, analyze_side_effects_free, value, comments, callees)
1606 } else {
1607 true
1608 }
1609 }
1610 ClassMember::Empty(_) => true,
1611 ClassMember::StaticBlock(_) => false,
1612 ClassMember::AutoAccessor(_) => false,
1613 };
1614 if is_static && !is_value_pure {
1615 return false;
1616 }
1617 true
1618}
1619
1620#[inline(never)]
1621pub fn is_pure_decl(
1622 parser: &mut JavascriptParser,
1623 analyze_side_effects_free: bool,
1624 stmt: &Decl,
1625 comments: &Comments<'_>,
1626 callees: Option<&mut Vec<(Atom, Span)>>,
1627) -> bool {
1628 match stmt {
1629 Decl::Class(class) => is_pure_class(
1630 parser,
1631 analyze_side_effects_free,
1632 &class.class,
1633 comments,
1634 callees,
1635 ),
1636 Decl::Fn(_) => true,
1637 Decl::Var(var) => is_pure_var_decl(parser, analyze_side_effects_free, var, comments, callees),
1638 Decl::Using(_) => false,
1639 }
1640}
1641
1642#[inline(never)]
1643pub fn is_pure_class(
1644 parser: &mut JavascriptParser,
1645 analyze_side_effects_free: bool,
1646 class: &Class,
1647 comments: &Comments<'_>,
1648 mut callees: Option<&mut Vec<(Atom, Span)>>,
1649) -> bool {
1650 if let Some(ref super_class) = class.super_class
1651 && !is_pure_expression(
1652 parser,
1653 analyze_side_effects_free,
1654 super_class,
1655 comments,
1656 callees.as_deref_mut(),
1657 )
1658 {
1659 return false;
1660 }
1661 let is_pure_key = |parser: &mut JavascriptParser,
1662 key: &PropName,
1663 callees: Option<&mut Vec<(Atom, Span)>>|
1664 -> bool {
1665 match key {
1666 PropName::BigInt(_) | PropName::Ident(_) | PropName::Str(_) | PropName::Num(_) => true,
1667 PropName::Computed(computed) => is_pure_expression(
1668 parser,
1669 analyze_side_effects_free,
1670 &computed.expr,
1671 comments,
1672 callees,
1673 ),
1674 }
1675 };
1676
1677 for item in &class.body {
1678 let pure = match item {
1679 ClassMember::Constructor(_) => class.super_class.is_none(),
1680 ClassMember::Method(method) => is_pure_key(parser, &method.key, callees.as_deref_mut()),
1681 ClassMember::PrivateMethod(method) => is_pure_expression(
1682 parser,
1683 analyze_side_effects_free,
1684 &Expr::PrivateName(method.key.clone_in(parser.ast.allocator)),
1685 comments,
1686 callees.as_deref_mut(),
1687 ),
1688 ClassMember::ClassProp(prop) => {
1689 is_pure_key(parser, &prop.key, callees.as_deref_mut())
1690 && (!prop.is_static
1691 || if let Some(ref value) = prop.value {
1692 is_pure_expression(
1693 parser,
1694 analyze_side_effects_free,
1695 value,
1696 comments,
1697 callees.as_deref_mut(),
1698 )
1699 } else {
1700 true
1701 })
1702 }
1703 ClassMember::PrivateProp(prop) => {
1704 is_pure_expression(
1705 parser,
1706 analyze_side_effects_free,
1707 &Expr::PrivateName(prop.key.clone_in(parser.ast.allocator)),
1708 comments,
1709 callees.as_deref_mut(),
1710 ) && (!prop.is_static
1711 || if let Some(ref value) = prop.value {
1712 is_pure_expression(
1713 parser,
1714 analyze_side_effects_free,
1715 value,
1716 comments,
1717 callees.as_deref_mut(),
1718 )
1719 } else {
1720 true
1721 })
1722 }
1723 ClassMember::Empty(_) => true,
1724 ClassMember::StaticBlock(_) => false, ClassMember::AutoAccessor(_) => false,
1726 };
1727 if !pure {
1728 return false;
1729 }
1730 }
1731 true
1732}
1733
1734#[inline(never)]
1735fn is_pure_var_decl<'a>(
1736 parser: &mut JavascriptParser,
1737 analyze_side_effects_free: bool,
1738 var: &'a VarDecl,
1739 comments: &'a Comments<'a>,
1740 mut callees: Option<&mut Vec<(Atom, Span)>>,
1741) -> bool {
1742 for decl in &var.decls {
1743 if let Some(ref init) = decl.init
1744 && !is_pure_expression(
1745 parser,
1746 analyze_side_effects_free,
1747 init,
1748 comments,
1749 callees.as_deref_mut(),
1750 )
1751 {
1752 return false;
1753 }
1754 }
1755 true
1756}