next_custom_transforms/transforms/
server_actions.rs

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