Skip to main content

react_compiler/entrypoint/
program.rs

1// Copyright (c) Meta Platforms, Inc. and affiliates.
2//
3// This source code is licensed under the MIT license found in the
4// LICENSE file in the root directory of this source tree.
5
6//! Main entrypoint for the React Compiler.
7//!
8//! This module is a port of Program.ts from the TypeScript compiler. It orchestrates
9//! the compilation of a program by:
10//! 1. Checking if compilation should be skipped
11//! 2. Validating restricted imports
12//! 3. Finding program-level suppressions
13//! 4. Discovering functions to compile (components, hooks)
14//! 5. Processing each function through the compilation pipeline
15//! 6. Applying compiled functions back to the AST
16
17use std::collections::HashMap;
18use std::collections::HashSet;
19
20use react_compiler_ast::File;
21use react_compiler_ast::Program;
22use react_compiler_ast::common::BaseNode;
23use react_compiler_ast::declarations::Declaration;
24use react_compiler_ast::declarations::ExportDefaultDecl;
25use react_compiler_ast::declarations::ExportDefaultDeclaration;
26use react_compiler_ast::declarations::ImportSpecifier;
27use react_compiler_ast::declarations::ModuleExportName;
28use react_compiler_ast::expressions::*;
29use react_compiler_ast::patterns::PatternLike;
30use react_compiler_ast::scope::ScopeId;
31use react_compiler_ast::scope::ScopeInfo;
32use react_compiler_ast::statements::*;
33use react_compiler_ast::visitor::AstWalker;
34use react_compiler_ast::visitor::MutVisitor;
35use react_compiler_ast::visitor::VisitResult;
36use react_compiler_ast::visitor::Visitor;
37use react_compiler_ast::visitor::walk_program_mut;
38use react_compiler_diagnostics::CompilerError;
39use react_compiler_diagnostics::CompilerErrorDetail;
40use react_compiler_diagnostics::CompilerErrorOrDiagnostic;
41use react_compiler_diagnostics::ErrorCategory;
42use react_compiler_diagnostics::SourceLocation;
43use react_compiler_hir::ReactFunctionType;
44use react_compiler_hir::environment_config::EnvironmentConfig;
45use react_compiler_lowering::FunctionNode;
46
47use super::compile_result::BindingRenameInfo;
48use super::compile_result::CodegenFunction;
49use super::compile_result::CompileResult;
50use super::compile_result::CompilerErrorDetailInfo;
51use super::compile_result::CompilerErrorInfo;
52use super::compile_result::CompilerErrorItemInfo;
53use super::compile_result::DebugLogEntry;
54use super::compile_result::LoggerEvent;
55use super::compile_result::LoggerPosition;
56use super::compile_result::LoggerSourceLocation;
57use super::compile_result::LoggerSuggestionInfo;
58use super::compile_result::LoggerSuggestionOp;
59use super::compile_result::OrderedLogItem;
60use super::imports::ProgramContext;
61use super::imports::add_imports_to_program;
62use super::imports::get_react_compiler_runtime_module;
63use super::imports::validate_restricted_imports;
64use super::pipeline;
65use super::plugin_options::CompilerOutputMode;
66use super::plugin_options::GatingConfig;
67use super::plugin_options::PluginOptions;
68use super::suppression::SuppressionRange;
69use super::suppression::filter_suppressions_that_affect_function;
70use super::suppression::find_program_suppressions;
71use super::suppression::suppressions_to_compiler_error;
72
73// -----------------------------------------------------------------------
74// Constants
75// -----------------------------------------------------------------------
76
77const DEFAULT_ESLINT_SUPPRESSIONS: &[&str] =
78    &["react-hooks/exhaustive-deps", "react-hooks/rules-of-hooks"];
79
80/// Directives that opt a function into memoization
81const OPT_IN_DIRECTIVES: &[&str] = &["use forget", "use memo"];
82
83/// Directives that opt a function out of memoization
84const OPT_OUT_DIRECTIVES: &[&str] = &["use no forget", "use no memo"];
85
86// -----------------------------------------------------------------------
87// Internal types
88// -----------------------------------------------------------------------
89
90/// A function found in the program that should be compiled
91#[allow(dead_code)]
92struct CompileSource<'a> {
93    kind: CompileSourceKind,
94    fn_node: FunctionNode<'a>,
95    /// Location of this function in the AST for logging
96    fn_name: Option<String>,
97    fn_loc: Option<SourceLocation>,
98    /// Original AST source location (with index and filename) for logger events.
99    fn_ast_loc: Option<react_compiler_ast::common::SourceLocation>,
100    fn_start: Option<u32>,
101    fn_end: Option<u32>,
102    fn_node_id: Option<u32>,
103    fn_type: ReactFunctionType,
104    /// Directives from the function body (for opt-in/opt-out checks)
105    body_directives: Vec<Directive>,
106}
107
108#[derive(Debug, Clone, Copy, PartialEq, Eq)]
109enum CompileSourceKind {
110    Original,
111    #[allow(dead_code)]
112    Outlined,
113}
114
115// -----------------------------------------------------------------------
116// Directive helpers
117// -----------------------------------------------------------------------
118
119/// Check if any opt-in directive is present in the given directives.
120/// Returns the first matching directive, or None.
121///
122/// Also checks for dynamic gating directives (`use memo if(...)`)
123fn try_find_directive_enabling_memoization<'a>(
124    directives: &'a [Directive],
125    opts: &PluginOptions,
126) -> Result<Option<&'a Directive>, CompilerError> {
127    // Check standard opt-in directives
128    let opt_in = directives
129        .iter()
130        .find(|d| OPT_IN_DIRECTIVES.contains(&d.value.value.as_str()));
131    if let Some(directive) = opt_in {
132        return Ok(Some(directive));
133    }
134
135    // Check dynamic gating directives
136    match find_directives_dynamic_gating(directives, opts) {
137        Ok(Some(result)) => Ok(Some(result.directive)),
138        Ok(None) => Ok(None),
139        Err(e) => Err(e),
140    }
141}
142
143/// Check if any opt-out directive is present in the given directives.
144fn find_directive_disabling_memoization<'a>(
145    directives: &'a [Directive],
146    opts: &PluginOptions,
147) -> Option<&'a Directive> {
148    if let Some(ref custom_directives) = opts.custom_opt_out_directives {
149        directives
150            .iter()
151            .find(|d| custom_directives.contains(&d.value.value))
152    } else {
153        directives
154            .iter()
155            .find(|d| OPT_OUT_DIRECTIVES.contains(&d.value.value.as_str()))
156    }
157}
158
159/// Result of a dynamic gating directive parse.
160struct DynamicGatingResult<'a> {
161    #[allow(dead_code)]
162    directive: &'a Directive,
163    gating: GatingConfig,
164}
165
166/// Check for dynamic gating directives like `use memo if(identifier)`.
167/// Returns the directive and gating config if found, or an error if malformed.
168fn find_directives_dynamic_gating<'a>(
169    directives: &'a [Directive],
170    opts: &PluginOptions,
171) -> Result<Option<DynamicGatingResult<'a>>, CompilerError> {
172    let dynamic_gating = match &opts.dynamic_gating {
173        Some(dg) => dg,
174        None => return Ok(None),
175    };
176
177    let mut errors: Vec<CompilerErrorDetail> = Vec::new();
178    let mut matches: Vec<(&'a Directive, String)> = Vec::new();
179
180    for directive in directives {
181        if let Some(ident) = parse_dynamic_gating_directive(&directive.value.value) {
182            if is_valid_identifier(ident) {
183                matches.push((directive, ident.to_string()));
184            } else {
185                let mut detail = CompilerErrorDetail::new(
186                    ErrorCategory::Gating,
187                    "Dynamic gating directive is not a valid JavaScript identifier",
188                )
189                .with_description(format!("Found '{}'", directive.value.value));
190                detail.loc = directive.base.loc.as_ref().map(convert_loc);
191                errors.push(detail);
192            }
193        }
194    }
195
196    if !errors.is_empty() {
197        let mut err = CompilerError::new();
198        for e in errors {
199            err.push_error_detail(e);
200        }
201        return Err(err);
202    }
203
204    if matches.len() > 1 {
205        let names: Vec<String> = matches.iter().map(|(d, _)| d.value.value.clone()).collect();
206        let mut err = CompilerError::new();
207        let mut detail = CompilerErrorDetail::new(
208            ErrorCategory::Gating,
209            "Multiple dynamic gating directives found",
210        )
211        .with_description(format!(
212            "Expected a single directive but found [{}]",
213            names.join(", ")
214        ));
215        detail.loc = matches[0].0.base.loc.as_ref().map(convert_loc);
216        err.push_error_detail(detail);
217        return Err(err);
218    }
219
220    if matches.len() == 1 {
221        Ok(Some(DynamicGatingResult {
222            directive: matches[0].0,
223            gating: GatingConfig {
224                source: dynamic_gating.source.clone(),
225                import_specifier_name: matches[0].1.clone(),
226            },
227        }))
228    } else {
229        Ok(None)
230    }
231}
232
233/// Parse a `use memo if(<condition>)` directive, returning the condition.
234/// Exact equivalent of the TS DYNAMIC_GATING_DIRECTIVE regex
235/// `^use memo if\(([^\)]*)\)$`: the condition may not contain `)` and the
236/// directive must end at the closing paren.
237fn parse_dynamic_gating_directive(value: &str) -> Option<&str> {
238    let condition = value
239        .strip_prefix("use memo if(")?
240        .strip_suffix(')')?;
241    if condition.contains(')') {
242        return None;
243    }
244    Some(condition)
245}
246
247/// Simple check for valid JavaScript identifier (alphanumeric + underscore + $, starting with letter/$/_ )
248/// Also rejects reserved words like `true`, `false`, `null`, etc.
249fn is_valid_identifier(s: &str) -> bool {
250    if s.is_empty() {
251        return false;
252    }
253    let mut chars = s.chars();
254    let first = chars.next().unwrap();
255    if !first.is_alphabetic() && first != '_' && first != '$' {
256        return false;
257    }
258    if !chars.all(|c| c.is_alphanumeric() || c == '_' || c == '$') {
259        return false;
260    }
261    // Check for reserved words (matching Babel's t.isValidIdentifier)
262    !matches!(
263        s,
264        "break"
265            | "case"
266            | "catch"
267            | "continue"
268            | "debugger"
269            | "default"
270            | "do"
271            | "else"
272            | "finally"
273            | "for"
274            | "function"
275            | "if"
276            | "in"
277            | "instanceof"
278            | "new"
279            | "return"
280            | "switch"
281            | "this"
282            | "throw"
283            | "try"
284            | "typeof"
285            | "var"
286            | "void"
287            | "while"
288            | "with"
289            | "class"
290            | "const"
291            | "enum"
292            | "export"
293            | "extends"
294            | "import"
295            | "super"
296            | "implements"
297            | "interface"
298            | "let"
299            | "package"
300            | "private"
301            | "protected"
302            | "public"
303            | "static"
304            | "yield"
305            | "null"
306            | "true"
307            | "false"
308            | "delete"
309    )
310}
311
312// -----------------------------------------------------------------------
313// Name helpers
314// -----------------------------------------------------------------------
315
316/// Check if a string follows the React hook naming convention (use[A-Z0-9]...).
317fn is_hook_name(s: &str) -> bool {
318    let bytes = s.as_bytes();
319    bytes.len() >= 4
320        && bytes[0] == b'u'
321        && bytes[1] == b's'
322        && bytes[2] == b'e'
323        && bytes
324            .get(3)
325            .map_or(false, |c| c.is_ascii_uppercase() || c.is_ascii_digit())
326}
327
328/// Check if a name looks like a React component (starts with uppercase letter).
329fn is_component_name(name: &str) -> bool {
330    name.chars()
331        .next()
332        .map_or(false, |c| c.is_ascii_uppercase())
333}
334
335/// Check if an expression is a hook call (identifier with hook name, or
336/// member expression `PascalCase.useHook`).
337fn expr_is_hook(expr: &Expression) -> bool {
338    match expr {
339        Expression::Identifier(id) => is_hook_name(&id.name),
340        Expression::MemberExpression(member) => {
341            if member.computed {
342                return false;
343            }
344            // Property must be a hook name
345            if !expr_is_hook(&member.property) {
346                return false;
347            }
348            // Object must be a PascalCase identifier
349            if let Expression::Identifier(obj) = member.object.as_ref() {
350                obj.name
351                    .chars()
352                    .next()
353                    .map_or(false, |c| c.is_ascii_uppercase())
354            } else {
355                false
356            }
357        }
358        _ => false,
359    }
360}
361
362/// Check if an expression is a React API call (e.g., `forwardRef` or `React.forwardRef`).
363#[allow(dead_code)]
364fn is_react_api(expr: &Expression, function_name: &str) -> bool {
365    match expr {
366        Expression::Identifier(id) => id.name == function_name,
367        Expression::MemberExpression(member) => {
368            if let Expression::Identifier(obj) = member.object.as_ref() {
369                if obj.name == "React" {
370                    if let Expression::Identifier(prop) = member.property.as_ref() {
371                        return prop.name == function_name;
372                    }
373                }
374            }
375            false
376        }
377        _ => false,
378    }
379}
380
381/// Get the inferred function name from a function's context.
382///
383/// For FunctionDeclaration: uses the `id` field.
384/// For FunctionExpression/ArrowFunctionExpression: infers from parent context
385/// (VariableDeclarator, etc.) which is passed explicitly since we don't have Babel paths.
386fn get_function_name_from_id(id: Option<&Identifier>) -> Option<String> {
387    id.map(|id| id.name.clone())
388}
389
390// -----------------------------------------------------------------------
391// AST traversal helpers
392// -----------------------------------------------------------------------
393
394/// Check if an expression is a "non-node" return value (indicating the function
395/// is not a React component). This matches the TS `isNonNode` function.
396fn is_non_node(expr: &Expression) -> bool {
397    matches!(
398        expr,
399        Expression::ObjectExpression(_)
400            | Expression::ArrowFunctionExpression(_)
401            | Expression::FunctionExpression(_)
402            | Expression::BigIntLiteral(_)
403            | Expression::ClassExpression(_)
404            | Expression::NewExpression(_)
405    )
406}
407
408/// Recursively check if a function body returns a non-React-node value.
409/// Walks all return statements in the function (not in nested functions).
410/// The last return statement visited (in DFS order) determines the result,
411/// rather than short-circuiting on the first non-node return.
412fn returns_non_node_in_stmts(stmts: &[Statement]) -> bool {
413    let mut result = false;
414    for stmt in stmts {
415        returns_non_node_in_stmt(stmt, &mut result);
416    }
417    result
418}
419
420fn returns_non_node_in_stmt(stmt: &Statement, result: &mut bool) {
421    match stmt {
422        Statement::ReturnStatement(ret) => {
423            *result = match &ret.argument {
424                Some(arg) => is_non_node(arg),
425                None => true, // bare `return;` with no argument is a non-node value
426            };
427        }
428        Statement::BlockStatement(block) => {
429            for s in &block.body {
430                returns_non_node_in_stmt(s, result);
431            }
432        }
433        Statement::IfStatement(if_stmt) => {
434            returns_non_node_in_stmt(&if_stmt.consequent, result);
435            if let Some(ref alt) = if_stmt.alternate {
436                returns_non_node_in_stmt(alt, result);
437            }
438        }
439        Statement::ForStatement(for_stmt) => returns_non_node_in_stmt(&for_stmt.body, result),
440        Statement::WhileStatement(while_stmt) => returns_non_node_in_stmt(&while_stmt.body, result),
441        Statement::DoWhileStatement(do_while) => returns_non_node_in_stmt(&do_while.body, result),
442        Statement::ForInStatement(for_in) => returns_non_node_in_stmt(&for_in.body, result),
443        Statement::ForOfStatement(for_of) => returns_non_node_in_stmt(&for_of.body, result),
444        Statement::SwitchStatement(switch) => {
445            for case in &switch.cases {
446                for s in &case.consequent {
447                    returns_non_node_in_stmt(s, result);
448                }
449            }
450        }
451        Statement::TryStatement(try_stmt) => {
452            for s in &try_stmt.block.body {
453                returns_non_node_in_stmt(s, result);
454            }
455            if let Some(ref handler) = try_stmt.handler {
456                for s in &handler.body.body {
457                    returns_non_node_in_stmt(s, result);
458                }
459            }
460            if let Some(ref finalizer) = try_stmt.finalizer {
461                for s in &finalizer.body {
462                    returns_non_node_in_stmt(s, result);
463                }
464            }
465        }
466        Statement::LabeledStatement(labeled) => returns_non_node_in_stmt(&labeled.body, result),
467        Statement::WithStatement(with) => returns_non_node_in_stmt(&with.body, result),
468        // Skip nested function/class declarations -- they have their own returns
469        Statement::FunctionDeclaration(_) | Statement::ClassDeclaration(_) => {}
470        // Unmodeled statements are opaque to return analysis; functions
471        // containing them bail out in lowering before this matters.
472        Statement::Unknown(_) => {}
473        _ => {}
474    }
475}
476
477/// Check if a function returns non-node values.
478/// For arrow functions with expression body, checks the expression directly.
479/// For block bodies, walks the statements.
480fn returns_non_node_fn(params: &[PatternLike], body: &FunctionBody) -> bool {
481    let _ = params;
482    match body {
483        FunctionBody::Block(block) => returns_non_node_in_stmts(&block.body),
484        FunctionBody::Expression(expr) => is_non_node(expr),
485    }
486}
487
488/// Check if a function body calls hooks or creates JSX.
489/// Traverses the function body (not nested functions) looking for:
490/// - CallExpression where callee is a hook
491/// - JSXElement or JSXFragment
492fn calls_hooks_or_creates_jsx_in_stmts(stmts: &[Statement]) -> bool {
493    for stmt in stmts {
494        if calls_hooks_or_creates_jsx_in_stmt(stmt) {
495            return true;
496        }
497    }
498    false
499}
500
501fn calls_hooks_or_creates_jsx_in_stmt(stmt: &Statement) -> bool {
502    match stmt {
503        Statement::ExpressionStatement(expr_stmt) => {
504            calls_hooks_or_creates_jsx_in_expr(&expr_stmt.expression)
505        }
506        Statement::ReturnStatement(ret) => {
507            if let Some(ref arg) = ret.argument {
508                calls_hooks_or_creates_jsx_in_expr(arg)
509            } else {
510                false
511            }
512        }
513        Statement::VariableDeclaration(var_decl) => {
514            for decl in &var_decl.declarations {
515                if let Some(ref init) = decl.init {
516                    if calls_hooks_or_creates_jsx_in_expr(init) {
517                        return true;
518                    }
519                }
520            }
521            false
522        }
523        Statement::BlockStatement(block) => calls_hooks_or_creates_jsx_in_stmts(&block.body),
524        Statement::IfStatement(if_stmt) => {
525            calls_hooks_or_creates_jsx_in_expr(&if_stmt.test)
526                || calls_hooks_or_creates_jsx_in_stmt(&if_stmt.consequent)
527                || if_stmt
528                    .alternate
529                    .as_ref()
530                    .map_or(false, |alt| calls_hooks_or_creates_jsx_in_stmt(alt))
531        }
532        Statement::ForStatement(for_stmt) => {
533            if let Some(ref init) = for_stmt.init {
534                match init.as_ref() {
535                    ForInit::Expression(expr) => {
536                        if calls_hooks_or_creates_jsx_in_expr(expr) {
537                            return true;
538                        }
539                    }
540                    ForInit::VariableDeclaration(var_decl) => {
541                        for decl in &var_decl.declarations {
542                            if let Some(ref init) = decl.init {
543                                if calls_hooks_or_creates_jsx_in_expr(init) {
544                                    return true;
545                                }
546                            }
547                        }
548                    }
549                }
550            }
551            if let Some(ref test) = for_stmt.test {
552                if calls_hooks_or_creates_jsx_in_expr(test) {
553                    return true;
554                }
555            }
556            if let Some(ref update) = for_stmt.update {
557                if calls_hooks_or_creates_jsx_in_expr(update) {
558                    return true;
559                }
560            }
561            calls_hooks_or_creates_jsx_in_stmt(&for_stmt.body)
562        }
563        Statement::WhileStatement(while_stmt) => {
564            calls_hooks_or_creates_jsx_in_expr(&while_stmt.test)
565                || calls_hooks_or_creates_jsx_in_stmt(&while_stmt.body)
566        }
567        Statement::DoWhileStatement(do_while) => {
568            calls_hooks_or_creates_jsx_in_stmt(&do_while.body)
569                || calls_hooks_or_creates_jsx_in_expr(&do_while.test)
570        }
571        Statement::ForInStatement(for_in) => {
572            calls_hooks_or_creates_jsx_in_expr(&for_in.right)
573                || calls_hooks_or_creates_jsx_in_stmt(&for_in.body)
574        }
575        Statement::ForOfStatement(for_of) => {
576            calls_hooks_or_creates_jsx_in_expr(&for_of.right)
577                || calls_hooks_or_creates_jsx_in_stmt(&for_of.body)
578        }
579        Statement::SwitchStatement(switch) => {
580            if calls_hooks_or_creates_jsx_in_expr(&switch.discriminant) {
581                return true;
582            }
583            for case in &switch.cases {
584                if let Some(ref test) = case.test {
585                    if calls_hooks_or_creates_jsx_in_expr(test) {
586                        return true;
587                    }
588                }
589                if calls_hooks_or_creates_jsx_in_stmts(&case.consequent) {
590                    return true;
591                }
592            }
593            false
594        }
595        Statement::ThrowStatement(throw) => calls_hooks_or_creates_jsx_in_expr(&throw.argument),
596        Statement::TryStatement(try_stmt) => {
597            if calls_hooks_or_creates_jsx_in_stmts(&try_stmt.block.body) {
598                return true;
599            }
600            if let Some(ref handler) = try_stmt.handler {
601                if calls_hooks_or_creates_jsx_in_stmts(&handler.body.body) {
602                    return true;
603                }
604            }
605            if let Some(ref finalizer) = try_stmt.finalizer {
606                if calls_hooks_or_creates_jsx_in_stmts(&finalizer.body) {
607                    return true;
608                }
609            }
610            false
611        }
612        Statement::LabeledStatement(labeled) => calls_hooks_or_creates_jsx_in_stmt(&labeled.body),
613        Statement::WithStatement(with) => {
614            calls_hooks_or_creates_jsx_in_expr(&with.object)
615                || calls_hooks_or_creates_jsx_in_stmt(&with.body)
616        }
617        // Recurse into class body to find JSX/hooks in methods (matching TS behavior
618        // where Babel's traverse enters class bodies, only skipping nested functions)
619        Statement::FunctionDeclaration(_) => false,
620        Statement::ClassDeclaration(class) => calls_hooks_or_creates_jsx_in_class_body(&class.body),
621        // Unmodeled statements are preserved verbatim and never compiled, so
622        // hook/JSX content inside them cannot affect compilation decisions.
623        Statement::Unknown(_) => false,
624        _ => false,
625    }
626}
627
628fn calls_hooks_or_creates_jsx_in_expr(expr: &Expression) -> bool {
629    match expr {
630        // JSX creates
631        Expression::JSXElement(_) | Expression::JSXFragment(_) => true,
632
633        // Hook calls
634        Expression::CallExpression(call) => {
635            if expr_is_hook(&call.callee) {
636                return true;
637            }
638            // Also check arguments for JSX/hooks (but not nested functions)
639            if calls_hooks_or_creates_jsx_in_expr(&call.callee) {
640                return true;
641            }
642            for arg in &call.arguments {
643                // Skip function arguments -- they are nested functions
644                if matches!(
645                    arg,
646                    Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_)
647                ) {
648                    continue;
649                }
650                if calls_hooks_or_creates_jsx_in_expr(arg) {
651                    return true;
652                }
653            }
654            false
655        }
656        Expression::OptionalCallExpression(call) => {
657            // Note: OptionalCallExpression is NOT treated as a hook call for
658            // the purpose of determining function type. The TS code only checks
659            // regular CallExpression nodes in callsHooksOrCreatesJsx.
660            // We still recurse into the callee and arguments to find other
661            // hook calls or JSX.
662            if calls_hooks_or_creates_jsx_in_expr(&call.callee) {
663                return true;
664            }
665            for arg in &call.arguments {
666                if matches!(
667                    arg,
668                    Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_)
669                ) {
670                    continue;
671                }
672                if calls_hooks_or_creates_jsx_in_expr(arg) {
673                    return true;
674                }
675            }
676            false
677        }
678
679        // Binary/logical
680        Expression::BinaryExpression(bin) => {
681            calls_hooks_or_creates_jsx_in_expr(&bin.left)
682                || calls_hooks_or_creates_jsx_in_expr(&bin.right)
683        }
684        Expression::LogicalExpression(log) => {
685            calls_hooks_or_creates_jsx_in_expr(&log.left)
686                || calls_hooks_or_creates_jsx_in_expr(&log.right)
687        }
688        Expression::ConditionalExpression(cond) => {
689            calls_hooks_or_creates_jsx_in_expr(&cond.test)
690                || calls_hooks_or_creates_jsx_in_expr(&cond.consequent)
691                || calls_hooks_or_creates_jsx_in_expr(&cond.alternate)
692        }
693        Expression::AssignmentExpression(assign) => {
694            calls_hooks_or_creates_jsx_in_expr(&assign.right)
695        }
696        Expression::SequenceExpression(seq) => seq
697            .expressions
698            .iter()
699            .any(|e| calls_hooks_or_creates_jsx_in_expr(e)),
700        Expression::UnaryExpression(unary) => calls_hooks_or_creates_jsx_in_expr(&unary.argument),
701        Expression::UpdateExpression(update) => {
702            calls_hooks_or_creates_jsx_in_expr(&update.argument)
703        }
704        Expression::MemberExpression(member) => {
705            calls_hooks_or_creates_jsx_in_expr(&member.object)
706                || calls_hooks_or_creates_jsx_in_expr(&member.property)
707        }
708        Expression::OptionalMemberExpression(member) => {
709            calls_hooks_or_creates_jsx_in_expr(&member.object)
710                || calls_hooks_or_creates_jsx_in_expr(&member.property)
711        }
712        Expression::SpreadElement(spread) => calls_hooks_or_creates_jsx_in_expr(&spread.argument),
713        Expression::AwaitExpression(await_expr) => {
714            calls_hooks_or_creates_jsx_in_expr(&await_expr.argument)
715        }
716        Expression::YieldExpression(yield_expr) => yield_expr
717            .argument
718            .as_ref()
719            .map_or(false, |arg| calls_hooks_or_creates_jsx_in_expr(arg)),
720        Expression::TaggedTemplateExpression(tagged) => {
721            calls_hooks_or_creates_jsx_in_expr(&tagged.tag)
722        }
723        Expression::TemplateLiteral(tl) => tl
724            .expressions
725            .iter()
726            .any(|e| calls_hooks_or_creates_jsx_in_expr(e)),
727        Expression::ArrayExpression(arr) => arr.elements.iter().any(|e| {
728            e.as_ref()
729                .map_or(false, |e| calls_hooks_or_creates_jsx_in_expr(e))
730        }),
731        Expression::ObjectExpression(obj) => obj.properties.iter().any(|prop| match prop {
732            ObjectExpressionProperty::ObjectProperty(p) => {
733                calls_hooks_or_creates_jsx_in_expr(&p.value)
734            }
735            ObjectExpressionProperty::SpreadElement(s) => {
736                calls_hooks_or_creates_jsx_in_expr(&s.argument)
737            }
738            // ObjectMethod: traverse into its body to find hooks/JSX.
739            // This matches the TS behavior where Babel's traverse enters
740            // ObjectMethod (only FunctionDeclaration, FunctionExpression,
741            // and ArrowFunctionExpression are skipped).
742            ObjectExpressionProperty::ObjectMethod(m) => {
743                calls_hooks_or_creates_jsx_in_stmts(&m.body.body)
744            }
745        }),
746        Expression::ParenthesizedExpression(paren) => {
747            calls_hooks_or_creates_jsx_in_expr(&paren.expression)
748        }
749        Expression::TSAsExpression(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression),
750        Expression::TSSatisfiesExpression(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression),
751        Expression::TSNonNullExpression(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression),
752        Expression::TSTypeAssertion(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression),
753        Expression::TSInstantiationExpression(ts) => {
754            calls_hooks_or_creates_jsx_in_expr(&ts.expression)
755        }
756        Expression::TypeCastExpression(tc) => calls_hooks_or_creates_jsx_in_expr(&tc.expression),
757        Expression::NewExpression(new) => {
758            if calls_hooks_or_creates_jsx_in_expr(&new.callee) {
759                return true;
760            }
761            new.arguments.iter().any(|a| {
762                if matches!(
763                    a,
764                    Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_)
765                ) {
766                    return false;
767                }
768                calls_hooks_or_creates_jsx_in_expr(a)
769            })
770        }
771
772        // Skip nested functions
773        Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) => false,
774
775        // Recurse into class body to find JSX/hooks in methods
776        Expression::ClassExpression(class) => calls_hooks_or_creates_jsx_in_class_body(&class.body),
777
778        // Leaf expressions
779        _ => false,
780    }
781}
782
783/// Recursively search a ClassBody for JSX elements or hook calls.
784/// Class body members are stored as serde_json::Value since they aren't fully typed.
785/// We search the JSON tree, skipping nested function nodes (matching TS behavior where
786/// Babel's traverse skips ArrowFunctionExpression, FunctionExpression, FunctionDeclaration
787/// but recurses into class methods).
788fn calls_hooks_or_creates_jsx_in_class_body(
789    body: &react_compiler_ast::expressions::ClassBody,
790) -> bool {
791    body.body
792        .iter()
793        .any(|member| calls_hooks_or_creates_jsx_in_json(&member.parse_value()))
794}
795
796fn calls_hooks_or_creates_jsx_in_json(value: &serde_json::Value) -> bool {
797    match value {
798        serde_json::Value::Object(obj) => {
799            // Check the node type
800            if let Some(serde_json::Value::String(node_type)) = obj.get("type") {
801                match node_type.as_str() {
802                    // JSX nodes
803                    "JSXElement" | "JSXFragment" => return true,
804                    // Skip nested function nodes (matching TS skipNestedFunctions)
805                    "ArrowFunctionExpression" | "FunctionExpression" | "FunctionDeclaration" => {
806                        return false;
807                    }
808                    // Hook calls: check if callee name starts with "use"
809                    "CallExpression" => {
810                        if let Some(callee) = obj.get("callee") {
811                            if json_expr_is_hook(callee) {
812                                return true;
813                            }
814                        }
815                    }
816                    _ => {}
817                }
818            }
819            // Recurse into all values of the object
820            obj.values().any(|v| calls_hooks_or_creates_jsx_in_json(v))
821        }
822        serde_json::Value::Array(arr) => arr.iter().any(|v| calls_hooks_or_creates_jsx_in_json(v)),
823        _ => false,
824    }
825}
826
827/// Check if a JSON expression node looks like a hook call.
828/// Handles both Identifier (e.g. `useState`) and MemberExpression
829/// (e.g. `React.useState`) patterns, reusing `is_hook_name` for
830/// consistent naming checks.
831fn json_expr_is_hook(callee: &serde_json::Value) -> bool {
832    if let serde_json::Value::Object(obj) = callee {
833        if let Some(serde_json::Value::String(node_type)) = obj.get("type") {
834            if node_type == "Identifier" {
835                if let Some(serde_json::Value::String(name)) = obj.get("name") {
836                    return is_hook_name(name);
837                }
838            } else if node_type == "MemberExpression" {
839                // Check for PascalCase.useHook pattern (non-computed)
840                let computed = obj
841                    .get("computed")
842                    .and_then(|v| v.as_bool())
843                    .unwrap_or(false);
844                if computed {
845                    return false;
846                }
847                // Property must be a hook name
848                if let Some(serde_json::Value::Object(prop)) = obj.get("property") {
849                    if prop.get("type").and_then(|v| v.as_str()) == Some("Identifier") {
850                        if let Some(name) = prop.get("name").and_then(|v| v.as_str()) {
851                            if !is_hook_name(name) {
852                                return false;
853                            }
854                            // Object must be PascalCase identifier
855                            if let Some(serde_json::Value::Object(obj_node)) = obj.get("object") {
856                                if obj_node.get("type").and_then(|v| v.as_str())
857                                    == Some("Identifier")
858                                {
859                                    if let Some(obj_name) =
860                                        obj_node.get("name").and_then(|v| v.as_str())
861                                    {
862                                        return is_component_name(obj_name);
863                                    }
864                                }
865                            }
866                        }
867                    }
868                }
869            }
870        }
871    }
872    false
873}
874
875/// Check if a function body calls hooks or creates JSX.
876fn calls_hooks_or_creates_jsx(params: &[PatternLike], body: &FunctionBody) -> bool {
877    // Check default param values (TS traverses the whole function node including params)
878    if calls_hooks_or_creates_jsx_in_params(params) {
879        return true;
880    }
881    match body {
882        FunctionBody::Block(block) => calls_hooks_or_creates_jsx_in_stmts(&block.body),
883        FunctionBody::Expression(expr) => calls_hooks_or_creates_jsx_in_expr(expr),
884    }
885}
886
887/// Check if any parameter default values contain hooks or JSX.
888fn calls_hooks_or_creates_jsx_in_params(params: &[PatternLike]) -> bool {
889    for param in params {
890        if calls_hooks_or_creates_jsx_in_pattern(param) {
891            return true;
892        }
893    }
894    false
895}
896
897fn calls_hooks_or_creates_jsx_in_pattern(pattern: &PatternLike) -> bool {
898    match pattern {
899        PatternLike::AssignmentPattern(assign) => {
900            // Check the default value expression
901            calls_hooks_or_creates_jsx_in_expr(&assign.right)
902                || calls_hooks_or_creates_jsx_in_pattern(&assign.left)
903        }
904        PatternLike::ObjectPattern(obj) => obj.properties.iter().any(|prop| match prop {
905            react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => {
906                calls_hooks_or_creates_jsx_in_pattern(&p.value)
907            }
908            react_compiler_ast::patterns::ObjectPatternProperty::RestElement(rest) => {
909                calls_hooks_or_creates_jsx_in_pattern(&rest.argument)
910            }
911        }),
912        PatternLike::ArrayPattern(arr) => arr.elements.iter().any(|elem| {
913            elem.as_ref()
914                .map_or(false, |e| calls_hooks_or_creates_jsx_in_pattern(e))
915        }),
916        PatternLike::RestElement(rest) => calls_hooks_or_creates_jsx_in_pattern(&rest.argument),
917        PatternLike::Identifier(_)
918        | PatternLike::MemberExpression(_)
919        | PatternLike::TSAsExpression(_)
920        | PatternLike::TSSatisfiesExpression(_)
921        | PatternLike::TSNonNullExpression(_)
922        | PatternLike::TSTypeAssertion(_)
923        | PatternLike::TypeCastExpression(_) => false,
924    }
925}
926
927/// Check if the function parameters are valid for a React component.
928/// Components can have 0 params, 1 param (props), or 2 params (props + ref).
929/// Check if a parameter's type annotation is valid for a React component prop.
930/// Returns false for primitive type annotations that indicate this is NOT a component.
931fn is_valid_props_annotation(param: &PatternLike) -> bool {
932    let type_annotation = match param {
933        PatternLike::Identifier(id) => id.type_annotation.as_ref(),
934        PatternLike::ObjectPattern(op) => op.type_annotation.as_ref(),
935        PatternLike::ArrayPattern(ap) => ap.type_annotation.as_ref(),
936        PatternLike::AssignmentPattern(ap) => ap.type_annotation.as_ref(),
937        PatternLike::RestElement(re) => re.type_annotation.as_ref(),
938        PatternLike::MemberExpression(_)
939        | PatternLike::TSAsExpression(_)
940        | PatternLike::TSSatisfiesExpression(_)
941        | PatternLike::TSNonNullExpression(_)
942        | PatternLike::TSTypeAssertion(_)
943        | PatternLike::TypeCastExpression(_) => None,
944    };
945    let annot = match type_annotation {
946        Some(raw) => raw.parse_value(),
947        None => return true, // No annotation = valid
948    };
949    let annot_type = match annot.get("type").and_then(|v| v.as_str()) {
950        Some(t) => t,
951        None => return true,
952    };
953    match annot_type {
954        "TSTypeAnnotation" => {
955            let inner_type = annot
956                .get("typeAnnotation")
957                .and_then(|v| v.get("type"))
958                .and_then(|v| v.as_str())
959                .unwrap_or("");
960            !matches!(
961                inner_type,
962                "TSArrayType"
963                    | "TSBigIntKeyword"
964                    | "TSBooleanKeyword"
965                    | "TSConstructorType"
966                    | "TSFunctionType"
967                    | "TSLiteralType"
968                    | "TSNeverKeyword"
969                    | "TSNumberKeyword"
970                    | "TSStringKeyword"
971                    | "TSSymbolKeyword"
972                    | "TSTupleType"
973            )
974        }
975        "TypeAnnotation" => {
976            let inner_type = annot
977                .get("typeAnnotation")
978                .and_then(|v| v.get("type"))
979                .and_then(|v| v.as_str())
980                .unwrap_or("");
981            !matches!(
982                inner_type,
983                "ArrayTypeAnnotation"
984                    | "BooleanLiteralTypeAnnotation"
985                    | "BooleanTypeAnnotation"
986                    | "EmptyTypeAnnotation"
987                    | "FunctionTypeAnnotation"
988                    | "NullLiteralTypeAnnotation"
989                    | "NumberLiteralTypeAnnotation"
990                    | "NumberTypeAnnotation"
991                    | "StringLiteralTypeAnnotation"
992                    | "StringTypeAnnotation"
993                    | "SymbolTypeAnnotation"
994                    | "ThisTypeAnnotation"
995                    | "TupleTypeAnnotation"
996            )
997        }
998        "Noop" => true,
999        _ => true,
1000    }
1001}
1002
1003fn is_valid_component_params(params: &[PatternLike]) -> bool {
1004    if params.is_empty() {
1005        return true;
1006    }
1007    if params.len() > 2 {
1008        return false;
1009    }
1010    // First param cannot be a rest element
1011    if matches!(params[0], PatternLike::RestElement(_)) {
1012        return false;
1013    }
1014    // Check type annotation on first param
1015    if !is_valid_props_annotation(&params[0]) {
1016        return false;
1017    }
1018    if params.len() == 1 {
1019        return true;
1020    }
1021    // If second param exists, it should look like a ref
1022    if let PatternLike::Identifier(ref id) = params[1] {
1023        id.name.contains("ref") || id.name.contains("Ref")
1024    } else {
1025        false
1026    }
1027}
1028
1029// -----------------------------------------------------------------------
1030// Unified function body type for traversal
1031// -----------------------------------------------------------------------
1032
1033/// Abstraction over function body types to simplify traversal code
1034enum FunctionBody<'a> {
1035    Block(&'a BlockStatement),
1036    Expression(&'a Expression),
1037}
1038
1039// -----------------------------------------------------------------------
1040// Function type detection
1041// -----------------------------------------------------------------------
1042
1043/// Determine the React function type for a function, given the compilation mode
1044/// and the function's name and context.
1045///
1046/// This is the Rust equivalent of `getReactFunctionType` in Program.ts.
1047fn get_react_function_type(
1048    name: Option<&str>,
1049    params: &[PatternLike],
1050    body: &FunctionBody,
1051    body_directives: &[Directive],
1052    is_declaration: bool,
1053    parent_callee_name: Option<&str>,
1054    opts: &PluginOptions,
1055    is_component_declaration: bool,
1056    is_hook_declaration: bool,
1057) -> Option<ReactFunctionType> {
1058    // Check for opt-in directives in the function body
1059    if let FunctionBody::Block(_) = body {
1060        let opt_in = try_find_directive_enabling_memoization(body_directives, opts);
1061        if let Ok(Some(_)) = opt_in {
1062            // If there's an opt-in directive, use name heuristics but fall back to Other
1063            return Some(
1064                get_component_or_hook_like(name, params, body, parent_callee_name)
1065                    .unwrap_or(ReactFunctionType::Other),
1066            );
1067        }
1068    }
1069
1070    // Component and hook declarations are known components/hooks
1071    // (Flow `component Foo() { ... }` and `hook useFoo() { ... }` syntax,
1072    //  detected via __componentDeclaration / __hookDeclaration from the Hermes parser)
1073    let component_syntax_type = if is_declaration {
1074        if is_component_declaration {
1075            Some(ReactFunctionType::Component)
1076        } else if is_hook_declaration {
1077            Some(ReactFunctionType::Hook)
1078        } else {
1079            None
1080        }
1081    } else {
1082        None
1083    };
1084
1085    match opts.compilation_mode.as_str() {
1086        "annotation" => {
1087            // opt-ins were checked above
1088            None
1089        }
1090        "infer" => {
1091            // Check if this is a component or hook-like function
1092            component_syntax_type
1093                .or_else(|| get_component_or_hook_like(name, params, body, parent_callee_name))
1094        }
1095        "syntax" => {
1096            // In syntax mode, only compile declared components/hooks
1097            component_syntax_type
1098        }
1099        "all" => Some(
1100            get_component_or_hook_like(name, params, body, parent_callee_name)
1101                .unwrap_or(ReactFunctionType::Other),
1102        ),
1103        _ => None,
1104    }
1105}
1106
1107/// Determine if a function looks like a React component or hook based on
1108/// naming conventions and code patterns.
1109///
1110/// Adapted from the ESLint rule at
1111/// https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/src/RulesOfHooks.js
1112fn get_component_or_hook_like(
1113    name: Option<&str>,
1114    params: &[PatternLike],
1115    body: &FunctionBody,
1116    parent_callee_name: Option<&str>,
1117) -> Option<ReactFunctionType> {
1118    if let Some(fn_name) = name {
1119        if is_component_name(fn_name) {
1120            // Check if it actually looks like a component
1121            let is_component = calls_hooks_or_creates_jsx(params, body)
1122                && is_valid_component_params(params)
1123                && !returns_non_node_fn(params, body);
1124            return if is_component {
1125                Some(ReactFunctionType::Component)
1126            } else {
1127                None
1128            };
1129        } else if is_hook_name(fn_name) {
1130            // Hooks have hook invocations or JSX, but can take any # of arguments
1131            return if calls_hooks_or_creates_jsx(params, body) {
1132                Some(ReactFunctionType::Hook)
1133            } else {
1134                None
1135            };
1136        }
1137    }
1138
1139    // For unnamed functions, check if they are forwardRef/memo callbacks
1140    if let Some(callee_name) = parent_callee_name {
1141        if callee_name == "forwardRef" || callee_name == "memo" {
1142            return if calls_hooks_or_creates_jsx(params, body) {
1143                Some(ReactFunctionType::Component)
1144            } else {
1145                None
1146            };
1147        }
1148    }
1149
1150    None
1151}
1152
1153/// Extract the callee name from a CallExpression if it's a React API call
1154/// (forwardRef, memo, React.forwardRef, React.memo).
1155fn get_callee_name_if_react_api(callee: &Expression) -> Option<&str> {
1156    match callee {
1157        Expression::Identifier(id) => {
1158            if id.name == "forwardRef" || id.name == "memo" {
1159                Some(&id.name)
1160            } else {
1161                None
1162            }
1163        }
1164        Expression::MemberExpression(member) => {
1165            if let Expression::Identifier(obj) = member.object.as_ref() {
1166                if obj.name == "React" {
1167                    if let Expression::Identifier(prop) = member.property.as_ref() {
1168                        if prop.name == "forwardRef" || prop.name == "memo" {
1169                            return Some(&prop.name);
1170                        }
1171                    }
1172                }
1173            }
1174            None
1175        }
1176        _ => None,
1177    }
1178}
1179
1180// -----------------------------------------------------------------------
1181// SourceLocation conversion
1182// -----------------------------------------------------------------------
1183
1184/// Convert an AST SourceLocation to a diagnostics SourceLocation
1185fn convert_loc(loc: &react_compiler_ast::common::SourceLocation) -> SourceLocation {
1186    SourceLocation {
1187        start: react_compiler_diagnostics::Position {
1188            line: loc.start.line,
1189            column: loc.start.column,
1190            index: loc.start.index,
1191        },
1192        end: react_compiler_diagnostics::Position {
1193            line: loc.end.line,
1194            column: loc.end.column,
1195            index: loc.end.index,
1196        },
1197    }
1198}
1199
1200fn base_node_loc(base: &BaseNode) -> Option<SourceLocation> {
1201    base.loc.as_ref().map(convert_loc)
1202}
1203
1204// -----------------------------------------------------------------------
1205// Error handling
1206// -----------------------------------------------------------------------
1207
1208/// Convert CompilerDiagnostic details into serializable CompilerErrorItemInfo items.
1209fn diagnostic_details_to_items(
1210    d: &react_compiler_diagnostics::CompilerDiagnostic,
1211    filename: Option<&str>,
1212) -> Option<Vec<CompilerErrorItemInfo>> {
1213    let items: Vec<CompilerErrorItemInfo> = d
1214        .details
1215        .iter()
1216        .map(|item| match item {
1217            react_compiler_diagnostics::CompilerDiagnosticDetail::Error {
1218                loc,
1219                message,
1220                identifier_name,
1221            } => CompilerErrorItemInfo {
1222                kind: "error".to_string(),
1223                loc: loc.as_ref().map(|l| {
1224                    let mut logger_loc = diag_loc_to_logger_loc(l, filename);
1225                    logger_loc.identifier_name = identifier_name.clone();
1226                    logger_loc
1227                }),
1228                message: message.clone(),
1229            },
1230            react_compiler_diagnostics::CompilerDiagnosticDetail::Hint { message } => {
1231                CompilerErrorItemInfo {
1232                    kind: "hint".to_string(),
1233                    loc: None,
1234                    message: Some(message.clone()),
1235                }
1236            }
1237        })
1238        .collect();
1239    if items.is_empty() { None } else { Some(items) }
1240}
1241
1242/// Convert an optional AST SourceLocation to a LoggerSourceLocation with filename.
1243fn to_logger_loc(
1244    ast_loc: Option<&react_compiler_ast::common::SourceLocation>,
1245    filename: Option<&str>,
1246) -> Option<LoggerSourceLocation> {
1247    ast_loc.map(|loc| LoggerSourceLocation {
1248        start: LoggerPosition {
1249            line: loc.start.line,
1250            column: loc.start.column,
1251            index: loc.start.index,
1252        },
1253        end: LoggerPosition {
1254            line: loc.end.line,
1255            column: loc.end.column,
1256            index: loc.end.index,
1257        },
1258        filename: filename.map(|s| s.to_string()),
1259        identifier_name: loc.identifier_name.clone(),
1260    })
1261}
1262
1263/// Convert a diagnostics SourceLocation to a LoggerSourceLocation with filename.
1264fn diag_loc_to_logger_loc(loc: &SourceLocation, filename: Option<&str>) -> LoggerSourceLocation {
1265    LoggerSourceLocation {
1266        start: LoggerPosition {
1267            line: loc.start.line,
1268            column: loc.start.column,
1269            index: loc.start.index,
1270        },
1271        end: LoggerPosition {
1272            line: loc.end.line,
1273            column: loc.end.column,
1274            index: loc.end.index,
1275        },
1276        filename: filename.map(|s| s.to_string()),
1277        identifier_name: None,
1278    }
1279}
1280
1281/// Convert diagnostic suggestions to logger suggestion infos.
1282fn suggestions_to_logger(
1283    suggestions: &Option<Vec<react_compiler_diagnostics::CompilerSuggestion>>,
1284) -> Option<Vec<LoggerSuggestionInfo>> {
1285    suggestions.as_ref().map(|suggestions| {
1286        suggestions
1287            .iter()
1288            .map(|s| {
1289                let op = match s.op {
1290                    react_compiler_diagnostics::CompilerSuggestionOperation::InsertBefore => {
1291                        LoggerSuggestionOp::InsertBefore
1292                    }
1293                    react_compiler_diagnostics::CompilerSuggestionOperation::InsertAfter => {
1294                        LoggerSuggestionOp::InsertAfter
1295                    }
1296                    react_compiler_diagnostics::CompilerSuggestionOperation::Remove => {
1297                        LoggerSuggestionOp::Remove
1298                    }
1299                    react_compiler_diagnostics::CompilerSuggestionOperation::Replace => {
1300                        LoggerSuggestionOp::Replace
1301                    }
1302                };
1303                LoggerSuggestionInfo {
1304                    description: s.description.clone(),
1305                    op,
1306                    range: s.range,
1307                    text: s.text.clone(),
1308                }
1309            })
1310            .collect()
1311    })
1312}
1313
1314/// Log an error as LoggerEvent(s) directly onto the ProgramContext.
1315fn log_error(
1316    err: &CompilerError,
1317    fn_ast_loc: Option<&react_compiler_ast::common::SourceLocation>,
1318    context: &mut ProgramContext,
1319) {
1320    // Use the filename from the AST node's loc (set by parser's sourceFilename option),
1321    // not from plugin options (which may have a different prefix like '/').
1322    let source_filename = fn_ast_loc.and_then(|loc| loc.filename.as_deref());
1323    let fn_loc = to_logger_loc(fn_ast_loc, source_filename);
1324
1325    // Detect simulated unknown exception (throwUnknownException__testonly).
1326    // In TS, non-CompilerError exceptions are logged as PipelineError with the
1327    // error message as data. Emit the same event shape.
1328    let is_simulated_unknown = err.details.len() == 1
1329        && err.details.iter().all(|d| match d {
1330            CompilerErrorOrDiagnostic::ErrorDetail(d) => {
1331                d.category == ErrorCategory::Invariant && d.reason == "unexpected error"
1332            }
1333            _ => false,
1334        });
1335    if is_simulated_unknown {
1336        context.log_event(LoggerEvent::PipelineError {
1337            fn_loc: fn_loc.clone(),
1338            data: "Error: unexpected error".to_string(),
1339        });
1340        return;
1341    }
1342
1343    for detail in &err.details {
1344        let detail_info = match detail {
1345            CompilerErrorOrDiagnostic::Diagnostic(d) => CompilerErrorDetailInfo {
1346                category: format!("{:?}", d.category),
1347                reason: d.reason.clone(),
1348                description: d.description.clone(),
1349                severity: format!("{:?}", d.logged_severity()),
1350                suggestions: suggestions_to_logger(&d.suggestions),
1351                details: diagnostic_details_to_items(d, source_filename),
1352                loc: None,
1353            },
1354            CompilerErrorOrDiagnostic::ErrorDetail(d) => CompilerErrorDetailInfo {
1355                category: format!("{:?}", d.category),
1356                reason: d.reason.clone(),
1357                description: d.description.clone(),
1358                severity: format!("{:?}", d.logged_severity()),
1359                suggestions: suggestions_to_logger(&d.suggestions),
1360                details: None,
1361                loc: d
1362                    .loc
1363                    .as_ref()
1364                    .map(|l| diag_loc_to_logger_loc(l, source_filename)),
1365            },
1366        };
1367        // Use CompileErrorWithLoc when fn_loc is present to match TS field ordering
1368        if let Some(ref loc) = fn_loc {
1369            context.log_event(LoggerEvent::CompileErrorWithLoc {
1370                fn_loc: loc.clone(),
1371                detail: detail_info,
1372            });
1373        } else {
1374            context.log_event(LoggerEvent::CompileError {
1375                fn_loc: None,
1376                detail: detail_info,
1377            });
1378        }
1379    }
1380}
1381
1382/// Handle an error according to the panicThreshold setting.
1383/// Returns Some(CompileResult::Error) if the error should be surfaced as fatal,
1384/// otherwise returns None (error was logged only).
1385fn handle_error(
1386    err: &CompilerError,
1387    fn_ast_loc: Option<&react_compiler_ast::common::SourceLocation>,
1388    context: &mut ProgramContext,
1389) -> Option<CompileResult> {
1390    // Log the error
1391    log_error(err, fn_ast_loc, context);
1392
1393    let should_panic = match context.opts.panic_threshold.as_str() {
1394        "all_errors" => true,
1395        "critical_errors" => err.has_errors(),
1396        _ => false,
1397    };
1398
1399    // Config errors always cause a panic
1400    let is_config_error = err.details.iter().any(|d| match d {
1401        CompilerErrorOrDiagnostic::Diagnostic(d) => d.category == ErrorCategory::Config,
1402        CompilerErrorOrDiagnostic::ErrorDetail(d) => d.category == ErrorCategory::Config,
1403    });
1404
1405    if should_panic || is_config_error {
1406        let source_fn = context.source_filename();
1407        let mut error_info = compiler_error_to_info(err, source_fn.as_deref());
1408
1409        // Detect simulated unknown exception (throwUnknownException__testonly).
1410        // In the TS compiler, this throws a plain Error('unexpected error'), not
1411        // a CompilerError. Set rawMessage so the JS side throws with the raw
1412        // message instead of formatting through formatCompilerError().
1413        let is_simulated_unknown = err.details.len() == 1
1414            && err.details.iter().all(|d| match d {
1415                CompilerErrorOrDiagnostic::ErrorDetail(d) => {
1416                    d.category == ErrorCategory::Invariant && d.reason == "unexpected error"
1417                }
1418                _ => false,
1419            });
1420        if is_simulated_unknown {
1421            error_info.raw_message = Some("unexpected error".to_string());
1422        }
1423
1424        // Pre-format the error message in Rust when possible, so the JS
1425        // shim can use it directly instead of calling formatCompilerError().
1426        if error_info.raw_message.is_none() {
1427            if let Some(ref source) = context.code {
1428                error_info.formatted_message = Some(
1429                    react_compiler_diagnostics::code_frame::format_compiler_error(
1430                        err,
1431                        source,
1432                        source_fn.as_deref(),
1433                    ),
1434                );
1435            }
1436        }
1437
1438        Some(CompileResult::Error {
1439            error: error_info,
1440            events: context.events.clone(),
1441            ordered_log: context.ordered_log.clone(),
1442            timing: Vec::new(),
1443        })
1444    } else {
1445        None
1446    }
1447}
1448
1449/// Convert a diagnostics CompilerError to a serializable CompilerErrorInfo.
1450fn compiler_error_to_info(err: &CompilerError, filename: Option<&str>) -> CompilerErrorInfo {
1451    let details: Vec<CompilerErrorDetailInfo> = err
1452        .details
1453        .iter()
1454        .map(|d| match d {
1455            CompilerErrorOrDiagnostic::Diagnostic(d) => CompilerErrorDetailInfo {
1456                category: format!("{:?}", d.category),
1457                reason: d.reason.clone(),
1458                description: d.description.clone(),
1459                severity: format!("{:?}", d.severity()),
1460                suggestions: suggestions_to_logger(&d.suggestions),
1461                details: diagnostic_details_to_items(d, filename),
1462                loc: None,
1463            },
1464            CompilerErrorOrDiagnostic::ErrorDetail(d) => CompilerErrorDetailInfo {
1465                category: format!("{:?}", d.category),
1466                reason: d.reason.clone(),
1467                description: d.description.clone(),
1468                severity: format!("{:?}", d.severity()),
1469                suggestions: suggestions_to_logger(&d.suggestions),
1470                details: None,
1471                loc: d.loc.as_ref().map(|l| diag_loc_to_logger_loc(l, filename)),
1472            },
1473        })
1474        .collect();
1475
1476    let (reason, description) = details
1477        .first()
1478        .map(|d| (d.reason.clone(), d.description.clone()))
1479        .unwrap_or_else(|| ("Unknown error".to_string(), None));
1480
1481    CompilerErrorInfo {
1482        reason,
1483        description,
1484        details,
1485        raw_message: None,
1486        formatted_message: None,
1487    }
1488}
1489
1490// -----------------------------------------------------------------------
1491// Compilation pipeline stubs
1492// -----------------------------------------------------------------------
1493
1494/// Attempt to compile a single function.
1495///
1496/// Returns `CodegenFunction` on success or `CompilerError` on failure.
1497/// Debug log entries are accumulated on `context.debug_logs`.
1498fn try_compile_function(
1499    source: &CompileSource<'_>,
1500    scope_info: &ScopeInfo,
1501    output_mode: CompilerOutputMode,
1502    env_config: &EnvironmentConfig,
1503    context: &mut ProgramContext,
1504) -> Result<CodegenFunction, CompilerError> {
1505    // Check for suppressions that affect this function
1506    if let (Some(start), Some(end)) = (source.fn_start, source.fn_end) {
1507        let affecting = filter_suppressions_that_affect_function(&context.suppressions, start, end);
1508        if !affecting.is_empty() {
1509            let owned: Vec<SuppressionRange> = affecting.into_iter().cloned().collect();
1510            let mut err = suppressions_to_compiler_error(&owned);
1511            // Suppression errors are returned (not thrown), so they should NOT
1512            // trigger CompileUnexpectedThrow.
1513            err.is_thrown = false;
1514            return Err(err);
1515        }
1516    }
1517
1518    // Run the compilation pipeline
1519    pipeline::compile_fn(
1520        &source.fn_node,
1521        source.fn_name.as_deref(),
1522        scope_info,
1523        source.fn_type,
1524        output_mode,
1525        env_config,
1526        context,
1527    )
1528}
1529
1530/// Process a single function: check directives, attempt compilation, handle results.
1531///
1532/// Returns `Ok(Some(codegen_fn))` when the function was compiled and should be applied,
1533/// `Ok(None)` when the function was skipped or lint-only,
1534/// or `Err(CompileResult)` if a fatal error should short-circuit the program.
1535fn process_fn(
1536    source: &CompileSource<'_>,
1537    scope_info: &ScopeInfo,
1538    output_mode: CompilerOutputMode,
1539    env_config: &EnvironmentConfig,
1540    context: &mut ProgramContext,
1541) -> Result<Option<CodegenFunction>, CompileResult> {
1542    // Parse directives from the function body
1543    let opt_in_result =
1544        try_find_directive_enabling_memoization(&source.body_directives, &context.opts);
1545    let opt_out = find_directive_disabling_memoization(&source.body_directives, &context.opts);
1546
1547    // If parsing opt-in directive fails, handle the error and skip
1548    let opt_in = match opt_in_result {
1549        Ok(d) => d,
1550        Err(err) => {
1551            // Apply panic threshold logic (same as compilation errors)
1552            if let Some(result) = handle_error(&err, source.fn_ast_loc.as_ref(), context) {
1553                return Err(result);
1554            }
1555            return Ok(None);
1556        }
1557    };
1558
1559    // Attempt compilation
1560    let compile_result = try_compile_function(source, scope_info, output_mode, env_config, context);
1561
1562    match compile_result {
1563        Err(err) => {
1564            // Emit CompileUnexpectedThrow for errors that were "thrown" from a pass
1565            // (not accumulated via env.record_error) and have all non-Invariant details.
1566            // Matches TS tryCompileFunction() catch block behavior.
1567            if err.is_thrown && err.is_all_non_invariant() {
1568                let source_filename = source
1569                    .fn_ast_loc
1570                    .as_ref()
1571                    .and_then(|loc| loc.filename.as_deref());
1572                context.log_event(LoggerEvent::CompileUnexpectedThrow {
1573                    fn_loc: to_logger_loc(source.fn_ast_loc.as_ref(), source_filename),
1574                    data: err.to_string_for_event(),
1575                });
1576            }
1577
1578            if opt_out.is_some() {
1579                // If there's an opt-out, just log the error (don't escalate)
1580                log_error(&err, source.fn_ast_loc.as_ref(), context);
1581            } else {
1582                // Apply panic threshold logic
1583                if let Some(result) = handle_error(&err, source.fn_ast_loc.as_ref(), context) {
1584                    return Err(result);
1585                }
1586            }
1587            Ok(None)
1588        }
1589        Ok(codegen_fn) => {
1590            // Check opt-out
1591            if !context.opts.ignore_use_no_forget && opt_out.is_some() {
1592                let opt_out_value = &opt_out.unwrap().value.value;
1593                let source_filename = source
1594                    .fn_ast_loc
1595                    .as_ref()
1596                    .and_then(|loc| loc.filename.as_deref());
1597                context.log_event(LoggerEvent::CompileSkip {
1598                    fn_loc: to_logger_loc(source.fn_ast_loc.as_ref(), source_filename),
1599                    reason: format!("Skipped due to '{}' directive.", opt_out_value),
1600                    loc: opt_out.and_then(|d| to_logger_loc(d.base.loc.as_ref(), source_filename)),
1601                });
1602                // The function is skipped due to opt-out. Do NOT register the memo
1603                // cache import here — it will be registered in apply_compiled_functions()
1604                // only for functions that are actually applied to the output.
1605                return Ok(None);
1606            }
1607
1608            // Log success with memo stats from CodegenFunction
1609            let source_filename = source
1610                .fn_ast_loc
1611                .as_ref()
1612                .and_then(|loc| loc.filename.as_deref());
1613            context.log_event(LoggerEvent::CompileSuccess {
1614                fn_loc: to_logger_loc(source.fn_ast_loc.as_ref(), source_filename),
1615                fn_name: codegen_fn.id.as_ref().map(|id| id.name.clone()),
1616                memo_slots: codegen_fn.memo_slots_used,
1617                memo_blocks: codegen_fn.memo_blocks,
1618                memo_values: codegen_fn.memo_values,
1619                pruned_memo_blocks: codegen_fn.pruned_memo_blocks,
1620                pruned_memo_values: codegen_fn.pruned_memo_values,
1621            });
1622
1623            // Check module scope opt-out
1624            if context.has_module_scope_opt_out {
1625                return Ok(None);
1626            }
1627
1628            // Check output mode — lint mode doesn't apply compiled functions
1629            if output_mode == CompilerOutputMode::Lint {
1630                return Ok(None);
1631            }
1632
1633            // Check annotation mode
1634            if context.opts.compilation_mode == "annotation" && opt_in.is_none() {
1635                return Ok(None);
1636            }
1637
1638            Ok(Some(codegen_fn))
1639        }
1640    }
1641}
1642
1643// -----------------------------------------------------------------------
1644// Import checking
1645// -----------------------------------------------------------------------
1646
1647/// Check if the program already has a `c` import from the React Compiler runtime module.
1648/// If so, the file was already compiled and should be skipped.
1649fn has_memo_cache_function_import(program: &Program, module_name: &str) -> bool {
1650    for stmt in &program.body {
1651        if let Statement::ImportDeclaration(import) = stmt {
1652            if import.source.value == module_name {
1653                for specifier in &import.specifiers {
1654                    if let ImportSpecifier::ImportSpecifier(data) = specifier {
1655                        let imported_name = match &data.imported {
1656                            ModuleExportName::Identifier(id) => &id.name,
1657                            ModuleExportName::StringLiteral(s) => &s.value,
1658                        };
1659                        if imported_name == "c" {
1660                            return true;
1661                        }
1662                    }
1663                }
1664            }
1665        }
1666    }
1667    false
1668}
1669
1670/// Check if compilation should be skipped for this program.
1671fn should_skip_compilation(program: &Program, options: &PluginOptions) -> bool {
1672    let runtime_module = get_react_compiler_runtime_module(&options.target);
1673    has_memo_cache_function_import(program, &runtime_module)
1674}
1675
1676// -----------------------------------------------------------------------
1677// Function discovery
1678// -----------------------------------------------------------------------
1679
1680/// Information about an expression that might be a function to compile
1681struct FunctionInfo<'a> {
1682    name: Option<String>,
1683    fn_node: FunctionNode<'a>,
1684    params: &'a [PatternLike],
1685    body: FunctionBody<'a>,
1686    body_directives: Vec<Directive>,
1687    base: &'a BaseNode,
1688    parent_callee_name: Option<String>,
1689    /// True if the node has `__componentDeclaration` set by the Hermes parser (Flow component syntax)
1690    is_component_declaration: bool,
1691    /// True if the node has `__hookDeclaration` set by the Hermes parser (Flow hook syntax)
1692    is_hook_declaration: bool,
1693}
1694
1695/// Extract function info from a FunctionDeclaration
1696fn fn_info_from_decl(decl: &FunctionDeclaration) -> FunctionInfo<'_> {
1697    FunctionInfo {
1698        name: get_function_name_from_id(decl.id.as_ref()),
1699        fn_node: FunctionNode::FunctionDeclaration(decl),
1700        params: &decl.params,
1701        body: FunctionBody::Block(&decl.body),
1702        body_directives: decl.body.directives.clone(),
1703        base: &decl.base,
1704        parent_callee_name: None,
1705        is_component_declaration: decl.component_declaration,
1706        is_hook_declaration: decl.hook_declaration,
1707    }
1708}
1709
1710/// Extract function info from a FunctionExpression
1711fn fn_info_from_func_expr<'a>(
1712    expr: &'a FunctionExpression,
1713    inferred_name: Option<String>,
1714    parent_callee_name: Option<String>,
1715) -> FunctionInfo<'a> {
1716    FunctionInfo {
1717        name: inferred_name,
1718        fn_node: FunctionNode::FunctionExpression(expr),
1719        params: &expr.params,
1720        body: FunctionBody::Block(&expr.body),
1721        body_directives: expr.body.directives.clone(),
1722        base: &expr.base,
1723        parent_callee_name,
1724        is_component_declaration: false,
1725        is_hook_declaration: false,
1726    }
1727}
1728
1729/// Extract function info from an ArrowFunctionExpression
1730fn fn_info_from_arrow<'a>(
1731    expr: &'a ArrowFunctionExpression,
1732    inferred_name: Option<String>,
1733    parent_callee_name: Option<String>,
1734) -> FunctionInfo<'a> {
1735    let (body, directives) = match expr.body.as_ref() {
1736        ArrowFunctionBody::BlockStatement(block) => {
1737            (FunctionBody::Block(block), block.directives.clone())
1738        }
1739        ArrowFunctionBody::Expression(e) => (FunctionBody::Expression(e), Vec::new()),
1740    };
1741    FunctionInfo {
1742        name: inferred_name,
1743        fn_node: FunctionNode::ArrowFunctionExpression(expr),
1744        params: &expr.params,
1745        body,
1746        body_directives: directives,
1747        base: &expr.base,
1748        parent_callee_name,
1749        is_component_declaration: false,
1750        is_hook_declaration: false,
1751    }
1752}
1753
1754/// Try to create a CompileSource from function info
1755fn try_make_compile_source<'a>(
1756    info: FunctionInfo<'a>,
1757    opts: &PluginOptions,
1758    context: &mut ProgramContext,
1759) -> Option<CompileSource<'a>> {
1760    // Skip if already compiled (identified by node_id)
1761    if let Some(nid) = info.base.node_id {
1762        if context.is_already_compiled(nid) {
1763            return None;
1764        }
1765    }
1766
1767    let fn_type = get_react_function_type(
1768        info.name.as_deref(),
1769        info.params,
1770        &info.body,
1771        &info.body_directives,
1772        info.is_component_declaration || info.is_hook_declaration,
1773        info.parent_callee_name.as_deref(),
1774        opts,
1775        info.is_component_declaration,
1776        info.is_hook_declaration,
1777    )?;
1778
1779    // Mark as compiled
1780    if let Some(nid) = info.base.node_id {
1781        context.mark_compiled(nid);
1782    }
1783
1784    Some(CompileSource {
1785        kind: CompileSourceKind::Original,
1786        fn_node: info.fn_node,
1787        fn_name: info.name,
1788        fn_loc: base_node_loc(info.base),
1789        fn_ast_loc: info.base.loc.clone(),
1790        fn_start: info.base.start,
1791        fn_end: info.base.end,
1792        fn_node_id: info.base.node_id,
1793        fn_type,
1794        body_directives: info.body_directives,
1795    })
1796}
1797
1798/// Get the variable declarator name (for inferring function names from `const Foo = () => {}`)
1799fn get_declarator_name(decl: &VariableDeclarator) -> Option<String> {
1800    match &decl.id {
1801        PatternLike::Identifier(id) => Some(id.name.clone()),
1802        _ => None,
1803    }
1804}
1805
1806// -----------------------------------------------------------------------
1807// FunctionDiscoveryVisitor — uses AstWalker to find compilable functions
1808// -----------------------------------------------------------------------
1809
1810/// Visitor that discovers functions to compile, matching the TypeScript
1811/// compiler's Babel `program.traverse` behavior.
1812///
1813/// Dynamically controls body traversal via `traverse_function_bodies()`:
1814/// functions that are queued for compilation have their bodies skipped
1815/// (matching Babel's `fn.skip()`), while non-compiled functions have their
1816/// bodies traversed to find nested component/hook declarations.
1817///
1818/// Tracks parent context via:
1819/// - `current_declarator_name`: set by `enter_variable_declarator`, used to
1820///   infer function names from `const Foo = () => {}`.
1821/// - `parent_callee_stack`: set by `enter_call_expression`, used to detect
1822///   forwardRef/memo wrappers around function expressions.
1823///
1824/// In 'all' mode, uses `scope_stack.len() > 1` to reject functions that are
1825/// not at program scope. The walker pushes the program scope first, then
1826/// nested scopes for for/switch/etc. — so `len() > 1` means the function
1827/// is inside a nested scope (not at program level), matching Babel's
1828/// `fn.scope.getProgramParent() !== fn.scope.parent` check.
1829struct FunctionDiscoveryVisitor<'a, 'ast> {
1830    opts: &'a PluginOptions,
1831    context: &'a mut ProgramContext,
1832    queue: Vec<CompileSource<'ast>>,
1833    /// The inferred name from the current VariableDeclarator, if any.
1834    current_declarator_name: Option<String>,
1835    /// Stack tracking callee names of enclosing CallExpressions.
1836    /// `Some(name)` when the callee is a React API (forwardRef/memo),
1837    /// `None` for other calls.
1838    parent_callee_stack: Vec<Option<String>>,
1839    /// Depth counter for loop expression positions (while.test, for-in.right, etc.).
1840    /// When > 0, functions are treated as non-program-scope in 'all' mode.
1841    loop_expression_depth: usize,
1842    /// Set by enter_* hooks: true when the function was queued for compilation,
1843    /// meaning the walker should NOT traverse its body (matching Babel's fn.skip()).
1844    /// When false, the walker DOES traverse the body to find nested declarations.
1845    skip_body: bool,
1846}
1847
1848impl<'a, 'ast> FunctionDiscoveryVisitor<'a, 'ast> {
1849    fn new(opts: &'a PluginOptions, context: &'a mut ProgramContext) -> Self {
1850        Self {
1851            opts,
1852            context,
1853            queue: Vec::new(),
1854            current_declarator_name: None,
1855            parent_callee_stack: Vec::new(),
1856            loop_expression_depth: 0,
1857            skip_body: false,
1858        }
1859    }
1860
1861    /// Check if in 'all' mode and the function is inside a nested scope.
1862    /// The walker pushes the function's own scope BEFORE calling enter hooks,
1863    /// so scope_stack = [program, ...parents, function_scope]. A top-level
1864    /// function has len=2 (program + function). Anything deeper means it's
1865    /// inside a nested scope (for/switch/etc.) and should be rejected.
1866    /// Also rejects functions found in loop expression positions (while.test,
1867    /// for-in.right, etc.) where Babel treats the scope as non-program.
1868    fn is_rejected_by_scope_check(&self, scope_stack: &[ScopeId]) -> bool {
1869        self.opts.compilation_mode == "all"
1870            && (scope_stack.len() > 2 || self.loop_expression_depth > 0)
1871    }
1872
1873    /// Get the current parent callee name (forwardRef/memo) if any.
1874    fn current_parent_callee(&self) -> Option<String> {
1875        self.parent_callee_stack.last().and_then(|opt| opt.clone())
1876    }
1877}
1878
1879impl<'a, 'ast> Visitor<'ast> for FunctionDiscoveryVisitor<'a, 'ast> {
1880    fn traverse_function_bodies(&self) -> bool {
1881        // Dynamic: only skip the body of functions that were queued for compilation.
1882        // Non-queued functions have their bodies traversed to find nested declarations
1883        // (matching Babel behavior where fn.skip() is only called for compiled functions).
1884        !self.skip_body
1885    }
1886
1887    fn enter_loop_expression(&mut self) {
1888        self.loop_expression_depth += 1;
1889    }
1890
1891    fn leave_loop_expression(&mut self) {
1892        self.loop_expression_depth -= 1;
1893    }
1894
1895    fn enter_variable_declarator(
1896        &mut self,
1897        node: &'ast VariableDeclarator,
1898        _scope_stack: &[ScopeId],
1899    ) {
1900        // Only infer the declarator name when the init is a direct function
1901        // expression, arrow, or call expression (for forwardRef/memo wrappers).
1902        // TS checks `path.parentPath.isVariableDeclarator()` which only matches
1903        // when the function IS the init, not when it's nested inside an object,
1904        // array, or other expression.
1905        if let Some(ref init) = node.init {
1906            match init.as_ref() {
1907                Expression::FunctionExpression(_)
1908                | Expression::ArrowFunctionExpression(_)
1909                | Expression::CallExpression(_) => {
1910                    self.current_declarator_name = get_declarator_name(node);
1911                }
1912                _ => {}
1913            }
1914        }
1915    }
1916
1917    fn leave_variable_declarator(
1918        &mut self,
1919        _node: &'ast VariableDeclarator,
1920        _scope_stack: &[ScopeId],
1921    ) {
1922        self.current_declarator_name = None;
1923    }
1924
1925    fn enter_call_expression(&mut self, node: &'ast CallExpression, _scope_stack: &[ScopeId]) {
1926        let callee_name = get_callee_name_if_react_api(&node.callee).map(|s| s.to_string());
1927        // In TS, the declarator name only flows through forwardRef/memo calls
1928        // (path.parentPath.isCallExpression() checks the callee). For any other
1929        // call expression, clear the name so nested functions don't inherit it.
1930        if callee_name.is_none() {
1931            self.current_declarator_name = None;
1932        }
1933        self.parent_callee_stack.push(callee_name);
1934    }
1935
1936    fn leave_call_expression(&mut self, _node: &'ast CallExpression, _scope_stack: &[ScopeId]) {
1937        let was_react_api = self
1938            .parent_callee_stack
1939            .pop()
1940            .and_then(|name| name)
1941            .is_some();
1942        // After a forwardRef/memo call finishes, clear the declarator name.
1943        // The name is only valid within the call's arguments — if a function
1944        // inside consumed it via .take(), great; if not, it shouldn't leak
1945        // to sibling or subsequent expressions.
1946        if was_react_api {
1947            self.current_declarator_name = None;
1948        }
1949    }
1950
1951    fn enter_function_declaration(
1952        &mut self,
1953        node: &'ast FunctionDeclaration,
1954        scope_stack: &[ScopeId],
1955    ) {
1956        self.skip_body = false;
1957        if self.is_rejected_by_scope_check(scope_stack) {
1958            return;
1959        }
1960        let info = fn_info_from_decl(node);
1961        if let Some(source) = try_make_compile_source(info, self.opts, self.context) {
1962            self.queue.push(source);
1963            self.skip_body = true;
1964        }
1965    }
1966
1967    fn enter_function_expression(
1968        &mut self,
1969        node: &'ast FunctionExpression,
1970        scope_stack: &[ScopeId],
1971    ) {
1972        self.skip_body = false;
1973        if self.is_rejected_by_scope_check(scope_stack) {
1974            return;
1975        }
1976        // TS getFunctionName for FunctionExpressions only returns names from parent
1977        // context (VariableDeclarator, AssignmentExpression, Property) — never from
1978        // the expression's own `id`. So we only use current_declarator_name here.
1979        let inferred_name = self.current_declarator_name.take();
1980        let parent_callee = self.current_parent_callee();
1981        let info = fn_info_from_func_expr(node, inferred_name, parent_callee);
1982        if let Some(source) = try_make_compile_source(info, self.opts, self.context) {
1983            self.queue.push(source);
1984            self.skip_body = true;
1985        }
1986    }
1987
1988    fn enter_arrow_function_expression(
1989        &mut self,
1990        node: &'ast ArrowFunctionExpression,
1991        scope_stack: &[ScopeId],
1992    ) {
1993        self.skip_body = false;
1994        if self.is_rejected_by_scope_check(scope_stack) {
1995            return;
1996        }
1997        let inferred_name = self.current_declarator_name.take();
1998        let parent_callee = self.current_parent_callee();
1999        let info = fn_info_from_arrow(node, inferred_name, parent_callee);
2000        if let Some(source) = try_make_compile_source(info, self.opts, self.context) {
2001            self.queue.push(source);
2002            self.skip_body = true;
2003        }
2004    }
2005
2006    fn enter_object_method(
2007        &mut self,
2008        _node: &'ast react_compiler_ast::expressions::ObjectMethod,
2009        _scope_stack: &[ScopeId],
2010    ) {
2011        self.skip_body = false;
2012    }
2013}
2014
2015/// Find all functions in the program that should be compiled.
2016///
2017/// Uses the `AstWalker` with a `FunctionDiscoveryVisitor` to traverse
2018/// the entire program, discovering functions at any depth. The visitor
2019/// dynamically controls body traversal: compiled functions have their
2020/// bodies skipped (matching Babel's `fn.skip()`), while non-compiled
2021/// functions have their bodies traversed to find nested declarations.
2022///
2023/// The visitor tracks parent context (VariableDeclarator names for
2024/// `const Foo = () => {}`, CallExpression callees for forwardRef/memo
2025/// wrappers) via enter/leave hooks.
2026///
2027/// Skips classes and their contents (the walker does not recurse into
2028/// class bodies).
2029fn find_functions_to_compile<'a>(
2030    program: &'a Program,
2031    opts: &PluginOptions,
2032    context: &mut ProgramContext,
2033    scope: &ScopeInfo,
2034) -> Vec<CompileSource<'a>> {
2035    let mut visitor = FunctionDiscoveryVisitor::new(opts, context);
2036    let mut walker = AstWalker::new(scope);
2037    walker.walk_program(&mut visitor, program);
2038    visitor.queue
2039}
2040
2041// -----------------------------------------------------------------------
2042// Main entry point
2043// -----------------------------------------------------------------------
2044
2045/// A successfully compiled function, ready to be applied to the AST.
2046struct CompiledFunction<'a> {
2047    #[allow(dead_code)]
2048    kind: CompileSourceKind,
2049    #[allow(dead_code)]
2050    source: &'a CompileSource<'a>,
2051    codegen_fn: CodegenFunction,
2052}
2053
2054/// The type of the original function node, used to determine what kind of
2055/// replacement node to create.
2056#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2057enum OriginalFnKind {
2058    FunctionDeclaration,
2059    FunctionExpression,
2060    ArrowFunctionExpression,
2061}
2062
2063/// Owned representation of a compiled function for AST replacement.
2064/// Does not borrow from the original program, so we can mutate the AST.
2065struct CompiledFnForReplacement {
2066    /// Start position of the original function (retained for range queries).
2067    fn_start: Option<u32>,
2068    /// Node ID of the original function, used to find it in the AST.
2069    fn_node_id: Option<u32>,
2070    /// The kind of the original function node.
2071    original_kind: OriginalFnKind,
2072    /// The compiled codegen output.
2073    codegen_fn: CodegenFunction,
2074    /// Whether this is an original function (vs outlined). Gating only applies to original.
2075    #[allow(dead_code)]
2076    source_kind: CompileSourceKind,
2077    /// The function name, if any.
2078    fn_name: Option<String>,
2079    /// Gating configuration (from dynamic gating or plugin options).
2080    gating: Option<GatingConfig>,
2081}
2082
2083/// Check if a compiled function is referenced before its declaration at the top level.
2084/// This is needed for the gating rewrite: hoisted function declarations that are
2085/// referenced before their declaration site need a special gating pattern.
2086fn get_functions_referenced_before_declaration(
2087    program: &Program,
2088    compiled_fns: &[CompiledFnForReplacement],
2089) -> HashSet<u32> {
2090    // Collect function names and their node_ids for compiled FunctionDeclarations
2091    let mut fn_names: HashMap<String, u32> = HashMap::new();
2092    for compiled in compiled_fns {
2093        if compiled.original_kind == OriginalFnKind::FunctionDeclaration {
2094            if let Some(ref name) = compiled.fn_name {
2095                if let Some(nid) = compiled.fn_node_id {
2096                    fn_names.insert(name.clone(), nid);
2097                }
2098            }
2099        }
2100    }
2101
2102    if fn_names.is_empty() {
2103        return HashSet::new();
2104    }
2105
2106    let mut referenced_before_decl: HashSet<u32> = HashSet::new();
2107
2108    // Walk through program body in order. For each statement, check if it references
2109    // any of the function names before the function's declaration.
2110    for stmt in &program.body {
2111        // Check if this statement IS one of the function declarations
2112        if let Statement::FunctionDeclaration(f) = stmt {
2113            if let Some(ref id) = f.id {
2114                fn_names.remove(&id.name);
2115            }
2116        }
2117        // For all remaining tracked names, check if the statement references them
2118        // at the top level (not inside nested functions)
2119        for (_name, nid) in &fn_names {
2120            if stmt_references_identifier_at_top_level(stmt, _name) {
2121                referenced_before_decl.insert(*nid);
2122            }
2123        }
2124    }
2125
2126    referenced_before_decl
2127}
2128
2129/// Check if a statement references an identifier at the top level (not inside nested functions).
2130fn stmt_references_identifier_at_top_level(stmt: &Statement, name: &str) -> bool {
2131    match stmt {
2132        Statement::FunctionDeclaration(_) => {
2133            // Don't look inside function declarations (they create their own scope)
2134            false
2135        }
2136        Statement::ExportDefaultDeclaration(export) => match export.declaration.as_ref() {
2137            ExportDefaultDecl::Expression(e) => expr_references_identifier_at_top_level(e, name),
2138            _ => false,
2139        },
2140        Statement::ExportNamedDeclaration(export) => {
2141            if let Some(ref decl) = export.declaration {
2142                match decl.as_ref() {
2143                    Declaration::VariableDeclaration(var_decl) => {
2144                        var_decl.declarations.iter().any(|d| {
2145                            d.init
2146                                .as_ref()
2147                                .map_or(false, |e| expr_references_identifier_at_top_level(e, name))
2148                        })
2149                    }
2150                    _ => false,
2151                }
2152            } else {
2153                // export { Name } - check specifiers
2154                export.specifiers.iter().any(|s| {
2155                    if let react_compiler_ast::declarations::ExportSpecifier::ExportSpecifier(
2156                        spec,
2157                    ) = s
2158                    {
2159                        match &spec.local {
2160                            ModuleExportName::Identifier(id) => id.name == name,
2161                            _ => false,
2162                        }
2163                    } else {
2164                        false
2165                    }
2166                })
2167            }
2168        }
2169        Statement::VariableDeclaration(var_decl) => var_decl.declarations.iter().any(|d| {
2170            d.init
2171                .as_ref()
2172                .map_or(false, |e| expr_references_identifier_at_top_level(e, name))
2173        }),
2174        Statement::ExpressionStatement(expr_stmt) => {
2175            expr_references_identifier_at_top_level(&expr_stmt.expression, name)
2176        }
2177        Statement::ReturnStatement(ret) => ret
2178            .argument
2179            .as_ref()
2180            .map_or(false, |e| expr_references_identifier_at_top_level(e, name)),
2181        // Unmodeled statements (e.g. `export = X`) can reference top-level
2182        // bindings; scan the raw node for a matching Identifier so the
2183        // gating reference-before-declaration analysis does not miss them.
2184        Statement::Unknown(unknown) => {
2185            raw_node_references_identifier(&unknown.raw().parse_value(), name)
2186        }
2187        _ => false,
2188    }
2189}
2190
2191/// Conservatively detect an `Identifier` node with the given name anywhere in
2192/// a raw unmodeled subtree.
2193fn raw_node_references_identifier(value: &serde_json::Value, name: &str) -> bool {
2194    match value {
2195        serde_json::Value::Object(map) => {
2196            if map.get("type").and_then(serde_json::Value::as_str) == Some("Identifier")
2197                && map.get("name").and_then(serde_json::Value::as_str) == Some(name)
2198            {
2199                return true;
2200            }
2201            map.values().any(|v| raw_node_references_identifier(v, name))
2202        }
2203        serde_json::Value::Array(items) => {
2204            items.iter().any(|v| raw_node_references_identifier(v, name))
2205        }
2206        _ => false,
2207    }
2208}
2209
2210/// Check if an expression references an identifier at the top level.
2211fn expr_references_identifier_at_top_level(expr: &Expression, name: &str) -> bool {
2212    match expr {
2213        Expression::Identifier(id) => id.name == name,
2214        Expression::CallExpression(call) => {
2215            expr_references_identifier_at_top_level(&call.callee, name)
2216                || call
2217                    .arguments
2218                    .iter()
2219                    .any(|a| expr_references_identifier_at_top_level(a, name))
2220        }
2221        Expression::MemberExpression(member) => {
2222            expr_references_identifier_at_top_level(&member.object, name)
2223        }
2224        Expression::ConditionalExpression(cond) => {
2225            expr_references_identifier_at_top_level(&cond.test, name)
2226                || expr_references_identifier_at_top_level(&cond.consequent, name)
2227                || expr_references_identifier_at_top_level(&cond.alternate, name)
2228        }
2229        Expression::BinaryExpression(bin) => {
2230            expr_references_identifier_at_top_level(&bin.left, name)
2231                || expr_references_identifier_at_top_level(&bin.right, name)
2232        }
2233        Expression::LogicalExpression(log) => {
2234            expr_references_identifier_at_top_level(&log.left, name)
2235                || expr_references_identifier_at_top_level(&log.right, name)
2236        }
2237        // Don't recurse into function expressions/arrows (they create their own scope)
2238        Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) => false,
2239        _ => false,
2240    }
2241}
2242
2243/// Build a function expression from a codegen function (compiled output).
2244fn build_compiled_function_expression(codegen: &CodegenFunction) -> Expression {
2245    Expression::FunctionExpression(FunctionExpression {
2246        base: BaseNode::typed("FunctionExpression"),
2247        id: codegen.id.clone(),
2248        params: codegen.params.clone(),
2249        body: codegen.body.clone(),
2250        generator: codegen.generator,
2251        is_async: codegen.is_async,
2252        return_type: None,
2253        type_parameters: None,
2254        predicate: None,
2255    })
2256}
2257
2258/// Build a function expression that preserves the original function's structure.
2259/// For FunctionDeclarations, converts to FunctionExpression.
2260/// For ArrowFunctionExpressions, keeps as-is.
2261fn clone_original_fn_as_expression(stmt: &Statement, node_id: u32) -> Option<Expression> {
2262    match stmt {
2263        Statement::FunctionDeclaration(f) => {
2264            if f.base.node_id == Some(node_id) {
2265                return Some(Expression::FunctionExpression(FunctionExpression {
2266                    base: BaseNode::typed("FunctionExpression"),
2267                    id: f.id.clone(),
2268                    params: f.params.clone(),
2269                    body: f.body.clone(),
2270                    generator: f.generator,
2271                    is_async: f.is_async,
2272                    return_type: None,
2273                    type_parameters: None,
2274                    predicate: None,
2275                }));
2276            }
2277            None
2278        }
2279        Statement::VariableDeclaration(var_decl) => {
2280            for d in &var_decl.declarations {
2281                if let Some(ref init) = d.init {
2282                    if let Some(e) = clone_original_expr_as_expression(init, node_id) {
2283                        return Some(e);
2284                    }
2285                }
2286            }
2287            None
2288        }
2289        Statement::ExportDefaultDeclaration(export) => match export.declaration.as_ref() {
2290            ExportDefaultDecl::FunctionDeclaration(f) => {
2291                if f.base.node_id == Some(node_id) {
2292                    return Some(Expression::FunctionExpression(FunctionExpression {
2293                        base: BaseNode::typed("FunctionExpression"),
2294                        id: f.id.clone(),
2295                        params: f.params.clone(),
2296                        body: f.body.clone(),
2297                        generator: f.generator,
2298                        is_async: f.is_async,
2299                        return_type: None,
2300                        type_parameters: None,
2301                        predicate: None,
2302                    }));
2303                }
2304                None
2305            }
2306            ExportDefaultDecl::Expression(e) => clone_original_expr_as_expression(e, node_id),
2307            _ => None,
2308        },
2309        Statement::ExportNamedDeclaration(export) => {
2310            if let Some(ref decl) = export.declaration {
2311                match decl.as_ref() {
2312                    Declaration::FunctionDeclaration(f) => {
2313                        if f.base.node_id == Some(node_id) {
2314                            return Some(Expression::FunctionExpression(FunctionExpression {
2315                                base: BaseNode::typed("FunctionExpression"),
2316                                id: f.id.clone(),
2317                                params: f.params.clone(),
2318                                body: f.body.clone(),
2319                                generator: f.generator,
2320                                is_async: f.is_async,
2321                                return_type: None,
2322                                type_parameters: None,
2323                                predicate: None,
2324                            }));
2325                        }
2326                        None
2327                    }
2328                    Declaration::VariableDeclaration(var_decl) => {
2329                        for d in &var_decl.declarations {
2330                            if let Some(ref init) = d.init {
2331                                if let Some(e) = clone_original_expr_as_expression(init, node_id) {
2332                                    return Some(e);
2333                                }
2334                            }
2335                        }
2336                        None
2337                    }
2338                    _ => None,
2339                }
2340            } else {
2341                None
2342            }
2343        }
2344        Statement::ExpressionStatement(expr_stmt) => {
2345            clone_original_expr_as_expression(&expr_stmt.expression, node_id)
2346        }
2347        // Recurse into block-containing statements
2348        Statement::BlockStatement(block) => {
2349            for s in &block.body {
2350                if let Some(e) = clone_original_fn_as_expression(s, node_id) {
2351                    return Some(e);
2352                }
2353            }
2354            None
2355        }
2356        Statement::IfStatement(if_stmt) => {
2357            if let Some(e) = clone_original_expr_as_expression(&if_stmt.test, node_id) {
2358                return Some(e);
2359            }
2360            if let Some(e) = clone_original_fn_as_expression(&if_stmt.consequent, node_id) {
2361                return Some(e);
2362            }
2363            if let Some(ref alt) = if_stmt.alternate {
2364                if let Some(e) = clone_original_fn_as_expression(alt, node_id) {
2365                    return Some(e);
2366                }
2367            }
2368            None
2369        }
2370        Statement::TryStatement(try_stmt) => {
2371            for s in &try_stmt.block.body {
2372                if let Some(e) = clone_original_fn_as_expression(s, node_id) {
2373                    return Some(e);
2374                }
2375            }
2376            if let Some(ref handler) = try_stmt.handler {
2377                for s in &handler.body.body {
2378                    if let Some(e) = clone_original_fn_as_expression(s, node_id) {
2379                        return Some(e);
2380                    }
2381                }
2382            }
2383            if let Some(ref finalizer) = try_stmt.finalizer {
2384                for s in &finalizer.body {
2385                    if let Some(e) = clone_original_fn_as_expression(s, node_id) {
2386                        return Some(e);
2387                    }
2388                }
2389            }
2390            None
2391        }
2392        Statement::SwitchStatement(switch_stmt) => {
2393            if let Some(e) = clone_original_expr_as_expression(&switch_stmt.discriminant, node_id) {
2394                return Some(e);
2395            }
2396            for case in &switch_stmt.cases {
2397                for s in &case.consequent {
2398                    if let Some(e) = clone_original_fn_as_expression(s, node_id) {
2399                        return Some(e);
2400                    }
2401                }
2402            }
2403            None
2404        }
2405        Statement::LabeledStatement(labeled) => {
2406            clone_original_fn_as_expression(&labeled.body, node_id)
2407        }
2408        Statement::ForStatement(for_stmt) => {
2409            if let Some(ref init) = for_stmt.init {
2410                match init.as_ref() {
2411                    ForInit::VariableDeclaration(var_decl) => {
2412                        for d in &var_decl.declarations {
2413                            if let Some(ref init_expr) = d.init {
2414                                if let Some(e) =
2415                                    clone_original_expr_as_expression(init_expr, node_id)
2416                                {
2417                                    return Some(e);
2418                                }
2419                            }
2420                        }
2421                    }
2422                    ForInit::Expression(expr) => {
2423                        if let Some(e) = clone_original_expr_as_expression(expr, node_id) {
2424                            return Some(e);
2425                        }
2426                    }
2427                }
2428            }
2429            if let Some(ref test) = for_stmt.test {
2430                if let Some(e) = clone_original_expr_as_expression(test, node_id) {
2431                    return Some(e);
2432                }
2433            }
2434            if let Some(ref update) = for_stmt.update {
2435                if let Some(e) = clone_original_expr_as_expression(update, node_id) {
2436                    return Some(e);
2437                }
2438            }
2439            clone_original_fn_as_expression(&for_stmt.body, node_id)
2440        }
2441        Statement::WhileStatement(while_stmt) => {
2442            if let Some(e) = clone_original_expr_as_expression(&while_stmt.test, node_id) {
2443                return Some(e);
2444            }
2445            clone_original_fn_as_expression(&while_stmt.body, node_id)
2446        }
2447        Statement::DoWhileStatement(do_while) => {
2448            if let Some(e) = clone_original_expr_as_expression(&do_while.test, node_id) {
2449                return Some(e);
2450            }
2451            clone_original_fn_as_expression(&do_while.body, node_id)
2452        }
2453        Statement::ForInStatement(for_in) => {
2454            if let Some(e) = clone_original_expr_as_expression(&for_in.right, node_id) {
2455                return Some(e);
2456            }
2457            clone_original_fn_as_expression(&for_in.body, node_id)
2458        }
2459        Statement::ForOfStatement(for_of) => {
2460            if let Some(e) = clone_original_expr_as_expression(&for_of.right, node_id) {
2461                return Some(e);
2462            }
2463            clone_original_fn_as_expression(&for_of.body, node_id)
2464        }
2465        Statement::WithStatement(with_stmt) => {
2466            if let Some(e) = clone_original_expr_as_expression(&with_stmt.object, node_id) {
2467                return Some(e);
2468            }
2469            clone_original_fn_as_expression(&with_stmt.body, node_id)
2470        }
2471        Statement::ReturnStatement(ret) => {
2472            if let Some(ref arg) = ret.argument {
2473                clone_original_expr_as_expression(arg, node_id)
2474            } else {
2475                None
2476            }
2477        }
2478        Statement::ThrowStatement(throw_stmt) => {
2479            clone_original_expr_as_expression(&throw_stmt.argument, node_id)
2480        }
2481        _ => None,
2482    }
2483}
2484
2485/// Clone an expression node for use as the original (fallback) in gating.
2486fn clone_original_expr_as_expression(expr: &Expression, node_id: u32) -> Option<Expression> {
2487    match expr {
2488        Expression::FunctionExpression(f) => {
2489            if f.base.node_id == Some(node_id) {
2490                return Some(Expression::FunctionExpression(f.clone()));
2491            }
2492            None
2493        }
2494        Expression::ArrowFunctionExpression(f) => {
2495            if f.base.node_id == Some(node_id) {
2496                return Some(Expression::ArrowFunctionExpression(f.clone()));
2497            }
2498            None
2499        }
2500        Expression::CallExpression(call) => {
2501            for arg in &call.arguments {
2502                if let Some(e) = clone_original_expr_as_expression(arg, node_id) {
2503                    return Some(e);
2504                }
2505            }
2506            None
2507        }
2508        Expression::ObjectExpression(obj) => {
2509            for prop in &obj.properties {
2510                match prop {
2511                    ObjectExpressionProperty::ObjectProperty(p) => {
2512                        if let Some(e) = clone_original_expr_as_expression(&p.value, node_id) {
2513                            return Some(e);
2514                        }
2515                    }
2516                    ObjectExpressionProperty::SpreadElement(s) => {
2517                        if let Some(e) = clone_original_expr_as_expression(&s.argument, node_id) {
2518                            return Some(e);
2519                        }
2520                    }
2521                    _ => {}
2522                }
2523            }
2524            None
2525        }
2526        Expression::ArrayExpression(arr) => {
2527            for elem in arr.elements.iter().flatten() {
2528                if let Some(e) = clone_original_expr_as_expression(elem, node_id) {
2529                    return Some(e);
2530                }
2531            }
2532            None
2533        }
2534        Expression::AssignmentExpression(assign) => {
2535            clone_original_expr_as_expression(&assign.right, node_id)
2536        }
2537        Expression::SequenceExpression(seq) => {
2538            for e in &seq.expressions {
2539                if let Some(e) = clone_original_expr_as_expression(e, node_id) {
2540                    return Some(e);
2541                }
2542            }
2543            None
2544        }
2545        Expression::ConditionalExpression(cond) => {
2546            if let Some(e) = clone_original_expr_as_expression(&cond.consequent, node_id) {
2547                return Some(e);
2548            }
2549            clone_original_expr_as_expression(&cond.alternate, node_id)
2550        }
2551        Expression::ParenthesizedExpression(paren) => {
2552            clone_original_expr_as_expression(&paren.expression, node_id)
2553        }
2554        _ => None,
2555    }
2556}
2557
2558/// Build a compiled arrow/function expression from a codegen function,
2559/// matching the original expression kind.
2560fn build_compiled_expression_matching_kind(
2561    codegen: &CodegenFunction,
2562    original_kind: OriginalFnKind,
2563) -> Expression {
2564    match original_kind {
2565        OriginalFnKind::ArrowFunctionExpression => {
2566            Expression::ArrowFunctionExpression(ArrowFunctionExpression {
2567                base: BaseNode::typed("ArrowFunctionExpression"),
2568                params: codegen.params.clone(),
2569                body: Box::new(ArrowFunctionBody::BlockStatement(codegen.body.clone())),
2570                id: None,
2571                generator: codegen.generator,
2572                is_async: codegen.is_async,
2573                expression: Some(false),
2574                return_type: None,
2575                type_parameters: None,
2576                predicate: None,
2577            })
2578        }
2579        _ => build_compiled_function_expression(codegen),
2580    }
2581}
2582
2583/// Apply compiled functions back to the AST by replacing original function nodes
2584/// with their compiled versions, inserting outlined functions, and adding imports.
2585fn apply_compiled_functions(
2586    compiled_fns: &[CompiledFnForReplacement],
2587    program: &mut Program,
2588    context: &mut ProgramContext,
2589) {
2590    if compiled_fns.is_empty() {
2591        return;
2592    }
2593
2594    // Check if any compiled functions have gating enabled
2595    let has_gating = compiled_fns.iter().any(|cf| cf.gating.is_some());
2596
2597    // If gating is enabled, determine which functions are referenced before declaration
2598    let referenced_before_decl = if has_gating {
2599        get_functions_referenced_before_declaration(program, compiled_fns)
2600    } else {
2601        HashSet::new()
2602    };
2603
2604    // For gated functions, we need to clone the original function expressions
2605    // BEFORE we start mutating the AST.
2606    let original_expressions: Vec<Option<Expression>> = if has_gating {
2607        compiled_fns
2608            .iter()
2609            .map(|compiled| {
2610                if compiled.gating.is_some() {
2611                    if let Some(node_id) = compiled.fn_node_id {
2612                        for stmt in program.body.iter() {
2613                            if let Some(expr) = clone_original_fn_as_expression(stmt, node_id) {
2614                                return Some(expr);
2615                            }
2616                        }
2617                    }
2618                    None
2619                } else {
2620                    None
2621                }
2622            })
2623            .collect()
2624    } else {
2625        compiled_fns.iter().map(|_| None).collect()
2626    };
2627
2628    // Collect outlined functions to insert (as FunctionDeclarations).
2629    // For FunctionDeclarations: insert right after the parent (matching TS insertAfter behavior)
2630    // For FunctionExpression/ArrowFunctionExpression: append at end of program body
2631    //   (matching TS pushContainer behavior)
2632    let mut outlined_decls: Vec<(Option<u32>, OriginalFnKind, FunctionDeclaration)> = Vec::new(); // (node_id, kind, decl)
2633
2634    // Replace each compiled function in the AST
2635    for (idx, compiled) in compiled_fns.iter().enumerate() {
2636        // Collect outlined functions for this compiled function
2637        for outlined in &compiled.codegen_fn.outlined {
2638            let outlined_decl = FunctionDeclaration {
2639                base: BaseNode::typed("FunctionDeclaration"),
2640                id: outlined.func.id.clone(),
2641                params: outlined.func.params.clone(),
2642                body: outlined.func.body.clone(),
2643                generator: outlined.func.generator,
2644                is_async: outlined.func.is_async,
2645                declare: None,
2646                return_type: None,
2647                type_parameters: None,
2648                predicate: None,
2649                component_declaration: false,
2650                hook_declaration: false,
2651            };
2652            outlined_decls.push((compiled.fn_node_id, compiled.original_kind, outlined_decl));
2653        }
2654
2655        if let Some(ref gating_config) = compiled.gating {
2656            let is_ref_before_decl = compiled
2657                .fn_node_id
2658                .map_or(false, |nid| referenced_before_decl.contains(&nid));
2659
2660            if is_ref_before_decl && compiled.original_kind == OriginalFnKind::FunctionDeclaration {
2661                // Use the hoisted function declaration gating pattern
2662                apply_gated_function_hoisted(program, compiled, gating_config, context);
2663            } else {
2664                // Use the conditional expression gating pattern
2665                let original_expr = original_expressions[idx].clone();
2666                apply_gated_function_conditional(
2667                    program,
2668                    compiled,
2669                    gating_config,
2670                    original_expr,
2671                    context,
2672                );
2673            }
2674        } else {
2675            // No gating: replace the function directly (original behavior)
2676            if let Some(node_id) = compiled.fn_node_id {
2677                let mut visitor = ReplaceFnVisitor { node_id, compiled };
2678                walk_program_mut(&mut visitor, program);
2679            }
2680        }
2681    }
2682
2683    // Insert outlined function declarations.
2684    // For FunctionDeclarations: insert right after the parent function at the same scope level.
2685    //   This requires recursive search since the parent may be nested inside other functions.
2686    //   Matches TS behavior: `originalFn.insertAfter(outlinedFn)`.
2687    // For FunctionExpression/ArrowFunctionExpression: push to program body (top level).
2688    //   Matches TS behavior: `program.pushContainer('body', [fn])`.
2689
2690    for (parent_node_id, original_kind, outlined_decl) in outlined_decls {
2691        let outlined_stmt = Statement::FunctionDeclaration(outlined_decl);
2692        match original_kind {
2693            OriginalFnKind::FunctionDeclaration => {
2694                if let Some(nid) = parent_node_id {
2695                    if !insert_after_fn_recursive(&mut program.body, nid, outlined_stmt.clone()) {
2696                        program.body.push(outlined_stmt);
2697                    }
2698                } else {
2699                    program.body.push(outlined_stmt);
2700                }
2701            }
2702            OriginalFnKind::FunctionExpression | OriginalFnKind::ArrowFunctionExpression => {
2703                program.body.push(outlined_stmt);
2704            }
2705        }
2706    }
2707
2708    // Register the memo cache import and rename useMemoCache references.
2709    let needs_memo_import = compiled_fns
2710        .iter()
2711        .any(|cf| cf.codegen_fn.memo_slots_used > 0);
2712    if needs_memo_import {
2713        let import_spec = context.add_memo_cache_import();
2714        let local_name = import_spec.name;
2715        let mut visitor = RenameIdentifierVisitor {
2716            old_name: "useMemoCache",
2717            new_name: &local_name,
2718        };
2719        walk_program_mut(&mut visitor, program);
2720    }
2721
2722    // Instrumentation and hook guard imports are pre-registered in compile_program
2723    // before compilation, so they are already in the imports map. No post-hoc
2724    // renaming needed since codegen uses the pre-resolved local names.
2725
2726    add_imports_to_program(program, context);
2727}
2728
2729/// Apply the conditional expression gating pattern.
2730///
2731/// For function declarations (non-export-default, non-hoisted):
2732///   `function Foo(props) { ... }` -> `const Foo = gating() ? function Foo(...) { compiled } : function Foo(...) { original };`
2733///
2734/// For export default function with name:
2735///   `export default function Foo(props) { ... }` -> `const Foo = gating() ? ... : ...; export default Foo;`
2736///
2737/// For export named function:
2738///   `export function Foo(props) { ... }` -> `export const Foo = gating() ? ... : ...;`
2739///
2740/// For arrow/function expressions:
2741///   Replace the expression inline with `gating() ? compiled : original`
2742fn apply_gated_function_conditional(
2743    program: &mut Program,
2744    compiled: &CompiledFnForReplacement,
2745    gating_config: &GatingConfig,
2746    original_expr: Option<Expression>,
2747    context: &mut ProgramContext,
2748) {
2749    let _start = match compiled.fn_start {
2750        Some(s) => s,
2751        None => return,
2752    };
2753    let node_id = match compiled.fn_node_id {
2754        Some(nid) => nid,
2755        None => return,
2756    };
2757
2758    // Add the gating import
2759    let gating_import = context.add_import_specifier(
2760        &gating_config.source,
2761        &gating_config.import_specifier_name,
2762        None,
2763    );
2764    let gating_callee_name = gating_import.name;
2765
2766    // Build the compiled expression
2767    let compiled_expr =
2768        build_compiled_expression_matching_kind(&compiled.codegen_fn, compiled.original_kind);
2769
2770    // Build the original (fallback) expression
2771    let original_expr = match original_expr {
2772        Some(e) => e,
2773        None => return, // shouldn't happen
2774    };
2775
2776    // Build: gating() ? compiled : original
2777    let gating_expression = Expression::ConditionalExpression(ConditionalExpression {
2778        base: BaseNode::typed("ConditionalExpression"),
2779        test: Box::new(Expression::CallExpression(CallExpression {
2780            base: BaseNode::typed("CallExpression"),
2781            callee: Box::new(Expression::Identifier(Identifier {
2782                base: BaseNode::typed("Identifier"),
2783                name: gating_callee_name,
2784                type_annotation: None,
2785                optional: None,
2786                decorators: None,
2787            })),
2788            arguments: vec![],
2789            type_parameters: None,
2790            type_arguments: None,
2791            optional: None,
2792        })),
2793        consequent: Box::new(compiled_expr),
2794        alternate: Box::new(original_expr),
2795    });
2796
2797    // Find and replace the function in the program body.
2798    // We need to track if this was an export default function with a name,
2799    // because we need to insert `export default Name;` after the replacement.
2800    let mut export_default_name: Option<(usize, String)> = None;
2801
2802    for (idx, stmt) in program.body.iter().enumerate() {
2803        if let Statement::ExportDefaultDeclaration(export) = stmt {
2804            if let ExportDefaultDecl::FunctionDeclaration(f) = export.declaration.as_ref() {
2805                if f.base.node_id == Some(node_id) {
2806                    if let Some(ref fn_id) = f.id {
2807                        export_default_name = Some((idx, fn_id.name.clone()));
2808                    }
2809                }
2810            }
2811        }
2812    }
2813
2814    let mut visitor = ReplaceWithGatedVisitor {
2815        node_id,
2816        gating_expression: &gating_expression,
2817    };
2818    walk_program_mut(&mut visitor, program);
2819
2820    // If this was an export default function with a name, insert `export default Name;` after
2821    if let Some((idx, name)) = export_default_name {
2822        program.body.insert(
2823            idx + 1,
2824            Statement::ExportDefaultDeclaration(ExportDefaultDeclaration {
2825                base: BaseNode::typed("ExportDefaultDeclaration"),
2826                declaration: Box::new(ExportDefaultDecl::Expression(Box::new(
2827                    Expression::Identifier(Identifier {
2828                        base: BaseNode::typed("Identifier"),
2829                        name,
2830                        type_annotation: None,
2831                        optional: None,
2832                        decorators: None,
2833                    }),
2834                ))),
2835                export_kind: None,
2836            }),
2837        );
2838    }
2839}
2840
2841/// Visitor that replaces a function with a gated conditional expression.
2842struct ReplaceWithGatedVisitor<'a> {
2843    node_id: u32,
2844    gating_expression: &'a Expression,
2845}
2846
2847impl MutVisitor for ReplaceWithGatedVisitor<'_> {
2848    fn visit_statement(&mut self, stmt: &mut Statement) -> VisitResult {
2849        // FunctionDeclaration → replace with `const Foo = gating() ? ... : ...;`
2850        if let Statement::FunctionDeclaration(f) = &*stmt {
2851            if f.base.node_id == Some(self.node_id) {
2852                let fn_name = f.id.clone().unwrap_or_else(|| Identifier {
2853                    base: BaseNode::typed("Identifier"),
2854                    name: "anonymous".to_string(),
2855                    type_annotation: None,
2856                    optional: None,
2857                    decorators: None,
2858                });
2859                let mut base = BaseNode::typed("VariableDeclaration");
2860                base.leading_comments = f.base.leading_comments.clone();
2861                base.trailing_comments = f.base.trailing_comments.clone();
2862                base.inner_comments = f.base.inner_comments.clone();
2863                *stmt = Statement::VariableDeclaration(VariableDeclaration {
2864                    base,
2865                    kind: VariableDeclarationKind::Const,
2866                    declarations: vec![VariableDeclarator {
2867                        base: BaseNode::typed("VariableDeclarator"),
2868                        id: PatternLike::Identifier(fn_name),
2869                        init: Some(Box::new(self.gating_expression.clone())),
2870                        definite: None,
2871                    }],
2872                    declare: None,
2873                });
2874                return VisitResult::Stop;
2875            }
2876        }
2877
2878        // ExportDefaultDeclaration with FunctionDeclaration
2879        if let Statement::ExportDefaultDeclaration(export) = stmt {
2880            let is_fn_decl_match = matches!(
2881                export.declaration.as_ref(),
2882                ExportDefaultDecl::FunctionDeclaration(f) if f.base.node_id == Some(self.node_id)
2883            );
2884            if is_fn_decl_match {
2885                if let ExportDefaultDecl::FunctionDeclaration(f) = export.declaration.as_ref() {
2886                    let fn_name = f.id.clone();
2887                    if let Some(fn_id) = fn_name {
2888                        let mut base = BaseNode::typed("VariableDeclaration");
2889                        base.leading_comments = export.base.leading_comments.clone();
2890                        base.trailing_comments = export.base.trailing_comments.clone();
2891                        base.inner_comments = export.base.inner_comments.clone();
2892                        *stmt = Statement::VariableDeclaration(VariableDeclaration {
2893                            base,
2894                            kind: VariableDeclarationKind::Const,
2895                            declarations: vec![VariableDeclarator {
2896                                base: BaseNode::typed("VariableDeclarator"),
2897                                id: PatternLike::Identifier(fn_id),
2898                                init: Some(Box::new(self.gating_expression.clone())),
2899                                definite: None,
2900                            }],
2901                            declare: None,
2902                        });
2903                        return VisitResult::Stop;
2904                    } else {
2905                        export.declaration = Box::new(ExportDefaultDecl::Expression(Box::new(
2906                            self.gating_expression.clone(),
2907                        )));
2908                        return VisitResult::Stop;
2909                    }
2910                }
2911            }
2912            // Expression case handled by walker recursion into visit_expression
2913        }
2914
2915        // ExportNamedDeclaration with FunctionDeclaration
2916        if let Statement::ExportNamedDeclaration(export) = stmt {
2917            if let Some(ref mut decl) = export.declaration {
2918                if let Declaration::FunctionDeclaration(f) = decl.as_mut() {
2919                    if f.base.node_id == Some(self.node_id) {
2920                        let fn_name = f.id.clone().unwrap_or_else(|| Identifier {
2921                            base: BaseNode::typed("Identifier"),
2922                            name: "anonymous".to_string(),
2923                            type_annotation: None,
2924                            optional: None,
2925                            decorators: None,
2926                        });
2927                        *decl = Box::new(Declaration::VariableDeclaration(VariableDeclaration {
2928                            base: BaseNode::typed("VariableDeclaration"),
2929                            kind: VariableDeclarationKind::Const,
2930                            declarations: vec![VariableDeclarator {
2931                                base: BaseNode::typed("VariableDeclarator"),
2932                                id: PatternLike::Identifier(fn_name),
2933                                init: Some(Box::new(self.gating_expression.clone())),
2934                                definite: None,
2935                            }],
2936                            declare: None,
2937                        }));
2938                        return VisitResult::Stop;
2939                    }
2940                }
2941            }
2942        }
2943
2944        VisitResult::Continue
2945    }
2946
2947    fn visit_expression(&mut self, expr: &mut Expression) -> VisitResult {
2948        match expr {
2949            Expression::FunctionExpression(f) if f.base.node_id == Some(self.node_id) => {
2950                *expr = self.gating_expression.clone();
2951                VisitResult::Stop
2952            }
2953            Expression::ArrowFunctionExpression(f) if f.base.node_id == Some(self.node_id) => {
2954                *expr = self.gating_expression.clone();
2955                VisitResult::Stop
2956            }
2957            _ => VisitResult::Continue,
2958        }
2959    }
2960}
2961
2962/// Apply the hoisted function declaration gating pattern.
2963///
2964/// This is used when a function declaration is referenced before its declaration site.
2965/// Instead of wrapping in a conditional expression (which would break hoisting), we:
2966/// 1. Rename the original function to `Foo_unoptimized`
2967/// 2. Insert a compiled function as `Foo_optimized`
2968/// 3. Insert a `const gating_result = gating()` before
2969/// 4. Insert a new `function Foo(arg0, ...) { if (gating_result) return Foo_optimized(...); else return Foo_unoptimized(...); }` after
2970fn apply_gated_function_hoisted(
2971    program: &mut Program,
2972    compiled: &CompiledFnForReplacement,
2973    gating_config: &GatingConfig,
2974    context: &mut ProgramContext,
2975) {
2976    let _start = match compiled.fn_start {
2977        Some(s) => s,
2978        None => return,
2979    };
2980    let node_id = match compiled.fn_node_id {
2981        Some(nid) => nid,
2982        None => return,
2983    };
2984
2985    let original_fn_name = match &compiled.fn_name {
2986        Some(name) => name.clone(),
2987        None => return,
2988    };
2989
2990    // Add the gating import
2991    let gating_import = context.add_import_specifier(
2992        &gating_config.source,
2993        &gating_config.import_specifier_name,
2994        None,
2995    );
2996    let gating_callee_name = gating_import.name.clone();
2997
2998    // Generate unique names
2999    let gating_result_name = context.new_uid(&format!("{}_result", gating_callee_name));
3000    let unoptimized_name = context.new_uid(&format!("{}_unoptimized", original_fn_name));
3001    let optimized_name = context.new_uid(&format!("{}_optimized", original_fn_name));
3002
3003    // Find the original function declaration and determine its params
3004    let mut original_params: Vec<PatternLike> = Vec::new();
3005    let mut fn_stmt_idx: Option<usize> = None;
3006
3007    for (idx, stmt) in program.body.iter().enumerate() {
3008        if let Statement::FunctionDeclaration(f) = stmt {
3009            if f.base.node_id == Some(node_id) {
3010                original_params = f.params.clone();
3011                fn_stmt_idx = Some(idx);
3012                break;
3013            }
3014        }
3015    }
3016
3017    let fn_idx = match fn_stmt_idx {
3018        Some(idx) => idx,
3019        None => return,
3020    };
3021
3022    // Rename the original function to `_unoptimized`
3023    if let Statement::FunctionDeclaration(f) = &mut program.body[fn_idx] {
3024        if let Some(ref mut id) = f.id {
3025            id.name = unoptimized_name.clone();
3026        }
3027    }
3028
3029    // Build the optimized function declaration (compiled version with renamed id)
3030    let compiled_fn_decl = FunctionDeclaration {
3031        base: BaseNode::typed("FunctionDeclaration"),
3032        id: Some(Identifier {
3033            base: BaseNode::typed("Identifier"),
3034            name: optimized_name.clone(),
3035            type_annotation: None,
3036            optional: None,
3037            decorators: None,
3038        }),
3039        params: compiled.codegen_fn.params.clone(),
3040        body: compiled.codegen_fn.body.clone(),
3041        generator: compiled.codegen_fn.generator,
3042        is_async: compiled.codegen_fn.is_async,
3043        declare: None,
3044        return_type: None,
3045        type_parameters: None,
3046        predicate: None,
3047        component_declaration: false,
3048        hook_declaration: false,
3049    };
3050
3051    // Build the gating result variable: `const gating_result = gating();`
3052    let gating_result_stmt = Statement::VariableDeclaration(VariableDeclaration {
3053        base: BaseNode::typed("VariableDeclaration"),
3054        kind: VariableDeclarationKind::Const,
3055        declarations: vec![VariableDeclarator {
3056            base: BaseNode::typed("VariableDeclarator"),
3057            id: PatternLike::Identifier(Identifier {
3058                base: BaseNode::typed("Identifier"),
3059                name: gating_result_name.clone(),
3060                type_annotation: None,
3061                optional: None,
3062                decorators: None,
3063            }),
3064            init: Some(Box::new(Expression::CallExpression(CallExpression {
3065                base: BaseNode::typed("CallExpression"),
3066                callee: Box::new(Expression::Identifier(Identifier {
3067                    base: BaseNode::typed("Identifier"),
3068                    name: gating_callee_name,
3069                    type_annotation: None,
3070                    optional: None,
3071                    decorators: None,
3072                })),
3073                arguments: vec![],
3074                type_parameters: None,
3075                type_arguments: None,
3076                optional: None,
3077            }))),
3078            definite: None,
3079        }],
3080        declare: None,
3081    });
3082
3083    // Build new params and args for the dispatcher function
3084    let num_params = original_params.len();
3085    let mut new_params: Vec<PatternLike> = Vec::new();
3086    let mut optimized_args: Vec<Expression> = Vec::new();
3087    let mut unoptimized_args: Vec<Expression> = Vec::new();
3088
3089    for i in 0..num_params {
3090        let arg_name = format!("arg{}", i);
3091        let is_rest = matches!(&original_params[i], PatternLike::RestElement(_));
3092
3093        if is_rest {
3094            new_params.push(PatternLike::RestElement(
3095                react_compiler_ast::patterns::RestElement {
3096                    base: BaseNode::typed("RestElement"),
3097                    argument: Box::new(PatternLike::Identifier(Identifier {
3098                        base: BaseNode::typed("Identifier"),
3099                        name: arg_name.clone(),
3100                        type_annotation: None,
3101                        optional: None,
3102                        decorators: None,
3103                    })),
3104                    type_annotation: None,
3105                    decorators: None,
3106                },
3107            ));
3108            optimized_args.push(Expression::SpreadElement(SpreadElement {
3109                base: BaseNode::typed("SpreadElement"),
3110                argument: Box::new(Expression::Identifier(Identifier {
3111                    base: BaseNode::typed("Identifier"),
3112                    name: arg_name.clone(),
3113                    type_annotation: None,
3114                    optional: None,
3115                    decorators: None,
3116                })),
3117            }));
3118            unoptimized_args.push(Expression::SpreadElement(SpreadElement {
3119                base: BaseNode::typed("SpreadElement"),
3120                argument: Box::new(Expression::Identifier(Identifier {
3121                    base: BaseNode::typed("Identifier"),
3122                    name: arg_name,
3123                    type_annotation: None,
3124                    optional: None,
3125                    decorators: None,
3126                })),
3127            }));
3128        } else {
3129            new_params.push(PatternLike::Identifier(Identifier {
3130                base: BaseNode::typed("Identifier"),
3131                name: arg_name.clone(),
3132                type_annotation: None,
3133                optional: None,
3134                decorators: None,
3135            }));
3136            optimized_args.push(Expression::Identifier(Identifier {
3137                base: BaseNode::typed("Identifier"),
3138                name: arg_name.clone(),
3139                type_annotation: None,
3140                optional: None,
3141                decorators: None,
3142            }));
3143            unoptimized_args.push(Expression::Identifier(Identifier {
3144                base: BaseNode::typed("Identifier"),
3145                name: arg_name,
3146                type_annotation: None,
3147                optional: None,
3148                decorators: None,
3149            }));
3150        }
3151    }
3152
3153    // Build the dispatcher function:
3154    // function Foo(arg0, ...) {
3155    //   if (gating_result) return Foo_optimized(arg0, ...);
3156    //   else return Foo_unoptimized(arg0, ...);
3157    // }
3158    let dispatcher_fn = Statement::FunctionDeclaration(FunctionDeclaration {
3159        base: BaseNode::typed("FunctionDeclaration"),
3160        id: Some(Identifier {
3161            base: BaseNode::typed("Identifier"),
3162            name: original_fn_name,
3163            type_annotation: None,
3164            optional: None,
3165            decorators: None,
3166        }),
3167        params: new_params,
3168        body: BlockStatement {
3169            base: BaseNode::typed("BlockStatement"),
3170            body: vec![Statement::IfStatement(IfStatement {
3171                base: BaseNode::typed("IfStatement"),
3172                test: Box::new(Expression::Identifier(Identifier {
3173                    base: BaseNode::typed("Identifier"),
3174                    name: gating_result_name,
3175                    type_annotation: None,
3176                    optional: None,
3177                    decorators: None,
3178                })),
3179                consequent: Box::new(Statement::ReturnStatement(ReturnStatement {
3180                    base: BaseNode::typed("ReturnStatement"),
3181                    argument: Some(Box::new(Expression::CallExpression(CallExpression {
3182                        base: BaseNode::typed("CallExpression"),
3183                        callee: Box::new(Expression::Identifier(Identifier {
3184                            base: BaseNode::typed("Identifier"),
3185                            name: optimized_name.clone(),
3186                            type_annotation: None,
3187                            optional: None,
3188                            decorators: None,
3189                        })),
3190                        arguments: optimized_args,
3191                        type_parameters: None,
3192                        type_arguments: None,
3193                        optional: None,
3194                    }))),
3195                })),
3196                alternate: Some(Box::new(Statement::ReturnStatement(ReturnStatement {
3197                    base: BaseNode::typed("ReturnStatement"),
3198                    argument: Some(Box::new(Expression::CallExpression(CallExpression {
3199                        base: BaseNode::typed("CallExpression"),
3200                        callee: Box::new(Expression::Identifier(Identifier {
3201                            base: BaseNode::typed("Identifier"),
3202                            name: unoptimized_name,
3203                            type_annotation: None,
3204                            optional: None,
3205                            decorators: None,
3206                        })),
3207                        arguments: unoptimized_args,
3208                        type_parameters: None,
3209                        type_arguments: None,
3210                        optional: None,
3211                    }))),
3212                }))),
3213            })],
3214            directives: vec![],
3215        },
3216        generator: false,
3217        is_async: false,
3218        declare: None,
3219        return_type: None,
3220        type_parameters: None,
3221        predicate: None,
3222        component_declaration: false,
3223        hook_declaration: false,
3224    });
3225
3226    // Insert nodes. The TS code uses insertBefore for the gating result and optimized fn,
3227    // and insertAfter for the dispatcher. The order in the output should be:
3228    //   ... (existing statements before fn_idx) ...
3229    //   const gating_result = gating();       <- inserted before
3230    //   function Foo_optimized() { ... }       <- inserted before
3231    //   function Foo_unoptimized() { ... }     <- the original (renamed)
3232    //   function Foo(arg0) { ... }             <- inserted after
3233    //   ... (existing statements after fn_idx) ...
3234    //
3235    // insertBefore inserts before the target, and insertAfter inserts after.
3236    // We insert in reverse order for insertAfter.
3237
3238    // Insert dispatcher after the original (now renamed) function
3239    program.body.insert(fn_idx + 1, dispatcher_fn);
3240
3241    // Insert optimized function before the original
3242    program
3243        .body
3244        .insert(fn_idx, Statement::FunctionDeclaration(compiled_fn_decl));
3245
3246    // Insert gating result before the optimized function
3247    program.body.insert(fn_idx, gating_result_stmt);
3248}
3249
3250/// Recursively search for a function at `start` position and insert `new_stmt`
3251/// right after it in the same block. Returns true if successfully inserted.
3252/// Searches through all nested structures: function bodies, object method bodies, etc.
3253fn insert_after_fn_recursive(
3254    stmts: &mut Vec<Statement>,
3255    node_id: u32,
3256    new_stmt: Statement,
3257) -> bool {
3258    // Check this level first
3259    if let Some(pos) = stmts
3260        .iter()
3261        .position(|s| stmt_has_fn_with_node_id(s, node_id))
3262    {
3263        stmts.insert(pos + 1, new_stmt);
3264        return true;
3265    }
3266    // Recurse into every statement that can contain nested blocks
3267    for stmt in stmts.iter_mut() {
3268        if insert_after_fn_in_stmt(stmt, node_id, &new_stmt) {
3269            return true;
3270        }
3271    }
3272    false
3273}
3274
3275fn insert_after_fn_in_stmt(stmt: &mut Statement, node_id: u32, new_stmt: &Statement) -> bool {
3276    match stmt {
3277        Statement::FunctionDeclaration(f) => {
3278            insert_after_fn_in_block(&mut f.body, node_id, new_stmt)
3279        }
3280        Statement::BlockStatement(b) => insert_after_fn_in_block(b, node_id, new_stmt),
3281        Statement::ExpressionStatement(e) => {
3282            insert_after_fn_in_expr(&mut e.expression, node_id, new_stmt)
3283        }
3284        Statement::ReturnStatement(r) => {
3285            if let Some(arg) = &mut r.argument {
3286                insert_after_fn_in_expr(arg, node_id, new_stmt)
3287            } else {
3288                false
3289            }
3290        }
3291        Statement::VariableDeclaration(v) => {
3292            for decl in &mut v.declarations {
3293                if let Some(init) = &mut decl.init {
3294                    if insert_after_fn_in_expr(init, node_id, new_stmt) {
3295                        return true;
3296                    }
3297                }
3298            }
3299            false
3300        }
3301        Statement::ExportDefaultDeclaration(e) => match e.declaration.as_mut() {
3302            ExportDefaultDecl::FunctionDeclaration(f) => {
3303                insert_after_fn_in_block(&mut f.body, node_id, new_stmt)
3304            }
3305            ExportDefaultDecl::Expression(expr) => insert_after_fn_in_expr(expr, node_id, new_stmt),
3306            _ => false,
3307        },
3308        Statement::ExportNamedDeclaration(e) => {
3309            if let Some(decl) = &mut e.declaration {
3310                match decl.as_mut() {
3311                    Declaration::FunctionDeclaration(f) => {
3312                        insert_after_fn_in_block(&mut f.body, node_id, new_stmt)
3313                    }
3314                    Declaration::VariableDeclaration(v) => {
3315                        for d in &mut v.declarations {
3316                            if let Some(init) = &mut d.init {
3317                                if insert_after_fn_in_expr(init, node_id, new_stmt) {
3318                                    return true;
3319                                }
3320                            }
3321                        }
3322                        false
3323                    }
3324                    _ => false,
3325                }
3326            } else {
3327                false
3328            }
3329        }
3330        Statement::IfStatement(i) => {
3331            insert_after_fn_in_stmt(&mut i.consequent, node_id, new_stmt)
3332                || i.alternate
3333                    .as_mut()
3334                    .map_or(false, |a| insert_after_fn_in_stmt(a, node_id, new_stmt))
3335        }
3336        Statement::ForStatement(f) => insert_after_fn_in_stmt(&mut f.body, node_id, new_stmt),
3337        Statement::WhileStatement(w) => insert_after_fn_in_stmt(&mut w.body, node_id, new_stmt),
3338        Statement::TryStatement(t) => {
3339            if insert_after_fn_in_block(&mut t.block, node_id, new_stmt) {
3340                return true;
3341            }
3342            if let Some(h) = &mut t.handler {
3343                if insert_after_fn_in_block(&mut h.body, node_id, new_stmt) {
3344                    return true;
3345                }
3346            }
3347            if let Some(f) = &mut t.finalizer {
3348                if insert_after_fn_in_block(f, node_id, new_stmt) {
3349                    return true;
3350                }
3351            }
3352            false
3353        }
3354        _ => false,
3355    }
3356}
3357
3358fn insert_after_fn_in_block(
3359    block: &mut react_compiler_ast::statements::BlockStatement,
3360    node_id: u32,
3361    new_stmt: &Statement,
3362) -> bool {
3363    if let Some(pos) = block
3364        .body
3365        .iter()
3366        .position(|s| stmt_has_fn_with_node_id(s, node_id))
3367    {
3368        block.body.insert(pos + 1, new_stmt.clone());
3369        return true;
3370    }
3371    for stmt in block.body.iter_mut() {
3372        if insert_after_fn_in_stmt(stmt, node_id, new_stmt) {
3373            return true;
3374        }
3375    }
3376    false
3377}
3378
3379fn insert_after_fn_in_expr(
3380    expr: &mut react_compiler_ast::expressions::Expression,
3381    node_id: u32,
3382    new_stmt: &Statement,
3383) -> bool {
3384    use react_compiler_ast::expressions::Expression;
3385    match expr {
3386        Expression::ObjectExpression(obj) => {
3387            for prop in &mut obj.properties {
3388                match prop {
3389                    react_compiler_ast::expressions::ObjectExpressionProperty::ObjectMethod(m) => {
3390                        if insert_after_fn_in_block(&mut m.body, node_id, new_stmt) {
3391                            return true;
3392                        }
3393                    }
3394                    react_compiler_ast::expressions::ObjectExpressionProperty::ObjectProperty(
3395                        p,
3396                    ) => {
3397                        if insert_after_fn_in_expr(&mut p.value, node_id, new_stmt) {
3398                            return true;
3399                        }
3400                    }
3401                    _ => {}
3402                }
3403            }
3404            false
3405        }
3406        Expression::ArrayExpression(arr) => {
3407            for elem in arr.elements.iter_mut().flatten() {
3408                if insert_after_fn_in_expr(elem, node_id, new_stmt) {
3409                    return true;
3410                }
3411            }
3412            false
3413        }
3414        Expression::ArrowFunctionExpression(arrow) => match arrow.body.as_mut() {
3415            react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => {
3416                insert_after_fn_in_block(block, node_id, new_stmt)
3417            }
3418            react_compiler_ast::expressions::ArrowFunctionBody::Expression(e) => {
3419                insert_after_fn_in_expr(e, node_id, new_stmt)
3420            }
3421        },
3422        Expression::FunctionExpression(f) => {
3423            insert_after_fn_in_block(&mut f.body, node_id, new_stmt)
3424        }
3425        Expression::CallExpression(c) => {
3426            for arg in &mut c.arguments {
3427                if insert_after_fn_in_expr(arg, node_id, new_stmt) {
3428                    return true;
3429                }
3430            }
3431            insert_after_fn_in_expr(&mut c.callee, node_id, new_stmt)
3432        }
3433        Expression::ConditionalExpression(c) => {
3434            insert_after_fn_in_expr(&mut c.consequent, node_id, new_stmt)
3435                || insert_after_fn_in_expr(&mut c.alternate, node_id, new_stmt)
3436        }
3437        Expression::AssignmentExpression(a) => {
3438            insert_after_fn_in_expr(&mut a.right, node_id, new_stmt)
3439        }
3440        Expression::TypeCastExpression(tc) => {
3441            insert_after_fn_in_expr(&mut tc.expression, node_id, new_stmt)
3442        }
3443        Expression::ParenthesizedExpression(p) => {
3444            insert_after_fn_in_expr(&mut p.expression, node_id, new_stmt)
3445        }
3446        Expression::TSAsExpression(ts) => {
3447            insert_after_fn_in_expr(&mut ts.expression, node_id, new_stmt)
3448        }
3449        Expression::SequenceExpression(s) => {
3450            for expr in &mut s.expressions {
3451                if insert_after_fn_in_expr(expr, node_id, new_stmt) {
3452                    return true;
3453                }
3454            }
3455            false
3456        }
3457        _ => false,
3458    }
3459}
3460
3461/// Check if a statement contains a function whose BaseNode.node_id matches.
3462fn stmt_has_fn_with_node_id(stmt: &Statement, node_id: u32) -> bool {
3463    match stmt {
3464        Statement::FunctionDeclaration(f) => f.base.node_id == Some(node_id),
3465        Statement::VariableDeclaration(var_decl) => var_decl.declarations.iter().any(|decl| {
3466            if let Some(ref init) = decl.init {
3467                expr_has_fn_with_node_id(init, node_id)
3468            } else {
3469                false
3470            }
3471        }),
3472        Statement::ExportDefaultDeclaration(export) => match export.declaration.as_ref() {
3473            ExportDefaultDecl::FunctionDeclaration(f) => f.base.node_id == Some(node_id),
3474            ExportDefaultDecl::Expression(e) => expr_has_fn_with_node_id(e, node_id),
3475            _ => false,
3476        },
3477        Statement::ExportNamedDeclaration(export) => {
3478            if let Some(ref decl) = export.declaration {
3479                match decl.as_ref() {
3480                    Declaration::FunctionDeclaration(f) => f.base.node_id == Some(node_id),
3481                    Declaration::VariableDeclaration(var_decl) => {
3482                        var_decl.declarations.iter().any(|d| {
3483                            if let Some(ref init) = d.init {
3484                                expr_has_fn_with_node_id(init, node_id)
3485                            } else {
3486                                false
3487                            }
3488                        })
3489                    }
3490                    _ => false,
3491                }
3492            } else {
3493                false
3494            }
3495        }
3496        Statement::ExpressionStatement(expr_stmt) => {
3497            expr_has_fn_with_node_id(&expr_stmt.expression, node_id)
3498        }
3499        // Recurse into block-containing statements
3500        Statement::BlockStatement(block) => block
3501            .body
3502            .iter()
3503            .any(|s| stmt_has_fn_with_node_id(s, node_id)),
3504        Statement::IfStatement(if_stmt) => {
3505            expr_has_fn_with_node_id(&if_stmt.test, node_id)
3506                || stmt_has_fn_with_node_id(&if_stmt.consequent, node_id)
3507                || if_stmt
3508                    .alternate
3509                    .as_ref()
3510                    .map_or(false, |alt| stmt_has_fn_with_node_id(alt, node_id))
3511        }
3512        Statement::TryStatement(try_stmt) => {
3513            try_stmt
3514                .block
3515                .body
3516                .iter()
3517                .any(|s| stmt_has_fn_with_node_id(s, node_id))
3518                || try_stmt.handler.as_ref().map_or(false, |h| {
3519                    h.body
3520                        .body
3521                        .iter()
3522                        .any(|s| stmt_has_fn_with_node_id(s, node_id))
3523                })
3524                || try_stmt.finalizer.as_ref().map_or(false, |f| {
3525                    f.body.iter().any(|s| stmt_has_fn_with_node_id(s, node_id))
3526                })
3527        }
3528        Statement::SwitchStatement(switch_stmt) => {
3529            expr_has_fn_with_node_id(&switch_stmt.discriminant, node_id)
3530                || switch_stmt.cases.iter().any(|case| {
3531                    case.consequent
3532                        .iter()
3533                        .any(|s| stmt_has_fn_with_node_id(s, node_id))
3534                })
3535        }
3536        Statement::LabeledStatement(labeled) => stmt_has_fn_with_node_id(&labeled.body, node_id),
3537        Statement::ForStatement(for_stmt) => {
3538            if let Some(ref init) = for_stmt.init {
3539                match init.as_ref() {
3540                    ForInit::VariableDeclaration(var_decl) => {
3541                        if var_decl.declarations.iter().any(|d| {
3542                            d.init
3543                                .as_ref()
3544                                .map_or(false, |e| expr_has_fn_with_node_id(e, node_id))
3545                        }) {
3546                            return true;
3547                        }
3548                    }
3549                    ForInit::Expression(expr) => {
3550                        if expr_has_fn_with_node_id(expr, node_id) {
3551                            return true;
3552                        }
3553                    }
3554                }
3555            }
3556            if for_stmt
3557                .test
3558                .as_ref()
3559                .map_or(false, |t| expr_has_fn_with_node_id(t, node_id))
3560            {
3561                return true;
3562            }
3563            if for_stmt
3564                .update
3565                .as_ref()
3566                .map_or(false, |u| expr_has_fn_with_node_id(u, node_id))
3567            {
3568                return true;
3569            }
3570            stmt_has_fn_with_node_id(&for_stmt.body, node_id)
3571        }
3572        Statement::WhileStatement(while_stmt) => {
3573            expr_has_fn_with_node_id(&while_stmt.test, node_id)
3574                || stmt_has_fn_with_node_id(&while_stmt.body, node_id)
3575        }
3576        Statement::DoWhileStatement(do_while) => {
3577            expr_has_fn_with_node_id(&do_while.test, node_id)
3578                || stmt_has_fn_with_node_id(&do_while.body, node_id)
3579        }
3580        Statement::ForInStatement(for_in) => {
3581            expr_has_fn_with_node_id(&for_in.right, node_id)
3582                || stmt_has_fn_with_node_id(&for_in.body, node_id)
3583        }
3584        Statement::ForOfStatement(for_of) => {
3585            expr_has_fn_with_node_id(&for_of.right, node_id)
3586                || stmt_has_fn_with_node_id(&for_of.body, node_id)
3587        }
3588        Statement::WithStatement(with_stmt) => {
3589            expr_has_fn_with_node_id(&with_stmt.object, node_id)
3590                || stmt_has_fn_with_node_id(&with_stmt.body, node_id)
3591        }
3592        Statement::ReturnStatement(ret) => ret
3593            .argument
3594            .as_ref()
3595            .map_or(false, |arg| expr_has_fn_with_node_id(arg, node_id)),
3596        Statement::ThrowStatement(throw_stmt) => {
3597            expr_has_fn_with_node_id(&throw_stmt.argument, node_id)
3598        }
3599        _ => false,
3600    }
3601}
3602
3603/// Check if an expression contains a function whose BaseNode.node_id matches.
3604fn expr_has_fn_with_node_id(expr: &Expression, node_id: u32) -> bool {
3605    match expr {
3606        Expression::FunctionExpression(f) => f.base.node_id == Some(node_id),
3607        Expression::ArrowFunctionExpression(f) => f.base.node_id == Some(node_id),
3608        // Check for forwardRef/memo wrappers: the inner function
3609        Expression::CallExpression(call) => call
3610            .arguments
3611            .iter()
3612            .any(|arg| expr_has_fn_with_node_id(arg, node_id)),
3613        _ => false,
3614    }
3615}
3616
3617/// Visitor that replaces a compiled function in the AST by matching `base.node_id`.
3618struct ReplaceFnVisitor<'a> {
3619    node_id: u32,
3620    compiled: &'a CompiledFnForReplacement,
3621}
3622
3623impl MutVisitor for ReplaceFnVisitor<'_> {
3624    fn visit_statement(&mut self, stmt: &mut Statement) -> VisitResult {
3625        match stmt {
3626            Statement::FunctionDeclaration(f) if f.base.node_id == Some(self.node_id) => {
3627                f.id = self.compiled.codegen_fn.id.clone();
3628                f.params = self.compiled.codegen_fn.params.clone();
3629                f.body = self.compiled.codegen_fn.body.clone();
3630                f.generator = self.compiled.codegen_fn.generator;
3631                f.is_async = self.compiled.codegen_fn.is_async;
3632                f.return_type = None;
3633                f.type_parameters = None;
3634                f.predicate = None;
3635                f.declare = None;
3636                return VisitResult::Stop;
3637            }
3638            Statement::ExportDefaultDeclaration(export) => {
3639                if let ExportDefaultDecl::FunctionDeclaration(f) = export.declaration.as_mut() {
3640                    if f.base.node_id == Some(self.node_id) {
3641                        f.id = self.compiled.codegen_fn.id.clone();
3642                        f.params = self.compiled.codegen_fn.params.clone();
3643                        f.body = self.compiled.codegen_fn.body.clone();
3644                        f.generator = self.compiled.codegen_fn.generator;
3645                        f.is_async = self.compiled.codegen_fn.is_async;
3646                        f.return_type = None;
3647                        f.type_parameters = None;
3648                        f.predicate = None;
3649                        f.declare = None;
3650                        return VisitResult::Stop;
3651                    }
3652                }
3653            }
3654            Statement::ExportNamedDeclaration(export) => {
3655                if let Some(ref mut decl) = export.declaration {
3656                    if let Declaration::FunctionDeclaration(f) = decl.as_mut() {
3657                        if f.base.node_id == Some(self.node_id) {
3658                            f.id = self.compiled.codegen_fn.id.clone();
3659                            f.params = self.compiled.codegen_fn.params.clone();
3660                            f.body = self.compiled.codegen_fn.body.clone();
3661                            f.generator = self.compiled.codegen_fn.generator;
3662                            f.is_async = self.compiled.codegen_fn.is_async;
3663                            f.return_type = None;
3664                            f.type_parameters = None;
3665                            f.predicate = None;
3666                            f.declare = None;
3667                            return VisitResult::Stop;
3668                        }
3669                    }
3670                }
3671            }
3672            _ => {}
3673        }
3674        VisitResult::Continue
3675    }
3676
3677    fn visit_expression(&mut self, expr: &mut Expression) -> VisitResult {
3678        match expr {
3679            Expression::FunctionExpression(f) if f.base.node_id == Some(self.node_id) => {
3680                f.id = self.compiled.codegen_fn.id.clone();
3681                f.params = self.compiled.codegen_fn.params.clone();
3682                f.body = self.compiled.codegen_fn.body.clone();
3683                f.generator = self.compiled.codegen_fn.generator;
3684                f.is_async = self.compiled.codegen_fn.is_async;
3685                f.return_type = None;
3686                f.type_parameters = None;
3687                VisitResult::Stop
3688            }
3689            Expression::ArrowFunctionExpression(f) if f.base.node_id == Some(self.node_id) => {
3690                f.params = self.compiled.codegen_fn.params.clone();
3691                f.body = Box::new(ArrowFunctionBody::BlockStatement(
3692                    self.compiled.codegen_fn.body.clone(),
3693                ));
3694                f.generator = self.compiled.codegen_fn.generator;
3695                f.is_async = self.compiled.codegen_fn.is_async;
3696                f.expression = Some(false);
3697                f.return_type = None;
3698                f.type_parameters = None;
3699                f.predicate = None;
3700                VisitResult::Stop
3701            }
3702            _ => VisitResult::Continue,
3703        }
3704    }
3705}
3706
3707/// Visitor that renames all occurrences of an identifier in expression position.
3708struct RenameIdentifierVisitor<'a> {
3709    old_name: &'a str,
3710    new_name: &'a str,
3711}
3712
3713impl MutVisitor for RenameIdentifierVisitor<'_> {
3714    fn visit_identifier(&mut self, node: &mut Identifier) -> VisitResult {
3715        if node.name == self.old_name {
3716            node.name = self.new_name.to_string();
3717        }
3718        VisitResult::Continue
3719    }
3720}
3721
3722/// Main entry point for the React Compiler.
3723///
3724/// Receives a full program AST, scope information (unused for now), and resolved options.
3725/// Returns a CompileResult indicating whether the AST was modified,
3726/// along with any logger events.
3727///
3728/// This function implements the logic from the TS entrypoint (Program.ts):
3729/// - shouldSkipCompilation: check for existing runtime imports
3730/// - validateRestrictedImports: check for blocklisted imports
3731/// - findProgramSuppressions: find eslint/flow suppression comments
3732/// - findFunctionsToCompile: traverse program to find components and hooks
3733/// - processFn: per-function compilation with directive and suppression handling
3734/// - applyCompiledFunctions: replace original functions with compiled versions
3735pub fn compile_program(mut file: File, scope: ScopeInfo, options: PluginOptions) -> CompileResult {
3736    // Compute output mode once, up front
3737    let output_mode = CompilerOutputMode::from_opts(&options);
3738
3739    // Create a temporary context for early-return paths (before full context is set up)
3740    let early_events: Vec<LoggerEvent> = Vec::new();
3741    let mut early_ordered_log: Vec<OrderedLogItem> = Vec::new();
3742
3743    // Log environment config for debugLogIRs
3744    if options.debug {
3745        early_ordered_log.push(OrderedLogItem::Debug {
3746            entry: DebugLogEntry::new(
3747                "EnvironmentConfig",
3748                serde_json::to_string_pretty(&options.environment).unwrap_or_default(),
3749            ),
3750        });
3751    }
3752
3753    // Check if we should compile this file at all (pre-resolved by JS shim)
3754    if !options.should_compile {
3755        return CompileResult::Success {
3756            ast: None,
3757            events: early_events,
3758            ordered_log: early_ordered_log,
3759            renames: Vec::new(),
3760            timing: Vec::new(),
3761        };
3762    }
3763
3764    let program = &file.program;
3765
3766    // Check for existing runtime imports (file already compiled)
3767    if should_skip_compilation(program, &options) {
3768        return CompileResult::Success {
3769            ast: None,
3770            events: early_events,
3771            ordered_log: early_ordered_log,
3772            renames: Vec::new(),
3773            timing: Vec::new(),
3774        };
3775    }
3776
3777    // Validate restricted imports from the environment config
3778    let restricted_imports = options.environment.validate_blocklisted_imports.clone();
3779
3780    // Determine if we should check for eslint suppressions
3781    let validate_exhaustive = options
3782        .environment
3783        .validate_exhaustive_memoization_dependencies;
3784    let validate_hooks = options.environment.validate_hooks_usage;
3785
3786    let eslint_rules: Option<Vec<String>> = if validate_exhaustive && validate_hooks {
3787        // Don't check for ESLint suppressions if both validations are enabled
3788        None
3789    } else {
3790        Some(options.eslint_suppression_rules.clone().unwrap_or_else(|| {
3791            DEFAULT_ESLINT_SUPPRESSIONS
3792                .iter()
3793                .map(|s| s.to_string())
3794                .collect()
3795        }))
3796    };
3797
3798    // Find program-level suppressions from comments
3799    let suppressions = find_program_suppressions(
3800        &file.comments,
3801        eslint_rules.as_deref(),
3802        options.flow_suppressions,
3803    );
3804
3805    // Check for module-scope opt-out directive
3806    let has_module_scope_opt_out =
3807        find_directive_disabling_memoization(&program.directives, &options).is_some();
3808
3809    // Create program context
3810    let mut context = ProgramContext::new(
3811        options.clone(),
3812        options.filename.clone(),
3813        // Pass the source code for fast refresh hash computation.
3814        options.source_code.clone(),
3815        suppressions,
3816        has_module_scope_opt_out,
3817    );
3818
3819    // Extract the source filename from the AST (set by parser's sourceFilename option).
3820    // This is the bare filename (e.g., "foo.ts") without path prefixes, which the TS
3821    // compiler uses in logger event source locations.
3822    let source_filename = program
3823        .base
3824        .loc
3825        .as_ref()
3826        .and_then(|loc| loc.filename.clone())
3827        .or_else(|| {
3828            // Fallback: try the first statement's loc
3829            program.body.first().and_then(|stmt| {
3830                let base = match stmt {
3831                    react_compiler_ast::statements::Statement::ExpressionStatement(s) => &s.base,
3832                    react_compiler_ast::statements::Statement::VariableDeclaration(s) => &s.base,
3833                    react_compiler_ast::statements::Statement::FunctionDeclaration(s) => &s.base,
3834                    _ => return None,
3835                };
3836                base.loc.as_ref().and_then(|loc| loc.filename.clone())
3837            })
3838        });
3839    context.set_source_filename(source_filename);
3840
3841    // Initialize known referenced names from scope bindings for UID collision detection
3842    context.init_from_scope(&scope);
3843
3844    // Seed context with early ordered log entries
3845    context.ordered_log.extend(early_ordered_log);
3846
3847    // Validate restricted imports (needs context for handle_error)
3848    if let Some(err) = validate_restricted_imports(program, &restricted_imports) {
3849        if let Some(result) = handle_error(&err, None, &mut context) {
3850            return result;
3851        }
3852        return CompileResult::Success {
3853            ast: None,
3854            events: context.events,
3855            ordered_log: context.ordered_log,
3856            renames: convert_renames(&context.renames),
3857            timing: Vec::new(),
3858        };
3859    }
3860
3861    // Pre-register instrumentation imports to get stable local names.
3862    // These are needed before compilation so codegen can use the correct names.
3863    let instrument_fn_name: Option<String>;
3864    let instrument_gating_name: Option<String>;
3865    let hook_guard_name: Option<String>;
3866
3867    if let Some(ref instrument_config) = options.environment.enable_emit_instrument_forget {
3868        let fn_spec = context.add_import_specifier(
3869            &instrument_config.fn_.source,
3870            &instrument_config.fn_.import_specifier_name,
3871            None,
3872        );
3873        instrument_fn_name = Some(fn_spec.name.clone());
3874        instrument_gating_name = instrument_config.gating.as_ref().map(|g| {
3875            let spec = context.add_import_specifier(&g.source, &g.import_specifier_name, None);
3876            spec.name.clone()
3877        });
3878    } else {
3879        instrument_fn_name = None;
3880        instrument_gating_name = None;
3881    }
3882
3883    if let Some(ref hook_guard_config) = options.environment.enable_emit_hook_guards {
3884        let spec = context.add_import_specifier(
3885            &hook_guard_config.source,
3886            &hook_guard_config.import_specifier_name,
3887            None,
3888        );
3889        hook_guard_name = Some(spec.name.clone());
3890    } else {
3891        hook_guard_name = None;
3892    }
3893
3894    // Store pre-resolved names on context for pipeline access
3895    context.instrument_fn_name = instrument_fn_name;
3896    context.instrument_gating_name = instrument_gating_name;
3897    context.hook_guard_name = hook_guard_name;
3898
3899    // Find all functions to compile
3900    let queue = find_functions_to_compile(program, &options, &mut context, &scope);
3901
3902    // Clone env_config once for all function compilations (avoids per-function clone
3903    // while satisfying the borrow checker — compile_fn needs &mut context + &env_config)
3904    let env_config = options.environment.clone();
3905
3906    // Process each function and collect compiled results
3907    let mut compiled_fns: Vec<CompiledFunction<'_>> = Vec::new();
3908
3909    for source in &queue {
3910        match process_fn(source, &scope, output_mode, &env_config, &mut context) {
3911            Ok(Some(codegen_fn)) => {
3912                compiled_fns.push(CompiledFunction {
3913                    kind: source.kind,
3914                    source,
3915                    codegen_fn,
3916                });
3917            }
3918            Ok(None) => {
3919                // Function was skipped or lint-only
3920            }
3921            Err(fatal_result) => {
3922                return fatal_result;
3923            }
3924        }
3925    }
3926
3927    // Emit CompileSuccess events for JSX-outlined functions (fn_type.is_some()).
3928    // In TS, outlined functions from outlineJSX are appended to the compilation queue
3929    // and processed after all original functions, so their events appear at the end.
3930    // Regular outlined functions (from OutlineFunctions pass) don't get separate events.
3931    for compiled in &compiled_fns {
3932        for outlined in &compiled.codegen_fn.outlined {
3933            if outlined.fn_type.is_some() {
3934                context.log_event(LoggerEvent::CompileSuccess {
3935                    fn_loc: None,
3936                    fn_name: outlined.func.id.as_ref().map(|id| id.name.clone()),
3937                    memo_slots: outlined.func.memo_slots_used,
3938                    memo_blocks: outlined.func.memo_blocks,
3939                    memo_values: outlined.func.memo_values,
3940                    pruned_memo_blocks: outlined.func.pruned_memo_blocks,
3941                    pruned_memo_values: outlined.func.pruned_memo_values,
3942                });
3943            }
3944        }
3945    }
3946
3947    // TS invariant: if there's a module scope opt-out, no functions should have been compiled
3948    if has_module_scope_opt_out {
3949        if !compiled_fns.is_empty() {
3950            let mut err = CompilerError::new();
3951            err.push_error_detail(CompilerErrorDetail::new(
3952                ErrorCategory::Invariant,
3953                "Unexpected compiled functions when module scope opt-out is present",
3954            ));
3955            handle_error(&err, None, &mut context);
3956        }
3957        return CompileResult::Success {
3958            ast: None,
3959            events: context.events,
3960            ordered_log: context.ordered_log,
3961            renames: convert_renames(&context.renames),
3962            timing: Vec::new(),
3963        };
3964    }
3965
3966    // Determine gating for each compiled function.
3967    // In the TS compiler, dynamic gating from directives takes precedence over plugin-level gating.
3968    // Gating only applies to 'original' functions, not 'outlined' ones.
3969    let function_gating_config = options.gating.clone();
3970
3971    // Convert compiled functions to owned representations (dropping borrows)
3972    // so we can mutate the AST.
3973    let replacements: Vec<CompiledFnForReplacement> = compiled_fns
3974        .into_iter()
3975        .map(|cf| {
3976            let original_kind = match cf.source.fn_node {
3977                FunctionNode::FunctionDeclaration(_) => OriginalFnKind::FunctionDeclaration,
3978                FunctionNode::FunctionExpression(_) => OriginalFnKind::FunctionExpression,
3979                FunctionNode::ArrowFunctionExpression(_) => OriginalFnKind::ArrowFunctionExpression,
3980            };
3981            // Determine per-function gating: dynamic gating from directives OR plugin-level gating.
3982            // Dynamic gating (from `use memo if(identifier)`) takes precedence.
3983            let gating = if cf.kind == CompileSourceKind::Original {
3984                // Check body directives for dynamic gating
3985                let dynamic_gating =
3986                    find_directives_dynamic_gating(&cf.source.body_directives, &options)
3987                        .ok()
3988                        .flatten()
3989                        .map(|r| r.gating);
3990                dynamic_gating.or_else(|| function_gating_config.clone())
3991            } else {
3992                None
3993            };
3994            CompiledFnForReplacement {
3995                fn_start: cf.source.fn_start,
3996                fn_node_id: cf.source.fn_node_id,
3997                original_kind,
3998                codegen_fn: cf.codegen_fn,
3999                source_kind: cf.kind,
4000                fn_name: cf.source.fn_name.clone(),
4001                gating,
4002            }
4003        })
4004        .collect();
4005    // Drop queue (and its borrows from file.program)
4006    drop(queue);
4007
4008    if replacements.is_empty() {
4009        // No functions to replace. Return renames for the Babel plugin to apply
4010        // (e.g., variable shadowing renames in lint mode). Imports are NOT added
4011        // when there are no replacements — matching TS behavior where
4012        // addImportsToProgram is only called when compiledFns.length > 0.
4013        return CompileResult::Success {
4014            ast: None,
4015            events: context.events,
4016            ordered_log: context.ordered_log,
4017            renames: convert_renames(&context.renames),
4018            timing: Vec::new(),
4019        };
4020    }
4021
4022    // Now we can mutate file.program
4023    apply_compiled_functions(&replacements, &mut file.program, &mut context);
4024
4025    let timing_entries = context.timing.into_entries();
4026
4027    // Return the compiled File by value; in-process Rust consumers use it
4028    // directly, and the napi consumer serializes the whole result as before.
4029    CompileResult::Success {
4030        ast: Some(file),
4031        events: context.events,
4032        ordered_log: context.ordered_log,
4033        renames: convert_renames(&context.renames),
4034        timing: timing_entries,
4035    }
4036}
4037
4038/// Convert internal BindingRename structs to the serializable BindingRenameInfo format.
4039fn convert_renames(
4040    renames: &[react_compiler_hir::environment::BindingRename],
4041) -> Vec<BindingRenameInfo> {
4042    renames
4043        .iter()
4044        .map(|r| BindingRenameInfo {
4045            original: r.original.clone(),
4046            renamed: r.renamed.clone(),
4047            declaration_start: r.declaration_start,
4048        })
4049        .collect()
4050}
4051
4052#[cfg(test)]
4053mod tests {
4054    use super::*;
4055
4056    #[test]
4057    fn test_is_hook_name() {
4058        assert!(is_hook_name("useState"));
4059        assert!(is_hook_name("useEffect"));
4060        assert!(is_hook_name("use0Something"));
4061        assert!(!is_hook_name("use"));
4062        assert!(!is_hook_name("useless")); // lowercase after use
4063        assert!(!is_hook_name("foo"));
4064        assert!(!is_hook_name(""));
4065    }
4066
4067    #[test]
4068    fn test_is_component_name() {
4069        assert!(is_component_name("MyComponent"));
4070        assert!(is_component_name("App"));
4071        assert!(!is_component_name("myComponent"));
4072        assert!(!is_component_name("app"));
4073        assert!(!is_component_name(""));
4074    }
4075
4076    #[test]
4077    fn test_is_valid_identifier() {
4078        assert!(is_valid_identifier("foo"));
4079        assert!(is_valid_identifier("_bar"));
4080        assert!(is_valid_identifier("$baz"));
4081        assert!(is_valid_identifier("foo123"));
4082        assert!(!is_valid_identifier(""));
4083        assert!(!is_valid_identifier("123foo"));
4084        assert!(!is_valid_identifier("foo bar"));
4085    }
4086
4087    #[test]
4088    fn test_is_valid_component_params_empty() {
4089        assert!(is_valid_component_params(&[]));
4090    }
4091
4092    #[test]
4093    fn test_is_valid_component_params_one_identifier() {
4094        let params = vec![PatternLike::Identifier(Identifier {
4095            base: BaseNode::default(),
4096            name: "props".to_string(),
4097            type_annotation: None,
4098            optional: None,
4099            decorators: None,
4100        })];
4101        assert!(is_valid_component_params(&params));
4102    }
4103
4104    #[test]
4105    fn test_is_valid_component_params_too_many() {
4106        let params = vec![
4107            PatternLike::Identifier(Identifier {
4108                base: BaseNode::default(),
4109                name: "a".to_string(),
4110                type_annotation: None,
4111                optional: None,
4112                decorators: None,
4113            }),
4114            PatternLike::Identifier(Identifier {
4115                base: BaseNode::default(),
4116                name: "b".to_string(),
4117                type_annotation: None,
4118                optional: None,
4119                decorators: None,
4120            }),
4121            PatternLike::Identifier(Identifier {
4122                base: BaseNode::default(),
4123                name: "c".to_string(),
4124                type_annotation: None,
4125                optional: None,
4126                decorators: None,
4127            }),
4128        ];
4129        assert!(!is_valid_component_params(&params));
4130    }
4131
4132    #[test]
4133    fn test_is_valid_component_params_with_ref() {
4134        let params = vec![
4135            PatternLike::Identifier(Identifier {
4136                base: BaseNode::default(),
4137                name: "props".to_string(),
4138                type_annotation: None,
4139                optional: None,
4140                decorators: None,
4141            }),
4142            PatternLike::Identifier(Identifier {
4143                base: BaseNode::default(),
4144                name: "ref".to_string(),
4145                type_annotation: None,
4146                optional: None,
4147                decorators: None,
4148            }),
4149        ];
4150        assert!(is_valid_component_params(&params));
4151    }
4152
4153    #[test]
4154    fn test_should_skip_compilation_no_import() {
4155        let program = Program {
4156            base: BaseNode::default(),
4157            body: vec![],
4158            directives: vec![],
4159            source_type: react_compiler_ast::SourceType::Module,
4160            interpreter: None,
4161            source_file: None,
4162        };
4163        let options = PluginOptions {
4164            should_compile: true,
4165            enable_reanimated: false,
4166            is_dev: false,
4167            filename: None,
4168            compilation_mode: "infer".to_string(),
4169            panic_threshold: "none".to_string(),
4170            target: super::super::plugin_options::CompilerTarget::Version("19".to_string()),
4171            gating: None,
4172            dynamic_gating: None,
4173            no_emit: false,
4174            output_mode: None,
4175            eslint_suppression_rules: None,
4176            flow_suppressions: true,
4177            ignore_use_no_forget: false,
4178            custom_opt_out_directives: None,
4179            environment: EnvironmentConfig::default(),
4180            source_code: None,
4181            profiling: false,
4182            debug: false,
4183        };
4184        assert!(!should_skip_compilation(&program, &options));
4185    }
4186}