1use std::{
2 cell::RefCell,
3 collections::{hash_map, BTreeMap},
4 convert::{TryFrom, TryInto},
5 mem::{replace, take},
6 path::{Path, PathBuf},
7 rc::Rc,
8 sync::Arc,
9};
10
11use base64::{display::Base64Display, prelude::BASE64_STANDARD};
12use hex::encode as hex_encode;
13use indoc::formatdoc;
14use pathdiff::diff_paths;
15use rustc_hash::{FxHashMap, FxHashSet};
16use serde::Deserialize;
17use sha1::{Digest, Sha1};
18use swc_core::{
19 atoms::{atom, Atom, Wtf8Atom},
20 common::{
21 comments::{Comment, CommentKind, Comments, SingleThreadedComments},
22 errors::HANDLER,
23 source_map::{SourceMapGenConfig, PURE_SP},
24 util::take::Take,
25 BytePos, FileName, Mark, SourceMap, Span, SyntaxContext, DUMMY_SP,
26 },
27 ecma::{
28 ast::*,
29 codegen::{self, text_writer::JsWriter, Emitter},
30 utils::{private_ident, quote_ident, ExprFactory},
31 visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith},
32 },
33 quote,
34};
35use turbo_rcstr::{rcstr, RcStr};
36
37use crate::FxIndexMap;
38
39#[derive(Clone, Copy, Debug, Deserialize)]
40pub enum ServerActionsMode {
41 Webpack,
42 Turbopack,
43}
44
45#[derive(Clone, Debug, Deserialize)]
46#[serde(deny_unknown_fields, rename_all = "camelCase")]
47pub struct Config {
48 pub is_react_server_layer: bool,
49 pub is_development: bool,
50 pub use_cache_enabled: bool,
51 pub hash_salt: String,
52 pub cache_kinds: FxHashSet<RcStr>,
53}
54
55#[derive(Clone, Debug)]
56enum Directive {
57 UseServer,
58 UseCache { cache_kind: RcStr },
59}
60
61#[derive(Clone, Debug)]
62enum DirectiveLocation {
63 Module,
64 FunctionBody,
65}
66
67#[derive(Clone, Debug)]
68enum ThisStatus {
69 Allowed,
70 Forbidden { directive: Directive },
71}
72
73#[derive(Clone)]
74struct ServerReferenceExport {
75 ident: Ident,
76 export_name: ModuleExportName,
77 reference_id: Atom,
78 needs_cache_runtime_wrapper: bool,
79}
80
81#[derive(Clone, Debug)]
82enum ServerActionsErrorKind {
83 ExportedSyncFunction {
84 span: Span,
85 in_action_file: bool,
86 },
87 ForbiddenExpression {
88 span: Span,
89 expr: String,
90 directive: Directive,
91 },
92 InlineSyncFunction {
93 span: Span,
94 directive: Directive,
95 },
96 InlineUseCacheInClassInstanceMethod {
97 span: Span,
98 },
99 InlineUseCacheInClientComponent {
100 span: Span,
101 },
102 InlineUseServerInClassInstanceMethod {
103 span: Span,
104 },
105 InlineUseServerInClientComponent {
106 span: Span,
107 },
108 MisplacedDirective {
109 span: Span,
110 directive: String,
111 location: DirectiveLocation,
112 },
113 MisplacedWrappedDirective {
114 span: Span,
115 directive: String,
116 location: DirectiveLocation,
117 },
118 MisspelledDirective {
119 span: Span,
120 directive: String,
121 expected_directive: String,
122 },
123 MultipleDirectives {
124 span: Span,
125 location: DirectiveLocation,
126 },
127 UnknownCacheKind {
128 span: Span,
129 cache_kind: RcStr,
130 },
131 UseCacheWithoutCacheComponents {
132 span: Span,
133 directive: String,
134 },
135 WrappedDirective {
136 span: Span,
137 directive: String,
138 },
139}
140
141#[tracing::instrument(level = tracing::Level::TRACE, skip_all)]
142pub fn server_actions<C: Comments>(
143 file_name: &FileName,
144 file_query: Option<RcStr>,
145 config: Config,
146 comments: C,
147 cm: Arc<SourceMap>,
148 use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
149 mode: ServerActionsMode,
150) -> impl Pass {
151 visit_mut_pass(ServerActions {
152 config,
153 mode,
154 comments,
155 cm,
156 file_name: file_name.to_string(),
157 file_query,
158 start_pos: BytePos(0),
159 file_directive: None,
160 current_export_name: None,
161 fn_decl_ident: None,
162 in_callee: false,
163 has_action: false,
164 has_cache: false,
165 this_status: ThisStatus::Allowed,
166
167 reference_index: 0,
168 in_module_level: true,
169 should_track_names: false,
170 has_server_reference_with_bound_args: false,
171
172 names: Default::default(),
173 declared_idents: Default::default(),
174
175 rewrite_fn_decl_to_proxy_decl: None,
177 rewrite_default_fn_expr_to_proxy_expr: None,
178 rewrite_expr_to_proxy_expr: None,
179
180 annotations: Default::default(),
181 extra_items: Default::default(),
182 hoisted_extra_items: Default::default(),
183 reference_ids_by_export_name: Default::default(),
184 server_reference_exports: Default::default(),
185
186 private_ctxt: SyntaxContext::empty().apply_mark(Mark::new()),
187
188 arrow_or_fn_expr_ident: None,
189 export_name_by_local_id: Default::default(),
190 local_ids_that_need_cache_runtime_wrapper_if_exported: FxHashSet::default(),
191
192 use_cache_telemetry_tracker,
193 })
194}
195
196fn generate_server_references_comment(
199 export_names_ordered_by_reference_id: &BTreeMap<&Atom, &ModuleExportName>,
200 entry_path_query: Option<(&str, &str)>,
201) -> String {
202 let export_map: BTreeMap<_, _> = export_names_ordered_by_reference_id
204 .iter()
205 .map(|(ref_id, export_name)| (*ref_id, export_name.atom()))
206 .collect();
207
208 format!(
209 " __next_internal_action_entry_do_not_use__ {} ",
210 if let Some(entry_path_query) = entry_path_query {
211 serde_json::to_string(&(&export_map, entry_path_query.0, entry_path_query.1))
212 } else {
213 serde_json::to_string(&export_map)
214 }
215 .unwrap()
216 )
217}
218
219struct ServerActions<C: Comments> {
220 #[allow(unused)]
221 config: Config,
222 file_name: String,
223 file_query: Option<RcStr>,
224 comments: C,
225 cm: Arc<SourceMap>,
226 mode: ServerActionsMode,
227
228 start_pos: BytePos,
229 file_directive: Option<Directive>,
230 current_export_name: Option<ModuleExportName>,
231 fn_decl_ident: Option<Ident>,
232 in_callee: bool,
233 has_action: bool,
234 has_cache: bool,
235 this_status: ThisStatus,
236
237 reference_index: u32,
238 in_module_level: bool,
239 should_track_names: bool,
240 has_server_reference_with_bound_args: bool,
241
242 names: Vec<Name>,
243 declared_idents: Vec<Ident>,
244
245 rewrite_fn_decl_to_proxy_decl: Option<VarDecl>,
247 rewrite_default_fn_expr_to_proxy_expr: Option<Box<Expr>>,
248 rewrite_expr_to_proxy_expr: Option<Box<Expr>>,
249
250 annotations: Vec<Stmt>,
251 extra_items: Vec<ModuleItem>,
252 hoisted_extra_items: Vec<ModuleItem>,
253
254 reference_ids_by_export_name: FxIndexMap<ModuleExportName, Atom>,
256
257 server_reference_exports: Vec<ServerReferenceExport>,
259
260 private_ctxt: SyntaxContext,
261
262 arrow_or_fn_expr_ident: Option<Ident>,
263 export_name_by_local_id: FxIndexMap<Id, ModuleExportName>,
264
265 local_ids_that_need_cache_runtime_wrapper_if_exported: FxHashSet<Id>,
271
272 use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
273}
274
275impl<C: Comments> ServerActions<C> {
276 fn generate_server_reference_id(
277 &self,
278 export_name: &ModuleExportName,
279 is_cache: bool,
280 params: Option<&Vec<Param>>,
281 ) -> Atom {
282 let mut hasher = Sha1::new();
287 hasher.update(self.config.hash_salt.as_bytes());
288 hasher.update(self.file_name.as_bytes());
289 hasher.update(b":");
290
291 let export_name_bytes = match export_name {
292 ModuleExportName::Ident(ident) => &ident.sym.as_bytes(),
293 ModuleExportName::Str(s) => &s.value.as_bytes(),
294 };
295
296 hasher.update(export_name_bytes);
297
298 let mut result = hasher.finalize().to_vec();
299
300 let type_bit = if is_cache { 1u8 } else { 0u8 };
328 let mut arg_mask = 0u8;
329 let mut rest_args = 0u8;
330
331 if let Some(params) = params {
332 for (i, param) in params.iter().enumerate() {
337 if let Pat::Rest(_) = param.pat {
338 arg_mask = 0b111111;
341 rest_args = 0b1;
342 break;
343 }
344 if i < 6 {
345 arg_mask |= 0b1 << (5 - i);
346 } else {
347 rest_args = 0b1;
350 break;
351 }
352 }
353 } else {
354 arg_mask = 0b111111;
357 rest_args = 0b1;
358 }
359
360 result.push((type_bit << 7) | (arg_mask << 1) | rest_args);
361 result.rotate_right(1);
362
363 Atom::from(hex_encode(result))
364 }
365
366 fn is_default_export(&self) -> bool {
367 matches!(
368 self.current_export_name,
369 Some(ModuleExportName::Ident(ref i)) if i.sym == *"default"
370 )
371 }
372
373 fn gen_action_ident(&mut self) -> Atom {
374 let id: Atom = format!("$$RSC_SERVER_ACTION_{0}", self.reference_index).into();
375 self.reference_index += 1;
376 id
377 }
378
379 fn gen_cache_ident(&mut self) -> Atom {
380 let id: Atom = format!("$$RSC_SERVER_CACHE_{0}", self.reference_index).into();
381 self.reference_index += 1;
382 id
383 }
384
385 fn create_bound_action_args_array_pat(&mut self, arg_len: usize) -> Pat {
386 Pat::Array(ArrayPat {
387 span: DUMMY_SP,
388 elems: (0..arg_len)
389 .map(|i| {
390 Some(Pat::Ident(
391 Ident::new(
392 format!("$$ACTION_ARG_{i}").into(),
393 DUMMY_SP,
394 self.private_ctxt,
395 )
396 .into(),
397 ))
398 })
399 .collect(),
400 optional: false,
401 type_ann: None,
402 })
403 }
404
405 fn get_directive_for_function(
408 &mut self,
409 maybe_body: Option<&mut BlockStmt>,
410 ) -> Option<Directive> {
411 let mut directive: Option<Directive> = None;
412
413 if let Some(body) = maybe_body {
416 let directive_visitor = &mut DirectiveVisitor {
417 config: &self.config,
418 directive: None,
419 has_file_directive: self.file_directive.is_some(),
420 is_allowed_position: true,
421 location: DirectiveLocation::FunctionBody,
422 use_cache_telemetry_tracker: self.use_cache_telemetry_tracker.clone(),
423 };
424
425 body.stmts.retain(|stmt| {
426 let has_directive = directive_visitor.visit_stmt(stmt);
427
428 !has_directive
429 });
430
431 directive = directive_visitor.directive.clone();
432 }
433
434 if self.current_export_name.is_some()
436 && directive.is_none()
437 && self.file_directive.is_some()
438 {
439 return self.file_directive.clone();
440 }
441
442 directive
443 }
444
445 fn get_directive_for_module(&mut self, stmts: &mut Vec<ModuleItem>) -> Option<Directive> {
446 let directive_visitor = &mut DirectiveVisitor {
447 config: &self.config,
448 directive: None,
449 has_file_directive: false,
450 is_allowed_position: true,
451 location: DirectiveLocation::Module,
452 use_cache_telemetry_tracker: self.use_cache_telemetry_tracker.clone(),
453 };
454
455 stmts.retain(|item| {
456 if let ModuleItem::Stmt(stmt) = item {
457 let has_directive = directive_visitor.visit_stmt(stmt);
458
459 !has_directive
460 } else {
461 directive_visitor.is_allowed_position = false;
462 true
463 }
464 });
465
466 directive_visitor.directive.clone()
467 }
468
469 fn maybe_hoist_and_create_proxy_for_server_action_arrow_expr(
470 &mut self,
471 ids_from_closure: Vec<Name>,
472 arrow: &mut ArrowExpr,
473 ) -> Box<Expr> {
474 let mut new_params: Vec<Param> = vec![];
475
476 if !ids_from_closure.is_empty() {
477 new_params.push(Param {
479 span: DUMMY_SP,
480 decorators: vec![],
481 pat: Pat::Ident(IdentName::new(atom!("$$ACTION_CLOSURE_BOUND"), DUMMY_SP).into()),
482 });
483 }
484
485 for p in arrow.params.iter() {
486 new_params.push(Param::from(p.clone()));
487 }
488
489 let action_name = self.gen_action_ident();
490 let action_ident = Ident::new(action_name.clone(), arrow.span, self.private_ctxt);
491 let action_id = self.generate_server_reference_id(
492 &ModuleExportName::Ident(action_ident.clone()),
493 false,
494 Some(&new_params),
495 );
496
497 self.has_action = true;
498 self.reference_ids_by_export_name.insert(
499 ModuleExportName::Ident(action_ident.clone()),
500 action_id.clone(),
501 );
502
503 if self.current_export_name.is_some() {
506 if let Some(arrow_ident) = &self.arrow_or_fn_expr_ident {
507 self.export_name_by_local_id
508 .swap_remove(&arrow_ident.to_id());
509 }
510 }
511
512 if let BlockStmtOrExpr::BlockStmt(block) = &mut *arrow.body {
513 block.visit_mut_with(&mut ClosureReplacer {
514 used_ids: &ids_from_closure,
515 private_ctxt: self.private_ctxt,
516 });
517 }
518
519 let mut new_body: BlockStmtOrExpr = *arrow.body.clone();
520
521 if !ids_from_closure.is_empty() {
522 let decryption_decl = VarDecl {
526 span: DUMMY_SP,
527 kind: VarDeclKind::Var,
528 declare: false,
529 decls: vec![VarDeclarator {
530 span: DUMMY_SP,
531 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
532 init: Some(Box::new(Expr::Await(AwaitExpr {
533 span: DUMMY_SP,
534 arg: Box::new(Expr::Call(CallExpr {
535 span: DUMMY_SP,
536 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
537 args: vec![
538 action_id.clone().as_arg(),
539 quote_ident!("$$ACTION_CLOSURE_BOUND").as_arg(),
540 ],
541 ..Default::default()
542 })),
543 }))),
544 definite: Default::default(),
545 }],
546 ..Default::default()
547 };
548
549 match &mut new_body {
550 BlockStmtOrExpr::BlockStmt(body) => {
551 body.stmts.insert(0, decryption_decl.into());
552 }
553 BlockStmtOrExpr::Expr(body_expr) => {
554 new_body = BlockStmtOrExpr::BlockStmt(BlockStmt {
555 span: DUMMY_SP,
556 stmts: vec![
557 decryption_decl.into(),
558 Stmt::Return(ReturnStmt {
559 span: DUMMY_SP,
560 arg: Some(body_expr.take()),
561 }),
562 ],
563 ..Default::default()
564 });
565 }
566 }
567 }
568
569 self.hoisted_extra_items
572 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
573 span: DUMMY_SP,
574 decl: VarDecl {
575 kind: VarDeclKind::Const,
576 span: DUMMY_SP,
577 decls: vec![VarDeclarator {
578 span: DUMMY_SP,
579 name: Pat::Ident(action_ident.clone().into()),
580 definite: false,
581 init: Some(Box::new(Expr::Fn(FnExpr {
582 ident: self.arrow_or_fn_expr_ident.clone(),
583 function: Box::new(Function {
584 params: new_params,
585 body: match new_body {
586 BlockStmtOrExpr::BlockStmt(body) => Some(body),
587 BlockStmtOrExpr::Expr(expr) => Some(BlockStmt {
588 span: DUMMY_SP,
589 stmts: vec![Stmt::Return(ReturnStmt {
590 span: DUMMY_SP,
591 arg: Some(expr),
592 })],
593 ..Default::default()
594 }),
595 },
596 is_async: true,
597 ..Default::default()
598 }),
599 }))),
600 }],
601 declare: Default::default(),
602 ctxt: self.private_ctxt,
603 }
604 .into(),
605 })));
606
607 self.hoisted_extra_items
608 .push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
609 span: DUMMY_SP,
610 expr: Box::new(annotate_ident_as_server_reference(
611 action_ident.clone(),
612 action_id.clone(),
613 arrow.span,
614 )),
615 })));
616
617 if ids_from_closure.is_empty() {
618 Box::new(action_ident.clone().into())
619 } else {
620 self.has_server_reference_with_bound_args = true;
621 Box::new(bind_args_to_ident(
622 action_ident.clone(),
623 ids_from_closure
624 .iter()
625 .cloned()
626 .map(|id| Some(id.as_arg()))
627 .collect(),
628 action_id.clone(),
629 ))
630 }
631 }
632
633 fn maybe_hoist_and_create_proxy_for_server_action_function(
634 &mut self,
635 ids_from_closure: Vec<Name>,
636 function: &mut Function,
637 fn_name: Option<Ident>,
638 ) -> Box<Expr> {
639 let mut new_params: Vec<Param> = vec![];
640
641 if !ids_from_closure.is_empty() {
642 new_params.push(Param {
644 span: DUMMY_SP,
645 decorators: vec![],
646 pat: Pat::Ident(IdentName::new(atom!("$$ACTION_CLOSURE_BOUND"), DUMMY_SP).into()),
647 });
648 }
649
650 new_params.append(&mut function.params);
651
652 let action_name: Atom = self.gen_action_ident();
653 let mut action_ident = Ident::new(action_name.clone(), function.span, self.private_ctxt);
654 if action_ident.span.lo == self.start_pos {
655 action_ident.span = Span::dummy_with_cmt();
656 }
657
658 let action_id = self.generate_server_reference_id(
659 &ModuleExportName::Ident(action_ident.clone()),
660 false,
661 Some(&new_params),
662 );
663
664 self.has_action = true;
665 self.reference_ids_by_export_name.insert(
666 ModuleExportName::Ident(action_ident.clone()),
667 action_id.clone(),
668 );
669
670 if self.current_export_name.is_some() {
673 if let Some(ref fn_name) = fn_name {
674 self.export_name_by_local_id.swap_remove(&fn_name.to_id());
675 }
676 }
677
678 function.body.visit_mut_with(&mut ClosureReplacer {
679 used_ids: &ids_from_closure,
680 private_ctxt: self.private_ctxt,
681 });
682
683 let mut new_body: Option<BlockStmt> = function.body.clone();
684
685 if !ids_from_closure.is_empty() {
686 let decryption_decl = VarDecl {
690 span: DUMMY_SP,
691 kind: VarDeclKind::Var,
692 decls: vec![VarDeclarator {
693 span: DUMMY_SP,
694 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
695 init: Some(Box::new(Expr::Await(AwaitExpr {
696 span: DUMMY_SP,
697 arg: Box::new(Expr::Call(CallExpr {
698 span: DUMMY_SP,
699 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
700 args: vec![
701 action_id.clone().as_arg(),
702 quote_ident!("$$ACTION_CLOSURE_BOUND").as_arg(),
703 ],
704 ..Default::default()
705 })),
706 }))),
707 definite: Default::default(),
708 }],
709 ..Default::default()
710 };
711
712 if let Some(body) = &mut new_body {
713 body.stmts.insert(0, decryption_decl.into());
714 } else {
715 new_body = Some(BlockStmt {
716 span: DUMMY_SP,
717 stmts: vec![decryption_decl.into()],
718 ..Default::default()
719 });
720 }
721 }
722
723 self.hoisted_extra_items
726 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
727 span: DUMMY_SP,
728 decl: VarDecl {
729 kind: VarDeclKind::Const,
730 span: DUMMY_SP,
731 decls: vec![VarDeclarator {
732 span: DUMMY_SP, name: Pat::Ident(action_ident.clone().into()),
734 definite: false,
735 init: Some(Box::new(Expr::Fn(FnExpr {
736 ident: fn_name,
737 function: Box::new(Function {
738 params: new_params,
739 body: new_body,
740 ..function.take()
741 }),
742 }))),
743 }],
744 declare: Default::default(),
745 ctxt: self.private_ctxt,
746 }
747 .into(),
748 })));
749
750 self.hoisted_extra_items
751 .push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
752 span: DUMMY_SP,
753 expr: Box::new(annotate_ident_as_server_reference(
754 action_ident.clone(),
755 action_id.clone(),
756 function.span,
757 )),
758 })));
759
760 if ids_from_closure.is_empty() {
761 Box::new(action_ident.clone().into())
762 } else {
763 self.has_server_reference_with_bound_args = true;
764 Box::new(bind_args_to_ident(
765 action_ident.clone(),
766 ids_from_closure
767 .iter()
768 .cloned()
769 .map(|id| Some(id.as_arg()))
770 .collect(),
771 action_id.clone(),
772 ))
773 }
774 }
775
776 fn maybe_hoist_and_create_proxy_for_cache_arrow_expr(
777 &mut self,
778 ids_from_closure: Vec<Name>,
779 cache_kind: RcStr,
780 arrow: &mut ArrowExpr,
781 ) -> Box<Expr> {
782 let mut new_params: Vec<Param> = vec![];
783
784 if !ids_from_closure.is_empty() {
788 new_params.push(Param {
789 span: DUMMY_SP,
790 decorators: vec![],
791 pat: self.create_bound_action_args_array_pat(ids_from_closure.len()),
792 });
793 }
794
795 for p in arrow.params.iter() {
796 new_params.push(Param::from(p.clone()));
797 }
798
799 let cache_name: Atom = self.gen_cache_ident();
800 let export_name: Atom = cache_name.clone();
801
802 let reference_id = self.generate_server_reference_id(
803 &ModuleExportName::Ident(export_name.clone().into()),
804 true,
805 Some(&new_params),
806 );
807
808 self.has_cache = true;
809 self.reference_ids_by_export_name.insert(
810 ModuleExportName::Ident(export_name.clone().into()),
811 reference_id.clone(),
812 );
813
814 if self.current_export_name.is_some() {
817 if let Some(arrow_ident) = &self.arrow_or_fn_expr_ident {
818 self.export_name_by_local_id
819 .swap_remove(&arrow_ident.to_id());
820 }
821 }
822
823 if let BlockStmtOrExpr::BlockStmt(block) = &mut *arrow.body {
824 block.visit_mut_with(&mut ClosureReplacer {
825 used_ids: &ids_from_closure,
826 private_ctxt: self.private_ctxt,
827 });
828 }
829
830 let inner_fn_body = match *arrow.body.take() {
831 BlockStmtOrExpr::BlockStmt(body) => Some(body),
832 BlockStmtOrExpr::Expr(expr) => Some(BlockStmt {
833 stmts: vec![Stmt::Return(ReturnStmt {
834 span: DUMMY_SP,
835 arg: Some(expr),
836 })],
837 ..Default::default()
838 }),
839 };
840
841 let cache_ident = create_and_hoist_cache_function(
842 cache_kind.as_str(),
843 reference_id.clone(),
844 ids_from_closure.len(),
845 cache_name,
846 self.arrow_or_fn_expr_ident.clone(),
847 new_params.clone(),
848 inner_fn_body,
849 arrow.span,
850 &mut self.hoisted_extra_items,
851 );
852
853 if let Some(Ident { sym, .. }) = &self.arrow_or_fn_expr_ident {
854 self.hoisted_extra_items
855 .push(ModuleItem::Stmt(assign_name_to_ident(
856 &cache_ident,
857 sym.as_str(),
858 )));
859 }
860
861 let bound_args: Vec<_> = ids_from_closure
862 .iter()
863 .cloned()
864 .map(|id| Some(id.as_arg()))
865 .collect();
866
867 if bound_args.is_empty() {
868 Box::new(cache_ident.clone().into())
869 } else {
870 self.has_server_reference_with_bound_args = true;
871 Box::new(bind_args_to_ident(
872 cache_ident.clone(),
873 bound_args,
874 reference_id.clone(),
875 ))
876 }
877 }
878
879 fn maybe_hoist_and_create_proxy_for_cache_function(
880 &mut self,
881 ids_from_closure: Vec<Name>,
882 fn_name: Option<Ident>,
883 cache_kind: RcStr,
884 function: &mut Function,
885 ) -> Box<Expr> {
886 let mut new_params: Vec<Param> = vec![];
887
888 if !ids_from_closure.is_empty() {
892 new_params.push(Param {
893 span: DUMMY_SP,
894 decorators: vec![],
895 pat: self.create_bound_action_args_array_pat(ids_from_closure.len()),
896 });
897 }
898
899 for p in function.params.iter() {
900 new_params.push(p.clone());
901 }
902
903 let cache_name: Atom = self.gen_cache_ident();
904
905 let reference_id = self.generate_server_reference_id(
906 &ModuleExportName::Ident(cache_name.clone().into()),
907 true,
908 Some(&new_params),
909 );
910
911 self.has_cache = true;
912 self.reference_ids_by_export_name.insert(
913 ModuleExportName::Ident(cache_name.clone().into()),
914 reference_id.clone(),
915 );
916
917 if self.current_export_name.is_some() {
920 if let Some(ref fn_name) = fn_name {
921 self.export_name_by_local_id.swap_remove(&fn_name.to_id());
922 }
923 }
924
925 function.body.visit_mut_with(&mut ClosureReplacer {
926 used_ids: &ids_from_closure,
927 private_ctxt: self.private_ctxt,
928 });
929
930 let function_body = function.body.take();
931 let function_span = function.span;
932
933 let cache_ident = create_and_hoist_cache_function(
934 cache_kind.as_str(),
935 reference_id.clone(),
936 ids_from_closure.len(),
937 cache_name,
938 fn_name.clone(),
939 new_params.clone(),
940 function_body,
941 function_span,
942 &mut self.hoisted_extra_items,
943 );
944
945 if let Some(Ident { ref sym, .. }) = fn_name {
946 self.hoisted_extra_items
947 .push(ModuleItem::Stmt(assign_name_to_ident(
948 &cache_ident,
949 sym.as_str(),
950 )));
951 } else if self.is_default_export() {
952 self.hoisted_extra_items
953 .push(ModuleItem::Stmt(assign_name_to_ident(
954 &cache_ident,
955 "default",
956 )));
957 }
958
959 let bound_args: Vec<_> = ids_from_closure
960 .iter()
961 .cloned()
962 .map(|id| Some(id.as_arg()))
963 .collect();
964
965 if bound_args.is_empty() {
966 Box::new(cache_ident.clone().into())
967 } else {
968 self.has_server_reference_with_bound_args = true;
969 Box::new(bind_args_to_ident(
970 cache_ident.clone(),
971 bound_args,
972 reference_id.clone(),
973 ))
974 }
975 }
976
977 fn validate_async_function(
980 &self,
981 is_async: bool,
982 span: Span,
983 fn_name: Option<&Ident>,
984 directive: &Directive,
985 ) -> bool {
986 if is_async {
987 true
988 } else {
989 emit_error(ServerActionsErrorKind::InlineSyncFunction {
990 span: fn_name.as_ref().map_or(span, |ident| ident.span),
991 directive: directive.clone(),
992 });
993 false
994 }
995 }
996
997 fn register_server_action_export(
999 &mut self,
1000 export_name: &ModuleExportName,
1001 fn_name: Option<&Ident>,
1002 params: Option<&Vec<Param>>,
1003 span: Span,
1004 take_fn_or_arrow_expr: &mut dyn FnMut() -> Box<Expr>,
1005 ) {
1006 if let Some(fn_name) = fn_name {
1007 let reference_id = self.generate_server_reference_id(export_name, false, params);
1008
1009 self.has_action = true;
1010 self.reference_ids_by_export_name
1011 .insert(export_name.clone(), reference_id.clone());
1012
1013 self.server_reference_exports.push(ServerReferenceExport {
1014 ident: fn_name.clone(),
1015 export_name: export_name.clone(),
1016 reference_id: reference_id.clone(),
1017 needs_cache_runtime_wrapper: false,
1018 });
1019 } else if self.is_default_export() {
1020 let action_ident = Ident::new(self.gen_action_ident(), span, self.private_ctxt);
1021 let reference_id = self.generate_server_reference_id(export_name, false, params);
1022
1023 self.has_action = true;
1024 self.reference_ids_by_export_name
1025 .insert(export_name.clone(), reference_id.clone());
1026
1027 self.server_reference_exports.push(ServerReferenceExport {
1028 ident: action_ident.clone(),
1029 export_name: export_name.clone(),
1030 reference_id: reference_id.clone(),
1031 needs_cache_runtime_wrapper: false,
1032 });
1033
1034 if self.config.is_react_server_layer {
1036 self.hoisted_extra_items
1037 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
1038 kind: VarDeclKind::Const,
1039 decls: vec![VarDeclarator {
1040 span: DUMMY_SP,
1041 name: Pat::Ident(action_ident.clone().into()),
1042 init: Some(take_fn_or_arrow_expr()),
1043 definite: false,
1044 }],
1045 ..Default::default()
1046 })))));
1047
1048 self.hoisted_extra_items
1049 .push(ModuleItem::Stmt(assign_name_to_ident(
1050 &action_ident,
1051 "default",
1052 )));
1053
1054 self.rewrite_default_fn_expr_to_proxy_expr =
1055 Some(Box::new(Expr::Ident(action_ident)));
1056 }
1057 }
1058 }
1059
1060 fn register_cache_export_on_client(
1062 &mut self,
1063 export_name: &ModuleExportName,
1064 fn_name: Option<&Ident>,
1065 params: Option<&Vec<Param>>,
1066 span: Span,
1067 ) {
1068 if let Some(fn_name) = fn_name {
1069 let reference_id = self.generate_server_reference_id(export_name, true, params);
1070
1071 self.has_cache = true;
1072 self.reference_ids_by_export_name
1073 .insert(export_name.clone(), reference_id.clone());
1074
1075 self.server_reference_exports.push(ServerReferenceExport {
1076 ident: fn_name.clone(),
1077 export_name: export_name.clone(),
1078 reference_id: reference_id.clone(),
1079 needs_cache_runtime_wrapper: false,
1080 });
1081 } else if self.is_default_export() {
1082 let cache_ident = Ident::new(self.gen_cache_ident(), span, self.private_ctxt);
1083 let reference_id = self.generate_server_reference_id(export_name, true, params);
1084
1085 self.has_cache = true;
1086 self.reference_ids_by_export_name
1087 .insert(export_name.clone(), reference_id.clone());
1088
1089 self.server_reference_exports.push(ServerReferenceExport {
1090 ident: cache_ident.clone(),
1091 export_name: export_name.clone(),
1092 reference_id: reference_id.clone(),
1093 needs_cache_runtime_wrapper: false,
1094 });
1095 }
1096 }
1097}
1098
1099impl<C: Comments> VisitMut for ServerActions<C> {
1100 fn visit_mut_export_decl(&mut self, decl: &mut ExportDecl) {
1101 decl.decl.visit_mut_with(self);
1105 }
1106
1107 fn visit_mut_export_default_decl(&mut self, decl: &mut ExportDefaultDecl) {
1108 let old_current_export_name = self.current_export_name.take();
1109 self.current_export_name = Some(ModuleExportName::Ident(atom!("default").into()));
1110 self.rewrite_default_fn_expr_to_proxy_expr = None;
1111 decl.decl.visit_mut_with(self);
1112 self.current_export_name = old_current_export_name;
1113 }
1114
1115 fn visit_mut_export_default_expr(&mut self, expr: &mut ExportDefaultExpr) {
1116 let old_current_export_name = self.current_export_name.take();
1117 self.current_export_name = Some(ModuleExportName::Ident(atom!("default").into()));
1118 expr.expr.visit_mut_with(self);
1119 self.current_export_name = old_current_export_name;
1120
1121 if matches!(&*expr.expr, Expr::Call(_)) {
1124 if matches!(self.file_directive, Some(Directive::UseServer)) {
1125 let export_name = ModuleExportName::Ident(atom!("default").into());
1126 let action_ident =
1127 Ident::new(self.gen_action_ident(), expr.span, self.private_ctxt);
1128 let action_id = self.generate_server_reference_id(&export_name, false, None);
1129
1130 self.has_action = true;
1131 self.reference_ids_by_export_name
1132 .insert(export_name.clone(), action_id.clone());
1133
1134 self.server_reference_exports.push(ServerReferenceExport {
1135 ident: action_ident.clone(),
1136 export_name: export_name.clone(),
1137 reference_id: action_id.clone(),
1138 needs_cache_runtime_wrapper: false,
1139 });
1140
1141 self.hoisted_extra_items
1142 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
1143 kind: VarDeclKind::Const,
1144 decls: vec![VarDeclarator {
1145 span: DUMMY_SP,
1146 name: Pat::Ident(action_ident.clone().into()),
1147 init: Some(expr.expr.take()),
1148 definite: false,
1149 }],
1150 ..Default::default()
1151 })))));
1152
1153 self.rewrite_default_fn_expr_to_proxy_expr =
1154 Some(Box::new(Expr::Ident(action_ident)));
1155 } else if matches!(self.file_directive, Some(Directive::UseCache { .. })) {
1156 let cache_ident = Ident::new(self.gen_cache_ident(), expr.span, self.private_ctxt);
1157
1158 self.export_name_by_local_id.insert(
1159 cache_ident.to_id(),
1160 ModuleExportName::Ident(atom!("default").into()),
1161 );
1162
1163 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1164 .insert(cache_ident.to_id());
1165
1166 self.hoisted_extra_items
1167 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
1168 kind: VarDeclKind::Const,
1169 decls: vec![VarDeclarator {
1170 span: DUMMY_SP,
1171 name: Pat::Ident(cache_ident.into()),
1172 init: Some(expr.expr.take()),
1173 definite: false,
1174 }],
1175 ..Default::default()
1176 })))));
1177
1178 }
1181 }
1182 }
1183
1184 fn visit_mut_fn_expr(&mut self, f: &mut FnExpr) {
1185 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1186 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1187 if let Some(ident) = &f.ident {
1188 self.arrow_or_fn_expr_ident = Some(ident.clone());
1189 }
1190 f.visit_mut_children_with(self);
1191 self.this_status = old_this_status;
1192 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1193 }
1194
1195 fn visit_mut_function(&mut self, f: &mut Function) {
1196 let directive = self.get_directive_for_function(f.body.as_mut());
1197 let declared_idents_until = self.declared_idents.len();
1198 let old_names = take(&mut self.names);
1199
1200 if let Some(directive) = &directive {
1201 self.this_status = ThisStatus::Forbidden {
1202 directive: directive.clone(),
1203 };
1204 }
1205
1206 {
1208 let old_in_module = replace(&mut self.in_module_level, false);
1209 let should_track_names = directive.is_some() || self.should_track_names;
1210 let old_should_track_names = replace(&mut self.should_track_names, should_track_names);
1211 let old_current_export_name = self.current_export_name.take();
1212 let old_fn_decl_ident = self.fn_decl_ident.take();
1213 f.visit_mut_children_with(self);
1214 self.in_module_level = old_in_module;
1215 self.should_track_names = old_should_track_names;
1216 self.current_export_name = old_current_export_name;
1217 self.fn_decl_ident = old_fn_decl_ident;
1218 }
1219
1220 let mut child_names = take(&mut self.names);
1221
1222 if self.should_track_names {
1223 self.names = [old_names, child_names.clone()].concat();
1224 }
1225
1226 if let Some(directive) = directive {
1227 let fn_name = self
1228 .fn_decl_ident
1229 .as_ref()
1230 .or(self.arrow_or_fn_expr_ident.as_ref())
1231 .cloned();
1232
1233 if !self.validate_async_function(f.is_async, f.span, fn_name.as_ref(), &directive) {
1234 if self.current_export_name.is_some() {
1237 if let Some(fn_name) = fn_name {
1238 self.export_name_by_local_id.swap_remove(&fn_name.to_id());
1239 }
1240 }
1241
1242 return;
1243 }
1244
1245 if HANDLER.with(|handler| handler.has_errors()) {
1248 return;
1249 }
1250
1251 if matches!(self.file_directive, Some(Directive::UseServer))
1254 && matches!(directive, Directive::UseServer)
1255 {
1256 if let Some(export_name) = self.current_export_name.clone() {
1257 let params = f.params.clone();
1258 let span = f.span;
1259
1260 self.register_server_action_export(
1261 &export_name,
1262 fn_name.as_ref(),
1263 Some(¶ms),
1264 span,
1265 &mut || {
1266 Box::new(Expr::Fn(FnExpr {
1267 ident: fn_name.clone(),
1268 function: Box::new(f.take()),
1269 }))
1270 },
1271 );
1272
1273 return;
1274 }
1275 }
1276
1277 if !self.config.is_react_server_layer {
1279 if matches!(directive, Directive::UseCache { .. }) {
1280 if let Some(export_name) = self.current_export_name.clone() {
1281 self.register_cache_export_on_client(
1282 &export_name,
1283 fn_name.as_ref(),
1284 Some(&f.params),
1285 f.span,
1286 );
1287 }
1288 }
1289
1290 return;
1291 }
1292
1293 if let Directive::UseCache { cache_kind } = directive {
1294 retain_names_from_declared_idents(
1297 &mut child_names,
1298 &self.declared_idents[..declared_idents_until],
1299 );
1300
1301 let new_expr = self.maybe_hoist_and_create_proxy_for_cache_function(
1302 child_names.clone(),
1303 self.fn_decl_ident
1304 .as_ref()
1305 .or(self.arrow_or_fn_expr_ident.as_ref())
1306 .cloned(),
1307 cache_kind,
1308 f,
1309 );
1310
1311 if self.is_default_export() {
1312 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1317 } else if let Some(ident) = &self.fn_decl_ident {
1318 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1320 span: DUMMY_SP,
1321 kind: VarDeclKind::Var,
1322 decls: vec![VarDeclarator {
1323 span: DUMMY_SP,
1324 name: Pat::Ident(ident.clone().into()),
1325 init: Some(new_expr),
1326 definite: false,
1327 }],
1328 ..Default::default()
1329 });
1330 } else {
1331 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1332 }
1333 } else {
1334 retain_names_from_declared_idents(
1337 &mut child_names,
1338 &self.declared_idents[..declared_idents_until],
1339 );
1340
1341 let new_expr = self.maybe_hoist_and_create_proxy_for_server_action_function(
1342 child_names,
1343 f,
1344 fn_name,
1345 );
1346
1347 if self.is_default_export() {
1348 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1353 } else if let Some(ident) = &self.fn_decl_ident {
1354 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1357 span: DUMMY_SP,
1358 kind: VarDeclKind::Var,
1359 decls: vec![VarDeclarator {
1360 span: DUMMY_SP,
1361 name: Pat::Ident(ident.clone().into()),
1362 init: Some(new_expr),
1363 definite: false,
1364 }],
1365 ..Default::default()
1366 });
1367 } else {
1368 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1369 }
1370 }
1371 }
1372 }
1373
1374 fn visit_mut_decl(&mut self, d: &mut Decl) {
1375 self.rewrite_fn_decl_to_proxy_decl = None;
1376 d.visit_mut_children_with(self);
1377
1378 if let Some(decl) = &self.rewrite_fn_decl_to_proxy_decl {
1379 *d = (*decl).clone().into();
1380 }
1381
1382 self.rewrite_fn_decl_to_proxy_decl = None;
1383 }
1384
1385 fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
1386 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1387 let old_current_export_name = self.current_export_name.take();
1388 if self.in_module_level {
1389 if let Some(export_name) = self.export_name_by_local_id.get(&f.ident.to_id()) {
1390 self.current_export_name = Some(export_name.clone());
1391 }
1392 }
1393 let old_fn_decl_ident = self.fn_decl_ident.replace(f.ident.clone());
1394 f.visit_mut_children_with(self);
1395 self.this_status = old_this_status;
1396 self.current_export_name = old_current_export_name;
1397 self.fn_decl_ident = old_fn_decl_ident;
1398 }
1399
1400 fn visit_mut_arrow_expr(&mut self, a: &mut ArrowExpr) {
1401 let directive = self.get_directive_for_function(
1404 if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
1405 Some(block)
1406 } else {
1407 None
1408 },
1409 );
1410
1411 if let Some(directive) = &directive {
1412 self.this_status = ThisStatus::Forbidden {
1413 directive: directive.clone(),
1414 };
1415 }
1416
1417 let declared_idents_until = self.declared_idents.len();
1418 let old_names = take(&mut self.names);
1419
1420 {
1421 let old_in_module = replace(&mut self.in_module_level, false);
1423 let should_track_names = directive.is_some() || self.should_track_names;
1424 let old_should_track_names = replace(&mut self.should_track_names, should_track_names);
1425 let old_current_export_name = self.current_export_name.take();
1426 {
1427 for n in &mut a.params {
1428 collect_idents_in_pat(n, &mut self.declared_idents);
1429 }
1430 }
1431 a.visit_mut_children_with(self);
1432 self.in_module_level = old_in_module;
1433 self.should_track_names = old_should_track_names;
1434 self.current_export_name = old_current_export_name;
1435 }
1436
1437 let mut child_names = take(&mut self.names);
1438
1439 if self.should_track_names {
1440 self.names = [old_names, child_names.clone()].concat();
1441 }
1442
1443 if let Some(directive) = directive {
1444 let arrow_ident = self.arrow_or_fn_expr_ident.clone();
1445
1446 if !self.validate_async_function(a.is_async, a.span, arrow_ident.as_ref(), &directive) {
1447 if self.current_export_name.is_some() {
1450 if let Some(arrow_ident) = arrow_ident {
1451 self.export_name_by_local_id
1452 .swap_remove(&arrow_ident.to_id());
1453 }
1454 }
1455
1456 return;
1457 }
1458
1459 if HANDLER.with(|handler| handler.has_errors()) {
1462 return;
1463 }
1464
1465 if matches!(self.file_directive, Some(Directive::UseServer))
1468 && matches!(directive, Directive::UseServer)
1469 {
1470 if let Some(export_name) = self.current_export_name.clone() {
1471 let params: Vec<Param> =
1472 a.params.iter().map(|p| Param::from(p.clone())).collect();
1473
1474 self.register_server_action_export(
1475 &export_name,
1476 arrow_ident.as_ref(),
1477 Some(¶ms),
1478 a.span,
1479 &mut || Box::new(Expr::Arrow(a.take())),
1480 );
1481
1482 return;
1483 }
1484 }
1485
1486 if !self.config.is_react_server_layer {
1488 if matches!(directive, Directive::UseCache { .. }) {
1489 if let Some(export_name) = self.current_export_name.clone() {
1490 let params: Vec<Param> =
1491 a.params.iter().map(|p| Param::from(p.clone())).collect();
1492
1493 self.register_cache_export_on_client(
1494 &export_name,
1495 arrow_ident.as_ref(),
1496 Some(¶ms),
1497 a.span,
1498 );
1499 }
1500 }
1501
1502 return;
1503 }
1504
1505 retain_names_from_declared_idents(
1508 &mut child_names,
1509 &self.declared_idents[..declared_idents_until],
1510 );
1511
1512 if let Directive::UseCache { cache_kind } = directive {
1513 self.rewrite_expr_to_proxy_expr =
1514 Some(self.maybe_hoist_and_create_proxy_for_cache_arrow_expr(
1515 child_names,
1516 cache_kind,
1517 a,
1518 ));
1519 } else {
1520 self.rewrite_expr_to_proxy_expr = Some(
1521 self.maybe_hoist_and_create_proxy_for_server_action_arrow_expr(child_names, a),
1522 );
1523 }
1524 }
1525 }
1526
1527 fn visit_mut_module(&mut self, m: &mut Module) {
1528 self.start_pos = m.span.lo;
1529 m.visit_mut_children_with(self);
1530 }
1531
1532 fn visit_mut_stmt(&mut self, n: &mut Stmt) {
1533 n.visit_mut_children_with(self);
1534
1535 if self.in_module_level {
1536 return;
1537 }
1538
1539 collect_decl_idents_in_stmt(n, &mut self.declared_idents);
1542 }
1543
1544 fn visit_mut_param(&mut self, n: &mut Param) {
1545 n.visit_mut_children_with(self);
1546
1547 if self.in_module_level {
1548 return;
1549 }
1550
1551 collect_idents_in_pat(&n.pat, &mut self.declared_idents);
1552 }
1553
1554 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
1555 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1556 let old_current_export_name = self.current_export_name.take();
1557
1558 match n {
1559 PropOrSpread::Prop(box Prop::KeyValue(KeyValueProp {
1560 key: PropName::Ident(ident_name),
1561 value: box Expr::Arrow(_) | box Expr::Fn(_),
1562 ..
1563 })) => {
1564 self.current_export_name = None;
1565 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1566 }
1567 PropOrSpread::Prop(box Prop::Method(MethodProp { key, .. })) => {
1568 let key = key.clone();
1569
1570 if let PropName::Ident(ident_name) = &key {
1571 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1572 }
1573
1574 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1575 self.rewrite_expr_to_proxy_expr = None;
1576 self.current_export_name = None;
1577 n.visit_mut_children_with(self);
1578 self.current_export_name = old_current_export_name.clone();
1579 self.this_status = old_this_status;
1580
1581 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1582 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
1583 key,
1584 value: expr,
1585 })));
1586 }
1587
1588 return;
1589 }
1590 _ => {}
1591 }
1592
1593 if !self.in_module_level && self.should_track_names {
1594 if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
1595 self.names.push(Name::from(&*i));
1596 self.should_track_names = false;
1597 n.visit_mut_children_with(self);
1598 self.should_track_names = true;
1599 return;
1600 }
1601 }
1602
1603 n.visit_mut_children_with(self);
1604 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1605 self.current_export_name = old_current_export_name;
1606 }
1607
1608 fn visit_mut_class(&mut self, n: &mut Class) {
1609 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1610 n.visit_mut_children_with(self);
1611 self.this_status = old_this_status;
1612 }
1613
1614 fn visit_mut_class_member(&mut self, n: &mut ClassMember) {
1615 if let ClassMember::Method(ClassMethod {
1616 is_abstract: false,
1617 is_static: true,
1618 kind: MethodKind::Method,
1619 key,
1620 span,
1621 accessibility: None | Some(Accessibility::Public),
1622 ..
1623 }) = n
1624 {
1625 let key = key.clone();
1626 let span = *span;
1627 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1628
1629 if let PropName::Ident(ident_name) = &key {
1630 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1631 }
1632
1633 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1634 let old_current_export_name = self.current_export_name.take();
1635 self.rewrite_expr_to_proxy_expr = None;
1636 self.current_export_name = None;
1637 n.visit_mut_children_with(self);
1638 self.this_status = old_this_status;
1639 self.current_export_name = old_current_export_name;
1640 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1641
1642 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1643 *n = ClassMember::ClassProp(ClassProp {
1644 span,
1645 key,
1646 value: Some(expr),
1647 is_static: true,
1648 ..Default::default()
1649 });
1650 }
1651 } else {
1652 n.visit_mut_children_with(self);
1653 }
1654 }
1655
1656 fn visit_mut_class_method(&mut self, n: &mut ClassMethod) {
1657 if n.is_static {
1658 n.visit_mut_children_with(self);
1659 } else {
1660 let (is_action_fn, is_cache_fn) = has_body_directive(&n.function.body);
1661
1662 if is_action_fn {
1663 emit_error(
1664 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span: n.span },
1665 );
1666 } else if is_cache_fn {
1667 emit_error(
1668 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span: n.span },
1669 );
1670 } else {
1671 n.visit_mut_children_with(self);
1672 }
1673 }
1674 }
1675
1676 fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
1677 if let Callee::Expr(box Expr::Ident(Ident { sym, .. })) = &mut n.callee {
1678 if sym == "jsxDEV" || sym == "_jsxDEV" {
1679 if n.args.len() > 4 {
1683 for arg in &mut n.args[0..4] {
1684 arg.visit_mut_with(self);
1685 }
1686 return;
1687 }
1688 }
1689 }
1690
1691 let old_current_export_name = self.current_export_name.take();
1692 n.visit_mut_children_with(self);
1693 self.current_export_name = old_current_export_name;
1694 }
1695
1696 fn visit_mut_callee(&mut self, n: &mut Callee) {
1697 let old_in_callee = replace(&mut self.in_callee, true);
1698 n.visit_mut_children_with(self);
1699 self.in_callee = old_in_callee;
1700 }
1701
1702 fn visit_mut_expr(&mut self, n: &mut Expr) {
1703 if !self.in_module_level && self.should_track_names {
1704 if let Ok(mut name) = Name::try_from(&*n) {
1705 if self.in_callee {
1706 if !name.1.is_empty() {
1709 name.1.pop();
1710 }
1711 }
1712
1713 self.names.push(name);
1714 self.should_track_names = false;
1715 n.visit_mut_children_with(self);
1716 self.should_track_names = true;
1717 return;
1718 }
1719 }
1720
1721 self.rewrite_expr_to_proxy_expr = None;
1722 n.visit_mut_children_with(self);
1723 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1724 *n = *expr;
1725 }
1726 }
1727
1728 fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
1729 self.file_directive = self.get_directive_for_module(stmts);
1730
1731 let in_cache_file = matches!(self.file_directive, Some(Directive::UseCache { .. }));
1732 let in_action_file = matches!(self.file_directive, Some(Directive::UseServer));
1733
1734 let should_track_exports = in_action_file || in_cache_file;
1736
1737 if should_track_exports {
1743 for stmt in stmts.iter() {
1744 match stmt {
1745 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export_default_expr)) => {
1746 if let Expr::Ident(ident) = &*export_default_expr.expr {
1747 self.export_name_by_local_id.insert(
1748 ident.to_id(),
1749 ModuleExportName::Ident(atom!("default").into()),
1750 );
1751 }
1752 }
1753 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(export_default_decl)) => {
1754 if let DefaultDecl::Fn(f) = &export_default_decl.decl {
1756 if let Some(ident) = &f.ident {
1757 self.export_name_by_local_id.insert(
1758 ident.to_id(),
1759 ModuleExportName::Ident(atom!("default").into()),
1760 );
1761 }
1762 }
1763 }
1764 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(export_decl)) => {
1765 match &export_decl.decl {
1767 Decl::Fn(f) => {
1768 self.export_name_by_local_id.insert(
1769 f.ident.to_id(),
1770 ModuleExportName::Ident(f.ident.clone()),
1771 );
1772 }
1773 Decl::Var(var) => {
1774 for decl in &var.decls {
1775 let mut idents = vec![];
1781 collect_idents_in_pat(&decl.name, &mut idents);
1782
1783 let is_destructuring = !matches!(&decl.name, Pat::Ident(_));
1784 let needs_wrapper = if is_destructuring {
1785 true
1786 } else if let Some(init) = &decl.init {
1787 may_need_cache_runtime_wrapper(init)
1788 } else {
1789 false
1790 };
1791
1792 for ident in idents {
1793 self.export_name_by_local_id.insert(
1794 ident.to_id(),
1795 ModuleExportName::Ident(ident.clone()),
1796 );
1797
1798 if needs_wrapper {
1799 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1800 .insert(ident.to_id());
1801 }
1802 }
1803 }
1804 }
1805 _ => {}
1806 }
1807 }
1808 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named_export)) => {
1809 if named_export.src.is_none() {
1810 for spec in &named_export.specifiers {
1811 match spec {
1812 ExportSpecifier::Named(ExportNamedSpecifier {
1813 orig: ModuleExportName::Ident(orig),
1814 exported: Some(exported),
1815 is_type_only: false,
1816 ..
1817 }) => {
1818 self.export_name_by_local_id
1820 .insert(orig.to_id(), exported.clone());
1821 }
1822 ExportSpecifier::Named(ExportNamedSpecifier {
1823 orig: ModuleExportName::Ident(orig),
1824 exported: None,
1825 is_type_only: false,
1826 ..
1827 }) => {
1828 self.export_name_by_local_id.insert(
1830 orig.to_id(),
1831 ModuleExportName::Ident(orig.clone()),
1832 );
1833 }
1834 _ => {}
1835 }
1836 }
1837 }
1838 }
1839 ModuleItem::Stmt(Stmt::Decl(Decl::Var(var_decl))) => {
1840 for decl in &var_decl.decls {
1842 if let Pat::Ident(ident_pat) = &decl.name {
1843 if let Some(init) = &decl.init {
1844 if may_need_cache_runtime_wrapper(init) {
1845 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1846 .insert(ident_pat.id.to_id());
1847 }
1848 }
1849 }
1850 }
1851 }
1852 ModuleItem::Stmt(Stmt::Decl(Decl::Fn(_fn_decl))) => {
1853 }
1856 ModuleItem::ModuleDecl(ModuleDecl::Import(import_decl)) => {
1857 for spec in &import_decl.specifiers {
1860 match spec {
1861 ImportSpecifier::Named(named) => {
1862 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1863 .insert(named.local.to_id());
1864 }
1865 ImportSpecifier::Default(default) => {
1866 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1867 .insert(default.local.to_id());
1868 }
1869 ImportSpecifier::Namespace(ns) => {
1870 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1871 .insert(ns.local.to_id());
1872 }
1873 }
1874 }
1875 }
1876 _ => {}
1877 }
1878 }
1879 }
1880
1881 let old_annotations = self.annotations.take();
1882 let mut new = Vec::with_capacity(stmts.len());
1883
1884 for mut stmt in stmts.take() {
1887 let mut should_remove_statement = false;
1888
1889 if should_track_exports {
1890 let mut disallowed_export_span = DUMMY_SP;
1891
1892 match &mut stmt {
1893 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, span })) => {
1894 match decl {
1895 Decl::Var(var) => {
1896 let mut has_export_needing_wrapper = false;
1897
1898 for decl in &var.decls {
1899 if let Pat::Ident(_) = &decl.name {
1900 if let Some(init) = &decl.init {
1901 if let Expr::Lit(_) = &**init {
1908 disallowed_export_span = *span;
1909 }
1910 }
1911 }
1912
1913 if in_cache_file {
1916 let mut idents: Vec<Ident> = Vec::new();
1917 collect_idents_in_pat(&decl.name, &mut idents);
1918
1919 for ident in idents {
1920 let needs_cache_runtime_wrapper = self
1921 .local_ids_that_need_cache_runtime_wrapper_if_exported
1922 .contains(&ident.to_id());
1923
1924 if needs_cache_runtime_wrapper {
1925 has_export_needing_wrapper = true;
1926 }
1927 }
1928 }
1929 }
1930
1931 if in_cache_file && has_export_needing_wrapper {
1934 stmt = ModuleItem::Stmt(Stmt::Decl(Decl::Var(var.clone())));
1935 }
1936 }
1937 Decl::Fn(_)
1938 | Decl::TsInterface(_)
1939 | Decl::TsTypeAlias(_)
1940 | Decl::TsEnum(_) => {}
1941 _ => {
1942 disallowed_export_span = *span;
1943 }
1944 }
1945 }
1946 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => {
1947 if !named.type_only {
1948 if let Some(src) = &named.src {
1949 if in_cache_file {
1951 let import_specs: Vec<ImportSpecifier> = named
1954 .specifiers
1955 .iter()
1956 .filter_map(|spec| {
1957 if let ExportSpecifier::Named(ExportNamedSpecifier {
1958 orig: ModuleExportName::Ident(orig),
1959 exported,
1960 is_type_only: false,
1961 ..
1962 }) = spec
1963 {
1964 let export_name =
1968 if let Some(exported) = exported {
1969 exported.clone()
1970 } else {
1971 ModuleExportName::Ident(orig.clone())
1972 };
1973
1974 self.export_name_by_local_id
1975 .insert(orig.to_id(), export_name);
1976
1977 self.local_ids_that_need_cache_runtime_wrapper_if_exported
1978 .insert(orig.to_id());
1979
1980 return Some(ImportSpecifier::Named(
1981 ImportNamedSpecifier {
1982 span: DUMMY_SP,
1983 local: orig.clone(),
1984 imported: None,
1985 is_type_only: false,
1986 },
1987 ));
1988 }
1989 None
1990 })
1991 .collect();
1992
1993 if !import_specs.is_empty() {
1994 self.extra_items.push(ModuleItem::ModuleDecl(
1996 ModuleDecl::Import(ImportDecl {
1997 span: named.span,
1998 specifiers: import_specs,
1999 src: src.clone(),
2000 type_only: false,
2001 with: named.with.clone(),
2002 phase: Default::default(),
2003 }),
2004 ));
2005 }
2006
2007 named.specifiers.retain(|spec| {
2010 matches!(
2011 spec,
2012 ExportSpecifier::Named(ExportNamedSpecifier {
2013 is_type_only: true,
2014 ..
2015 })
2016 )
2017 });
2018
2019 if named.specifiers.is_empty() {
2022 should_remove_statement = true;
2023 }
2024 } else if named.specifiers.iter().any(|s| match s {
2025 ExportSpecifier::Namespace(_) | ExportSpecifier::Default(_) => {
2026 true
2027 }
2028 ExportSpecifier::Named(s) => !s.is_type_only,
2029 }) {
2030 disallowed_export_span = named.span;
2031 }
2032 } else {
2033 if in_cache_file {
2037 named.specifiers.retain(|spec| {
2038 if let ExportSpecifier::Named(ExportNamedSpecifier {
2039 orig: ModuleExportName::Ident(ident),
2040 is_type_only: false,
2041 ..
2042 }) = spec
2043 {
2044 !self
2045 .local_ids_that_need_cache_runtime_wrapper_if_exported
2046 .contains(&ident.to_id())
2047 } else {
2048 true
2049 }
2050 });
2051
2052 if named.specifiers.is_empty() {
2053 should_remove_statement = true;
2054 }
2055 }
2056 }
2057 }
2058 }
2059 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
2060 decl,
2061 span,
2062 })) => match decl {
2063 DefaultDecl::Fn(_) | DefaultDecl::TsInterfaceDecl(_) => {}
2064 _ => {
2065 disallowed_export_span = *span;
2066 }
2067 },
2068 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(default_expr)) => {
2069 match &mut *default_expr.expr {
2070 Expr::Fn(_) | Expr::Arrow(_) => {}
2071 Expr::Ident(ident) => {
2072 if in_cache_file {
2075 let needs_cache_runtime_wrapper = self
2076 .local_ids_that_need_cache_runtime_wrapper_if_exported
2077 .contains(&ident.to_id());
2078
2079 if needs_cache_runtime_wrapper {
2080 should_remove_statement = true;
2081 }
2082 }
2083 }
2084 Expr::Call(_call) => {
2085 if in_cache_file {
2089 should_remove_statement = true;
2090 }
2091 }
2092 _ => {
2093 disallowed_export_span = default_expr.span;
2094 }
2095 }
2096 }
2097 ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll {
2098 span,
2099 type_only,
2100 ..
2101 })) => {
2102 if !*type_only {
2103 disallowed_export_span = *span;
2104 }
2105 }
2106 _ => {}
2107 }
2108
2109 if disallowed_export_span != DUMMY_SP {
2111 emit_error(ServerActionsErrorKind::ExportedSyncFunction {
2112 span: disallowed_export_span,
2113 in_action_file,
2114 });
2115 return;
2116 }
2117 }
2118
2119 stmt.visit_mut_with(self);
2120
2121 let new_stmt = if should_remove_statement {
2122 None
2123 } else if let Some(expr) = self.rewrite_default_fn_expr_to_proxy_expr.take() {
2124 Some(ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
2125 ExportDefaultExpr {
2126 span: DUMMY_SP,
2127 expr,
2128 },
2129 )))
2130 } else {
2131 Some(stmt)
2132 };
2133
2134 if self.config.is_react_server_layer || self.file_directive.is_none() {
2135 new.append(&mut self.hoisted_extra_items);
2136 if let Some(stmt) = new_stmt {
2137 new.push(stmt);
2138 }
2139 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
2140 new.append(&mut self.extra_items);
2141 }
2142 }
2143
2144 if should_track_exports {
2147 for (id, export_name) in &self.export_name_by_local_id {
2148 if self.reference_ids_by_export_name.contains_key(export_name) {
2149 continue;
2150 }
2151
2152 if in_cache_file
2153 && !self
2154 .local_ids_that_need_cache_runtime_wrapper_if_exported
2155 .contains(id)
2156 {
2157 continue;
2158 }
2159
2160 self.server_reference_exports.push(ServerReferenceExport {
2161 ident: Ident::from(id.clone()),
2162 export_name: export_name.clone(),
2163 reference_id: self.generate_server_reference_id(
2164 export_name,
2165 in_cache_file,
2166 None,
2167 ),
2168 needs_cache_runtime_wrapper: in_cache_file,
2169 });
2170 }
2171 }
2172
2173 if in_action_file || in_cache_file && !self.config.is_react_server_layer {
2174 self.reference_ids_by_export_name.extend(
2175 self.server_reference_exports
2176 .iter()
2177 .map(|e| (e.export_name.clone(), e.reference_id.clone())),
2178 );
2179
2180 if !self.reference_ids_by_export_name.is_empty() {
2181 self.has_action |= in_action_file;
2182 self.has_cache |= in_cache_file;
2183 }
2184 };
2185
2186 let create_ref_ident = private_ident!("createServerReference");
2189 let call_server_ident = private_ident!("callServer");
2190 let find_source_map_url_ident = private_ident!("findSourceMapURL");
2191
2192 let client_layer_import = ((self.has_action || self.has_cache)
2193 && !self.config.is_react_server_layer)
2194 .then(|| {
2195 ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2202 span: DUMMY_SP,
2203 specifiers: vec![
2204 ImportSpecifier::Named(ImportNamedSpecifier {
2205 span: DUMMY_SP,
2206 local: create_ref_ident.clone(),
2207 imported: None,
2208 is_type_only: false,
2209 }),
2210 ImportSpecifier::Named(ImportNamedSpecifier {
2211 span: DUMMY_SP,
2212 local: call_server_ident.clone(),
2213 imported: None,
2214 is_type_only: false,
2215 }),
2216 ImportSpecifier::Named(ImportNamedSpecifier {
2217 span: DUMMY_SP,
2218 local: find_source_map_url_ident.clone(),
2219 imported: None,
2220 is_type_only: false,
2221 }),
2222 ],
2223 src: Box::new(Str {
2224 span: DUMMY_SP,
2225 value: atom!("private-next-rsc-action-client-wrapper").into(),
2226 raw: None,
2227 }),
2228 type_only: false,
2229 with: None,
2230 phase: Default::default(),
2231 }))
2232 });
2233
2234 let mut client_layer_exports = FxIndexMap::default();
2235
2236 if should_track_exports {
2238 let server_reference_exports = self.server_reference_exports.take();
2239
2240 for ServerReferenceExport {
2241 ident,
2242 export_name,
2243 reference_id: ref_id,
2244 needs_cache_runtime_wrapper,
2245 } in &server_reference_exports
2246 {
2247 if !self.config.is_react_server_layer {
2248 if matches!(export_name, ModuleExportName::Ident(i) if i.sym == *"default") {
2249 let export_expr = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
2250 ExportDefaultExpr {
2251 span: DUMMY_SP,
2252 expr: Box::new(Expr::Call(CallExpr {
2253 span: if self.config.is_react_server_layer
2257 || self.config.is_development
2258 {
2259 self.comments.add_pure_comment(ident.span.lo);
2260 ident.span
2261 } else {
2262 PURE_SP
2263 },
2264 callee: Callee::Expr(Box::new(Expr::Ident(
2265 create_ref_ident.clone(),
2266 ))),
2267 args: vec![
2268 ref_id.clone().as_arg(),
2269 call_server_ident.clone().as_arg(),
2270 Expr::undefined(DUMMY_SP).as_arg(),
2271 find_source_map_url_ident.clone().as_arg(),
2272 "default".as_arg(),
2273 ],
2274 ..Default::default()
2275 })),
2276 },
2277 ));
2278 client_layer_exports.insert(
2279 atom!("default"),
2280 (
2281 vec![export_expr],
2282 ModuleExportName::Ident(atom!("default").into()),
2283 ref_id.clone(),
2284 ),
2285 );
2286 } else {
2287 let var_name = if in_cache_file {
2288 self.gen_cache_ident()
2289 } else {
2290 self.gen_action_ident()
2291 };
2292
2293 let var_ident = Ident::new(var_name.clone(), DUMMY_SP, self.private_ctxt);
2294
2295 let name_span =
2299 if self.config.is_react_server_layer || self.config.is_development {
2300 ident.span
2301 } else {
2302 DUMMY_SP
2303 };
2304
2305 let export_name_str: Wtf8Atom = match export_name {
2306 ModuleExportName::Ident(i) => i.sym.clone().into(),
2307 ModuleExportName::Str(s) => s.value.clone(),
2308 };
2309
2310 let var_decl = ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
2311 span: DUMMY_SP,
2312 kind: VarDeclKind::Const,
2313 decls: vec![VarDeclarator {
2314 span: DUMMY_SP,
2315 name: Pat::Ident(
2316 IdentName::new(var_name.clone(), name_span).into(),
2317 ),
2318 init: Some(Box::new(Expr::Call(CallExpr {
2319 span: PURE_SP,
2320 callee: Callee::Expr(Box::new(Expr::Ident(
2321 create_ref_ident.clone(),
2322 ))),
2323 args: vec![
2324 ref_id.clone().as_arg(),
2325 call_server_ident.clone().as_arg(),
2326 Expr::undefined(DUMMY_SP).as_arg(),
2327 find_source_map_url_ident.clone().as_arg(),
2328 export_name_str.as_arg(),
2329 ],
2330 ..Default::default()
2331 }))),
2332 definite: false,
2333 }],
2334 ..Default::default()
2335 }))));
2336
2337 let exported_name =
2341 if self.config.is_react_server_layer || self.config.is_development {
2342 export_name.clone()
2343 } else {
2344 strip_export_name_span(export_name)
2345 };
2346
2347 let export_named =
2348 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(NamedExport {
2349 span: DUMMY_SP,
2350 specifiers: vec![ExportSpecifier::Named(ExportNamedSpecifier {
2351 span: DUMMY_SP,
2352 orig: ModuleExportName::Ident(var_ident),
2353 exported: Some(exported_name),
2354 is_type_only: false,
2355 })],
2356 src: None,
2357 type_only: false,
2358 with: None,
2359 }));
2360
2361 client_layer_exports.insert(
2362 var_name,
2363 (
2364 vec![var_decl, export_named],
2365 export_name.clone(),
2366 ref_id.clone(),
2367 ),
2368 );
2369 }
2370 } else if in_cache_file {
2371 if !*needs_cache_runtime_wrapper {
2376 continue;
2377 }
2378
2379 let wrapper_ident = Ident::new(
2381 format!("$$RSC_SERVER_CACHE_{}", export_name.atom()).into(),
2382 ident.span,
2383 self.private_ctxt,
2384 );
2385
2386 self.has_cache = true;
2387 self.reference_ids_by_export_name
2388 .insert(export_name.clone(), ref_id.clone());
2389
2390 self.extra_items
2392 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
2393 kind: VarDeclKind::Let,
2394 decls: vec![VarDeclarator {
2395 span: ident.span,
2396 name: Pat::Ident(wrapper_ident.clone().into()),
2397 init: Some(Box::new(Expr::Ident(ident.clone()))),
2398 definite: false,
2399 }],
2400 ..Default::default()
2401 })))));
2402
2403 let wrapper_stmts = {
2404 let mut stmts = vec![
2405 Stmt::Expr(ExprStmt {
2407 span: DUMMY_SP,
2408 expr: Box::new(Expr::Assign(AssignExpr {
2409 span: DUMMY_SP,
2410 op: op!("="),
2411 left: AssignTarget::Simple(SimpleAssignTarget::Ident(
2412 wrapper_ident.clone().into(),
2413 )),
2414 right: Box::new(create_cache_wrapper(
2415 "default",
2416 ref_id.clone(),
2417 0,
2418 None,
2421 Expr::Ident(ident.clone()),
2422 ident.span,
2423 )),
2424 })),
2425 }),
2426 Stmt::Expr(ExprStmt {
2428 span: DUMMY_SP,
2429 expr: Box::new(annotate_ident_as_server_reference(
2430 wrapper_ident.clone(),
2431 ref_id.clone(),
2432 ident.span,
2433 )),
2434 }),
2435 ];
2436
2437 if !ident.sym.starts_with("$$RSC_SERVER_") {
2439 stmts.push(assign_name_to_ident(&wrapper_ident, &ident.sym));
2441 }
2442
2443 stmts
2444 };
2445
2446 self.extra_items.push(ModuleItem::Stmt(Stmt::If(IfStmt {
2448 test: Box::new(Expr::Bin(BinExpr {
2449 span: DUMMY_SP,
2450 op: op!("==="),
2451 left: Box::new(Expr::Unary(UnaryExpr {
2452 span: DUMMY_SP,
2453 op: op!("typeof"),
2454 arg: Box::new(Expr::Ident(ident.clone())),
2455 })),
2456 right: Box::new(Expr::Lit(Lit::Str(Str {
2457 span: DUMMY_SP,
2458 value: atom!("function").into(),
2459 raw: None,
2460 }))),
2461 })),
2462 cons: Box::new(Stmt::Block(BlockStmt {
2463 stmts: wrapper_stmts,
2464 ..Default::default()
2465 })),
2466 ..Default::default()
2467 })));
2468
2469 if matches!(export_name, ModuleExportName::Ident(i) if i.sym == *"default") {
2471 self.extra_items.push(ModuleItem::ModuleDecl(
2472 ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
2473 span: DUMMY_SP,
2474 expr: Box::new(Expr::Ident(wrapper_ident)),
2475 }),
2476 ));
2477 } else {
2478 self.extra_items
2479 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
2480 NamedExport {
2481 span: DUMMY_SP,
2482 specifiers: vec![ExportSpecifier::Named(
2483 ExportNamedSpecifier {
2484 span: DUMMY_SP,
2485 orig: ModuleExportName::Ident(wrapper_ident),
2486 exported: Some(export_name.clone()),
2487 is_type_only: false,
2488 },
2489 )],
2490 src: None,
2491 type_only: false,
2492 with: None,
2493 },
2494 )));
2495 }
2496 } else {
2497 self.annotations.push(Stmt::Expr(ExprStmt {
2498 span: DUMMY_SP,
2499 expr: Box::new(annotate_ident_as_server_reference(
2500 ident.clone(),
2501 ref_id.clone(),
2502 ident.span,
2503 )),
2504 }));
2505 }
2506 }
2507
2508 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
2516 new.append(&mut self.extra_items);
2517
2518 if !in_cache_file && !server_reference_exports.is_empty() {
2520 let ensure_ident = private_ident!("ensureServerEntryExports");
2521 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2522 span: DUMMY_SP,
2523 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2524 span: DUMMY_SP,
2525 local: ensure_ident.clone(),
2526 imported: None,
2527 is_type_only: false,
2528 })],
2529 src: Box::new(Str {
2530 span: DUMMY_SP,
2531 value: atom!("private-next-rsc-action-validate").into(),
2532 raw: None,
2533 }),
2534 type_only: false,
2535 with: None,
2536 phase: Default::default(),
2537 })));
2538 new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2539 span: DUMMY_SP,
2540 expr: Box::new(Expr::Call(CallExpr {
2541 span: DUMMY_SP,
2542 callee: Callee::Expr(Box::new(Expr::Ident(ensure_ident))),
2543 args: vec![ExprOrSpread {
2544 spread: None,
2545 expr: Box::new(Expr::Array(ArrayLit {
2546 span: DUMMY_SP,
2547 elems: server_reference_exports
2548 .iter()
2549 .map(|ServerReferenceExport { ident, .. }| {
2550 Some(ExprOrSpread {
2551 spread: None,
2552 expr: Box::new(Expr::Ident(ident.clone())),
2553 })
2554 })
2555 .collect(),
2556 })),
2557 }],
2558 ..Default::default()
2559 })),
2560 })));
2561 }
2562
2563 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
2565 }
2566 }
2567
2568 if self.has_cache && self.config.is_react_server_layer {
2571 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2572 span: DUMMY_SP,
2573 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2574 span: DUMMY_SP,
2575 local: quote_ident!("$$cache__").into(),
2576 imported: Some(quote_ident!("cache").into()),
2577 is_type_only: false,
2578 })],
2579 src: Box::new(Str {
2580 span: DUMMY_SP,
2581 value: atom!("private-next-rsc-cache-wrapper").into(),
2582 raw: None,
2583 }),
2584 type_only: false,
2585 with: None,
2586 phase: Default::default(),
2587 })));
2588
2589 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2590 span: DUMMY_SP,
2591 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2592 span: DUMMY_SP,
2593 local: quote_ident!("$$reactCache__").into(),
2594 imported: Some(quote_ident!("cache").into()),
2595 is_type_only: false,
2596 })],
2597 src: Box::new(Str {
2598 span: DUMMY_SP,
2599 value: atom!("react").into(),
2600 raw: None,
2601 }),
2602 type_only: false,
2603 with: None,
2604 phase: Default::default(),
2605 })));
2606
2607 new.rotate_right(2);
2609 }
2610
2611 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
2612 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2615 span: DUMMY_SP,
2616 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2617 span: DUMMY_SP,
2618 local: quote_ident!("registerServerReference").into(),
2619 imported: None,
2620 is_type_only: false,
2621 })],
2622 src: Box::new(Str {
2623 span: DUMMY_SP,
2624 value: atom!("private-next-rsc-server-reference").into(),
2625 raw: None,
2626 }),
2627 type_only: false,
2628 with: None,
2629 phase: Default::default(),
2630 })));
2631
2632 let mut import_count = 1;
2633
2634 if self.has_server_reference_with_bound_args {
2636 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2639 span: DUMMY_SP,
2640 specifiers: vec![
2641 ImportSpecifier::Named(ImportNamedSpecifier {
2642 span: DUMMY_SP,
2643 local: quote_ident!("encryptActionBoundArgs").into(),
2644 imported: None,
2645 is_type_only: false,
2646 }),
2647 ImportSpecifier::Named(ImportNamedSpecifier {
2648 span: DUMMY_SP,
2649 local: quote_ident!("decryptActionBoundArgs").into(),
2650 imported: None,
2651 is_type_only: false,
2652 }),
2653 ],
2654 src: Box::new(Str {
2655 span: DUMMY_SP,
2656 value: atom!("private-next-rsc-action-encryption").into(),
2657 raw: None,
2658 }),
2659 type_only: false,
2660 with: None,
2661 phase: Default::default(),
2662 })));
2663 import_count += 1;
2664 }
2665
2666 new.rotate_right(import_count);
2668 }
2669
2670 if self.has_action || self.has_cache {
2671 let export_names_ordered_by_reference_id = self
2674 .reference_ids_by_export_name
2675 .iter()
2676 .map(|(export_name, reference_id)| (reference_id, export_name))
2677 .collect::<BTreeMap<_, _>>();
2678
2679 if self.config.is_react_server_layer {
2680 self.comments.add_leading(
2682 self.start_pos,
2683 Comment {
2684 span: DUMMY_SP,
2685 kind: CommentKind::Block,
2686 text: generate_server_references_comment(
2687 &export_names_ordered_by_reference_id,
2688 match self.mode {
2689 ServerActionsMode::Webpack => None,
2690 ServerActionsMode::Turbopack => Some(("", "")),
2691 },
2692 )
2693 .into(),
2694 },
2695 );
2696 } else {
2697 match self.mode {
2698 ServerActionsMode::Webpack => {
2699 self.comments.add_leading(
2700 self.start_pos,
2701 Comment {
2702 span: DUMMY_SP,
2703 kind: CommentKind::Block,
2704 text: generate_server_references_comment(
2705 &export_names_ordered_by_reference_id,
2706 None,
2707 )
2708 .into(),
2709 },
2710 );
2711 new.push(client_layer_import.unwrap());
2712 new.rotate_right(1);
2713 new.extend(
2714 client_layer_exports
2715 .into_iter()
2716 .flat_map(|(_, (items, _, _))| items),
2717 );
2718 }
2719 ServerActionsMode::Turbopack => {
2720 new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2721 expr: Box::new(Expr::Lit(Lit::Str(
2722 atom!("use turbopack no side effects").into(),
2723 ))),
2724 span: DUMMY_SP,
2725 })));
2726 new.rotate_right(1);
2727 for (_, (items, export_name, ref_id)) in client_layer_exports {
2728 let mut module_items = vec![
2729 ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2730 expr: Box::new(Expr::Lit(Lit::Str(
2731 atom!("use turbopack no side effects").into(),
2732 ))),
2733 span: DUMMY_SP,
2734 })),
2735 client_layer_import.clone().unwrap(),
2736 ];
2737 module_items.extend(items);
2738
2739 let stripped_export_name = strip_export_name_span(&export_name);
2742
2743 new.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
2744 NamedExport {
2745 specifiers: vec![ExportSpecifier::Named(
2746 ExportNamedSpecifier {
2747 span: DUMMY_SP,
2748 orig: stripped_export_name,
2749 exported: None,
2750 is_type_only: false,
2751 },
2752 )],
2753 src: Some(Box::new(
2754 program_to_data_url(
2755 &self.file_name,
2756 &self.cm,
2757 module_items,
2758 Comment {
2759 span: DUMMY_SP,
2760 kind: CommentKind::Block,
2761 text: generate_server_references_comment(
2762 &std::iter::once((&ref_id, &export_name))
2763 .collect(),
2764 Some((
2765 &self.file_name,
2766 self.file_query.as_ref().map_or("", |v| v),
2767 )),
2768 )
2769 .into(),
2770 },
2771 )
2772 .into(),
2773 )),
2774 span: DUMMY_SP,
2775 type_only: false,
2776 with: None,
2777 },
2778 )));
2779 }
2780 }
2781 }
2782 }
2783 }
2784
2785 *stmts = new;
2786
2787 self.annotations = old_annotations;
2788 }
2789
2790 fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
2791 let old_annotations = self.annotations.take();
2792
2793 let mut new = Vec::with_capacity(stmts.len());
2794 for mut stmt in stmts.take() {
2795 stmt.visit_mut_with(self);
2796
2797 new.push(stmt);
2798 new.append(&mut self.annotations);
2799 }
2800
2801 *stmts = new;
2802
2803 self.annotations = old_annotations;
2804 }
2805
2806 fn visit_mut_jsx_attr(&mut self, attr: &mut JSXAttr) {
2807 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2808
2809 if let (Some(JSXAttrValue::JSXExprContainer(container)), JSXAttrName::Ident(ident_name)) =
2810 (&attr.value, &attr.name)
2811 {
2812 match &container.expr {
2813 JSXExpr::Expr(box Expr::Arrow(_)) | JSXExpr::Expr(box Expr::Fn(_)) => {
2814 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
2815 }
2816 _ => {}
2817 }
2818 }
2819
2820 attr.visit_mut_children_with(self);
2821 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2822 }
2823
2824 fn visit_mut_var_declarator(&mut self, var_declarator: &mut VarDeclarator) {
2825 let old_current_export_name = self.current_export_name.take();
2826 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2827
2828 if let (Pat::Ident(ident), Some(box Expr::Arrow(_) | box Expr::Fn(_))) =
2829 (&var_declarator.name, &var_declarator.init)
2830 {
2831 if self.in_module_level {
2832 if let Some(export_name) = self.export_name_by_local_id.get(&ident.to_id()) {
2833 self.current_export_name = Some(export_name.clone());
2834 }
2835 }
2836
2837 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2838 }
2839
2840 var_declarator.visit_mut_children_with(self);
2841
2842 self.current_export_name = old_current_export_name;
2843 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2844 }
2845
2846 fn visit_mut_assign_expr(&mut self, assign_expr: &mut AssignExpr) {
2847 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
2848
2849 if let (
2850 AssignTarget::Simple(SimpleAssignTarget::Ident(ident)),
2851 box Expr::Arrow(_) | box Expr::Fn(_),
2852 ) = (&assign_expr.left, &assign_expr.right)
2853 {
2854 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2855 }
2856
2857 assign_expr.visit_mut_children_with(self);
2858 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2859 }
2860
2861 fn visit_mut_this_expr(&mut self, n: &mut ThisExpr) {
2862 if let ThisStatus::Forbidden { directive } = &self.this_status {
2863 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2864 span: n.span,
2865 expr: "this".into(),
2866 directive: directive.clone(),
2867 });
2868 }
2869 }
2870
2871 fn visit_mut_super(&mut self, n: &mut Super) {
2872 if let ThisStatus::Forbidden { directive } = &self.this_status {
2873 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2874 span: n.span,
2875 expr: "super".into(),
2876 directive: directive.clone(),
2877 });
2878 }
2879 }
2880
2881 fn visit_mut_ident(&mut self, n: &mut Ident) {
2882 if n.sym == *"arguments" {
2883 if let ThisStatus::Forbidden { directive } = &self.this_status {
2884 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2885 span: n.span,
2886 expr: "arguments".into(),
2887 directive: directive.clone(),
2888 });
2889 }
2890 }
2891 }
2892
2893 noop_visit_mut_type!();
2894}
2895
2896fn retain_names_from_declared_idents(
2897 child_names: &mut Vec<Name>,
2898 current_declared_idents: &[Ident],
2899) {
2900 let mut retained_names = Vec::new();
2902
2903 for name in child_names.iter() {
2904 let mut should_retain = true;
2905
2906 for another_name in child_names.iter() {
2912 if name != another_name
2913 && name.0 == another_name.0
2914 && name.1.len() >= another_name.1.len()
2915 {
2916 let mut is_prefix = true;
2917 for i in 0..another_name.1.len() {
2918 if name.1[i] != another_name.1[i] {
2919 is_prefix = false;
2920 break;
2921 }
2922 }
2923 if is_prefix {
2924 should_retain = false;
2925 break;
2926 }
2927 }
2928 }
2929
2930 if should_retain
2931 && current_declared_idents
2932 .iter()
2933 .any(|ident| ident.to_id() == name.0)
2934 && !retained_names.contains(name)
2935 {
2936 retained_names.push(name.clone());
2937 }
2938 }
2939
2940 *child_names = retained_names;
2942}
2943
2944fn may_need_cache_runtime_wrapper(expr: &Expr) -> bool {
2947 match expr {
2948 Expr::Arrow(_) | Expr::Fn(_) => false,
2950 Expr::Object(_) | Expr::Array(_) | Expr::Lit(_) => false,
2952 _ => true,
2954 }
2955}
2956
2957fn create_cache_wrapper(
2960 cache_kind: &str,
2961 reference_id: Atom,
2962 bound_args_length: usize,
2963 fn_ident: Option<Ident>,
2964 target_expr: Expr,
2965 original_span: Span,
2966) -> Expr {
2967 let cache_call = CallExpr {
2968 span: original_span,
2969 callee: quote_ident!("$$cache__").as_callee(),
2970 args: vec![
2971 Box::new(Expr::from(cache_kind)).as_arg(),
2972 Box::new(Expr::from(reference_id.as_str())).as_arg(),
2973 Box::new(Expr::Lit(Lit::Num(Number {
2974 span: DUMMY_SP,
2975 value: bound_args_length as f64,
2976 raw: None,
2977 })))
2978 .as_arg(),
2979 Box::new(target_expr).as_arg(),
2980 Box::new(Expr::Ident(private_ident!(DUMMY_SP, "arguments"))).as_arg(),
2981 ],
2982 ..Default::default()
2983 };
2984
2985 let wrapper_fn_expr = Box::new(Expr::Fn(FnExpr {
2987 ident: fn_ident,
2988 function: Box::new(Function {
2989 body: Some(BlockStmt {
2990 stmts: vec![Stmt::Return(ReturnStmt {
2991 span: DUMMY_SP,
2992 arg: Some(Box::new(Expr::Call(cache_call))),
2993 })],
2994 ..Default::default()
2995 }),
2996 span: original_span,
2997 ..Default::default()
2998 }),
2999 }));
3000
3001 Expr::Call(CallExpr {
3002 callee: quote_ident!("$$reactCache__").as_callee(),
3003 args: vec![wrapper_fn_expr.as_arg()],
3004 ..Default::default()
3005 })
3006}
3007
3008#[allow(clippy::too_many_arguments)]
3009fn create_and_hoist_cache_function(
3010 cache_kind: &str,
3011 reference_id: Atom,
3012 bound_args_length: usize,
3013 cache_name: Atom,
3014 fn_ident: Option<Ident>,
3015 params: Vec<Param>,
3016 body: Option<BlockStmt>,
3017 original_span: Span,
3018 hoisted_extra_items: &mut Vec<ModuleItem>,
3019) -> Ident {
3020 let cache_ident = private_ident!(Span::dummy_with_cmt(), cache_name.clone());
3021 let inner_fn_name: Atom = format!("{}_INNER", cache_name).into();
3022 let inner_fn_ident = private_ident!(Span::dummy_with_cmt(), inner_fn_name);
3023
3024 let inner_fn_expr = FnExpr {
3025 ident: fn_ident.clone(),
3026 function: Box::new(Function {
3027 params,
3028 body,
3029 span: original_span,
3030 is_async: true,
3031 ..Default::default()
3032 }),
3033 };
3034
3035 hoisted_extra_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
3036 span: original_span,
3037 kind: VarDeclKind::Const,
3038 decls: vec![VarDeclarator {
3039 span: original_span,
3040 name: Pat::Ident(BindingIdent {
3041 id: inner_fn_ident.clone(),
3042 type_ann: None,
3043 }),
3044 init: Some(Box::new(Expr::Fn(inner_fn_expr))),
3045 definite: false,
3046 }],
3047 ..Default::default()
3048 })))));
3049
3050 if fn_ident.is_none() {
3053 hoisted_extra_items.push(ModuleItem::Stmt(assign_name_to_ident(&inner_fn_ident, "")));
3054 }
3055
3056 let wrapper_fn = Box::new(create_cache_wrapper(
3057 cache_kind,
3058 reference_id.clone(),
3059 bound_args_length,
3060 fn_ident.clone(),
3061 Expr::Ident(inner_fn_ident),
3062 original_span,
3063 ));
3064
3065 hoisted_extra_items.push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
3066 span: DUMMY_SP,
3067 decl: VarDecl {
3068 kind: VarDeclKind::Var,
3069 decls: vec![VarDeclarator {
3070 span: original_span,
3071 name: Pat::Ident(cache_ident.clone().into()),
3072 init: Some(wrapper_fn),
3073 definite: false,
3074 }],
3075 ..Default::default()
3076 }
3077 .into(),
3078 })));
3079
3080 hoisted_extra_items.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
3081 span: DUMMY_SP,
3082 expr: Box::new(annotate_ident_as_server_reference(
3083 cache_ident.clone(),
3084 reference_id,
3085 original_span,
3086 )),
3087 })));
3088
3089 cache_ident
3090}
3091
3092fn assign_name_to_ident(ident: &Ident, name: &str) -> Stmt {
3093 quote!(
3095 "Object[\"defineProperty\"]($action, \"name\", { value: $name });"
3103 as Stmt,
3104 action: Ident = ident.clone(),
3105 name: Expr = name.into(),
3106 )
3107}
3108
3109fn annotate_ident_as_server_reference(ident: Ident, action_id: Atom, original_span: Span) -> Expr {
3110 Expr::Call(CallExpr {
3112 span: original_span,
3113 callee: quote_ident!("registerServerReference").as_callee(),
3114 args: vec![
3115 ExprOrSpread {
3116 spread: None,
3117 expr: Box::new(Expr::Ident(ident)),
3118 },
3119 ExprOrSpread {
3120 spread: None,
3121 expr: Box::new(action_id.clone().into()),
3122 },
3123 ExprOrSpread {
3124 spread: None,
3125 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
3126 },
3127 ],
3128 ..Default::default()
3129 })
3130}
3131
3132fn bind_args_to_ident(ident: Ident, bound: Vec<Option<ExprOrSpread>>, action_id: Atom) -> Expr {
3133 Expr::Call(CallExpr {
3135 span: DUMMY_SP,
3136 callee: Expr::Member(MemberExpr {
3137 span: DUMMY_SP,
3138 obj: Box::new(ident.into()),
3139 prop: MemberProp::Ident(quote_ident!("bind")),
3140 })
3141 .as_callee(),
3142 args: vec![
3143 ExprOrSpread {
3144 spread: None,
3145 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
3146 },
3147 ExprOrSpread {
3148 spread: None,
3149 expr: Box::new(Expr::Call(CallExpr {
3150 span: DUMMY_SP,
3151 callee: quote_ident!("encryptActionBoundArgs").as_callee(),
3152 args: std::iter::once(ExprOrSpread {
3153 spread: None,
3154 expr: Box::new(action_id.into()),
3155 })
3156 .chain(bound.into_iter().flatten())
3157 .collect(),
3158 ..Default::default()
3159 })),
3160 },
3161 ],
3162 ..Default::default()
3163 })
3164}
3165
3166fn detect_similar_strings(a: &str, b: &str) -> bool {
3181 let mut a = a.chars().collect::<Vec<char>>();
3182 let mut b = b.chars().collect::<Vec<char>>();
3183
3184 if a.len() < b.len() {
3185 (a, b) = (b, a);
3186 }
3187
3188 if a.len() == b.len() {
3189 let mut diff = 0;
3191 for i in 0..a.len() {
3192 if a[i] != b[i] {
3193 diff += 1;
3194 if diff > 2 {
3195 return false;
3196 }
3197 }
3198 }
3199
3200 diff != 0
3202 } else {
3203 if a.len() - b.len() > 1 {
3204 return false;
3205 }
3206
3207 for i in 0..b.len() {
3209 if a[i] != b[i] {
3210 return a[i + 1..] == b[i..];
3216 }
3217 }
3218
3219 true
3221 }
3222}
3223
3224fn has_body_directive(maybe_body: &Option<BlockStmt>) -> (bool, bool) {
3229 let mut is_action_fn = false;
3230 let mut is_cache_fn = false;
3231
3232 if let Some(body) = maybe_body {
3233 for stmt in body.stmts.iter() {
3234 match stmt {
3235 Stmt::Expr(ExprStmt {
3236 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
3237 ..
3238 }) => {
3239 if value == "use server" {
3240 is_action_fn = true;
3241 break;
3242 } else if value == "use cache" || value.starts_with("use cache: ") {
3243 is_cache_fn = true;
3244 break;
3245 }
3246 }
3247 _ => break,
3248 }
3249 }
3250 }
3251
3252 (is_action_fn, is_cache_fn)
3253}
3254
3255fn collect_idents_in_array_pat(elems: &[Option<Pat>], idents: &mut Vec<Ident>) {
3256 for elem in elems.iter().flatten() {
3257 match elem {
3258 Pat::Ident(ident) => {
3259 idents.push(ident.id.clone());
3260 }
3261 Pat::Array(array) => {
3262 collect_idents_in_array_pat(&array.elems, idents);
3263 }
3264 Pat::Object(object) => {
3265 collect_idents_in_object_pat(&object.props, idents);
3266 }
3267 Pat::Rest(rest) => {
3268 if let Pat::Ident(ident) = &*rest.arg {
3269 idents.push(ident.id.clone());
3270 }
3271 }
3272 Pat::Assign(AssignPat { left, .. }) => {
3273 collect_idents_in_pat(left, idents);
3274 }
3275 Pat::Expr(..) | Pat::Invalid(..) => {}
3276 }
3277 }
3278}
3279
3280fn collect_idents_in_object_pat(props: &[ObjectPatProp], idents: &mut Vec<Ident>) {
3281 for prop in props {
3282 match prop {
3283 ObjectPatProp::KeyValue(KeyValuePatProp { value, .. }) => {
3284 match &**value {
3287 Pat::Ident(ident) => {
3288 idents.push(ident.id.clone());
3289 }
3290 Pat::Array(array) => {
3291 collect_idents_in_array_pat(&array.elems, idents);
3292 }
3293 Pat::Object(object) => {
3294 collect_idents_in_object_pat(&object.props, idents);
3295 }
3296 _ => {}
3297 }
3298 }
3299 ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
3300 idents.push(key.id.clone());
3302 }
3303 ObjectPatProp::Rest(RestPat { arg, .. }) => {
3304 if let Pat::Ident(ident) = &**arg {
3305 idents.push(ident.id.clone());
3306 }
3307 }
3308 }
3309 }
3310}
3311
3312fn collect_idents_in_var_decls(decls: &[VarDeclarator], idents: &mut Vec<Ident>) {
3313 for decl in decls {
3314 collect_idents_in_pat(&decl.name, idents);
3315 }
3316}
3317
3318fn collect_idents_in_pat(pat: &Pat, idents: &mut Vec<Ident>) {
3319 match pat {
3320 Pat::Ident(ident) => {
3321 idents.push(ident.id.clone());
3322 }
3323 Pat::Array(array) => {
3324 collect_idents_in_array_pat(&array.elems, idents);
3325 }
3326 Pat::Object(object) => {
3327 collect_idents_in_object_pat(&object.props, idents);
3328 }
3329 Pat::Assign(AssignPat { left, .. }) => {
3330 collect_idents_in_pat(left, idents);
3331 }
3332 Pat::Rest(RestPat { arg, .. }) => {
3333 if let Pat::Ident(ident) = &**arg {
3334 idents.push(ident.id.clone());
3335 }
3336 }
3337 Pat::Expr(..) | Pat::Invalid(..) => {}
3338 }
3339}
3340
3341fn collect_decl_idents_in_stmt(stmt: &Stmt, idents: &mut Vec<Ident>) {
3342 if let Stmt::Decl(decl) = stmt {
3343 match decl {
3344 Decl::Var(var) => {
3345 collect_idents_in_var_decls(&var.decls, idents);
3346 }
3347 Decl::Fn(fn_decl) => {
3348 idents.push(fn_decl.ident.clone());
3349 }
3350 _ => {}
3351 }
3352 }
3353}
3354
3355struct DirectiveVisitor<'a> {
3356 config: &'a Config,
3357 location: DirectiveLocation,
3358 directive: Option<Directive>,
3359 has_file_directive: bool,
3360 is_allowed_position: bool,
3361 use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
3362}
3363
3364impl DirectiveVisitor<'_> {
3365 fn visit_stmt(&mut self, stmt: &Stmt) -> bool {
3370 let in_fn_body = matches!(self.location, DirectiveLocation::FunctionBody);
3371 let allow_inline = self.config.is_react_server_layer || self.has_file_directive;
3372
3373 match stmt {
3374 Stmt::Expr(ExprStmt {
3375 expr: box Expr::Lit(Lit::Str(Str { value, span, .. })),
3376 ..
3377 }) => {
3378 if value == "use server" {
3379 if in_fn_body && !allow_inline {
3380 emit_error(ServerActionsErrorKind::InlineUseServerInClientComponent {
3381 span: *span,
3382 })
3383 } else if let Some(Directive::UseCache { .. }) = self.directive {
3384 emit_error(ServerActionsErrorKind::MultipleDirectives {
3385 span: *span,
3386 location: self.location.clone(),
3387 });
3388 } else if self.is_allowed_position {
3389 self.directive = Some(Directive::UseServer);
3390
3391 return true;
3392 } else {
3393 emit_error(ServerActionsErrorKind::MisplacedDirective {
3394 span: *span,
3395 directive: value.to_string_lossy().into_owned(),
3396 location: self.location.clone(),
3397 });
3398 }
3399 } else if detect_similar_strings(&value.to_string_lossy(), "use server") {
3400 emit_error(ServerActionsErrorKind::MisspelledDirective {
3402 span: *span,
3403 directive: value.to_string_lossy().into_owned(),
3404 expected_directive: "use server".to_string(),
3405 });
3406 } else if value == "use action" {
3407 emit_error(ServerActionsErrorKind::MisspelledDirective {
3408 span: *span,
3409 directive: value.to_string_lossy().into_owned(),
3410 expected_directive: "use server".to_string(),
3411 });
3412 } else
3413 if let Some(rest) = value.as_str().and_then(|s| s.strip_prefix("use cache"))
3415 {
3416 if in_fn_body && !allow_inline {
3419 emit_error(ServerActionsErrorKind::InlineUseCacheInClientComponent {
3420 span: *span,
3421 })
3422 } else if let Some(Directive::UseServer) = self.directive {
3423 emit_error(ServerActionsErrorKind::MultipleDirectives {
3424 span: *span,
3425 location: self.location.clone(),
3426 });
3427 } else if self.is_allowed_position {
3428 if !self.config.use_cache_enabled {
3429 emit_error(ServerActionsErrorKind::UseCacheWithoutCacheComponents {
3430 span: *span,
3431 directive: value.to_string_lossy().into_owned(),
3432 });
3433 }
3434
3435 if rest.is_empty() {
3436 self.directive = Some(Directive::UseCache {
3437 cache_kind: rcstr!("default"),
3438 });
3439
3440 self.increment_cache_usage_counter("default");
3441
3442 return true;
3443 }
3444
3445 if rest.starts_with(": ") {
3446 let cache_kind = RcStr::from(rest.split_at(": ".len()).1.to_string());
3447
3448 if !cache_kind.is_empty() {
3449 if !self.config.cache_kinds.contains(&cache_kind) {
3450 emit_error(ServerActionsErrorKind::UnknownCacheKind {
3451 span: *span,
3452 cache_kind: cache_kind.clone(),
3453 });
3454 }
3455
3456 self.increment_cache_usage_counter(&cache_kind);
3457 self.directive = Some(Directive::UseCache { cache_kind });
3458
3459 return true;
3460 }
3461 }
3462
3463 let expected_directive = if let Some(colon_pos) = rest.find(':') {
3466 let kind = rest[colon_pos + 1..].trim();
3467
3468 if kind.is_empty() {
3469 "use cache: <cache-kind>".to_string()
3470 } else {
3471 format!("use cache: {kind}")
3472 }
3473 } else {
3474 let kind = rest.trim();
3475
3476 if kind.is_empty() {
3477 "use cache".to_string()
3478 } else {
3479 format!("use cache: {kind}")
3480 }
3481 };
3482
3483 emit_error(ServerActionsErrorKind::MisspelledDirective {
3484 span: *span,
3485 directive: value.to_string_lossy().into_owned(),
3486 expected_directive,
3487 });
3488
3489 return true;
3490 } else {
3491 emit_error(ServerActionsErrorKind::MisplacedDirective {
3492 span: *span,
3493 directive: value.to_string_lossy().into_owned(),
3494 location: self.location.clone(),
3495 });
3496 }
3497 } else {
3498 if detect_similar_strings(&value.to_string_lossy(), "use cache") {
3500 emit_error(ServerActionsErrorKind::MisspelledDirective {
3501 span: *span,
3502 directive: value.to_string_lossy().into_owned(),
3503 expected_directive: "use cache".to_string(),
3504 });
3505 }
3506 }
3507 }
3508 Stmt::Expr(ExprStmt {
3509 expr:
3510 box Expr::Paren(ParenExpr {
3511 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
3512 ..
3513 }),
3514 span,
3515 ..
3516 }) => {
3517 if value == "use server"
3519 || detect_similar_strings(&value.to_string_lossy(), "use server")
3520 {
3521 if self.is_allowed_position {
3522 emit_error(ServerActionsErrorKind::WrappedDirective {
3523 span: *span,
3524 directive: "use server".to_string(),
3525 });
3526 } else {
3527 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
3528 span: *span,
3529 directive: "use server".to_string(),
3530 location: self.location.clone(),
3531 });
3532 }
3533 } else if value == "use cache"
3534 || detect_similar_strings(&value.to_string_lossy(), "use cache")
3535 {
3536 if self.is_allowed_position {
3537 emit_error(ServerActionsErrorKind::WrappedDirective {
3538 span: *span,
3539 directive: "use cache".to_string(),
3540 });
3541 } else {
3542 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
3543 span: *span,
3544 directive: "use cache".to_string(),
3545 location: self.location.clone(),
3546 });
3547 }
3548 }
3549 }
3550 _ => {
3551 self.is_allowed_position = false;
3553 }
3554 };
3555
3556 false
3557 }
3558
3559 fn increment_cache_usage_counter(&mut self, cache_kind: &str) {
3561 let mut tracker_map = RefCell::borrow_mut(&self.use_cache_telemetry_tracker);
3562 let entry = tracker_map.entry(cache_kind.to_string());
3563 match entry {
3564 hash_map::Entry::Occupied(mut occupied) => {
3565 *occupied.get_mut() += 1;
3566 }
3567 hash_map::Entry::Vacant(vacant) => {
3568 vacant.insert(1);
3569 }
3570 }
3571 }
3572}
3573
3574pub(crate) struct ClosureReplacer<'a> {
3575 used_ids: &'a [Name],
3576 private_ctxt: SyntaxContext,
3577}
3578
3579impl ClosureReplacer<'_> {
3580 fn index(&self, e: &Expr) -> Option<usize> {
3581 let name = Name::try_from(e).ok()?;
3582 self.used_ids.iter().position(|used_id| *used_id == name)
3583 }
3584}
3585
3586impl VisitMut for ClosureReplacer<'_> {
3587 fn visit_mut_expr(&mut self, e: &mut Expr) {
3588 e.visit_mut_children_with(self);
3589
3590 if let Some(index) = self.index(e) {
3591 *e = Expr::Ident(Ident::new(
3592 format!("$$ACTION_ARG_{index}").into(),
3594 DUMMY_SP,
3595 self.private_ctxt,
3596 ));
3597 }
3598 }
3599
3600 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
3601 n.visit_mut_children_with(self);
3602
3603 if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
3604 let name = Name::from(&*i);
3605 if let Some(index) = self.used_ids.iter().position(|used_id| *used_id == name) {
3606 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
3607 key: PropName::Ident(i.clone().into()),
3608 value: Box::new(Expr::Ident(Ident::new(
3609 format!("$$ACTION_ARG_{index}").into(),
3611 DUMMY_SP,
3612 self.private_ctxt,
3613 ))),
3614 })));
3615 }
3616 }
3617 }
3618
3619 noop_visit_mut_type!();
3620}
3621
3622#[derive(Debug, Clone, PartialEq, Eq)]
3623struct NamePart {
3624 prop: Atom,
3625 is_member: bool,
3626 optional: bool,
3627}
3628
3629#[derive(Debug, Clone, PartialEq, Eq)]
3630struct Name(Id, Vec<NamePart>);
3631
3632impl From<&'_ Ident> for Name {
3633 fn from(value: &Ident) -> Self {
3634 Name(value.to_id(), vec![])
3635 }
3636}
3637
3638impl TryFrom<&'_ Expr> for Name {
3639 type Error = ();
3640
3641 fn try_from(value: &Expr) -> Result<Self, Self::Error> {
3642 match value {
3643 Expr::Ident(i) => Ok(Name(i.to_id(), vec![])),
3644 Expr::Member(e) => e.try_into(),
3645 Expr::OptChain(e) => e.try_into(),
3646 _ => Err(()),
3647 }
3648 }
3649}
3650
3651impl TryFrom<&'_ MemberExpr> for Name {
3652 type Error = ();
3653
3654 fn try_from(value: &MemberExpr) -> Result<Self, Self::Error> {
3655 match &value.prop {
3656 MemberProp::Ident(prop) => {
3657 let mut obj: Name = value.obj.as_ref().try_into()?;
3658 obj.1.push(NamePart {
3659 prop: prop.sym.clone(),
3660 is_member: true,
3661 optional: false,
3662 });
3663 Ok(obj)
3664 }
3665 _ => Err(()),
3666 }
3667 }
3668}
3669
3670impl TryFrom<&'_ OptChainExpr> for Name {
3671 type Error = ();
3672
3673 fn try_from(value: &OptChainExpr) -> Result<Self, Self::Error> {
3674 match &*value.base {
3675 OptChainBase::Member(m) => match &m.prop {
3676 MemberProp::Ident(prop) => {
3677 let mut obj: Name = m.obj.as_ref().try_into()?;
3678 obj.1.push(NamePart {
3679 prop: prop.sym.clone(),
3680 is_member: false,
3681 optional: value.optional,
3682 });
3683 Ok(obj)
3684 }
3685 _ => Err(()),
3686 },
3687 OptChainBase::Call(_) => Err(()),
3688 }
3689 }
3690}
3691
3692impl From<Name> for Box<Expr> {
3693 fn from(value: Name) -> Self {
3694 let mut expr = Box::new(Expr::Ident(value.0.into()));
3695
3696 for NamePart {
3697 prop,
3698 is_member,
3699 optional,
3700 } in value.1.into_iter()
3701 {
3702 #[allow(clippy::replace_box)]
3703 if is_member {
3704 expr = Box::new(Expr::Member(MemberExpr {
3705 span: DUMMY_SP,
3706 obj: expr,
3707 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
3708 }));
3709 } else {
3710 expr = Box::new(Expr::OptChain(OptChainExpr {
3711 span: DUMMY_SP,
3712 base: Box::new(OptChainBase::Member(MemberExpr {
3713 span: DUMMY_SP,
3714 obj: expr,
3715 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
3716 })),
3717 optional,
3718 }));
3719 }
3720 }
3721
3722 expr
3723 }
3724}
3725
3726fn emit_error(error_kind: ServerActionsErrorKind) {
3727 let (span, msg) = match error_kind {
3728 ServerActionsErrorKind::ExportedSyncFunction {
3729 span,
3730 in_action_file,
3731 } => (
3732 span,
3733 formatdoc! {
3734 r#"
3735 Only async functions are allowed to be exported in a {directive} file.
3736 "#,
3737 directive = if in_action_file {
3738 "\"use server\""
3739 } else {
3740 "\"use cache\""
3741 }
3742 },
3743 ),
3744 ServerActionsErrorKind::ForbiddenExpression {
3745 span,
3746 expr,
3747 directive,
3748 } => (
3749 span,
3750 formatdoc! {
3751 r#"
3752 {subject} cannot use `{expr}`.
3753 "#,
3754 subject = if let Directive::UseServer = directive {
3755 "Server Actions"
3756 } else {
3757 "\"use cache\" functions"
3758 }
3759 },
3760 ),
3761 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span } => (
3762 span,
3763 formatdoc! {
3764 r#"
3765 It is not allowed to define inline "use cache" annotated class instance methods.
3766 To define cached functions, use functions, object method properties, or static class methods instead.
3767 "#
3768 },
3769 ),
3770 ServerActionsErrorKind::InlineUseCacheInClientComponent { span } => (
3771 span,
3772 formatdoc! {
3773 r#"
3774 It is not allowed to define inline "use cache" annotated functions in Client Components.
3775 To use "use cache" functions in a Client Component, you can either export them from a separate file with "use cache" or "use server" at the top, or pass them down through props from a Server Component.
3776 "#
3777 },
3778 ),
3779 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span } => (
3780 span,
3781 formatdoc! {
3782 r#"
3783 It is not allowed to define inline "use server" annotated class instance methods.
3784 To define Server Actions, use functions, object method properties, or static class methods instead.
3785 "#
3786 },
3787 ),
3788 ServerActionsErrorKind::InlineUseServerInClientComponent { span } => (
3789 span,
3790 formatdoc! {
3791 r#"
3792 It is not allowed to define inline "use server" annotated Server Actions in Client Components.
3793 To use Server Actions in a Client Component, you can either export them from a separate file with "use server" at the top, or pass them down through props from a Server Component.
3794
3795 Read more: https://nextjs.org/docs/app/api-reference/directives/use-server#using-server-functions-in-a-client-component
3796 "#
3797 },
3798 ),
3799 ServerActionsErrorKind::InlineSyncFunction { span, directive } => (
3800 span,
3801 formatdoc! {
3802 r#"
3803 {subject} must be async functions.
3804 "#,
3805 subject = if let Directive::UseServer = directive {
3806 "Server Actions"
3807 } else {
3808 "\"use cache\" functions"
3809 }
3810 },
3811 ),
3812 ServerActionsErrorKind::MisplacedDirective {
3813 span,
3814 directive,
3815 location,
3816 } => (
3817 span,
3818 formatdoc! {
3819 r#"
3820 The "{directive}" directive must be at the top of the {location}.
3821 "#,
3822 location = match location {
3823 DirectiveLocation::Module => "file",
3824 DirectiveLocation::FunctionBody => "function body",
3825 }
3826 },
3827 ),
3828 ServerActionsErrorKind::MisplacedWrappedDirective {
3829 span,
3830 directive,
3831 location,
3832 } => (
3833 span,
3834 formatdoc! {
3835 r#"
3836 The "{directive}" directive must be at the top of the {location}, and cannot be wrapped in parentheses.
3837 "#,
3838 location = match location {
3839 DirectiveLocation::Module => "file",
3840 DirectiveLocation::FunctionBody => "function body",
3841 }
3842 },
3843 ),
3844 ServerActionsErrorKind::MisspelledDirective {
3845 span,
3846 directive,
3847 expected_directive,
3848 } => (
3849 span,
3850 formatdoc! {
3851 r#"
3852 Did you mean "{expected_directive}"? "{directive}" is not a supported directive name."
3853 "#
3854 },
3855 ),
3856 ServerActionsErrorKind::MultipleDirectives { span, location } => (
3857 span,
3858 formatdoc! {
3859 r#"
3860 Conflicting directives "use server" and "use cache" found in the same {location}. You cannot place both directives at the top of a {location}. Please remove one of them.
3861 "#,
3862 location = match location {
3863 DirectiveLocation::Module => "file",
3864 DirectiveLocation::FunctionBody => "function body",
3865 }
3866 },
3867 ),
3868 ServerActionsErrorKind::UnknownCacheKind { span, cache_kind } => (
3869 span,
3870 formatdoc! {
3871 r#"
3872 Unknown cache kind "{cache_kind}". Please configure a cache handler for this kind in the `cacheHandlers` object in your Next.js config.
3873 "#
3874 },
3875 ),
3876 ServerActionsErrorKind::UseCacheWithoutCacheComponents { span, directive } => (
3877 span,
3878 formatdoc! {
3879 r#"
3880 To use "{directive}", please enable the feature flag `cacheComponents` in your Next.js config.
3881
3882 Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage
3883 "#
3884 },
3885 ),
3886 ServerActionsErrorKind::WrappedDirective { span, directive } => (
3887 span,
3888 formatdoc! {
3889 r#"
3890 The "{directive}" directive cannot be wrapped in parentheses.
3891 "#
3892 },
3893 ),
3894 };
3895
3896 HANDLER.with(|handler| handler.struct_span_err(span, &msg).emit());
3897}
3898
3899fn strip_export_name_span(export_name: &ModuleExportName) -> ModuleExportName {
3902 match export_name {
3903 ModuleExportName::Ident(i) => {
3904 ModuleExportName::Ident(Ident::new(i.sym.clone(), DUMMY_SP, i.ctxt))
3905 }
3906 ModuleExportName::Str(s) => ModuleExportName::Str(Str {
3907 span: DUMMY_SP,
3908 value: s.value.clone(),
3909 raw: None,
3910 }),
3911 }
3912}
3913
3914fn program_to_data_url(
3915 file_name: &str,
3916 cm: &Arc<SourceMap>,
3917 body: Vec<ModuleItem>,
3918 prepend_comment: Comment,
3919) -> String {
3920 let module_span = Span::dummy_with_cmt();
3921 let comments = SingleThreadedComments::default();
3922 comments.add_leading(module_span.lo, prepend_comment);
3923
3924 let program = &Program::Module(Module {
3925 span: module_span,
3926 body,
3927 shebang: None,
3928 });
3929
3930 let mut output = vec![];
3931 let mut mappings = vec![];
3932 let mut emitter = Emitter {
3933 cfg: codegen::Config::default().with_minify(true),
3934 cm: cm.clone(),
3935 wr: Box::new(JsWriter::new(
3936 cm.clone(),
3937 " ",
3938 &mut output,
3939 Some(&mut mappings),
3940 )),
3941 comments: Some(&comments),
3942 };
3943
3944 emitter.emit_program(program).unwrap();
3945 drop(emitter);
3946
3947 pub struct InlineSourcesContentConfig<'a> {
3948 folder_path: Option<&'a Path>,
3949 }
3950 impl SourceMapGenConfig for InlineSourcesContentConfig<'_> {
3953 fn file_name_to_source(&self, file: &FileName) -> String {
3954 let FileName::Custom(file) = file else {
3955 return file.to_string();
3957 };
3958 let Some(folder_path) = &self.folder_path else {
3959 return file.to_string();
3960 };
3961
3962 if let Some(rel_path) = diff_paths(file, folder_path) {
3963 format!("./{}", rel_path.display())
3964 } else {
3965 file.to_string()
3966 }
3967 }
3968
3969 fn inline_sources_content(&self, _f: &FileName) -> bool {
3970 true
3971 }
3972 }
3973
3974 let map = cm.build_source_map(
3975 &mappings,
3976 None,
3977 InlineSourcesContentConfig {
3978 folder_path: PathBuf::from(format!("[project]/{file_name}")).parent(),
3979 },
3980 );
3981 let map = {
3982 if map.get_token_count() > 0 {
3983 let mut buf = vec![];
3984 map.to_writer(&mut buf)
3985 .expect("failed to generate sourcemap");
3986 Some(buf)
3987 } else {
3988 None
3989 }
3990 };
3991
3992 let mut output = String::from_utf8(output).expect("codegen generated non-utf8 output");
3993 if let Some(map) = map {
3994 output.extend(
3995 format!(
3996 "\n//# sourceMappingURL=data:application/json;base64,{}",
3997 Base64Display::new(&map, &BASE64_STANDARD)
3998 )
3999 .chars(),
4000 );
4001 }
4002 format!("data:text/javascript,{}", urlencoding::encode(&output))
4003}