next_custom_transforms/transforms/
server_actions.rs

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