Skip to main content

next_custom_transforms/transforms/
server_actions.rs

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