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