next_custom_transforms/transforms/
server_actions.rs

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