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