1use std::{
2 collections::{BTreeMap, HashSet},
3 convert::{TryFrom, TryInto},
4 mem::{replace, take},
5};
6
7use hex::encode as hex_encode;
8use indoc::formatdoc;
9use rustc_hash::FxHashSet;
10use serde::Deserialize;
11use sha1::{Digest, Sha1};
12use swc_core::{
13 common::{
14 comments::{Comment, CommentKind, Comments},
15 errors::HANDLER,
16 util::take::Take,
17 BytePos, FileName, Mark, Span, SyntaxContext, DUMMY_SP,
18 },
19 ecma::{
20 ast::*,
21 atoms::JsWord,
22 utils::{private_ident, quote_ident, ExprFactory},
23 visit::{noop_visit_mut_type, visit_mut_pass, VisitMut, VisitMutWith},
24 },
25};
26use turbo_rcstr::RcStr;
27
28#[derive(Clone, Debug, Deserialize)]
29#[serde(deny_unknown_fields, rename_all = "camelCase")]
30pub struct Config {
31 pub is_react_server_layer: bool,
32 pub dynamic_io_enabled: bool,
33 pub hash_salt: String,
34 pub cache_kinds: FxHashSet<RcStr>,
35}
36
37#[derive(Clone, Debug)]
38enum Directive {
39 UseServer,
40 UseCache { cache_kind: RcStr },
41}
42
43#[derive(Clone, Debug)]
44enum DirectiveLocation {
45 Module,
46 FunctionBody,
47}
48
49#[derive(Clone, Debug)]
50enum ThisStatus {
51 Allowed,
52 Forbidden { directive: Directive },
53}
54
55#[derive(Clone, Debug)]
56enum ServerActionsErrorKind {
57 ExportedSyncFunction {
58 span: Span,
59 in_action_file: bool,
60 },
61 ForbiddenExpression {
62 span: Span,
63 expr: String,
64 directive: Directive,
65 },
66 InlineSyncFunction {
67 span: Span,
68 directive: Directive,
69 },
70 InlineUseCacheInClassInstanceMethod {
71 span: Span,
72 },
73 InlineUseCacheInClientComponent {
74 span: Span,
75 },
76 InlineUseServerInClassInstanceMethod {
77 span: Span,
78 },
79 InlineUseServerInClientComponent {
80 span: Span,
81 },
82 MisplacedDirective {
83 span: Span,
84 directive: String,
85 location: DirectiveLocation,
86 },
87 MisplacedWrappedDirective {
88 span: Span,
89 directive: String,
90 location: DirectiveLocation,
91 },
92 MisspelledDirective {
93 span: Span,
94 directive: String,
95 expected_directive: String,
96 },
97 MultipleDirectives {
98 span: Span,
99 location: DirectiveLocation,
100 },
101 UnknownCacheKind {
102 span: Span,
103 cache_kind: RcStr,
104 },
105 UseCacheWithoutDynamicIO {
106 span: Span,
107 directive: String,
108 },
109 WrappedDirective {
110 span: Span,
111 directive: String,
112 },
113}
114
115pub type ActionsMap = BTreeMap<String, String>;
118
119#[tracing::instrument(level = tracing::Level::TRACE, skip_all)]
120pub fn server_actions<C: Comments>(file_name: &FileName, config: Config, comments: C) -> impl Pass {
121 visit_mut_pass(ServerActions {
122 config,
123 comments,
124 file_name: file_name.to_string(),
125 start_pos: BytePos(0),
126 file_directive: None,
127 in_exported_expr: false,
128 in_default_export_decl: false,
129 fn_decl_ident: None,
130 in_callee: false,
131 has_action: false,
132 has_cache: false,
133 this_status: ThisStatus::Allowed,
134
135 reference_index: 0,
136 in_module_level: true,
137 should_track_names: false,
138
139 names: Default::default(),
140 declared_idents: Default::default(),
141
142 exported_idents: Default::default(),
143
144 rewrite_fn_decl_to_proxy_decl: None,
146 rewrite_default_fn_expr_to_proxy_expr: None,
147 rewrite_expr_to_proxy_expr: None,
148
149 annotations: Default::default(),
150 extra_items: Default::default(),
151 hoisted_extra_items: Default::default(),
152 export_actions: Default::default(),
153
154 private_ctxt: SyntaxContext::empty().apply_mark(Mark::new()),
155
156 arrow_or_fn_expr_ident: None,
157 exported_local_ids: HashSet::new(),
158 })
159}
160
161fn generate_server_actions_comment(actions: ActionsMap) -> String {
164 format!(
165 " __next_internal_action_entry_do_not_use__ {} ",
166 serde_json::to_string(&actions).unwrap()
167 )
168}
169
170struct ServerActions<C: Comments> {
171 #[allow(unused)]
172 config: Config,
173 file_name: String,
174 comments: C,
175
176 start_pos: BytePos,
177 file_directive: Option<Directive>,
178 in_exported_expr: bool,
179 in_default_export_decl: bool,
180 fn_decl_ident: Option<Ident>,
181 in_callee: bool,
182 has_action: bool,
183 has_cache: bool,
184 this_status: ThisStatus,
185
186 reference_index: u32,
187 in_module_level: bool,
188 should_track_names: bool,
189
190 names: Vec<Name>,
191 declared_idents: Vec<Ident>,
192
193 rewrite_fn_decl_to_proxy_decl: Option<VarDecl>,
195 rewrite_default_fn_expr_to_proxy_expr: Option<Box<Expr>>,
196 rewrite_expr_to_proxy_expr: Option<Box<Expr>>,
197
198 exported_idents: Vec<(
199 Ident,
200 String,
201 String,
202 )>,
203
204 annotations: Vec<Stmt>,
205 extra_items: Vec<ModuleItem>,
206 hoisted_extra_items: Vec<ModuleItem>,
207 export_actions: Vec<(String, String)>,
208
209 private_ctxt: SyntaxContext,
210
211 arrow_or_fn_expr_ident: Option<Ident>,
212 exported_local_ids: HashSet<Id>,
213}
214
215impl<C: Comments> ServerActions<C> {
216 fn generate_server_reference_id(
217 &self,
218 export_name: &str,
219 is_cache: bool,
220 params: Option<&Vec<Param>>,
221 ) -> String {
222 let mut hasher = Sha1::new();
227 hasher.update(self.config.hash_salt.as_bytes());
228 hasher.update(self.file_name.as_bytes());
229 hasher.update(b":");
230 hasher.update(export_name.as_bytes());
231 let mut result = hasher.finalize().to_vec();
232
233 let type_bit = if is_cache { 1u8 } else { 0u8 };
261 let mut arg_mask = 0u8;
262 let mut rest_args = 0u8;
263
264 if let Some(params) = params {
265 for (i, param) in params.iter().enumerate() {
270 if let Pat::Rest(_) = param.pat {
271 arg_mask = 0b111111;
274 rest_args = 0b1;
275 break;
276 }
277 if i < 6 {
278 arg_mask |= 0b1 << (5 - i);
279 } else {
280 rest_args = 0b1;
283 break;
284 }
285 }
286 } else {
287 arg_mask = 0b111111;
290 rest_args = 0b1;
291 }
292
293 result.push((type_bit << 7) | (arg_mask << 1) | rest_args);
294 result.rotate_right(1);
295
296 hex_encode(result)
297 }
298
299 fn gen_action_ident(&mut self) -> JsWord {
300 let id: JsWord = format!("$$RSC_SERVER_ACTION_{0}", self.reference_index).into();
301 self.reference_index += 1;
302 id
303 }
304
305 fn gen_cache_ident(&mut self) -> JsWord {
306 let id: JsWord = format!("$$RSC_SERVER_CACHE_{0}", self.reference_index).into();
307 self.reference_index += 1;
308 id
309 }
310
311 fn gen_ref_ident(&mut self) -> JsWord {
312 let id: JsWord = format!("$$RSC_SERVER_REF_{0}", self.reference_index).into();
313 self.reference_index += 1;
314 id
315 }
316
317 fn create_bound_action_args_array_pat(&mut self, arg_len: usize) -> Pat {
318 Pat::Array(ArrayPat {
319 span: DUMMY_SP,
320 elems: (0..arg_len)
321 .map(|i| {
322 Some(Pat::Ident(
323 Ident::new(
324 format!("$$ACTION_ARG_{i}").into(),
325 DUMMY_SP,
326 self.private_ctxt,
327 )
328 .into(),
329 ))
330 })
331 .collect(),
332 optional: false,
333 type_ann: None,
334 })
335 }
336
337 fn get_directive_for_function(
340 &mut self,
341 maybe_body: Option<&mut BlockStmt>,
342 ) -> Option<Directive> {
343 let mut directive: Option<Directive> = None;
344
345 if let Some(body) = maybe_body {
348 let directive_visitor = &mut DirectiveVisitor {
349 config: &self.config,
350 directive: None,
351 has_file_directive: self.file_directive.is_some(),
352 is_allowed_position: true,
353 location: DirectiveLocation::FunctionBody,
354 };
355
356 body.stmts.retain(|stmt| {
357 let has_directive = directive_visitor.visit_stmt(stmt);
358
359 !has_directive
360 });
361
362 directive = directive_visitor.directive.clone();
363 }
364
365 if self.in_exported_expr && directive.is_none() && self.file_directive.is_some() {
367 return self.file_directive.clone();
368 }
369
370 directive
371 }
372
373 fn get_directive_for_module(&mut self, stmts: &mut Vec<ModuleItem>) -> Option<Directive> {
374 let directive_visitor = &mut DirectiveVisitor {
375 config: &self.config,
376 directive: None,
377 has_file_directive: false,
378 is_allowed_position: true,
379 location: DirectiveLocation::Module,
380 };
381
382 stmts.retain(|item| {
383 if let ModuleItem::Stmt(stmt) = item {
384 let has_directive = directive_visitor.visit_stmt(stmt);
385
386 !has_directive
387 } else {
388 directive_visitor.is_allowed_position = false;
389 true
390 }
391 });
392
393 directive_visitor.directive.clone()
394 }
395
396 fn maybe_hoist_and_create_proxy_for_server_action_arrow_expr(
397 &mut self,
398 ids_from_closure: Vec<Name>,
399 arrow: &mut ArrowExpr,
400 ) -> Box<Expr> {
401 let mut new_params: Vec<Param> = vec![];
402
403 if !ids_from_closure.is_empty() {
404 new_params.push(Param {
406 span: DUMMY_SP,
407 decorators: vec![],
408 pat: Pat::Ident(IdentName::new("$$ACTION_CLOSURE_BOUND".into(), DUMMY_SP).into()),
409 });
410 }
411
412 for p in arrow.params.iter() {
413 new_params.push(Param::from(p.clone()));
414 }
415
416 let action_name = self.gen_action_ident().to_string();
417 let action_ident = Ident::new(action_name.clone().into(), arrow.span, self.private_ctxt);
418 let action_id = self.generate_server_reference_id(&action_name, false, Some(&new_params));
419
420 self.has_action = true;
421 self.export_actions
422 .push((action_name.to_string(), action_id.clone()));
423
424 let register_action_expr = bind_args_to_ref_expr(
425 annotate_ident_as_server_reference(
426 action_ident.clone(),
427 action_id.clone(),
428 arrow.span,
429 &self.comments,
430 ),
431 ids_from_closure
432 .iter()
433 .cloned()
434 .map(|id| Some(id.as_arg()))
435 .collect(),
436 action_id.clone(),
437 );
438
439 if let BlockStmtOrExpr::BlockStmt(block) = &mut *arrow.body {
440 block.visit_mut_with(&mut ClosureReplacer {
441 used_ids: &ids_from_closure,
442 private_ctxt: self.private_ctxt,
443 });
444 }
445
446 let mut new_body: BlockStmtOrExpr = *arrow.body.clone();
447
448 if !ids_from_closure.is_empty() {
449 let decryption_decl = VarDecl {
453 span: DUMMY_SP,
454 kind: VarDeclKind::Var,
455 declare: false,
456 decls: vec![VarDeclarator {
457 span: DUMMY_SP,
458 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
459 init: Some(Box::new(Expr::Await(AwaitExpr {
460 span: DUMMY_SP,
461 arg: Box::new(Expr::Call(CallExpr {
462 span: DUMMY_SP,
463 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
464 args: vec![
465 action_id.as_arg(),
466 quote_ident!("$$ACTION_CLOSURE_BOUND").as_arg(),
467 ],
468 ..Default::default()
469 })),
470 }))),
471 definite: Default::default(),
472 }],
473 ..Default::default()
474 };
475
476 match &mut new_body {
477 BlockStmtOrExpr::BlockStmt(body) => {
478 body.stmts.insert(0, decryption_decl.into());
479 }
480 BlockStmtOrExpr::Expr(body_expr) => {
481 new_body = BlockStmtOrExpr::BlockStmt(BlockStmt {
482 span: DUMMY_SP,
483 stmts: vec![
484 decryption_decl.into(),
485 Stmt::Return(ReturnStmt {
486 span: DUMMY_SP,
487 arg: Some(body_expr.take()),
488 }),
489 ],
490 ..Default::default()
491 });
492 }
493 }
494 }
495
496 self.hoisted_extra_items
499 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
500 span: DUMMY_SP,
501 decl: VarDecl {
502 kind: VarDeclKind::Const,
503 span: DUMMY_SP,
504 decls: vec![VarDeclarator {
505 span: DUMMY_SP,
506 name: Pat::Ident(action_ident.clone().into()),
507 definite: false,
508 init: Some(Box::new(Expr::Fn(FnExpr {
509 ident: self.arrow_or_fn_expr_ident.clone(),
510 function: Box::new(Function {
511 params: new_params,
512 body: match new_body {
513 BlockStmtOrExpr::BlockStmt(body) => Some(body),
514 BlockStmtOrExpr::Expr(expr) => Some(BlockStmt {
515 span: DUMMY_SP,
516 stmts: vec![Stmt::Return(ReturnStmt {
517 span: DUMMY_SP,
518 arg: Some(expr),
519 })],
520 ..Default::default()
521 }),
522 },
523 decorators: vec![],
524 span: DUMMY_SP,
525 is_generator: false,
526 is_async: true,
527 ..Default::default()
528 }),
529 }))),
530 }],
531 declare: Default::default(),
532 ctxt: self.private_ctxt,
533 }
534 .into(),
535 })));
536
537 Box::new(register_action_expr.clone())
538 }
539
540 fn maybe_hoist_and_create_proxy_for_server_action_function(
541 &mut self,
542 ids_from_closure: Vec<Name>,
543 function: &mut Function,
544 fn_name: Option<Ident>,
545 ) -> Box<Expr> {
546 let mut new_params: Vec<Param> = vec![];
547
548 if !ids_from_closure.is_empty() {
549 new_params.push(Param {
551 span: DUMMY_SP,
552 decorators: vec![],
553 pat: Pat::Ident(IdentName::new("$$ACTION_CLOSURE_BOUND".into(), DUMMY_SP).into()),
554 });
555 }
556
557 new_params.append(&mut function.params);
558
559 let action_name: JsWord = self.gen_action_ident();
560 let action_ident = Ident::new(action_name.clone(), function.span, self.private_ctxt);
561 let action_id = self.generate_server_reference_id(&action_name, false, Some(&new_params));
562
563 self.has_action = true;
564 self.export_actions
565 .push((action_name.to_string(), action_id.clone()));
566
567 let register_action_expr = bind_args_to_ref_expr(
568 annotate_ident_as_server_reference(
569 action_ident.clone(),
570 action_id.clone(),
571 function.span,
572 &self.comments,
573 ),
574 ids_from_closure
575 .iter()
576 .cloned()
577 .map(|id| Some(id.as_arg()))
578 .collect(),
579 action_id.clone(),
580 );
581
582 function.body.visit_mut_with(&mut ClosureReplacer {
583 used_ids: &ids_from_closure,
584 private_ctxt: self.private_ctxt,
585 });
586
587 let mut new_body: Option<BlockStmt> = function.body.clone();
588
589 if !ids_from_closure.is_empty() {
590 let decryption_decl = VarDecl {
594 span: DUMMY_SP,
595 kind: VarDeclKind::Var,
596 decls: vec![VarDeclarator {
597 span: DUMMY_SP,
598 name: self.create_bound_action_args_array_pat(ids_from_closure.len()),
599 init: Some(Box::new(Expr::Await(AwaitExpr {
600 span: DUMMY_SP,
601 arg: Box::new(Expr::Call(CallExpr {
602 span: DUMMY_SP,
603 callee: quote_ident!("decryptActionBoundArgs").as_callee(),
604 args: vec![
605 action_id.as_arg(),
606 quote_ident!("$$ACTION_CLOSURE_BOUND").as_arg(),
607 ],
608 ..Default::default()
609 })),
610 }))),
611 definite: Default::default(),
612 }],
613 ..Default::default()
614 };
615
616 if let Some(body) = &mut new_body {
617 body.stmts.insert(0, decryption_decl.into());
618 } else {
619 new_body = Some(BlockStmt {
620 span: DUMMY_SP,
621 stmts: vec![decryption_decl.into()],
622 ..Default::default()
623 });
624 }
625 }
626
627 self.hoisted_extra_items
630 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
631 span: DUMMY_SP,
632 decl: VarDecl {
633 kind: VarDeclKind::Const,
634 span: DUMMY_SP,
635 decls: vec![VarDeclarator {
636 span: DUMMY_SP, name: Pat::Ident(action_ident.clone().into()),
638 definite: false,
639 init: Some(Box::new(Expr::Fn(FnExpr {
640 ident: fn_name,
641 function: Box::new(Function {
642 params: new_params,
643 body: new_body,
644 ..function.take()
645 }),
646 }))),
647 }],
648 declare: Default::default(),
649 ctxt: self.private_ctxt,
650 }
651 .into(),
652 })));
653
654 Box::new(register_action_expr)
655 }
656
657 fn maybe_hoist_and_create_proxy_for_cache_arrow_expr(
658 &mut self,
659 ids_from_closure: Vec<Name>,
660 cache_kind: RcStr,
661 arrow: &mut ArrowExpr,
662 ) -> Box<Expr> {
663 let mut new_params: Vec<Param> = vec![];
664
665 if !ids_from_closure.is_empty() {
669 new_params.push(Param {
670 span: DUMMY_SP,
671 decorators: vec![],
672 pat: self.create_bound_action_args_array_pat(ids_from_closure.len()),
673 });
674 }
675
676 for p in arrow.params.iter() {
677 new_params.push(Param::from(p.clone()));
678 }
679
680 let cache_name: JsWord = self.gen_cache_ident();
681 let cache_ident = private_ident!(cache_name.clone());
682 let export_name: JsWord = cache_name;
683
684 let reference_id = self.generate_server_reference_id(&export_name, true, Some(&new_params));
685
686 self.has_cache = true;
687 self.export_actions
688 .push((export_name.to_string(), reference_id.clone()));
689
690 if let BlockStmtOrExpr::BlockStmt(block) = &mut *arrow.body {
691 block.visit_mut_with(&mut ClosureReplacer {
692 used_ids: &ids_from_closure,
693 private_ctxt: self.private_ctxt,
694 });
695 }
696
697 self.hoisted_extra_items
700 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
701 span: DUMMY_SP,
702 decl: VarDecl {
703 span: DUMMY_SP,
704 kind: VarDeclKind::Var,
705 decls: vec![VarDeclarator {
706 span: DUMMY_SP,
707 name: Pat::Ident(cache_ident.clone().into()),
708 init: Some(wrap_cache_expr(
709 Box::new(Expr::Fn(FnExpr {
710 ident: None,
711 function: Box::new(Function {
712 params: new_params,
713 body: match *arrow.body.take() {
714 BlockStmtOrExpr::BlockStmt(body) => Some(body),
715 BlockStmtOrExpr::Expr(expr) => Some(BlockStmt {
716 span: DUMMY_SP,
717 stmts: vec![Stmt::Return(ReturnStmt {
718 span: DUMMY_SP,
719 arg: Some(expr),
720 })],
721 ..Default::default()
722 }),
723 },
724 decorators: vec![],
725 span: DUMMY_SP,
726 is_generator: false,
727 is_async: true,
728 ..Default::default()
729 }),
730 })),
731 &cache_kind,
732 &reference_id,
733 ids_from_closure.len(),
734 )),
735 definite: false,
736 }],
737 ..Default::default()
738 }
739 .into(),
740 })));
741
742 if let Some(Ident { sym, .. }) = &self.arrow_or_fn_expr_ident {
743 assign_name_to_ident(&cache_ident, sym.as_str(), &mut self.hoisted_extra_items);
744 }
745
746 let bound_args: Vec<_> = ids_from_closure
747 .iter()
748 .cloned()
749 .map(|id| Some(id.as_arg()))
750 .collect();
751
752 let register_action_expr = annotate_ident_as_server_reference(
753 cache_ident.clone(),
754 reference_id.clone(),
755 arrow.span,
756 &self.comments,
757 );
758
759 if !bound_args.is_empty() {
763 let ref_ident = private_ident!(self.gen_ref_ident());
764
765 let ref_decl = VarDecl {
766 span: DUMMY_SP,
767 kind: VarDeclKind::Var,
768 decls: vec![VarDeclarator {
769 span: DUMMY_SP,
770 name: Pat::Ident(ref_ident.clone().into()),
771 init: Some(Box::new(register_action_expr.clone())),
772 definite: false,
773 }],
774 ..Default::default()
775 };
776
777 self.extra_items
779 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(ref_decl)))));
780
781 Box::new(bind_args_to_ref_expr(
782 Expr::Ident(ref_ident.clone()),
783 bound_args,
784 reference_id.clone(),
785 ))
786 } else {
787 Box::new(register_action_expr)
788 }
789 }
790
791 fn maybe_hoist_and_create_proxy_for_cache_function(
792 &mut self,
793 ids_from_closure: Vec<Name>,
794 fn_name: Option<Ident>,
795 cache_kind: RcStr,
796 function: &mut Function,
797 ) -> Box<Expr> {
798 let mut new_params: Vec<Param> = vec![];
799
800 if !ids_from_closure.is_empty() {
804 new_params.push(Param {
805 span: DUMMY_SP,
806 decorators: vec![],
807 pat: self.create_bound_action_args_array_pat(ids_from_closure.len()),
808 });
809 }
810
811 for p in function.params.iter() {
812 new_params.push(p.clone());
813 }
814
815 let cache_name: JsWord = self.gen_cache_ident();
816 let cache_ident = private_ident!(cache_name.clone());
817
818 let reference_id = self.generate_server_reference_id(&cache_name, true, Some(&new_params));
819
820 self.has_cache = true;
821 self.export_actions
822 .push((cache_name.to_string(), reference_id.clone()));
823
824 let register_action_expr = annotate_ident_as_server_reference(
825 cache_ident.clone(),
826 reference_id.clone(),
827 function.span,
828 &self.comments,
829 );
830
831 function.body.visit_mut_with(&mut ClosureReplacer {
832 used_ids: &ids_from_closure,
833 private_ctxt: self.private_ctxt,
834 });
835
836 self.hoisted_extra_items
838 .push(ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
839 span: DUMMY_SP,
840 decl: VarDecl {
841 span: DUMMY_SP,
842 kind: VarDeclKind::Var,
843 decls: vec![VarDeclarator {
844 span: DUMMY_SP,
845 name: Pat::Ident(cache_ident.clone().into()),
846 init: Some(wrap_cache_expr(
847 Box::new(Expr::Fn(FnExpr {
848 ident: fn_name.clone(),
849 function: Box::new(Function {
850 params: new_params,
851 ..function.take()
852 }),
853 })),
854 &cache_kind,
855 &reference_id,
856 ids_from_closure.len(),
857 )),
858 definite: false,
859 }],
860 ..Default::default()
861 }
862 .into(),
863 })));
864
865 if let Some(Ident { sym, .. }) = fn_name {
866 assign_name_to_ident(&cache_ident, sym.as_str(), &mut self.hoisted_extra_items);
867 } else if self.in_default_export_decl {
868 assign_name_to_ident(&cache_ident, "default", &mut self.hoisted_extra_items);
869 }
870
871 let bound_args: Vec<_> = ids_from_closure
872 .iter()
873 .cloned()
874 .map(|id| Some(id.as_arg()))
875 .collect();
876
877 if !bound_args.is_empty() {
881 let ref_ident = private_ident!(self.gen_ref_ident());
882
883 let ref_decl = VarDecl {
884 span: DUMMY_SP,
885 kind: VarDeclKind::Var,
886 decls: vec![VarDeclarator {
887 span: DUMMY_SP,
888 name: Pat::Ident(ref_ident.clone().into()),
889 init: Some(Box::new(register_action_expr.clone())),
890 definite: false,
891 }],
892 ..Default::default()
893 };
894
895 self.extra_items
897 .push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(ref_decl)))));
898
899 Box::new(bind_args_to_ref_expr(
900 Expr::Ident(ref_ident.clone()),
901 bound_args,
902 reference_id.clone(),
903 ))
904 } else {
905 Box::new(register_action_expr)
906 }
907 }
908}
909
910impl<C: Comments> VisitMut for ServerActions<C> {
911 fn visit_mut_export_decl(&mut self, decl: &mut ExportDecl) {
912 let old_in_exported_expr = replace(&mut self.in_exported_expr, true);
913 decl.decl.visit_mut_with(self);
914 self.in_exported_expr = old_in_exported_expr;
915 }
916
917 fn visit_mut_export_default_decl(&mut self, decl: &mut ExportDefaultDecl) {
918 let old_in_exported_expr = replace(&mut self.in_exported_expr, true);
919 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, true);
920 self.rewrite_default_fn_expr_to_proxy_expr = None;
921 decl.decl.visit_mut_with(self);
922 self.in_exported_expr = old_in_exported_expr;
923 self.in_default_export_decl = old_in_default_export_decl;
924 }
925
926 fn visit_mut_export_default_expr(&mut self, expr: &mut ExportDefaultExpr) {
927 let old_in_exported_expr = replace(&mut self.in_exported_expr, true);
928 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, true);
929 expr.expr.visit_mut_with(self);
930 self.in_exported_expr = old_in_exported_expr;
931 self.in_default_export_decl = old_in_default_export_decl;
932 }
933
934 fn visit_mut_fn_expr(&mut self, f: &mut FnExpr) {
935 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
936 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
937 if let Some(ident) = &f.ident {
938 self.arrow_or_fn_expr_ident = Some(ident.clone());
939 }
940 f.visit_mut_children_with(self);
941 self.this_status = old_this_status;
942 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
943 }
944
945 fn visit_mut_function(&mut self, f: &mut Function) {
946 let directive = self.get_directive_for_function(f.body.as_mut());
947 let declared_idents_until = self.declared_idents.len();
948 let old_names = take(&mut self.names);
949
950 if let Some(directive) = &directive {
951 self.this_status = ThisStatus::Forbidden {
952 directive: directive.clone(),
953 };
954 }
955
956 {
958 let old_in_module = replace(&mut self.in_module_level, false);
959 let should_track_names = directive.is_some() || self.should_track_names;
960 let old_should_track_names = replace(&mut self.should_track_names, should_track_names);
961 let old_in_exported_expr = replace(&mut self.in_exported_expr, false);
962 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, false);
963 let old_fn_decl_ident = self.fn_decl_ident.take();
964 f.visit_mut_children_with(self);
965 self.in_module_level = old_in_module;
966 self.should_track_names = old_should_track_names;
967 self.in_exported_expr = old_in_exported_expr;
968 self.in_default_export_decl = old_in_default_export_decl;
969 self.fn_decl_ident = old_fn_decl_ident;
970 }
971
972 if let Some(directive) = directive {
973 if !f.is_async {
974 emit_error(ServerActionsErrorKind::InlineSyncFunction {
975 span: f.span,
976 directive,
977 });
978
979 return;
980 }
981
982 let has_errors = HANDLER.with(|handler| handler.has_errors());
983
984 if has_errors || !self.config.is_react_server_layer {
986 return;
987 }
988
989 let mut child_names = take(&mut self.names);
990
991 if self.should_track_names {
992 self.names = [old_names, child_names.clone()].concat();
993 }
994
995 if let Directive::UseCache { cache_kind } = directive {
996 retain_names_from_declared_idents(
999 &mut child_names,
1000 &self.declared_idents[..declared_idents_until],
1001 );
1002
1003 let new_expr = self.maybe_hoist_and_create_proxy_for_cache_function(
1004 child_names.clone(),
1005 self.fn_decl_ident
1006 .clone()
1007 .or(self.arrow_or_fn_expr_ident.clone()),
1008 cache_kind,
1009 f,
1010 );
1011
1012 if self.in_default_export_decl {
1013 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1018 } else if let Some(ident) = &self.fn_decl_ident {
1019 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1021 span: DUMMY_SP,
1022 kind: VarDeclKind::Var,
1023 decls: vec![VarDeclarator {
1024 span: DUMMY_SP,
1025 name: Pat::Ident(ident.clone().into()),
1026 init: Some(new_expr),
1027 definite: false,
1028 }],
1029 ..Default::default()
1030 });
1031 } else {
1032 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1033 }
1034 } else if !(matches!(self.file_directive, Some(Directive::UseServer))
1035 && self.in_exported_expr)
1036 {
1037 retain_names_from_declared_idents(
1040 &mut child_names,
1041 &self.declared_idents[..declared_idents_until],
1042 );
1043
1044 let new_expr = self.maybe_hoist_and_create_proxy_for_server_action_function(
1045 child_names,
1046 f,
1047 self.fn_decl_ident
1048 .clone()
1049 .or(self.arrow_or_fn_expr_ident.clone()),
1050 );
1051
1052 if self.in_default_export_decl {
1053 self.rewrite_default_fn_expr_to_proxy_expr = Some(new_expr);
1058 } else if let Some(ident) = &self.fn_decl_ident {
1059 self.rewrite_fn_decl_to_proxy_decl = Some(VarDecl {
1062 span: DUMMY_SP,
1063 kind: VarDeclKind::Var,
1064 decls: vec![VarDeclarator {
1065 span: DUMMY_SP,
1066 name: Pat::Ident(ident.clone().into()),
1067 init: Some(new_expr),
1068 definite: false,
1069 }],
1070 ..Default::default()
1071 });
1072 } else {
1073 self.rewrite_expr_to_proxy_expr = Some(new_expr);
1074 }
1075 }
1076 }
1077 }
1078
1079 fn visit_mut_decl(&mut self, d: &mut Decl) {
1080 self.rewrite_fn_decl_to_proxy_decl = None;
1081 d.visit_mut_children_with(self);
1082
1083 if let Some(decl) = &self.rewrite_fn_decl_to_proxy_decl {
1084 *d = (*decl).clone().into();
1085 }
1086
1087 self.rewrite_fn_decl_to_proxy_decl = None;
1088 }
1089
1090 fn visit_mut_fn_decl(&mut self, f: &mut FnDecl) {
1091 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1092 let old_in_exported_expr = self.in_exported_expr;
1093 if self.in_module_level && self.exported_local_ids.contains(&f.ident.to_id()) {
1094 self.in_exported_expr = true
1095 }
1096 let old_fn_decl_ident = self.fn_decl_ident.replace(f.ident.clone());
1097 f.visit_mut_children_with(self);
1098 self.this_status = old_this_status;
1099 self.in_exported_expr = old_in_exported_expr;
1100 self.fn_decl_ident = old_fn_decl_ident;
1101 }
1102
1103 fn visit_mut_arrow_expr(&mut self, a: &mut ArrowExpr) {
1104 let directive = self.get_directive_for_function(
1107 if let BlockStmtOrExpr::BlockStmt(block) = &mut *a.body {
1108 Some(block)
1109 } else {
1110 None
1111 },
1112 );
1113
1114 if let Some(directive) = &directive {
1115 self.this_status = ThisStatus::Forbidden {
1116 directive: directive.clone(),
1117 };
1118 }
1119
1120 let declared_idents_until = self.declared_idents.len();
1121 let old_names = take(&mut self.names);
1122
1123 {
1124 let old_in_module = replace(&mut self.in_module_level, false);
1126 let should_track_names = directive.is_some() || self.should_track_names;
1127 let old_should_track_names = replace(&mut self.should_track_names, should_track_names);
1128 let old_in_exported_expr = replace(&mut self.in_exported_expr, false);
1129 let old_in_default_export_decl = replace(&mut self.in_default_export_decl, false);
1130 {
1131 for n in &mut a.params {
1132 collect_idents_in_pat(n, &mut self.declared_idents);
1133 }
1134 }
1135 a.visit_mut_children_with(self);
1136 self.in_module_level = old_in_module;
1137 self.should_track_names = old_should_track_names;
1138 self.in_exported_expr = old_in_exported_expr;
1139 self.in_default_export_decl = old_in_default_export_decl;
1140 }
1141
1142 if let Some(directive) = directive {
1143 if !a.is_async {
1144 emit_error(ServerActionsErrorKind::InlineSyncFunction {
1145 span: a.span,
1146 directive,
1147 });
1148
1149 return;
1150 }
1151
1152 let has_errors = HANDLER.with(|handler| handler.has_errors());
1153
1154 if has_errors || !self.config.is_react_server_layer {
1157 return;
1158 }
1159
1160 let mut child_names = take(&mut self.names);
1161
1162 if self.should_track_names {
1163 self.names = [old_names, child_names.clone()].concat();
1164 }
1165
1166 retain_names_from_declared_idents(
1169 &mut child_names,
1170 &self.declared_idents[..declared_idents_until],
1171 );
1172
1173 if let Directive::UseCache { cache_kind } = directive {
1174 self.rewrite_expr_to_proxy_expr =
1175 Some(self.maybe_hoist_and_create_proxy_for_cache_arrow_expr(
1176 child_names,
1177 cache_kind,
1178 a,
1179 ));
1180 } else if !matches!(self.file_directive, Some(Directive::UseServer)) {
1181 self.rewrite_expr_to_proxy_expr = Some(
1182 self.maybe_hoist_and_create_proxy_for_server_action_arrow_expr(child_names, a),
1183 );
1184 }
1185 }
1186 }
1187
1188 fn visit_mut_module(&mut self, m: &mut Module) {
1189 self.start_pos = m.span.lo;
1190 m.visit_mut_children_with(self);
1191 }
1192
1193 fn visit_mut_stmt(&mut self, n: &mut Stmt) {
1194 n.visit_mut_children_with(self);
1195
1196 if self.in_module_level {
1197 return;
1198 }
1199
1200 collect_decl_idents_in_stmt(n, &mut self.declared_idents);
1203 }
1204
1205 fn visit_mut_param(&mut self, n: &mut Param) {
1206 n.visit_mut_children_with(self);
1207
1208 if self.in_module_level {
1209 return;
1210 }
1211
1212 collect_idents_in_pat(&n.pat, &mut self.declared_idents);
1213 }
1214
1215 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
1216 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1217 let old_in_exported_expr = self.in_exported_expr;
1218
1219 match n {
1220 PropOrSpread::Prop(box Prop::KeyValue(KeyValueProp {
1221 key: PropName::Ident(ident_name),
1222 value: box Expr::Arrow(_) | box Expr::Fn(_),
1223 ..
1224 })) => {
1225 self.in_exported_expr = false;
1226 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1227 }
1228 PropOrSpread::Prop(box Prop::Method(MethodProp { key, .. })) => {
1229 let key = key.clone();
1230
1231 if let PropName::Ident(ident_name) = &key {
1232 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1233 }
1234
1235 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1236 self.rewrite_expr_to_proxy_expr = None;
1237 self.in_exported_expr = false;
1238 n.visit_mut_children_with(self);
1239 self.in_exported_expr = old_in_exported_expr;
1240 self.this_status = old_this_status;
1241
1242 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1243 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
1244 key,
1245 value: expr,
1246 })));
1247 }
1248
1249 return;
1250 }
1251 _ => {}
1252 }
1253
1254 if !self.in_module_level && self.should_track_names {
1255 if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
1256 self.names.push(Name::from(&*i));
1257 self.should_track_names = false;
1258 n.visit_mut_children_with(self);
1259 self.should_track_names = true;
1260 return;
1261 }
1262 }
1263
1264 n.visit_mut_children_with(self);
1265 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1266 self.in_exported_expr = old_in_exported_expr;
1267 }
1268
1269 fn visit_mut_class(&mut self, n: &mut Class) {
1270 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1271 n.visit_mut_children_with(self);
1272 self.this_status = old_this_status;
1273 }
1274
1275 fn visit_mut_class_member(&mut self, n: &mut ClassMember) {
1276 if let ClassMember::Method(ClassMethod {
1277 is_abstract: false,
1278 is_static: true,
1279 kind: MethodKind::Method,
1280 key,
1281 span,
1282 accessibility: None | Some(Accessibility::Public),
1283 ..
1284 }) = n
1285 {
1286 let key = key.clone();
1287 let span = *span;
1288 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
1289
1290 if let PropName::Ident(ident_name) = &key {
1291 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
1292 }
1293
1294 let old_this_status = replace(&mut self.this_status, ThisStatus::Allowed);
1295 self.rewrite_expr_to_proxy_expr = None;
1296 self.in_exported_expr = false;
1297 n.visit_mut_children_with(self);
1298 self.this_status = old_this_status;
1299 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
1300
1301 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1302 *n = ClassMember::ClassProp(ClassProp {
1303 span,
1304 key,
1305 value: Some(expr),
1306 is_static: true,
1307 ..Default::default()
1308 });
1309 }
1310 } else {
1311 n.visit_mut_children_with(self);
1312 }
1313 }
1314
1315 fn visit_mut_class_method(&mut self, n: &mut ClassMethod) {
1316 if n.is_static {
1317 n.visit_mut_children_with(self);
1318 } else {
1319 let (is_action_fn, is_cache_fn) = has_body_directive(&n.function.body);
1320
1321 if is_action_fn {
1322 emit_error(
1323 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span: n.span },
1324 );
1325 } else if is_cache_fn {
1326 emit_error(
1327 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span: n.span },
1328 );
1329 } else {
1330 n.visit_mut_children_with(self);
1331 }
1332 }
1333 }
1334
1335 fn visit_mut_call_expr(&mut self, n: &mut CallExpr) {
1336 if let Callee::Expr(box Expr::Ident(Ident { sym, .. })) = &mut n.callee {
1337 if sym == "jsxDEV" || sym == "_jsxDEV" {
1338 if n.args.len() > 4 {
1342 for arg in &mut n.args[0..4] {
1343 arg.visit_mut_with(self);
1344 }
1345 return;
1346 }
1347 }
1348 }
1349
1350 n.visit_mut_children_with(self);
1351 }
1352
1353 fn visit_mut_callee(&mut self, n: &mut Callee) {
1354 let old_in_callee = replace(&mut self.in_callee, true);
1355 n.visit_mut_children_with(self);
1356 self.in_callee = old_in_callee;
1357 }
1358
1359 fn visit_mut_expr(&mut self, n: &mut Expr) {
1360 if !self.in_module_level && self.should_track_names {
1361 if let Ok(mut name) = Name::try_from(&*n) {
1362 if self.in_callee {
1363 if !name.1.is_empty() {
1366 name.1.pop();
1367 }
1368 }
1369
1370 self.names.push(name);
1371 self.should_track_names = false;
1372 n.visit_mut_children_with(self);
1373 self.should_track_names = true;
1374 return;
1375 }
1376 }
1377
1378 self.rewrite_expr_to_proxy_expr = None;
1379 n.visit_mut_children_with(self);
1380 if let Some(expr) = self.rewrite_expr_to_proxy_expr.take() {
1381 *n = *expr;
1382 }
1383 }
1384
1385 fn visit_mut_module_items(&mut self, stmts: &mut Vec<ModuleItem>) {
1386 self.file_directive = self.get_directive_for_module(stmts);
1387
1388 let in_cache_file = matches!(self.file_directive, Some(Directive::UseCache { .. }));
1389 let in_action_file = matches!(self.file_directive, Some(Directive::UseServer));
1390
1391 if in_cache_file {
1392 for stmt in stmts.iter() {
1403 match stmt {
1404 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(export_default_expr)) => {
1405 if let Expr::Ident(ident) = &*export_default_expr.expr {
1406 self.exported_local_ids.insert(ident.to_id());
1407 }
1408 }
1409 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named_export)) => {
1410 if named_export.src.is_none() {
1411 for spec in &named_export.specifiers {
1412 if let ExportSpecifier::Named(ExportNamedSpecifier {
1413 orig: ModuleExportName::Ident(ident),
1414 ..
1415 }) = spec
1416 {
1417 self.exported_local_ids.insert(ident.to_id());
1418 }
1419 }
1420 }
1421 }
1422 _ => {}
1423 }
1424 }
1425 }
1426
1427 let should_track_exports = self.file_directive.is_some();
1429
1430 let old_annotations = self.annotations.take();
1431 let mut new = Vec::with_capacity(stmts.len());
1432
1433 for mut stmt in stmts.take() {
1434 if should_track_exports {
1437 let mut disallowed_export_span = DUMMY_SP;
1438
1439 match &mut stmt {
1441 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl { decl, span })) => {
1442 match decl {
1443 Decl::Fn(f) => {
1444 let (is_action_fn, is_cache_fn) =
1447 has_body_directive(&f.function.body);
1448
1449 let ref_id = if is_action_fn {
1450 false
1451 } else if is_cache_fn {
1452 true
1453 } else {
1454 in_cache_file
1455 };
1456
1457 if !(is_cache_fn && self.config.is_react_server_layer) {
1463 self.exported_idents.push((
1464 f.ident.clone(),
1465 f.ident.sym.to_string(),
1466 self.generate_server_reference_id(
1467 f.ident.sym.as_ref(),
1468 ref_id,
1469 Some(&f.function.params),
1470 ),
1471 ));
1472 }
1473 }
1474 Decl::Var(var) => {
1475 let mut idents: Vec<Ident> = Vec::new();
1477 collect_idents_in_var_decls(&var.decls, &mut idents);
1478
1479 for ident in &idents {
1480 self.exported_idents.push((
1481 ident.clone(),
1482 ident.sym.to_string(),
1483 self.generate_server_reference_id(
1484 ident.sym.as_ref(),
1485 in_cache_file,
1486 None,
1487 ),
1488 ));
1489 }
1490
1491 for decl in &mut var.decls {
1492 if let Some(init) = &decl.init {
1493 if let Expr::Lit(_) = &**init {
1494 disallowed_export_span = *span;
1496 }
1497 }
1498 }
1499 }
1500 _ => {
1501 disallowed_export_span = *span;
1502 }
1503 }
1504 }
1505 ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(named)) => {
1506 if named.src.is_some() {
1507 disallowed_export_span = named.span;
1508 } else {
1509 for spec in &mut named.specifiers {
1510 if let ExportSpecifier::Named(ExportNamedSpecifier {
1511 orig: ModuleExportName::Ident(ident),
1512 exported,
1513 ..
1514 }) = spec
1515 {
1516 if let Some(export_name) = exported {
1517 if let ModuleExportName::Ident(Ident { sym, .. }) =
1518 export_name
1519 {
1520 self.exported_idents.push((
1522 ident.clone(),
1523 sym.to_string(),
1524 self.generate_server_reference_id(
1525 sym.as_ref(),
1526 in_cache_file,
1527 None,
1528 ),
1529 ));
1530 } else if let ModuleExportName::Str(str) = export_name {
1531 self.exported_idents.push((
1533 ident.clone(),
1534 str.value.to_string(),
1535 self.generate_server_reference_id(
1536 str.value.as_ref(),
1537 in_cache_file,
1538 None,
1539 ),
1540 ));
1541 }
1542 } else {
1543 self.exported_idents.push((
1545 ident.clone(),
1546 ident.sym.to_string(),
1547 self.generate_server_reference_id(
1548 ident.sym.as_ref(),
1549 in_cache_file,
1550 None,
1551 ),
1552 ));
1553 }
1554 } else {
1555 disallowed_export_span = named.span;
1556 }
1557 }
1558 }
1559 }
1560 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
1561 decl,
1562 span,
1563 ..
1564 })) => match decl {
1565 DefaultDecl::Fn(f) => {
1566 let (is_action_fn, is_cache_fn) = has_body_directive(&f.function.body);
1567
1568 let is_cache = if is_action_fn {
1569 false
1570 } else if is_cache_fn {
1571 true
1572 } else {
1573 in_cache_file
1574 };
1575
1576 if !(is_cache_fn && self.config.is_react_server_layer) {
1582 let ref_id = self.generate_server_reference_id(
1583 "default",
1584 is_cache,
1585 Some(&f.function.params),
1586 );
1587
1588 if let Some(ident) = &f.ident {
1589 self.exported_idents.push((
1591 ident.clone(),
1592 "default".into(),
1593 ref_id,
1594 ));
1595 } else {
1596 let span = f.function.span;
1599
1600 let new_ident = Ident::new(
1601 self.gen_action_ident(),
1602 span,
1603 self.private_ctxt,
1604 );
1605
1606 f.ident = Some(new_ident.clone());
1607
1608 self.exported_idents.push((
1609 new_ident.clone(),
1610 "default".into(),
1611 ref_id,
1612 ));
1613
1614 assign_name_to_ident(
1615 &new_ident,
1616 "default",
1617 &mut self.extra_items,
1618 );
1619 }
1620 }
1621 }
1622 _ => {
1623 disallowed_export_span = *span;
1624 }
1625 },
1626 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(default_expr)) => {
1627 match &mut *default_expr.expr {
1628 Expr::Fn(_f) => {}
1629 Expr::Arrow(arrow) => {
1630 let span = arrow.span;
1633
1634 let (is_action_fn, is_cache_fn) =
1635 has_body_directive(&if let BlockStmtOrExpr::BlockStmt(block) =
1636 &*arrow.body
1637 {
1638 Some(block.clone())
1639 } else {
1640 None
1641 });
1642
1643 let is_cache = if is_action_fn {
1644 false
1645 } else if is_cache_fn {
1646 true
1647 } else {
1648 in_cache_file
1649 };
1650
1651 if !(is_cache_fn && self.config.is_react_server_layer) {
1657 let new_ident = Ident::new(
1658 self.gen_action_ident(),
1659 span,
1660 self.private_ctxt,
1661 );
1662
1663 self.exported_idents.push((
1664 new_ident.clone(),
1665 "default".into(),
1666 self.generate_server_reference_id(
1667 "default",
1668 is_cache,
1669 Some(
1670 &arrow
1671 .params
1672 .iter()
1673 .map(|p| Param::from(p.clone()))
1674 .collect(),
1675 ),
1676 ),
1677 ));
1678
1679 create_var_declarator(&new_ident, &mut self.extra_items);
1680 assign_name_to_ident(
1681 &new_ident,
1682 "default",
1683 &mut self.extra_items,
1684 );
1685
1686 *default_expr.expr =
1687 assign_arrow_expr(&new_ident, Expr::Arrow(arrow.clone()));
1688 }
1689 }
1690 Expr::Ident(ident) => {
1691 self.exported_idents.push((
1693 ident.clone(),
1694 "default".into(),
1695 self.generate_server_reference_id(
1696 "default",
1697 in_cache_file,
1698 None,
1699 ),
1700 ));
1701 }
1702 Expr::Call(call) => {
1703 let span = call.span;
1706
1707 let new_ident =
1708 Ident::new(self.gen_action_ident(), span, self.private_ctxt);
1709
1710 self.exported_idents.push((
1711 new_ident.clone(),
1712 "default".into(),
1713 self.generate_server_reference_id(
1714 "default",
1715 in_cache_file,
1716 None,
1717 ),
1718 ));
1719
1720 create_var_declarator(&new_ident, &mut self.extra_items);
1721 assign_name_to_ident(&new_ident, "default", &mut self.extra_items);
1722
1723 *default_expr.expr =
1724 assign_arrow_expr(&new_ident, Expr::Call(call.clone()));
1725 }
1726 _ => {
1727 disallowed_export_span = default_expr.span;
1728 }
1729 }
1730 }
1731 ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll { span, .. })) => {
1732 disallowed_export_span = *span;
1733 }
1734 _ => {}
1735 }
1736
1737 if disallowed_export_span != DUMMY_SP {
1738 emit_error(ServerActionsErrorKind::ExportedSyncFunction {
1739 span: disallowed_export_span,
1740 in_action_file,
1741 });
1742
1743 return;
1744 }
1745 }
1746
1747 stmt.visit_mut_with(self);
1748
1749 let mut new_stmt = stmt;
1750
1751 if let Some(expr) = &self.rewrite_default_fn_expr_to_proxy_expr {
1752 new_stmt =
1754 ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
1755 span: DUMMY_SP,
1756 expr: expr.clone(),
1757 }));
1758 self.rewrite_default_fn_expr_to_proxy_expr = None;
1759 }
1760
1761 if self.config.is_react_server_layer || self.file_directive.is_none() {
1762 new.append(&mut self.hoisted_extra_items);
1763 new.push(new_stmt);
1764 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
1765 new.append(&mut self.extra_items);
1766 }
1767 }
1768
1769 let mut actions = self.export_actions.take();
1770
1771 if in_action_file || in_cache_file && !self.config.is_react_server_layer {
1772 actions.extend(
1773 self.exported_idents
1774 .iter()
1775 .map(|e| (e.1.clone(), e.2.clone())),
1776 );
1777
1778 if !actions.is_empty() {
1779 self.has_action |= in_action_file;
1780 self.has_cache |= in_cache_file;
1781 }
1782 };
1783
1784 let actions = actions
1786 .into_iter()
1787 .map(|a| (a.1, a.0))
1788 .collect::<ActionsMap>();
1789
1790 let create_ref_ident = private_ident!("createServerReference");
1793 let call_server_ident = private_ident!("callServer");
1794 let find_source_map_url_ident = private_ident!("findSourceMapURL");
1795
1796 if (self.has_action || self.has_cache) && !self.config.is_react_server_layer {
1797 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1804 span: DUMMY_SP,
1805 specifiers: vec![
1806 ImportSpecifier::Named(ImportNamedSpecifier {
1807 span: DUMMY_SP,
1808 local: create_ref_ident.clone(),
1809 imported: None,
1810 is_type_only: false,
1811 }),
1812 ImportSpecifier::Named(ImportNamedSpecifier {
1813 span: DUMMY_SP,
1814 local: call_server_ident.clone(),
1815 imported: None,
1816 is_type_only: false,
1817 }),
1818 ImportSpecifier::Named(ImportNamedSpecifier {
1819 span: DUMMY_SP,
1820 local: find_source_map_url_ident.clone(),
1821 imported: None,
1822 is_type_only: false,
1823 }),
1824 ],
1825 src: Box::new(Str {
1826 span: DUMMY_SP,
1827 value: "private-next-rsc-action-client-wrapper".into(),
1828 raw: None,
1829 }),
1830 type_only: false,
1831 with: None,
1832 phase: Default::default(),
1833 })));
1834 new.rotate_right(1);
1835 }
1836
1837 if should_track_exports {
1839 for (ident, export_name, ref_id) in self.exported_idents.iter() {
1840 if !self.config.is_react_server_layer {
1841 if export_name == "default" {
1842 self.comments.add_pure_comment(ident.span.lo);
1843
1844 let export_expr = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
1845 ExportDefaultExpr {
1846 span: DUMMY_SP,
1847 expr: Box::new(Expr::Call(CallExpr {
1848 span: ident.span,
1849 callee: Callee::Expr(Box::new(Expr::Ident(
1850 create_ref_ident.clone(),
1851 ))),
1852 args: vec![
1853 ref_id.clone().as_arg(),
1854 call_server_ident.clone().as_arg(),
1855 Expr::undefined(DUMMY_SP).as_arg(),
1856 find_source_map_url_ident.clone().as_arg(),
1857 "default".as_arg(),
1858 ],
1859 ..Default::default()
1860 })),
1861 },
1862 ));
1863 new.push(export_expr);
1864 } else {
1865 let call_expr_span = Span::dummy_with_cmt();
1866 self.comments.add_pure_comment(call_expr_span.lo);
1867
1868 let export_expr =
1869 ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
1870 span: DUMMY_SP,
1871 decl: Decl::Var(Box::new(VarDecl {
1872 span: DUMMY_SP,
1873 kind: VarDeclKind::Var,
1874 decls: vec![VarDeclarator {
1875 span: DUMMY_SP,
1876 name: Pat::Ident(
1877 IdentName::new(export_name.clone().into(), ident.span)
1878 .into(),
1879 ),
1880 init: Some(Box::new(Expr::Call(CallExpr {
1881 span: call_expr_span,
1882 callee: Callee::Expr(Box::new(Expr::Ident(
1883 create_ref_ident.clone(),
1884 ))),
1885 args: vec![
1886 ref_id.clone().as_arg(),
1887 call_server_ident.clone().as_arg(),
1888 Expr::undefined(DUMMY_SP).as_arg(),
1889 find_source_map_url_ident.clone().as_arg(),
1890 export_name.clone().as_arg(),
1891 ],
1892 ..Default::default()
1893 }))),
1894 definite: false,
1895 }],
1896 ..Default::default()
1897 })),
1898 }));
1899 new.push(export_expr);
1900 }
1901 } else if !in_cache_file {
1902 self.annotations.push(Stmt::Expr(ExprStmt {
1903 span: DUMMY_SP,
1904 expr: Box::new(annotate_ident_as_server_reference(
1905 ident.clone(),
1906 ref_id.to_string(),
1907 ident.span,
1908 &self.comments,
1909 )),
1910 }));
1911 }
1912 }
1913
1914 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
1922 new.append(&mut self.extra_items);
1923
1924 if !in_cache_file && !self.exported_idents.is_empty() {
1926 let ensure_ident = private_ident!("ensureServerEntryExports");
1927 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1928 span: DUMMY_SP,
1929 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
1930 span: DUMMY_SP,
1931 local: ensure_ident.clone(),
1932 imported: None,
1933 is_type_only: false,
1934 })],
1935 src: Box::new(Str {
1936 span: DUMMY_SP,
1937 value: "private-next-rsc-action-validate".into(),
1938 raw: None,
1939 }),
1940 type_only: false,
1941 with: None,
1942 phase: Default::default(),
1943 })));
1944 new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
1945 span: DUMMY_SP,
1946 expr: Box::new(Expr::Call(CallExpr {
1947 span: DUMMY_SP,
1948 callee: Callee::Expr(Box::new(Expr::Ident(ensure_ident))),
1949 args: vec![ExprOrSpread {
1950 spread: None,
1951 expr: Box::new(Expr::Array(ArrayLit {
1952 span: DUMMY_SP,
1953 elems: self
1954 .exported_idents
1955 .iter()
1956 .map(|(ident, _, _)| {
1957 Some(ExprOrSpread {
1958 spread: None,
1959 expr: Box::new(Expr::Ident(ident.clone())),
1960 })
1961 })
1962 .collect(),
1963 })),
1964 }],
1965 ..Default::default()
1966 })),
1967 })));
1968 }
1969
1970 new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
1972 }
1973 }
1974
1975 if self.has_action || self.has_cache {
1976 self.comments.add_leading(
1978 self.start_pos,
1979 Comment {
1980 span: DUMMY_SP,
1981 kind: CommentKind::Block,
1982 text: generate_server_actions_comment(actions).into(),
1983 },
1984 );
1985 }
1986
1987 if self.has_cache && self.config.is_react_server_layer {
1989 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1990 span: DUMMY_SP,
1991 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
1992 span: DUMMY_SP,
1993 local: quote_ident!("$$cache__").into(),
1994 imported: Some(quote_ident!("cache").into()),
1995 is_type_only: false,
1996 })],
1997 src: Box::new(Str {
1998 span: DUMMY_SP,
1999 value: "private-next-rsc-cache-wrapper".into(),
2000 raw: None,
2001 }),
2002 type_only: false,
2003 with: None,
2004 phase: Default::default(),
2005 })));
2006
2007 new.rotate_right(1);
2009 }
2010
2011 if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
2012 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2015 span: DUMMY_SP,
2016 specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2017 span: DUMMY_SP,
2018 local: quote_ident!("registerServerReference").into(),
2019 imported: None,
2020 is_type_only: false,
2021 })],
2022 src: Box::new(Str {
2023 span: DUMMY_SP,
2024 value: "private-next-rsc-server-reference".into(),
2025 raw: None,
2026 }),
2027 type_only: false,
2028 with: None,
2029 phase: Default::default(),
2030 })));
2031
2032 new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2036 span: DUMMY_SP,
2037 specifiers: vec![
2038 ImportSpecifier::Named(ImportNamedSpecifier {
2039 span: DUMMY_SP,
2040 local: quote_ident!("encryptActionBoundArgs").into(),
2041 imported: None,
2042 is_type_only: false,
2043 }),
2044 ImportSpecifier::Named(ImportNamedSpecifier {
2045 span: DUMMY_SP,
2046 local: quote_ident!("decryptActionBoundArgs").into(),
2047 imported: None,
2048 is_type_only: false,
2049 }),
2050 ],
2051 src: Box::new(Str {
2052 span: DUMMY_SP,
2053 value: "private-next-rsc-action-encryption".into(),
2054 raw: None,
2055 }),
2056 type_only: false,
2057 with: None,
2058 phase: Default::default(),
2059 })));
2060
2061 new.rotate_right(2);
2063 }
2064
2065 *stmts = new;
2066
2067 self.annotations = old_annotations;
2068 }
2069
2070 fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
2071 let old_annotations = self.annotations.take();
2072
2073 let mut new = Vec::with_capacity(stmts.len());
2074 for mut stmt in stmts.take() {
2075 stmt.visit_mut_with(self);
2076
2077 new.push(stmt);
2078 new.append(&mut self.annotations);
2079 }
2080
2081 *stmts = new;
2082
2083 self.annotations = old_annotations;
2084 }
2085
2086 fn visit_mut_jsx_attr(&mut self, attr: &mut JSXAttr) {
2087 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2088
2089 if let (Some(JSXAttrValue::JSXExprContainer(container)), JSXAttrName::Ident(ident_name)) =
2090 (&attr.value, &attr.name)
2091 {
2092 match &container.expr {
2093 JSXExpr::Expr(box Expr::Arrow(_)) | JSXExpr::Expr(box Expr::Fn(_)) => {
2094 self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
2095 }
2096 _ => {}
2097 }
2098 }
2099
2100 attr.visit_mut_children_with(self);
2101 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2102 }
2103
2104 fn visit_mut_var_declarator(&mut self, var_declarator: &mut VarDeclarator) {
2105 let old_in_exported_expr = self.in_exported_expr;
2106 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2107
2108 if let (Pat::Ident(ident), Some(box Expr::Arrow(_) | box Expr::Fn(_))) =
2109 (&var_declarator.name, &var_declarator.init)
2110 {
2111 if self.in_module_level && self.exported_local_ids.contains(&ident.to_id()) {
2112 self.in_exported_expr = true
2113 }
2114
2115 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2116 }
2117
2118 var_declarator.visit_mut_children_with(self);
2119
2120 self.in_exported_expr = old_in_exported_expr;
2121 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2122 }
2123
2124 fn visit_mut_assign_expr(&mut self, assign_expr: &mut AssignExpr) {
2125 let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
2126
2127 if let (
2128 AssignTarget::Simple(SimpleAssignTarget::Ident(ident)),
2129 box Expr::Arrow(_) | box Expr::Fn(_),
2130 ) = (&assign_expr.left, &assign_expr.right)
2131 {
2132 if !ident.id.to_id().0.starts_with("$$RSC_SERVER_") {
2134 self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2135 }
2136 }
2137
2138 assign_expr.visit_mut_children_with(self);
2139 self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2140 }
2141
2142 fn visit_mut_this_expr(&mut self, n: &mut ThisExpr) {
2143 if let ThisStatus::Forbidden { directive } = &self.this_status {
2144 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2145 span: n.span,
2146 expr: "this".into(),
2147 directive: directive.clone(),
2148 });
2149 }
2150 }
2151
2152 fn visit_mut_super(&mut self, n: &mut Super) {
2153 if let ThisStatus::Forbidden { directive } = &self.this_status {
2154 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2155 span: n.span,
2156 expr: "super".into(),
2157 directive: directive.clone(),
2158 });
2159 }
2160 }
2161
2162 fn visit_mut_ident(&mut self, n: &mut Ident) {
2163 if n.sym == *"arguments" {
2164 if let ThisStatus::Forbidden { directive } = &self.this_status {
2165 emit_error(ServerActionsErrorKind::ForbiddenExpression {
2166 span: n.span,
2167 expr: "arguments".into(),
2168 directive: directive.clone(),
2169 });
2170 }
2171 }
2172 }
2173
2174 noop_visit_mut_type!();
2175}
2176
2177fn retain_names_from_declared_idents(
2178 child_names: &mut Vec<Name>,
2179 current_declared_idents: &[Ident],
2180) {
2181 let mut retained_names = Vec::new();
2183
2184 for name in child_names.iter() {
2185 let mut should_retain = true;
2186
2187 for another_name in child_names.iter() {
2193 if name != another_name
2194 && name.0 == another_name.0
2195 && name.1.len() >= another_name.1.len()
2196 {
2197 let mut is_prefix = true;
2198 for i in 0..another_name.1.len() {
2199 if name.1[i] != another_name.1[i] {
2200 is_prefix = false;
2201 break;
2202 }
2203 }
2204 if is_prefix {
2205 should_retain = false;
2206 break;
2207 }
2208 }
2209 }
2210
2211 if should_retain
2212 && current_declared_idents
2213 .iter()
2214 .any(|ident| ident.to_id() == name.0)
2215 && !retained_names.contains(name)
2216 {
2217 retained_names.push(name.clone());
2218 }
2219 }
2220
2221 *child_names = retained_names;
2223}
2224
2225fn wrap_cache_expr(expr: Box<Expr>, name: &str, id: &str, bound_args_len: usize) -> Box<Expr> {
2226 Box::new(Expr::Call(CallExpr {
2228 span: DUMMY_SP,
2229 callee: quote_ident!("$$cache__").as_callee(),
2230 args: vec![
2231 ExprOrSpread {
2232 spread: None,
2233 expr: Box::new(name.into()),
2234 },
2235 ExprOrSpread {
2236 spread: None,
2237 expr: Box::new(id.into()),
2238 },
2239 Number::from(bound_args_len).as_arg(),
2240 expr.as_arg(),
2241 ],
2242 ..Default::default()
2243 }))
2244}
2245
2246fn create_var_declarator(ident: &Ident, extra_items: &mut Vec<ModuleItem>) {
2247 extra_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
2249 span: DUMMY_SP,
2250 kind: VarDeclKind::Var,
2251 decls: vec![VarDeclarator {
2252 span: DUMMY_SP,
2253 name: ident.clone().into(),
2254 init: None,
2255 definite: Default::default(),
2256 }],
2257 ..Default::default()
2258 })))));
2259}
2260
2261fn assign_name_to_ident(ident: &Ident, name: &str, extra_items: &mut Vec<ModuleItem>) {
2262 extra_items.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2264 span: DUMMY_SP,
2265 expr: Box::new(Expr::Call(CallExpr {
2266 span: DUMMY_SP,
2267 callee: Callee::Expr(Box::new(Expr::Member(MemberExpr {
2268 span: DUMMY_SP,
2269 obj: Box::new(Expr::Ident(Ident::new(
2270 "Object".into(),
2271 DUMMY_SP,
2272 ident.ctxt,
2273 ))),
2274 prop: MemberProp::Ident(IdentName::new("defineProperty".into(), DUMMY_SP)),
2275 }))),
2276 args: vec![
2277 ExprOrSpread {
2278 spread: None,
2279 expr: Box::new(Expr::Ident(ident.clone())),
2280 },
2281 ExprOrSpread {
2282 spread: None,
2283 expr: Box::new("name".into()),
2284 },
2285 ExprOrSpread {
2286 spread: None,
2287 expr: Box::new(Expr::Object(ObjectLit {
2288 span: DUMMY_SP,
2289 props: vec![
2290 PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
2291 key: PropName::Str("value".into()),
2292 value: Box::new(name.into()),
2293 }))),
2294 PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
2295 key: PropName::Str("writable".into()),
2296 value: Box::new(false.into()),
2297 }))),
2298 ],
2299 })),
2300 },
2301 ],
2302 ..Default::default()
2303 })),
2304 })));
2305}
2306
2307fn assign_arrow_expr(ident: &Ident, expr: Expr) -> Expr {
2308 if let Expr::Paren(_paren) = &expr {
2309 expr
2310 } else {
2311 Expr::Paren(ParenExpr {
2313 span: DUMMY_SP,
2314 expr: Box::new(Expr::Assign(AssignExpr {
2315 span: DUMMY_SP,
2316 left: ident.clone().into(),
2317 op: op!("="),
2318 right: Box::new(expr),
2319 })),
2320 })
2321 }
2322}
2323
2324fn annotate_ident_as_server_reference(
2325 ident: Ident,
2326 action_id: String,
2327 original_span: Span,
2328 comments: &dyn Comments,
2329) -> Expr {
2330 if !original_span.lo.is_dummy() {
2331 comments.add_leading(
2332 original_span.lo,
2333 Comment {
2334 kind: CommentKind::Block,
2335 span: original_span,
2336 text: "#__TURBOPACK_DISABLE_EXPORT_MERGING__".into(),
2337 },
2338 );
2339 }
2340
2341 Expr::Call(CallExpr {
2343 span: original_span,
2344 callee: quote_ident!("registerServerReference").as_callee(),
2345 args: vec![
2346 ExprOrSpread {
2347 spread: None,
2348 expr: Box::new(Expr::Ident(ident)),
2349 },
2350 ExprOrSpread {
2351 spread: None,
2352 expr: Box::new(action_id.clone().into()),
2353 },
2354 ExprOrSpread {
2355 spread: None,
2356 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
2357 },
2358 ],
2359 ..Default::default()
2360 })
2361}
2362
2363fn bind_args_to_ref_expr(expr: Expr, bound: Vec<Option<ExprOrSpread>>, action_id: String) -> Expr {
2364 if bound.is_empty() {
2365 expr
2366 } else {
2367 Expr::Call(CallExpr {
2369 span: DUMMY_SP,
2370 callee: Expr::Member(MemberExpr {
2371 span: DUMMY_SP,
2372 obj: Box::new(expr),
2373 prop: MemberProp::Ident(quote_ident!("bind")),
2374 })
2375 .as_callee(),
2376 args: vec![
2377 ExprOrSpread {
2378 spread: None,
2379 expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
2380 },
2381 ExprOrSpread {
2382 spread: None,
2383 expr: Box::new(Expr::Call(CallExpr {
2384 span: DUMMY_SP,
2385 callee: quote_ident!("encryptActionBoundArgs").as_callee(),
2386 args: vec![
2387 ExprOrSpread {
2388 spread: None,
2389 expr: Box::new(action_id.into()),
2390 },
2391 ExprOrSpread {
2392 spread: None,
2393 expr: Box::new(Expr::Array(ArrayLit {
2394 span: DUMMY_SP,
2395 elems: bound,
2396 })),
2397 },
2398 ],
2399 ..Default::default()
2400 })),
2401 },
2402 ],
2403 ..Default::default()
2404 })
2405 }
2406}
2407
2408fn detect_similar_strings(a: &str, b: &str) -> bool {
2423 let mut a = a.chars().collect::<Vec<char>>();
2424 let mut b = b.chars().collect::<Vec<char>>();
2425
2426 if a.len() < b.len() {
2427 (a, b) = (b, a);
2428 }
2429
2430 if a.len() == b.len() {
2431 let mut diff = 0;
2433 for i in 0..a.len() {
2434 if a[i] != b[i] {
2435 diff += 1;
2436 if diff > 2 {
2437 return false;
2438 }
2439 }
2440 }
2441
2442 diff != 0
2444 } else {
2445 if a.len() - b.len() > 1 {
2446 return false;
2447 }
2448
2449 for i in 0..b.len() {
2451 if a[i] != b[i] {
2452 return a[i + 1..] == b[i..];
2458 }
2459 }
2460
2461 true
2463 }
2464}
2465
2466fn has_body_directive(maybe_body: &Option<BlockStmt>) -> (bool, bool) {
2471 let mut is_action_fn = false;
2472 let mut is_cache_fn = false;
2473
2474 if let Some(body) = maybe_body {
2475 for stmt in body.stmts.iter() {
2476 match stmt {
2477 Stmt::Expr(ExprStmt {
2478 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
2479 ..
2480 }) => {
2481 if value == "use server" {
2482 is_action_fn = true;
2483 break;
2484 } else if value == "use cache" || value.starts_with("use cache: ") {
2485 is_cache_fn = true;
2486 break;
2487 }
2488 }
2489 _ => break,
2490 }
2491 }
2492 }
2493
2494 (is_action_fn, is_cache_fn)
2495}
2496
2497fn collect_idents_in_array_pat(elems: &[Option<Pat>], idents: &mut Vec<Ident>) {
2498 for elem in elems.iter().flatten() {
2499 match elem {
2500 Pat::Ident(ident) => {
2501 idents.push(ident.id.clone());
2502 }
2503 Pat::Array(array) => {
2504 collect_idents_in_array_pat(&array.elems, idents);
2505 }
2506 Pat::Object(object) => {
2507 collect_idents_in_object_pat(&object.props, idents);
2508 }
2509 Pat::Rest(rest) => {
2510 if let Pat::Ident(ident) = &*rest.arg {
2511 idents.push(ident.id.clone());
2512 }
2513 }
2514 Pat::Assign(AssignPat { left, .. }) => {
2515 collect_idents_in_pat(left, idents);
2516 }
2517 Pat::Expr(..) | Pat::Invalid(..) => {}
2518 }
2519 }
2520}
2521
2522fn collect_idents_in_object_pat(props: &[ObjectPatProp], idents: &mut Vec<Ident>) {
2523 for prop in props {
2524 match prop {
2525 ObjectPatProp::KeyValue(KeyValuePatProp { key, value }) => {
2526 if let PropName::Ident(ident) = key {
2527 idents.push(Ident::new(
2528 ident.sym.clone(),
2529 ident.span,
2530 SyntaxContext::empty(),
2531 ));
2532 }
2533
2534 match &**value {
2535 Pat::Ident(ident) => {
2536 idents.push(ident.id.clone());
2537 }
2538 Pat::Array(array) => {
2539 collect_idents_in_array_pat(&array.elems, idents);
2540 }
2541 Pat::Object(object) => {
2542 collect_idents_in_object_pat(&object.props, idents);
2543 }
2544 _ => {}
2545 }
2546 }
2547 ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
2548 idents.push(key.id.clone());
2549 }
2550 ObjectPatProp::Rest(RestPat { arg, .. }) => {
2551 if let Pat::Ident(ident) = &**arg {
2552 idents.push(ident.id.clone());
2553 }
2554 }
2555 }
2556 }
2557}
2558
2559fn collect_idents_in_var_decls(decls: &[VarDeclarator], idents: &mut Vec<Ident>) {
2560 for decl in decls {
2561 collect_idents_in_pat(&decl.name, idents);
2562 }
2563}
2564
2565fn collect_idents_in_pat(pat: &Pat, idents: &mut Vec<Ident>) {
2566 match pat {
2567 Pat::Ident(ident) => {
2568 idents.push(ident.id.clone());
2569 }
2570 Pat::Array(array) => {
2571 collect_idents_in_array_pat(&array.elems, idents);
2572 }
2573 Pat::Object(object) => {
2574 collect_idents_in_object_pat(&object.props, idents);
2575 }
2576 Pat::Assign(AssignPat { left, .. }) => {
2577 collect_idents_in_pat(left, idents);
2578 }
2579 Pat::Rest(RestPat { arg, .. }) => {
2580 if let Pat::Ident(ident) = &**arg {
2581 idents.push(ident.id.clone());
2582 }
2583 }
2584 Pat::Expr(..) | Pat::Invalid(..) => {}
2585 }
2586}
2587
2588fn collect_decl_idents_in_stmt(stmt: &Stmt, idents: &mut Vec<Ident>) {
2589 if let Stmt::Decl(decl) = stmt {
2590 match decl {
2591 Decl::Var(var) => {
2592 collect_idents_in_var_decls(&var.decls, idents);
2593 }
2594 Decl::Fn(fn_decl) => {
2595 idents.push(fn_decl.ident.clone());
2596 }
2597 _ => {}
2598 }
2599 }
2600}
2601
2602struct DirectiveVisitor<'a> {
2603 config: &'a Config,
2604 location: DirectiveLocation,
2605 directive: Option<Directive>,
2606 has_file_directive: bool,
2607 is_allowed_position: bool,
2608}
2609
2610impl DirectiveVisitor<'_> {
2611 fn visit_stmt(&mut self, stmt: &Stmt) -> bool {
2616 let in_fn_body = matches!(self.location, DirectiveLocation::FunctionBody);
2617 let allow_inline = self.config.is_react_server_layer || self.has_file_directive;
2618
2619 match stmt {
2620 Stmt::Expr(ExprStmt {
2621 expr: box Expr::Lit(Lit::Str(Str { value, span, .. })),
2622 ..
2623 }) => {
2624 if value == "use server" {
2625 if in_fn_body && !allow_inline {
2626 emit_error(ServerActionsErrorKind::InlineUseServerInClientComponent {
2627 span: *span,
2628 })
2629 } else if let Some(Directive::UseCache { .. }) = self.directive {
2630 emit_error(ServerActionsErrorKind::MultipleDirectives {
2631 span: *span,
2632 location: self.location.clone(),
2633 });
2634 } else if self.is_allowed_position {
2635 self.directive = Some(Directive::UseServer);
2636
2637 return true;
2638 } else {
2639 emit_error(ServerActionsErrorKind::MisplacedDirective {
2640 span: *span,
2641 directive: value.to_string(),
2642 location: self.location.clone(),
2643 });
2644 }
2645 } else if detect_similar_strings(value, "use server") {
2646 emit_error(ServerActionsErrorKind::MisspelledDirective {
2648 span: *span,
2649 directive: value.to_string(),
2650 expected_directive: "use server".to_string(),
2651 });
2652 } else
2653 if value == "use cache" || value.starts_with("use cache: ") {
2655 if in_fn_body && !allow_inline {
2656 emit_error(ServerActionsErrorKind::InlineUseCacheInClientComponent {
2657 span: *span,
2658 })
2659 } else if let Some(Directive::UseServer) = self.directive {
2660 emit_error(ServerActionsErrorKind::MultipleDirectives {
2661 span: *span,
2662 location: self.location.clone(),
2663 });
2664 } else if self.is_allowed_position {
2665 if !self.config.dynamic_io_enabled {
2666 emit_error(ServerActionsErrorKind::UseCacheWithoutDynamicIO {
2667 span: *span,
2668 directive: value.to_string(),
2669 });
2670 }
2671
2672 if value == "use cache" {
2673 self.directive = Some(Directive::UseCache {
2674 cache_kind: RcStr::from("default"),
2675 });
2676 } else {
2677 let cache_kind = RcStr::from(value.split_at("use cache: ".len()).1);
2679
2680 if !self.config.cache_kinds.contains(&cache_kind) {
2681 emit_error(ServerActionsErrorKind::UnknownCacheKind {
2682 span: *span,
2683 cache_kind: cache_kind.clone(),
2684 });
2685 }
2686
2687 self.directive = Some(Directive::UseCache { cache_kind });
2688 }
2689
2690 return true;
2691 } else {
2692 emit_error(ServerActionsErrorKind::MisplacedDirective {
2693 span: *span,
2694 directive: value.to_string(),
2695 location: self.location.clone(),
2696 });
2697 }
2698 } else {
2699 if detect_similar_strings(value, "use cache") {
2701 emit_error(ServerActionsErrorKind::MisspelledDirective {
2702 span: *span,
2703 directive: value.to_string(),
2704 expected_directive: "use cache".to_string(),
2705 });
2706 }
2707 }
2708 }
2709 Stmt::Expr(ExprStmt {
2710 expr:
2711 box Expr::Paren(ParenExpr {
2712 expr: box Expr::Lit(Lit::Str(Str { value, .. })),
2713 ..
2714 }),
2715 span,
2716 ..
2717 }) => {
2718 if value == "use server" || detect_similar_strings(value, "use server") {
2720 if self.is_allowed_position {
2721 emit_error(ServerActionsErrorKind::WrappedDirective {
2722 span: *span,
2723 directive: "use server".to_string(),
2724 });
2725 } else {
2726 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
2727 span: *span,
2728 directive: "use server".to_string(),
2729 location: self.location.clone(),
2730 });
2731 }
2732 } else if value == "use cache" || detect_similar_strings(value, "use cache") {
2733 if self.is_allowed_position {
2734 emit_error(ServerActionsErrorKind::WrappedDirective {
2735 span: *span,
2736 directive: "use cache".to_string(),
2737 });
2738 } else {
2739 emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
2740 span: *span,
2741 directive: "use cache".to_string(),
2742 location: self.location.clone(),
2743 });
2744 }
2745 }
2746 }
2747 _ => {
2748 self.is_allowed_position = false;
2750 }
2751 };
2752
2753 false
2754 }
2755}
2756
2757pub(crate) struct ClosureReplacer<'a> {
2758 used_ids: &'a [Name],
2759 private_ctxt: SyntaxContext,
2760}
2761
2762impl ClosureReplacer<'_> {
2763 fn index(&self, e: &Expr) -> Option<usize> {
2764 let name = Name::try_from(e).ok()?;
2765 self.used_ids.iter().position(|used_id| *used_id == name)
2766 }
2767}
2768
2769impl VisitMut for ClosureReplacer<'_> {
2770 fn visit_mut_expr(&mut self, e: &mut Expr) {
2771 e.visit_mut_children_with(self);
2772
2773 if let Some(index) = self.index(e) {
2774 *e = Expr::Ident(Ident::new(
2775 format!("$$ACTION_ARG_{index}").into(),
2777 DUMMY_SP,
2778 self.private_ctxt,
2779 ));
2780 }
2781 }
2782
2783 fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
2784 n.visit_mut_children_with(self);
2785
2786 if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
2787 let name = Name::from(&*i);
2788 if let Some(index) = self.used_ids.iter().position(|used_id| *used_id == name) {
2789 *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
2790 key: PropName::Ident(i.clone().into()),
2791 value: Box::new(Expr::Ident(Ident::new(
2792 format!("$$ACTION_ARG_{index}").into(),
2794 DUMMY_SP,
2795 self.private_ctxt,
2796 ))),
2797 })));
2798 }
2799 }
2800 }
2801
2802 noop_visit_mut_type!();
2803}
2804
2805#[derive(Debug, Clone, PartialEq, Eq)]
2806struct NamePart {
2807 prop: JsWord,
2808 is_member: bool,
2809 optional: bool,
2810}
2811
2812#[derive(Debug, Clone, PartialEq, Eq)]
2813struct Name(Id, Vec<NamePart>);
2814
2815impl From<&'_ Ident> for Name {
2816 fn from(value: &Ident) -> Self {
2817 Name(value.to_id(), vec![])
2818 }
2819}
2820
2821impl TryFrom<&'_ Expr> for Name {
2822 type Error = ();
2823
2824 fn try_from(value: &Expr) -> Result<Self, Self::Error> {
2825 match value {
2826 Expr::Ident(i) => Ok(Name(i.to_id(), vec![])),
2827 Expr::Member(e) => e.try_into(),
2828 Expr::OptChain(e) => e.try_into(),
2829 _ => Err(()),
2830 }
2831 }
2832}
2833
2834impl TryFrom<&'_ MemberExpr> for Name {
2835 type Error = ();
2836
2837 fn try_from(value: &MemberExpr) -> Result<Self, Self::Error> {
2838 match &value.prop {
2839 MemberProp::Ident(prop) => {
2840 let mut obj: Name = value.obj.as_ref().try_into()?;
2841 obj.1.push(NamePart {
2842 prop: prop.sym.clone(),
2843 is_member: true,
2844 optional: false,
2845 });
2846 Ok(obj)
2847 }
2848 _ => Err(()),
2849 }
2850 }
2851}
2852
2853impl TryFrom<&'_ OptChainExpr> for Name {
2854 type Error = ();
2855
2856 fn try_from(value: &OptChainExpr) -> Result<Self, Self::Error> {
2857 match &*value.base {
2858 OptChainBase::Member(m) => match &m.prop {
2859 MemberProp::Ident(prop) => {
2860 let mut obj: Name = m.obj.as_ref().try_into()?;
2861 obj.1.push(NamePart {
2862 prop: prop.sym.clone(),
2863 is_member: false,
2864 optional: value.optional,
2865 });
2866 Ok(obj)
2867 }
2868 _ => Err(()),
2869 },
2870 OptChainBase::Call(_) => Err(()),
2871 }
2872 }
2873}
2874
2875impl From<Name> for Box<Expr> {
2876 fn from(value: Name) -> Self {
2877 let mut expr = Box::new(Expr::Ident(value.0.into()));
2878
2879 for NamePart {
2880 prop,
2881 is_member,
2882 optional,
2883 } in value.1.into_iter()
2884 {
2885 if is_member {
2886 expr = Box::new(Expr::Member(MemberExpr {
2887 span: DUMMY_SP,
2888 obj: expr,
2889 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
2890 }));
2891 } else {
2892 expr = Box::new(Expr::OptChain(OptChainExpr {
2893 span: DUMMY_SP,
2894 base: Box::new(OptChainBase::Member(MemberExpr {
2895 span: DUMMY_SP,
2896 obj: expr,
2897 prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
2898 })),
2899 optional,
2900 }));
2901 }
2902 }
2903
2904 expr
2905 }
2906}
2907
2908fn emit_error(error_kind: ServerActionsErrorKind) {
2909 let (span, msg) = match error_kind {
2910 ServerActionsErrorKind::ExportedSyncFunction {
2911 span,
2912 in_action_file,
2913 } => (
2914 span,
2915 formatdoc! {
2916 r#"
2917 Only async functions are allowed to be exported in a {directive} file.
2918 "#,
2919 directive = if in_action_file {
2920 "\"use server\""
2921 } else {
2922 "\"use cache\""
2923 }
2924 },
2925 ),
2926 ServerActionsErrorKind::ForbiddenExpression {
2927 span,
2928 expr,
2929 directive,
2930 } => (
2931 span,
2932 formatdoc! {
2933 r#"
2934 {subject} cannot use `{expr}`.
2935 "#,
2936 subject = if let Directive::UseServer = directive {
2937 "Server Actions"
2938 } else {
2939 "\"use cache\" functions"
2940 }
2941 },
2942 ),
2943 ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span } => (
2944 span,
2945 formatdoc! {
2946 r#"
2947 It is not allowed to define inline "use cache" annotated class instance methods.
2948 To define cached functions, use functions, object method properties, or static class methods instead.
2949 "#
2950 },
2951 ),
2952 ServerActionsErrorKind::InlineUseCacheInClientComponent { span } => (
2953 span,
2954 formatdoc! {
2955 r#"
2956 It is not allowed to define inline "use cache" annotated functions in Client Components.
2957 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.
2958 "#
2959 },
2960 ),
2961 ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span } => (
2962 span,
2963 formatdoc! {
2964 r#"
2965 It is not allowed to define inline "use server" annotated class instance methods.
2966 To define Server Actions, use functions, object method properties, or static class methods instead.
2967 "#
2968 },
2969 ),
2970 ServerActionsErrorKind::InlineUseServerInClientComponent { span } => (
2971 span,
2972 formatdoc! {
2973 r#"
2974 It is not allowed to define inline "use server" annotated Server Actions in Client Components.
2975 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.
2976
2977 Read more: https://nextjs.org/docs/app/api-reference/functions/server-actions#with-client-components
2978 "#
2979 },
2980 ),
2981 ServerActionsErrorKind::InlineSyncFunction { span, directive } => (
2982 span,
2983 formatdoc! {
2984 r#"
2985 {subject} must be async functions.
2986 "#,
2987 subject = if let Directive::UseServer = directive {
2988 "Server Actions"
2989 } else {
2990 "\"use cache\" functions"
2991 }
2992 },
2993 ),
2994 ServerActionsErrorKind::MisplacedDirective {
2995 span,
2996 directive,
2997 location,
2998 } => (
2999 span,
3000 formatdoc! {
3001 r#"
3002 The "{directive}" directive must be at the top of the {location}.
3003 "#,
3004 location = match location {
3005 DirectiveLocation::Module => "file",
3006 DirectiveLocation::FunctionBody => "function body",
3007 }
3008 },
3009 ),
3010 ServerActionsErrorKind::MisplacedWrappedDirective {
3011 span,
3012 directive,
3013 location,
3014 } => (
3015 span,
3016 formatdoc! {
3017 r#"
3018 The "{directive}" directive must be at the top of the {location}, and cannot be wrapped in parentheses.
3019 "#,
3020 location = match location {
3021 DirectiveLocation::Module => "file",
3022 DirectiveLocation::FunctionBody => "function body",
3023 }
3024 },
3025 ),
3026 ServerActionsErrorKind::MisspelledDirective {
3027 span,
3028 directive,
3029 expected_directive,
3030 } => (
3031 span,
3032 formatdoc! {
3033 r#"
3034 Did you mean "{expected_directive}"? "{directive}" is not a supported directive name."
3035 "#
3036 },
3037 ),
3038 ServerActionsErrorKind::MultipleDirectives { span, location } => (
3039 span,
3040 formatdoc! {
3041 r#"
3042 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.
3043 "#,
3044 location = match location {
3045 DirectiveLocation::Module => "file",
3046 DirectiveLocation::FunctionBody => "function body",
3047 }
3048 },
3049 ),
3050 ServerActionsErrorKind::UnknownCacheKind { span, cache_kind } => (
3051 span,
3052 formatdoc! {
3053 r#"
3054 Unknown cache kind "{cache_kind}". Please configure a cache handler for this kind in the "experimental.cacheHandlers" object in your Next.js config.
3055 "#
3056 },
3057 ),
3058 ServerActionsErrorKind::UseCacheWithoutDynamicIO { span, directive } => (
3059 span,
3060 formatdoc! {
3061 r#"
3062 To use "{directive}", please enable the experimental feature flag "dynamicIO" in your Next.js config.
3063
3064 Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage
3065 "#
3066 },
3067 ),
3068 ServerActionsErrorKind::WrappedDirective { span, directive } => (
3069 span,
3070 formatdoc! {
3071 r#"
3072 The "{directive}" directive cannot be wrapped in parentheses.
3073 "#
3074 },
3075 ),
3076 };
3077
3078 HANDLER.with(|handler| handler.struct_span_err(span, &msg).emit());
3079}