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    UseCacheWithoutExperimentalFlag {
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.src.is_some() {
1538                            disallowed_export_span = named.span;
1539                        } else {
1540                            for spec in &mut named.specifiers {
1541                                if let ExportSpecifier::Named(ExportNamedSpecifier {
1542                                    orig: ModuleExportName::Ident(ident),
1543                                    exported,
1544                                    ..
1545                                }) = spec
1546                                {
1547                                    if let Some(export_name) = exported {
1548                                        if let ModuleExportName::Ident(Ident { sym, .. }) =
1549                                            export_name
1550                                        {
1551                                            // export { foo as bar }
1552                                            self.exported_idents.push((
1553                                                ident.clone(),
1554                                                sym.clone(),
1555                                                self.generate_server_reference_id(
1556                                                    sym.as_ref(),
1557                                                    in_cache_file,
1558                                                    None,
1559                                                ),
1560                                            ));
1561                                        } else if let ModuleExportName::Str(str) = export_name {
1562                                            // export { foo as "bar" }
1563                                            self.exported_idents.push((
1564                                                ident.clone(),
1565                                                str.value.clone(),
1566                                                self.generate_server_reference_id(
1567                                                    str.value.as_ref(),
1568                                                    in_cache_file,
1569                                                    None,
1570                                                ),
1571                                            ));
1572                                        }
1573                                    } else {
1574                                        // export { foo }
1575                                        self.exported_idents.push((
1576                                            ident.clone(),
1577                                            ident.sym.clone(),
1578                                            self.generate_server_reference_id(
1579                                                ident.sym.as_ref(),
1580                                                in_cache_file,
1581                                                None,
1582                                            ),
1583                                        ));
1584                                    }
1585                                } else {
1586                                    disallowed_export_span = named.span;
1587                                }
1588                            }
1589                        }
1590                    }
1591                    ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultDecl(ExportDefaultDecl {
1592                        decl,
1593                        span,
1594                        ..
1595                    })) => match decl {
1596                        DefaultDecl::Fn(f) => {
1597                            let (is_action_fn, is_cache_fn) = has_body_directive(&f.function.body);
1598
1599                            let is_cache = if is_action_fn {
1600                                false
1601                            } else if is_cache_fn {
1602                                true
1603                            } else {
1604                                in_cache_file
1605                            };
1606
1607                            // If it's a self-annotated cache function, we need to skip
1608                            // collecting the exported ident. Otherwise it will be double-
1609                            // annotated.
1610                            // TODO(shu): This is a workaround. We should have a better way
1611                            // to skip self-annotated exports here.
1612                            if !(is_cache_fn && self.config.is_react_server_layer) {
1613                                let ref_id = self.generate_server_reference_id(
1614                                    "default",
1615                                    is_cache,
1616                                    Some(&f.function.params),
1617                                );
1618
1619                                if let Some(ident) = &f.ident {
1620                                    // export default function foo() {}
1621                                    self.exported_idents.push((
1622                                        ident.clone(),
1623                                        atom!("default"),
1624                                        ref_id,
1625                                    ));
1626                                } else {
1627                                    // export default function() {}
1628                                    // Use the span from the function expression
1629                                    let span = f.function.span;
1630
1631                                    let new_ident = Ident::new(
1632                                        self.gen_action_ident(),
1633                                        span,
1634                                        self.private_ctxt,
1635                                    );
1636
1637                                    f.ident = Some(new_ident.clone());
1638
1639                                    self.exported_idents.push((
1640                                        new_ident.clone(),
1641                                        atom!("default"),
1642                                        ref_id,
1643                                    ));
1644
1645                                    assign_name_to_ident(
1646                                        &new_ident,
1647                                        "default",
1648                                        &mut self.extra_items,
1649                                    );
1650                                }
1651                            }
1652                        }
1653                        DefaultDecl::TsInterfaceDecl(_) => {}
1654                        _ => {
1655                            disallowed_export_span = *span;
1656                        }
1657                    },
1658                    ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(default_expr)) => {
1659                        match &mut *default_expr.expr {
1660                            Expr::Fn(_f) => {}
1661                            Expr::Arrow(arrow) => {
1662                                // export default async () => {}
1663                                // Use the span of the arrow function
1664                                let span = arrow.span;
1665
1666                                let (is_action_fn, is_cache_fn) =
1667                                    has_body_directive(&if let BlockStmtOrExpr::BlockStmt(block) =
1668                                        &*arrow.body
1669                                    {
1670                                        Some(block.clone())
1671                                    } else {
1672                                        None
1673                                    });
1674
1675                                let is_cache = if is_action_fn {
1676                                    false
1677                                } else if is_cache_fn {
1678                                    true
1679                                } else {
1680                                    in_cache_file
1681                                };
1682
1683                                // If it's a self-annotated cache function, we need to skip
1684                                // collecting the exported ident. Otherwise it will be double-
1685                                // annotated.
1686                                // TODO(shu): This is a workaround. We should have a better way
1687                                // to skip self-annotated exports here.
1688                                if !(is_cache_fn && self.config.is_react_server_layer) {
1689                                    let new_ident = Ident::new(
1690                                        self.gen_action_ident(),
1691                                        span,
1692                                        self.private_ctxt,
1693                                    );
1694
1695                                    self.exported_idents.push((
1696                                        new_ident.clone(),
1697                                        atom!("default"),
1698                                        self.generate_server_reference_id(
1699                                            "default",
1700                                            is_cache,
1701                                            Some(
1702                                                &arrow
1703                                                    .params
1704                                                    .iter()
1705                                                    .map(|p| Param::from(p.clone()))
1706                                                    .collect(),
1707                                            ),
1708                                        ),
1709                                    ));
1710
1711                                    create_var_declarator(&new_ident, &mut self.extra_items);
1712                                    assign_name_to_ident(
1713                                        &new_ident,
1714                                        "default",
1715                                        &mut self.extra_items,
1716                                    );
1717
1718                                    *default_expr.expr =
1719                                        assign_arrow_expr(&new_ident, Expr::Arrow(arrow.clone()));
1720                                }
1721                            }
1722                            Expr::Ident(ident) => {
1723                                // export default foo
1724                                self.exported_idents.push((
1725                                    ident.clone(),
1726                                    atom!("default"),
1727                                    self.generate_server_reference_id(
1728                                        "default",
1729                                        in_cache_file,
1730                                        None,
1731                                    ),
1732                                ));
1733                            }
1734                            Expr::Call(call) => {
1735                                // export default fn()
1736                                // Determining a useful span here is tricky.
1737                                let span = call.span;
1738
1739                                let new_ident =
1740                                    Ident::new(self.gen_action_ident(), span, self.private_ctxt);
1741
1742                                self.exported_idents.push((
1743                                    new_ident.clone(),
1744                                    atom!("default"),
1745                                    self.generate_server_reference_id(
1746                                        "default",
1747                                        in_cache_file,
1748                                        None,
1749                                    ),
1750                                ));
1751
1752                                create_var_declarator(&new_ident, &mut self.extra_items);
1753                                assign_name_to_ident(&new_ident, "default", &mut self.extra_items);
1754
1755                                *default_expr.expr =
1756                                    assign_arrow_expr(&new_ident, Expr::Call(call.clone()));
1757                            }
1758                            _ => {
1759                                disallowed_export_span = default_expr.span;
1760                            }
1761                        }
1762                    }
1763                    ModuleItem::ModuleDecl(ModuleDecl::ExportAll(ExportAll { span, .. })) => {
1764                        disallowed_export_span = *span;
1765                    }
1766                    _ => {}
1767                }
1768
1769                if disallowed_export_span != DUMMY_SP {
1770                    emit_error(ServerActionsErrorKind::ExportedSyncFunction {
1771                        span: disallowed_export_span,
1772                        in_action_file,
1773                    });
1774
1775                    return;
1776                }
1777            }
1778
1779            stmt.visit_mut_with(self);
1780
1781            let mut new_stmt = stmt;
1782
1783            if let Some(expr) = &self.rewrite_default_fn_expr_to_proxy_expr {
1784                // If this happens, we need to replace the statement with a default export expr.
1785                new_stmt =
1786                    ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(ExportDefaultExpr {
1787                        span: DUMMY_SP,
1788                        expr: expr.clone(),
1789                    }));
1790                self.rewrite_default_fn_expr_to_proxy_expr = None;
1791            }
1792
1793            if self.config.is_react_server_layer || self.file_directive.is_none() {
1794                new.append(&mut self.hoisted_extra_items);
1795                new.push(new_stmt);
1796                new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
1797                new.append(&mut self.extra_items);
1798            }
1799        }
1800
1801        let mut actions = self.export_actions.take();
1802
1803        if in_action_file || in_cache_file && !self.config.is_react_server_layer {
1804            actions.extend(
1805                self.exported_idents
1806                    .iter()
1807                    .map(|e| (e.1.clone(), e.2.clone())),
1808            );
1809
1810            if !actions.is_empty() {
1811                self.has_action |= in_action_file;
1812                self.has_cache |= in_cache_file;
1813            }
1814        };
1815
1816        // Make it a hashmap of id -> name.
1817        let actions = actions
1818            .into_iter()
1819            .map(|a| (a.1, a.0))
1820            .collect::<ActionsMap>();
1821
1822        // If it's compiled in the client layer, each export field needs to be
1823        // wrapped by a reference creation call.
1824        let create_ref_ident = private_ident!("createServerReference");
1825        let call_server_ident = private_ident!("callServer");
1826        let find_source_map_url_ident = private_ident!("findSourceMapURL");
1827
1828        let client_layer_import = ((self.has_action || self.has_cache)
1829            && !self.config.is_react_server_layer)
1830            .then(|| {
1831                // import {
1832                //   createServerReference,
1833                //   callServer,
1834                //   findSourceMapURL
1835                // } from 'private-next-rsc-action-client-wrapper'
1836                // createServerReference("action_id")
1837                ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1838                    span: DUMMY_SP,
1839                    specifiers: vec![
1840                        ImportSpecifier::Named(ImportNamedSpecifier {
1841                            span: DUMMY_SP,
1842                            local: create_ref_ident.clone(),
1843                            imported: None,
1844                            is_type_only: false,
1845                        }),
1846                        ImportSpecifier::Named(ImportNamedSpecifier {
1847                            span: DUMMY_SP,
1848                            local: call_server_ident.clone(),
1849                            imported: None,
1850                            is_type_only: false,
1851                        }),
1852                        ImportSpecifier::Named(ImportNamedSpecifier {
1853                            span: DUMMY_SP,
1854                            local: find_source_map_url_ident.clone(),
1855                            imported: None,
1856                            is_type_only: false,
1857                        }),
1858                    ],
1859                    src: Box::new(Str {
1860                        span: DUMMY_SP,
1861                        value: atom!("private-next-rsc-action-client-wrapper"),
1862                        raw: None,
1863                    }),
1864                    type_only: false,
1865                    with: None,
1866                    phase: Default::default(),
1867                }))
1868            });
1869
1870        let mut client_layer_exports = FxIndexMap::default();
1871
1872        // If it's a "use server" or a "use cache" file, all exports need to be annotated.
1873        if should_track_exports {
1874            for (ident, export_name, ref_id) in self.exported_idents.iter() {
1875                if !self.config.is_react_server_layer {
1876                    if export_name == "default" {
1877                        let export_expr = ModuleItem::ModuleDecl(ModuleDecl::ExportDefaultExpr(
1878                            ExportDefaultExpr {
1879                                span: DUMMY_SP,
1880                                expr: Box::new(Expr::Call(CallExpr {
1881                                    // In development we generate these spans for sourcemapping with
1882                                    // better logs/errors
1883                                    // For production this is not generated because it would leak
1884                                    // server code when available from the browser.
1885                                    span: if self.config.is_react_server_layer
1886                                        || self.config.is_development
1887                                    {
1888                                        self.comments.add_pure_comment(ident.span.lo);
1889                                        ident.span
1890                                    } else {
1891                                        PURE_SP
1892                                    },
1893                                    callee: Callee::Expr(Box::new(Expr::Ident(
1894                                        create_ref_ident.clone(),
1895                                    ))),
1896                                    args: vec![
1897                                        ref_id.clone().as_arg(),
1898                                        call_server_ident.clone().as_arg(),
1899                                        Expr::undefined(DUMMY_SP).as_arg(),
1900                                        find_source_map_url_ident.clone().as_arg(),
1901                                        "default".as_arg(),
1902                                    ],
1903                                    ..Default::default()
1904                                })),
1905                            },
1906                        ));
1907                        client_layer_exports
1908                            .insert(atom!("default"), (export_expr, ref_id.clone()));
1909                    } else {
1910                        let export_expr =
1911                            ModuleItem::ModuleDecl(ModuleDecl::ExportDecl(ExportDecl {
1912                                span: DUMMY_SP,
1913                                decl: Decl::Var(Box::new(VarDecl {
1914                                    span: DUMMY_SP,
1915                                    kind: VarDeclKind::Var,
1916                                    decls: vec![VarDeclarator {
1917                                        span: DUMMY_SP,
1918                                        name: Pat::Ident(
1919                                            IdentName::new(
1920                                                export_name.clone(),
1921                                                // In development we generate these spans for
1922                                                // sourcemapping with better logs/errors
1923                                                // For production this is not generated because it
1924                                                // would leak server code when available from the
1925                                                // browser.
1926                                                if self.config.is_react_server_layer
1927                                                    || self.config.is_development
1928                                                {
1929                                                    ident.span
1930                                                } else {
1931                                                    DUMMY_SP
1932                                                },
1933                                            )
1934                                            .into(),
1935                                        ),
1936                                        init: Some(Box::new(Expr::Call(CallExpr {
1937                                            span: PURE_SP,
1938                                            callee: Callee::Expr(Box::new(Expr::Ident(
1939                                                create_ref_ident.clone(),
1940                                            ))),
1941                                            args: vec![
1942                                                ref_id.clone().as_arg(),
1943                                                call_server_ident.clone().as_arg(),
1944                                                Expr::undefined(DUMMY_SP).as_arg(),
1945                                                find_source_map_url_ident.clone().as_arg(),
1946                                                export_name.clone().as_arg(),
1947                                            ],
1948                                            ..Default::default()
1949                                        }))),
1950                                        definite: false,
1951                                    }],
1952                                    ..Default::default()
1953                                })),
1954                            }));
1955                        client_layer_exports
1956                            .insert(export_name.clone(), (export_expr, ref_id.clone()));
1957                    }
1958                } else if !in_cache_file {
1959                    self.annotations.push(Stmt::Expr(ExprStmt {
1960                        span: DUMMY_SP,
1961                        expr: Box::new(annotate_ident_as_server_reference(
1962                            ident.clone(),
1963                            ref_id.clone(),
1964                            ident.span,
1965                        )),
1966                    }));
1967                }
1968            }
1969
1970            // Ensure that the exports are functions by appending a runtime check:
1971            //
1972            //   import { ensureServerEntryExports } from 'private-next-rsc-action-validate'
1973            //   ensureServerEntryExports([action1, action2, ...])
1974            //
1975            // But it's only needed for the server layer, because on the client
1976            // layer they're transformed into references already.
1977            if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
1978                new.append(&mut self.extra_items);
1979
1980                // For "use cache" files, there's no need to do extra annotations.
1981                if !in_cache_file && !self.exported_idents.is_empty() {
1982                    let ensure_ident = private_ident!("ensureServerEntryExports");
1983                    new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
1984                        span: DUMMY_SP,
1985                        specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
1986                            span: DUMMY_SP,
1987                            local: ensure_ident.clone(),
1988                            imported: None,
1989                            is_type_only: false,
1990                        })],
1991                        src: Box::new(Str {
1992                            span: DUMMY_SP,
1993                            value: atom!("private-next-rsc-action-validate"),
1994                            raw: None,
1995                        }),
1996                        type_only: false,
1997                        with: None,
1998                        phase: Default::default(),
1999                    })));
2000                    new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2001                        span: DUMMY_SP,
2002                        expr: Box::new(Expr::Call(CallExpr {
2003                            span: DUMMY_SP,
2004                            callee: Callee::Expr(Box::new(Expr::Ident(ensure_ident))),
2005                            args: vec![ExprOrSpread {
2006                                spread: None,
2007                                expr: Box::new(Expr::Array(ArrayLit {
2008                                    span: DUMMY_SP,
2009                                    elems: self
2010                                        .exported_idents
2011                                        .iter()
2012                                        .map(|(ident, _, _)| {
2013                                            Some(ExprOrSpread {
2014                                                spread: None,
2015                                                expr: Box::new(Expr::Ident(ident.clone())),
2016                                            })
2017                                        })
2018                                        .collect(),
2019                                })),
2020                            }],
2021                            ..Default::default()
2022                        })),
2023                    })));
2024                }
2025
2026                // Append annotations to the end of the file.
2027                new.extend(self.annotations.drain(..).map(ModuleItem::Stmt));
2028            }
2029        }
2030
2031        // import { cache as $$cache__ } from "private-next-rsc-cache-wrapper";
2032        if self.has_cache && self.config.is_react_server_layer {
2033            new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2034                span: DUMMY_SP,
2035                specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2036                    span: DUMMY_SP,
2037                    local: quote_ident!("$$cache__").into(),
2038                    imported: Some(quote_ident!("cache").into()),
2039                    is_type_only: false,
2040                })],
2041                src: Box::new(Str {
2042                    span: DUMMY_SP,
2043                    value: atom!("private-next-rsc-cache-wrapper"),
2044                    raw: None,
2045                }),
2046                type_only: false,
2047                with: None,
2048                phase: Default::default(),
2049            })));
2050
2051            // Make it the first item
2052            new.rotate_right(1);
2053        }
2054
2055        if (self.has_action || self.has_cache) && self.config.is_react_server_layer {
2056            // Inlined actions are only allowed on the server layer.
2057            // import { registerServerReference } from 'private-next-rsc-server-reference'
2058            new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2059                span: DUMMY_SP,
2060                specifiers: vec![ImportSpecifier::Named(ImportNamedSpecifier {
2061                    span: DUMMY_SP,
2062                    local: quote_ident!("registerServerReference").into(),
2063                    imported: None,
2064                    is_type_only: false,
2065                })],
2066                src: Box::new(Str {
2067                    span: DUMMY_SP,
2068                    value: atom!("private-next-rsc-server-reference"),
2069                    raw: None,
2070                }),
2071                type_only: false,
2072                with: None,
2073                phase: Default::default(),
2074            })));
2075
2076            // Encryption and decryption only happens on the server layer.
2077            // import { encryptActionBoundArgs, decryptActionBoundArgs } from
2078            // 'private-next-rsc-action-encryption'
2079            new.push(ModuleItem::ModuleDecl(ModuleDecl::Import(ImportDecl {
2080                span: DUMMY_SP,
2081                specifiers: vec![
2082                    ImportSpecifier::Named(ImportNamedSpecifier {
2083                        span: DUMMY_SP,
2084                        local: quote_ident!("encryptActionBoundArgs").into(),
2085                        imported: None,
2086                        is_type_only: false,
2087                    }),
2088                    ImportSpecifier::Named(ImportNamedSpecifier {
2089                        span: DUMMY_SP,
2090                        local: quote_ident!("decryptActionBoundArgs").into(),
2091                        imported: None,
2092                        is_type_only: false,
2093                    }),
2094                ],
2095                src: Box::new(Str {
2096                    span: DUMMY_SP,
2097                    value: atom!("private-next-rsc-action-encryption"),
2098                    raw: None,
2099                }),
2100                type_only: false,
2101                with: None,
2102                phase: Default::default(),
2103            })));
2104
2105            // Make it the first item
2106            new.rotate_right(2);
2107        }
2108
2109        if self.has_action || self.has_cache {
2110            if self.config.is_react_server_layer {
2111                // Prepend a special comment to the top of the file.
2112                self.comments.add_leading(
2113                    self.start_pos,
2114                    Comment {
2115                        span: DUMMY_SP,
2116                        kind: CommentKind::Block,
2117                        text: generate_server_actions_comment(
2118                            &actions,
2119                            match self.mode {
2120                                ServerActionsMode::Webpack => None,
2121                                ServerActionsMode::Turbopack => Some(("", "")),
2122                            },
2123                        )
2124                        .into(),
2125                    },
2126                );
2127            } else {
2128                match self.mode {
2129                    ServerActionsMode::Webpack => {
2130                        self.comments.add_leading(
2131                            self.start_pos,
2132                            Comment {
2133                                span: DUMMY_SP,
2134                                kind: CommentKind::Block,
2135                                text: generate_server_actions_comment(&actions, None).into(),
2136                            },
2137                        );
2138                        new.push(client_layer_import.unwrap());
2139                        new.rotate_right(1);
2140                        new.extend(client_layer_exports.into_iter().map(|(_, (v, _))| v));
2141                    }
2142                    ServerActionsMode::Turbopack => {
2143                        new.push(ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2144                            expr: Box::new(Expr::Lit(Lit::Str(
2145                                atom!("use turbopack no side effects").into(),
2146                            ))),
2147                            span: DUMMY_SP,
2148                        })));
2149                        new.rotate_right(1);
2150                        for (export, (stmt, ref_id)) in client_layer_exports {
2151                            new.push(ModuleItem::ModuleDecl(ModuleDecl::ExportNamed(
2152                                NamedExport {
2153                                    specifiers: vec![ExportSpecifier::Named(
2154                                        ExportNamedSpecifier {
2155                                            span: DUMMY_SP,
2156                                            orig: ModuleExportName::Ident(export.clone().into()),
2157                                            exported: None,
2158                                            is_type_only: false,
2159                                        },
2160                                    )],
2161                                    src: Some(Box::new(
2162                                        program_to_data_url(
2163                                            &self.file_name,
2164                                            &self.cm,
2165                                            vec![
2166                                                ModuleItem::Stmt(Stmt::Expr(ExprStmt {
2167                                                    expr: Box::new(Expr::Lit(Lit::Str(
2168                                                        atom!("use turbopack no side effects")
2169                                                            .into(),
2170                                                    ))),
2171                                                    span: DUMMY_SP,
2172                                                })),
2173                                                client_layer_import.clone().unwrap(),
2174                                                stmt,
2175                                            ],
2176                                            Comment {
2177                                                span: DUMMY_SP,
2178                                                kind: CommentKind::Block,
2179                                                text: generate_server_actions_comment(
2180                                                    &std::iter::once((ref_id, export)).collect(),
2181                                                    Some((
2182                                                        &self.file_name,
2183                                                        self.file_query.as_ref().map_or("", |v| v),
2184                                                    )),
2185                                                )
2186                                                .into(),
2187                                            },
2188                                        )
2189                                        .into(),
2190                                    )),
2191                                    span: DUMMY_SP,
2192                                    type_only: false,
2193                                    with: None,
2194                                },
2195                            )));
2196                        }
2197                    }
2198                }
2199            }
2200        }
2201
2202        *stmts = new;
2203
2204        self.annotations = old_annotations;
2205    }
2206
2207    fn visit_mut_stmts(&mut self, stmts: &mut Vec<Stmt>) {
2208        let old_annotations = self.annotations.take();
2209
2210        let mut new = Vec::with_capacity(stmts.len());
2211        for mut stmt in stmts.take() {
2212            stmt.visit_mut_with(self);
2213
2214            new.push(stmt);
2215            new.append(&mut self.annotations);
2216        }
2217
2218        *stmts = new;
2219
2220        self.annotations = old_annotations;
2221    }
2222
2223    fn visit_mut_jsx_attr(&mut self, attr: &mut JSXAttr) {
2224        let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2225
2226        if let (Some(JSXAttrValue::JSXExprContainer(container)), JSXAttrName::Ident(ident_name)) =
2227            (&attr.value, &attr.name)
2228        {
2229            match &container.expr {
2230                JSXExpr::Expr(box Expr::Arrow(_)) | JSXExpr::Expr(box Expr::Fn(_)) => {
2231                    self.arrow_or_fn_expr_ident = Some(ident_name.clone().into());
2232                }
2233                _ => {}
2234            }
2235        }
2236
2237        attr.visit_mut_children_with(self);
2238        self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2239    }
2240
2241    fn visit_mut_var_declarator(&mut self, var_declarator: &mut VarDeclarator) {
2242        let old_in_exported_expr = self.in_exported_expr;
2243        let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.take();
2244
2245        if let (Pat::Ident(ident), Some(box Expr::Arrow(_) | box Expr::Fn(_))) =
2246            (&var_declarator.name, &var_declarator.init)
2247        {
2248            if self.in_module_level && self.exported_local_ids.contains(&ident.to_id()) {
2249                self.in_exported_expr = true
2250            }
2251
2252            self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2253        }
2254
2255        var_declarator.visit_mut_children_with(self);
2256
2257        self.in_exported_expr = old_in_exported_expr;
2258        self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2259    }
2260
2261    fn visit_mut_assign_expr(&mut self, assign_expr: &mut AssignExpr) {
2262        let old_arrow_or_fn_expr_ident = self.arrow_or_fn_expr_ident.clone();
2263
2264        if let (
2265            AssignTarget::Simple(SimpleAssignTarget::Ident(ident)),
2266            box Expr::Arrow(_) | box Expr::Fn(_),
2267        ) = (&assign_expr.left, &assign_expr.right)
2268        {
2269            // Ignore assignment expressions that we created.
2270            if !ident.id.to_id().0.starts_with("$$RSC_SERVER_") {
2271                self.arrow_or_fn_expr_ident = Some(ident.id.clone());
2272            }
2273        }
2274
2275        assign_expr.visit_mut_children_with(self);
2276        self.arrow_or_fn_expr_ident = old_arrow_or_fn_expr_ident;
2277    }
2278
2279    fn visit_mut_this_expr(&mut self, n: &mut ThisExpr) {
2280        if let ThisStatus::Forbidden { directive } = &self.this_status {
2281            emit_error(ServerActionsErrorKind::ForbiddenExpression {
2282                span: n.span,
2283                expr: "this".into(),
2284                directive: directive.clone(),
2285            });
2286        }
2287    }
2288
2289    fn visit_mut_super(&mut self, n: &mut Super) {
2290        if let ThisStatus::Forbidden { directive } = &self.this_status {
2291            emit_error(ServerActionsErrorKind::ForbiddenExpression {
2292                span: n.span,
2293                expr: "super".into(),
2294                directive: directive.clone(),
2295            });
2296        }
2297    }
2298
2299    fn visit_mut_ident(&mut self, n: &mut Ident) {
2300        if n.sym == *"arguments" {
2301            if let ThisStatus::Forbidden { directive } = &self.this_status {
2302                emit_error(ServerActionsErrorKind::ForbiddenExpression {
2303                    span: n.span,
2304                    expr: "arguments".into(),
2305                    directive: directive.clone(),
2306                });
2307            }
2308        }
2309    }
2310
2311    noop_visit_mut_type!();
2312}
2313
2314fn retain_names_from_declared_idents(
2315    child_names: &mut Vec<Name>,
2316    current_declared_idents: &[Ident],
2317) {
2318    // Collect the names to retain in a separate vector
2319    let mut retained_names = Vec::new();
2320
2321    for name in child_names.iter() {
2322        let mut should_retain = true;
2323
2324        // Merge child_names. For example if both `foo.bar` and `foo.bar.baz` are used,
2325        // we only need to keep `foo.bar` as it covers the other.
2326
2327        // Currently this is O(n^2) and we can potentially improve this to O(n log n)
2328        // by sorting or using a hashset.
2329        for another_name in child_names.iter() {
2330            if name != another_name
2331                && name.0 == another_name.0
2332                && name.1.len() >= another_name.1.len()
2333            {
2334                let mut is_prefix = true;
2335                for i in 0..another_name.1.len() {
2336                    if name.1[i] != another_name.1[i] {
2337                        is_prefix = false;
2338                        break;
2339                    }
2340                }
2341                if is_prefix {
2342                    should_retain = false;
2343                    break;
2344                }
2345            }
2346        }
2347
2348        if should_retain
2349            && current_declared_idents
2350                .iter()
2351                .any(|ident| ident.to_id() == name.0)
2352            && !retained_names.contains(name)
2353        {
2354            retained_names.push(name.clone());
2355        }
2356    }
2357
2358    // Replace the original child_names with the retained names
2359    *child_names = retained_names;
2360}
2361
2362fn wrap_cache_expr(expr: Box<Expr>, name: &str, id: &str, bound_args_len: usize) -> Box<Expr> {
2363    // expr -> $$cache__("name", "id", 0, expr)
2364    Box::new(Expr::Call(CallExpr {
2365        span: DUMMY_SP,
2366        callee: quote_ident!("$$cache__").as_callee(),
2367        args: vec![
2368            ExprOrSpread {
2369                spread: None,
2370                expr: Box::new(name.into()),
2371            },
2372            ExprOrSpread {
2373                spread: None,
2374                expr: Box::new(id.into()),
2375            },
2376            Number::from(bound_args_len).as_arg(),
2377            expr.as_arg(),
2378        ],
2379        ..Default::default()
2380    }))
2381}
2382
2383fn create_var_declarator(ident: &Ident, extra_items: &mut Vec<ModuleItem>) {
2384    // Create the variable `var $$ACTION_0;`
2385    extra_items.push(ModuleItem::Stmt(Stmt::Decl(Decl::Var(Box::new(VarDecl {
2386        span: DUMMY_SP,
2387        kind: VarDeclKind::Var,
2388        decls: vec![VarDeclarator {
2389            span: DUMMY_SP,
2390            name: ident.clone().into(),
2391            init: None,
2392            definite: Default::default(),
2393        }],
2394        ..Default::default()
2395    })))));
2396}
2397
2398fn assign_name_to_ident(ident: &Ident, name: &str, extra_items: &mut Vec<ModuleItem>) {
2399    // Assign a name with `Object.defineProperty($$ACTION_0, 'name', {value: 'default'})`
2400    extra_items.push(quote!(
2401        // WORKAROUND for https://github.com/microsoft/TypeScript/issues/61165
2402        // This should just be
2403        //
2404        //   "Object.defineProperty($action, \"name\", { value: $name, writable: false });"
2405        //
2406        // but due to the above typescript bug, `Object.defineProperty` calls are typechecked incorrectly
2407        // in js files, and it can cause false positives when typechecking our fixture files.
2408        "Object[\"defineProperty\"]($action, \"name\", { value: $name, writable: false });"
2409            as ModuleItem,
2410        action: Ident = ident.clone(),
2411        name: Expr = name.into(),
2412    ));
2413}
2414
2415fn assign_arrow_expr(ident: &Ident, expr: Expr) -> Expr {
2416    if let Expr::Paren(_paren) = &expr {
2417        expr
2418    } else {
2419        // Create the assignment `($$ACTION_0 = arrow)`
2420        Expr::Paren(ParenExpr {
2421            span: DUMMY_SP,
2422            expr: Box::new(Expr::Assign(AssignExpr {
2423                span: DUMMY_SP,
2424                left: ident.clone().into(),
2425                op: op!("="),
2426                right: Box::new(expr),
2427            })),
2428        })
2429    }
2430}
2431
2432fn annotate_ident_as_server_reference(ident: Ident, action_id: Atom, original_span: Span) -> Expr {
2433    // registerServerReference(reference, id, null)
2434    Expr::Call(CallExpr {
2435        span: original_span,
2436        callee: quote_ident!("registerServerReference").as_callee(),
2437        args: vec![
2438            ExprOrSpread {
2439                spread: None,
2440                expr: Box::new(Expr::Ident(ident)),
2441            },
2442            ExprOrSpread {
2443                spread: None,
2444                expr: Box::new(action_id.clone().into()),
2445            },
2446            ExprOrSpread {
2447                spread: None,
2448                expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
2449            },
2450        ],
2451        ..Default::default()
2452    })
2453}
2454
2455fn bind_args_to_ident(ident: Ident, bound: Vec<Option<ExprOrSpread>>, action_id: Atom) -> Expr {
2456    // ident.bind(null, [encryptActionBoundArgs("id", arg1, arg2, ...)])
2457    Expr::Call(CallExpr {
2458        span: DUMMY_SP,
2459        callee: Expr::Member(MemberExpr {
2460            span: DUMMY_SP,
2461            obj: Box::new(ident.into()),
2462            prop: MemberProp::Ident(quote_ident!("bind")),
2463        })
2464        .as_callee(),
2465        args: vec![
2466            ExprOrSpread {
2467                spread: None,
2468                expr: Box::new(Expr::Lit(Lit::Null(Null { span: DUMMY_SP }))),
2469            },
2470            ExprOrSpread {
2471                spread: None,
2472                expr: Box::new(Expr::Call(CallExpr {
2473                    span: DUMMY_SP,
2474                    callee: quote_ident!("encryptActionBoundArgs").as_callee(),
2475                    args: std::iter::once(ExprOrSpread {
2476                        spread: None,
2477                        expr: Box::new(action_id.into()),
2478                    })
2479                    .chain(bound.into_iter().flatten())
2480                    .collect(),
2481                    ..Default::default()
2482                })),
2483            },
2484        ],
2485        ..Default::default()
2486    })
2487}
2488
2489// Detects if two strings are similar (but not the same).
2490// This implementation is fast and simple as it allows only one
2491// edit (add, remove, edit, swap), instead of using a N^2 Levenshtein algorithm.
2492//
2493// Example of similar strings of "use server":
2494// "use servers",
2495// "use-server",
2496// "use sevrer",
2497// "use srever",
2498// "use servre",
2499// "user server",
2500//
2501// This avoids accidental typos as there's currently no other static analysis
2502// tool to help when these mistakes happen.
2503fn detect_similar_strings(a: &str, b: &str) -> bool {
2504    let mut a = a.chars().collect::<Vec<char>>();
2505    let mut b = b.chars().collect::<Vec<char>>();
2506
2507    if a.len() < b.len() {
2508        (a, b) = (b, a);
2509    }
2510
2511    if a.len() == b.len() {
2512        // Same length, get the number of character differences.
2513        let mut diff = 0;
2514        for i in 0..a.len() {
2515            if a[i] != b[i] {
2516                diff += 1;
2517                if diff > 2 {
2518                    return false;
2519                }
2520            }
2521        }
2522
2523        // Should be 1 or 2, but not 0.
2524        diff != 0
2525    } else {
2526        if a.len() - b.len() > 1 {
2527            return false;
2528        }
2529
2530        // A has one more character than B.
2531        for i in 0..b.len() {
2532            if a[i] != b[i] {
2533                // This should be the only difference, a[i+1..] should be equal to b[i..].
2534                // Otherwise, they're not considered similar.
2535                // A: "use srerver"
2536                // B: "use server"
2537                //          ^
2538                return a[i + 1..] == b[i..];
2539            }
2540        }
2541
2542        // This happens when the last character of A is an extra character.
2543        true
2544    }
2545}
2546
2547// Check if the function or arrow function has any action or cache directives,
2548// without mutating the function body or erroring out.
2549// This is used to quickly determine if we need to use the module-level
2550// directives for this function or not.
2551fn has_body_directive(maybe_body: &Option<BlockStmt>) -> (bool, bool) {
2552    let mut is_action_fn = false;
2553    let mut is_cache_fn = false;
2554
2555    if let Some(body) = maybe_body {
2556        for stmt in body.stmts.iter() {
2557            match stmt {
2558                Stmt::Expr(ExprStmt {
2559                    expr: box Expr::Lit(Lit::Str(Str { value, .. })),
2560                    ..
2561                }) => {
2562                    if value == "use server" {
2563                        is_action_fn = true;
2564                        break;
2565                    } else if value == "use cache" || value.starts_with("use cache: ") {
2566                        is_cache_fn = true;
2567                        break;
2568                    }
2569                }
2570                _ => break,
2571            }
2572        }
2573    }
2574
2575    (is_action_fn, is_cache_fn)
2576}
2577
2578fn collect_idents_in_array_pat(elems: &[Option<Pat>], idents: &mut Vec<Ident>) {
2579    for elem in elems.iter().flatten() {
2580        match elem {
2581            Pat::Ident(ident) => {
2582                idents.push(ident.id.clone());
2583            }
2584            Pat::Array(array) => {
2585                collect_idents_in_array_pat(&array.elems, idents);
2586            }
2587            Pat::Object(object) => {
2588                collect_idents_in_object_pat(&object.props, idents);
2589            }
2590            Pat::Rest(rest) => {
2591                if let Pat::Ident(ident) = &*rest.arg {
2592                    idents.push(ident.id.clone());
2593                }
2594            }
2595            Pat::Assign(AssignPat { left, .. }) => {
2596                collect_idents_in_pat(left, idents);
2597            }
2598            Pat::Expr(..) | Pat::Invalid(..) => {}
2599        }
2600    }
2601}
2602
2603fn collect_idents_in_object_pat(props: &[ObjectPatProp], idents: &mut Vec<Ident>) {
2604    for prop in props {
2605        match prop {
2606            ObjectPatProp::KeyValue(KeyValuePatProp { key, value }) => {
2607                if let PropName::Ident(ident) = key {
2608                    idents.push(Ident::new(
2609                        ident.sym.clone(),
2610                        ident.span,
2611                        SyntaxContext::empty(),
2612                    ));
2613                }
2614
2615                match &**value {
2616                    Pat::Ident(ident) => {
2617                        idents.push(ident.id.clone());
2618                    }
2619                    Pat::Array(array) => {
2620                        collect_idents_in_array_pat(&array.elems, idents);
2621                    }
2622                    Pat::Object(object) => {
2623                        collect_idents_in_object_pat(&object.props, idents);
2624                    }
2625                    _ => {}
2626                }
2627            }
2628            ObjectPatProp::Assign(AssignPatProp { key, .. }) => {
2629                idents.push(key.id.clone());
2630            }
2631            ObjectPatProp::Rest(RestPat { arg, .. }) => {
2632                if let Pat::Ident(ident) = &**arg {
2633                    idents.push(ident.id.clone());
2634                }
2635            }
2636        }
2637    }
2638}
2639
2640fn collect_idents_in_var_decls(decls: &[VarDeclarator], idents: &mut Vec<Ident>) {
2641    for decl in decls {
2642        collect_idents_in_pat(&decl.name, idents);
2643    }
2644}
2645
2646fn collect_idents_in_pat(pat: &Pat, idents: &mut Vec<Ident>) {
2647    match pat {
2648        Pat::Ident(ident) => {
2649            idents.push(ident.id.clone());
2650        }
2651        Pat::Array(array) => {
2652            collect_idents_in_array_pat(&array.elems, idents);
2653        }
2654        Pat::Object(object) => {
2655            collect_idents_in_object_pat(&object.props, idents);
2656        }
2657        Pat::Assign(AssignPat { left, .. }) => {
2658            collect_idents_in_pat(left, idents);
2659        }
2660        Pat::Rest(RestPat { arg, .. }) => {
2661            if let Pat::Ident(ident) = &**arg {
2662                idents.push(ident.id.clone());
2663            }
2664        }
2665        Pat::Expr(..) | Pat::Invalid(..) => {}
2666    }
2667}
2668
2669fn collect_decl_idents_in_stmt(stmt: &Stmt, idents: &mut Vec<Ident>) {
2670    if let Stmt::Decl(decl) = stmt {
2671        match decl {
2672            Decl::Var(var) => {
2673                collect_idents_in_var_decls(&var.decls, idents);
2674            }
2675            Decl::Fn(fn_decl) => {
2676                idents.push(fn_decl.ident.clone());
2677            }
2678            _ => {}
2679        }
2680    }
2681}
2682
2683struct DirectiveVisitor<'a> {
2684    config: &'a Config,
2685    location: DirectiveLocation,
2686    directive: Option<Directive>,
2687    has_file_directive: bool,
2688    is_allowed_position: bool,
2689    use_cache_telemetry_tracker: Rc<RefCell<FxHashMap<String, usize>>>,
2690}
2691
2692impl DirectiveVisitor<'_> {
2693    /**
2694     * Returns `true` if the statement contains a server directive.
2695     * The found directive is assigned to `DirectiveVisitor::directive`.
2696     */
2697    fn visit_stmt(&mut self, stmt: &Stmt) -> bool {
2698        let in_fn_body = matches!(self.location, DirectiveLocation::FunctionBody);
2699        let allow_inline = self.config.is_react_server_layer || self.has_file_directive;
2700
2701        match stmt {
2702            Stmt::Expr(ExprStmt {
2703                expr: box Expr::Lit(Lit::Str(Str { value, span, .. })),
2704                ..
2705            }) => {
2706                if value == "use server" {
2707                    if in_fn_body && !allow_inline {
2708                        emit_error(ServerActionsErrorKind::InlineUseServerInClientComponent {
2709                            span: *span,
2710                        })
2711                    } else if let Some(Directive::UseCache { .. }) = self.directive {
2712                        emit_error(ServerActionsErrorKind::MultipleDirectives {
2713                            span: *span,
2714                            location: self.location.clone(),
2715                        });
2716                    } else if self.is_allowed_position {
2717                        self.directive = Some(Directive::UseServer);
2718
2719                        return true;
2720                    } else {
2721                        emit_error(ServerActionsErrorKind::MisplacedDirective {
2722                            span: *span,
2723                            directive: value.to_string(),
2724                            location: self.location.clone(),
2725                        });
2726                    }
2727                } else if detect_similar_strings(value, "use server") {
2728                    // Detect typo of "use server"
2729                    emit_error(ServerActionsErrorKind::MisspelledDirective {
2730                        span: *span,
2731                        directive: value.to_string(),
2732                        expected_directive: "use server".to_string(),
2733                    });
2734                } else if value == "use action" {
2735                    emit_error(ServerActionsErrorKind::MisspelledDirective {
2736                        span: *span,
2737                        directive: value.to_string(),
2738                        expected_directive: "use server".to_string(),
2739                    });
2740                } else
2741                // `use cache` or `use cache: foo`
2742                if value == "use cache" || value.starts_with("use cache: ") {
2743                    // Increment telemetry counter tracking usage of "use cache" directives
2744
2745                    if in_fn_body && !allow_inline {
2746                        emit_error(ServerActionsErrorKind::InlineUseCacheInClientComponent {
2747                            span: *span,
2748                        })
2749                    } else if let Some(Directive::UseServer) = self.directive {
2750                        emit_error(ServerActionsErrorKind::MultipleDirectives {
2751                            span: *span,
2752                            location: self.location.clone(),
2753                        });
2754                    } else if self.is_allowed_position {
2755                        if !self.config.use_cache_enabled {
2756                            emit_error(ServerActionsErrorKind::UseCacheWithoutExperimentalFlag {
2757                                span: *span,
2758                                directive: value.to_string(),
2759                            });
2760                        }
2761
2762                        if value == "use cache" {
2763                            self.directive = Some(Directive::UseCache {
2764                                cache_kind: rcstr!("default"),
2765                            });
2766                            self.increment_cache_usage_counter("default");
2767                        } else {
2768                            // Slice the value after "use cache: "
2769                            let cache_kind = RcStr::from(value.split_at("use cache: ".len()).1);
2770
2771                            if !self.config.cache_kinds.contains(&cache_kind) {
2772                                emit_error(ServerActionsErrorKind::UnknownCacheKind {
2773                                    span: *span,
2774                                    cache_kind: cache_kind.clone(),
2775                                });
2776                            }
2777
2778                            self.increment_cache_usage_counter(&cache_kind);
2779                            self.directive = Some(Directive::UseCache { cache_kind });
2780                        }
2781
2782                        return true;
2783                    } else {
2784                        emit_error(ServerActionsErrorKind::MisplacedDirective {
2785                            span: *span,
2786                            directive: value.to_string(),
2787                            location: self.location.clone(),
2788                        });
2789                    }
2790                } else {
2791                    // Detect typo of "use cache"
2792                    if detect_similar_strings(value, "use cache") {
2793                        emit_error(ServerActionsErrorKind::MisspelledDirective {
2794                            span: *span,
2795                            directive: value.to_string(),
2796                            expected_directive: "use cache".to_string(),
2797                        });
2798                    }
2799                }
2800            }
2801            Stmt::Expr(ExprStmt {
2802                expr:
2803                    box Expr::Paren(ParenExpr {
2804                        expr: box Expr::Lit(Lit::Str(Str { value, .. })),
2805                        ..
2806                    }),
2807                span,
2808                ..
2809            }) => {
2810                // Match `("use server")`.
2811                if value == "use server" || detect_similar_strings(value, "use server") {
2812                    if self.is_allowed_position {
2813                        emit_error(ServerActionsErrorKind::WrappedDirective {
2814                            span: *span,
2815                            directive: "use server".to_string(),
2816                        });
2817                    } else {
2818                        emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
2819                            span: *span,
2820                            directive: "use server".to_string(),
2821                            location: self.location.clone(),
2822                        });
2823                    }
2824                } else if value == "use cache" || detect_similar_strings(value, "use cache") {
2825                    if self.is_allowed_position {
2826                        emit_error(ServerActionsErrorKind::WrappedDirective {
2827                            span: *span,
2828                            directive: "use cache".to_string(),
2829                        });
2830                    } else {
2831                        emit_error(ServerActionsErrorKind::MisplacedWrappedDirective {
2832                            span: *span,
2833                            directive: "use cache".to_string(),
2834                            location: self.location.clone(),
2835                        });
2836                    }
2837                }
2838            }
2839            _ => {
2840                // Directives must not be placed after other statements.
2841                self.is_allowed_position = false;
2842            }
2843        };
2844
2845        false
2846    }
2847
2848    // Increment telemetry counter tracking usage of "use cache" directives
2849    fn increment_cache_usage_counter(&mut self, cache_kind: &str) {
2850        let mut tracker_map = RefCell::borrow_mut(&self.use_cache_telemetry_tracker);
2851        let entry = tracker_map.entry(cache_kind.to_string());
2852        match entry {
2853            hash_map::Entry::Occupied(mut occupied) => {
2854                *occupied.get_mut() += 1;
2855            }
2856            hash_map::Entry::Vacant(vacant) => {
2857                vacant.insert(1);
2858            }
2859        }
2860    }
2861}
2862
2863pub(crate) struct ClosureReplacer<'a> {
2864    used_ids: &'a [Name],
2865    private_ctxt: SyntaxContext,
2866}
2867
2868impl ClosureReplacer<'_> {
2869    fn index(&self, e: &Expr) -> Option<usize> {
2870        let name = Name::try_from(e).ok()?;
2871        self.used_ids.iter().position(|used_id| *used_id == name)
2872    }
2873}
2874
2875impl VisitMut for ClosureReplacer<'_> {
2876    fn visit_mut_expr(&mut self, e: &mut Expr) {
2877        e.visit_mut_children_with(self);
2878
2879        if let Some(index) = self.index(e) {
2880            *e = Expr::Ident(Ident::new(
2881                // $$ACTION_ARG_0
2882                format!("$$ACTION_ARG_{index}").into(),
2883                DUMMY_SP,
2884                self.private_ctxt,
2885            ));
2886        }
2887    }
2888
2889    fn visit_mut_prop_or_spread(&mut self, n: &mut PropOrSpread) {
2890        n.visit_mut_children_with(self);
2891
2892        if let PropOrSpread::Prop(box Prop::Shorthand(i)) = n {
2893            let name = Name::from(&*i);
2894            if let Some(index) = self.used_ids.iter().position(|used_id| *used_id == name) {
2895                *n = PropOrSpread::Prop(Box::new(Prop::KeyValue(KeyValueProp {
2896                    key: PropName::Ident(i.clone().into()),
2897                    value: Box::new(Expr::Ident(Ident::new(
2898                        // $$ACTION_ARG_0
2899                        format!("$$ACTION_ARG_{index}").into(),
2900                        DUMMY_SP,
2901                        self.private_ctxt,
2902                    ))),
2903                })));
2904            }
2905        }
2906    }
2907
2908    noop_visit_mut_type!();
2909}
2910
2911#[derive(Debug, Clone, PartialEq, Eq)]
2912struct NamePart {
2913    prop: Atom,
2914    is_member: bool,
2915    optional: bool,
2916}
2917
2918#[derive(Debug, Clone, PartialEq, Eq)]
2919struct Name(Id, Vec<NamePart>);
2920
2921impl From<&'_ Ident> for Name {
2922    fn from(value: &Ident) -> Self {
2923        Name(value.to_id(), vec![])
2924    }
2925}
2926
2927impl TryFrom<&'_ Expr> for Name {
2928    type Error = ();
2929
2930    fn try_from(value: &Expr) -> Result<Self, Self::Error> {
2931        match value {
2932            Expr::Ident(i) => Ok(Name(i.to_id(), vec![])),
2933            Expr::Member(e) => e.try_into(),
2934            Expr::OptChain(e) => e.try_into(),
2935            _ => Err(()),
2936        }
2937    }
2938}
2939
2940impl TryFrom<&'_ MemberExpr> for Name {
2941    type Error = ();
2942
2943    fn try_from(value: &MemberExpr) -> Result<Self, Self::Error> {
2944        match &value.prop {
2945            MemberProp::Ident(prop) => {
2946                let mut obj: Name = value.obj.as_ref().try_into()?;
2947                obj.1.push(NamePart {
2948                    prop: prop.sym.clone(),
2949                    is_member: true,
2950                    optional: false,
2951                });
2952                Ok(obj)
2953            }
2954            _ => Err(()),
2955        }
2956    }
2957}
2958
2959impl TryFrom<&'_ OptChainExpr> for Name {
2960    type Error = ();
2961
2962    fn try_from(value: &OptChainExpr) -> Result<Self, Self::Error> {
2963        match &*value.base {
2964            OptChainBase::Member(m) => match &m.prop {
2965                MemberProp::Ident(prop) => {
2966                    let mut obj: Name = m.obj.as_ref().try_into()?;
2967                    obj.1.push(NamePart {
2968                        prop: prop.sym.clone(),
2969                        is_member: false,
2970                        optional: value.optional,
2971                    });
2972                    Ok(obj)
2973                }
2974                _ => Err(()),
2975            },
2976            OptChainBase::Call(_) => Err(()),
2977        }
2978    }
2979}
2980
2981impl From<Name> for Box<Expr> {
2982    fn from(value: Name) -> Self {
2983        let mut expr = Box::new(Expr::Ident(value.0.into()));
2984
2985        for NamePart {
2986            prop,
2987            is_member,
2988            optional,
2989        } in value.1.into_iter()
2990        {
2991            if is_member {
2992                expr = Box::new(Expr::Member(MemberExpr {
2993                    span: DUMMY_SP,
2994                    obj: expr,
2995                    prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
2996                }));
2997            } else {
2998                expr = Box::new(Expr::OptChain(OptChainExpr {
2999                    span: DUMMY_SP,
3000                    base: Box::new(OptChainBase::Member(MemberExpr {
3001                        span: DUMMY_SP,
3002                        obj: expr,
3003                        prop: MemberProp::Ident(IdentName::new(prop, DUMMY_SP)),
3004                    })),
3005                    optional,
3006                }));
3007            }
3008        }
3009
3010        expr
3011    }
3012}
3013
3014fn emit_error(error_kind: ServerActionsErrorKind) {
3015    let (span, msg) = match error_kind {
3016        ServerActionsErrorKind::ExportedSyncFunction {
3017            span,
3018            in_action_file,
3019        } => (
3020            span,
3021            formatdoc! {
3022                r#"
3023                    Only async functions are allowed to be exported in a {directive} file.
3024                "#,
3025                directive = if in_action_file {
3026                    "\"use server\""
3027                } else {
3028                    "\"use cache\""
3029                }
3030            },
3031        ),
3032        ServerActionsErrorKind::ForbiddenExpression {
3033            span,
3034            expr,
3035            directive,
3036        } => (
3037            span,
3038            formatdoc! {
3039                r#"
3040                    {subject} cannot use `{expr}`.
3041                "#,
3042                subject = if let Directive::UseServer = directive {
3043                    "Server Actions"
3044                } else {
3045                    "\"use cache\" functions"
3046                }
3047            },
3048        ),
3049        ServerActionsErrorKind::InlineUseCacheInClassInstanceMethod { span } => (
3050            span,
3051            formatdoc! {
3052                r#"
3053                    It is not allowed to define inline "use cache" annotated class instance methods.
3054                    To define cached functions, use functions, object method properties, or static class methods instead.
3055                "#
3056            },
3057        ),
3058        ServerActionsErrorKind::InlineUseCacheInClientComponent { span } => (
3059            span,
3060            formatdoc! {
3061                r#"
3062                    It is not allowed to define inline "use cache" annotated functions in Client Components.
3063                    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.
3064                "#
3065            },
3066        ),
3067        ServerActionsErrorKind::InlineUseServerInClassInstanceMethod { span } => (
3068            span,
3069            formatdoc! {
3070                r#"
3071                    It is not allowed to define inline "use server" annotated class instance methods.
3072                    To define Server Actions, use functions, object method properties, or static class methods instead.
3073                "#
3074            },
3075        ),
3076        ServerActionsErrorKind::InlineUseServerInClientComponent { span } => (
3077            span,
3078            formatdoc! {
3079                r#"
3080                    It is not allowed to define inline "use server" annotated Server Actions in Client Components.
3081                    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.
3082
3083                    Read more: https://nextjs.org/docs/app/api-reference/directives/use-server#using-server-functions-in-a-client-component
3084                "#
3085            },
3086        ),
3087        ServerActionsErrorKind::InlineSyncFunction { span, directive } => (
3088            span,
3089            formatdoc! {
3090                r#"
3091                    {subject} must be async functions.
3092                "#,
3093                subject = if let Directive::UseServer = directive {
3094                    "Server Actions"
3095                } else {
3096                    "\"use cache\" functions"
3097                }
3098            },
3099        ),
3100        ServerActionsErrorKind::MisplacedDirective {
3101            span,
3102            directive,
3103            location,
3104        } => (
3105            span,
3106            formatdoc! {
3107                r#"
3108                    The "{directive}" directive must be at the top of the {location}.
3109                "#,
3110                location = match location {
3111                    DirectiveLocation::Module => "file",
3112                    DirectiveLocation::FunctionBody => "function body",
3113                }
3114            },
3115        ),
3116        ServerActionsErrorKind::MisplacedWrappedDirective {
3117            span,
3118            directive,
3119            location,
3120        } => (
3121            span,
3122            formatdoc! {
3123                r#"
3124                    The "{directive}" directive must be at the top of the {location}, and cannot be wrapped in parentheses.
3125                "#,
3126                location = match location {
3127                    DirectiveLocation::Module => "file",
3128                    DirectiveLocation::FunctionBody => "function body",
3129                }
3130            },
3131        ),
3132        ServerActionsErrorKind::MisspelledDirective {
3133            span,
3134            directive,
3135            expected_directive,
3136        } => (
3137            span,
3138            formatdoc! {
3139                r#"
3140                    Did you mean "{expected_directive}"? "{directive}" is not a supported directive name."
3141                "#
3142            },
3143        ),
3144        ServerActionsErrorKind::MultipleDirectives { span, location } => (
3145            span,
3146            formatdoc! {
3147                r#"
3148                    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.
3149                "#,
3150                location = match location {
3151                    DirectiveLocation::Module => "file",
3152                    DirectiveLocation::FunctionBody => "function body",
3153                }
3154            },
3155        ),
3156        ServerActionsErrorKind::UnknownCacheKind { span, cache_kind } => (
3157            span,
3158            formatdoc! {
3159                r#"
3160                    Unknown cache kind "{cache_kind}". Please configure a cache handler for this kind in the "experimental.cacheHandlers" object in your Next.js config.
3161                "#
3162            },
3163        ),
3164        ServerActionsErrorKind::UseCacheWithoutExperimentalFlag { span, directive } => (
3165            span,
3166            formatdoc! {
3167                r#"
3168                    To use "{directive}", please enable the experimental feature flag "useCache" in your Next.js config.
3169
3170                    Read more: https://nextjs.org/docs/canary/app/api-reference/directives/use-cache#usage
3171                "#
3172            },
3173        ),
3174        ServerActionsErrorKind::WrappedDirective { span, directive } => (
3175            span,
3176            formatdoc! {
3177                r#"
3178                    The "{directive}" directive cannot be wrapped in parentheses.
3179                "#
3180            },
3181        ),
3182    };
3183
3184    HANDLER.with(|handler| handler.struct_span_err(span, &msg).emit());
3185}
3186
3187fn program_to_data_url(
3188    file_name: &str,
3189    cm: &Arc<SourceMap>,
3190    body: Vec<ModuleItem>,
3191    prepend_comment: Comment,
3192) -> String {
3193    let module_span = Span::dummy_with_cmt();
3194    let comments = SingleThreadedComments::default();
3195    comments.add_leading(module_span.lo, prepend_comment);
3196
3197    let program = &Program::Module(Module {
3198        span: module_span,
3199        body,
3200        shebang: None,
3201    });
3202
3203    let mut output = vec![];
3204    let mut mappings = vec![];
3205    let mut emitter = Emitter {
3206        cfg: codegen::Config::default().with_minify(true),
3207        cm: cm.clone(),
3208        wr: Box::new(JsWriter::new(
3209            cm.clone(),
3210            " ",
3211            &mut output,
3212            Some(&mut mappings),
3213        )),
3214        comments: Some(&comments),
3215    };
3216
3217    emitter.emit_program(program).unwrap();
3218    drop(emitter);
3219
3220    pub struct InlineSourcesContentConfig<'a> {
3221        folder_path: Option<&'a Path>,
3222    }
3223    // This module will be placed at `some/path/to/data:28a9d2` where the original input file lives
3224    // at `some/path/to/actions.js`. So we need to generate a relative path, usually `./actions.js`
3225    impl SourceMapGenConfig for InlineSourcesContentConfig<'_> {
3226        fn file_name_to_source(&self, file: &FileName) -> String {
3227            let FileName::Custom(file) = file else {
3228                // Turbopack uses FileName::Custom for the `[project]/...` paths
3229                return file.to_string();
3230            };
3231            let Some(folder_path) = &self.folder_path else {
3232                return file.to_string();
3233            };
3234
3235            if let Some(rel_path) = diff_paths(file, folder_path) {
3236                format!("./{}", rel_path.display())
3237            } else {
3238                file.to_string()
3239            }
3240        }
3241
3242        fn inline_sources_content(&self, _f: &FileName) -> bool {
3243            true
3244        }
3245    }
3246
3247    let map = cm.build_source_map(
3248        &mappings,
3249        None,
3250        InlineSourcesContentConfig {
3251            folder_path: PathBuf::from(format!("[project]/{file_name}")).parent(),
3252        },
3253    );
3254    let map = {
3255        if map.get_token_count() > 0 {
3256            let mut buf = vec![];
3257            map.to_writer(&mut buf)
3258                .expect("failed to generate sourcemap");
3259            Some(buf)
3260        } else {
3261            None
3262        }
3263    };
3264
3265    let mut output = String::from_utf8(output).expect("codegen generated non-utf8 output");
3266    if let Some(map) = map {
3267        output.extend(
3268            format!(
3269                "\n//# sourceMappingURL=data:application/json;base64,{}",
3270                Base64Display::new(&map, &BASE64_STANDARD)
3271            )
3272            .chars(),
3273        );
3274    }
3275    format!("data:text/javascript,{}", urlencoding::encode(&output))
3276}