1use 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
73const DEFAULT_ESLINT_SUPPRESSIONS: &[&str] =
78 &["react-hooks/exhaustive-deps", "react-hooks/rules-of-hooks"];
79
80const OPT_IN_DIRECTIVES: &[&str] = &["use forget", "use memo"];
82
83const OPT_OUT_DIRECTIVES: &[&str] = &["use no forget", "use no memo"];
85
86#[allow(dead_code)]
92struct CompileSource<'a> {
93 kind: CompileSourceKind,
94 fn_node: FunctionNode<'a>,
95 fn_name: Option<String>,
97 fn_loc: Option<SourceLocation>,
98 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 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
115fn try_find_directive_enabling_memoization<'a>(
124 directives: &'a [Directive],
125 opts: &PluginOptions,
126) -> Result<Option<&'a Directive>, CompilerError> {
127 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 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
143fn 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
159struct DynamicGatingResult<'a> {
161 #[allow(dead_code)]
162 directive: &'a Directive,
163 gating: GatingConfig,
164}
165
166fn 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
233fn 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
247fn 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 !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
312fn 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
328fn is_component_name(name: &str) -> bool {
330 name.chars()
331 .next()
332 .map_or(false, |c| c.is_ascii_uppercase())
333}
334
335fn 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 if !expr_is_hook(&member.property) {
346 return false;
347 }
348 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#[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
381fn get_function_name_from_id(id: Option<&Identifier>) -> Option<String> {
387 id.map(|id| id.name.clone())
388}
389
390fn 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
408fn 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, };
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 Statement::FunctionDeclaration(_) | Statement::ClassDeclaration(_) => {}
470 Statement::Unknown(_) => {}
473 _ => {}
474 }
475}
476
477fn 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
488fn 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 Statement::FunctionDeclaration(_) => false,
620 Statement::ClassDeclaration(class) => calls_hooks_or_creates_jsx_in_class_body(&class.body),
621 Statement::Unknown(_) => false,
624 _ => false,
625 }
626}
627
628fn calls_hooks_or_creates_jsx_in_expr(expr: &Expression) -> bool {
629 match expr {
630 Expression::JSXElement(_) | Expression::JSXFragment(_) => true,
632
633 Expression::CallExpression(call) => {
635 if expr_is_hook(&call.callee) {
636 return true;
637 }
638 if calls_hooks_or_creates_jsx_in_expr(&call.callee) {
640 return true;
641 }
642 for arg in &call.arguments {
643 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 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 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 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 Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) => false,
774
775 Expression::ClassExpression(class) => calls_hooks_or_creates_jsx_in_class_body(&class.body),
777
778 _ => false,
780 }
781}
782
783fn 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 if let Some(serde_json::Value::String(node_type)) = obj.get("type") {
801 match node_type.as_str() {
802 "JSXElement" | "JSXFragment" => return true,
804 "ArrowFunctionExpression" | "FunctionExpression" | "FunctionDeclaration" => {
806 return false;
807 }
808 "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 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
827fn 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 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 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 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
875fn calls_hooks_or_creates_jsx(params: &[PatternLike], body: &FunctionBody) -> bool {
877 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
887fn 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 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
927fn 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, };
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 if matches!(params[0], PatternLike::RestElement(_)) {
1012 return false;
1013 }
1014 if !is_valid_props_annotation(¶ms[0]) {
1016 return false;
1017 }
1018 if params.len() == 1 {
1019 return true;
1020 }
1021 if let PatternLike::Identifier(ref id) = params[1] {
1023 id.name.contains("ref") || id.name.contains("Ref")
1024 } else {
1025 false
1026 }
1027}
1028
1029enum FunctionBody<'a> {
1035 Block(&'a BlockStatement),
1036 Expression(&'a Expression),
1037}
1038
1039fn 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 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 return Some(
1064 get_component_or_hook_like(name, params, body, parent_callee_name)
1065 .unwrap_or(ReactFunctionType::Other),
1066 );
1067 }
1068 }
1069
1070 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 None
1089 }
1090 "infer" => {
1091 component_syntax_type
1093 .or_else(|| get_component_or_hook_like(name, params, body, parent_callee_name))
1094 }
1095 "syntax" => {
1096 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
1107fn 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 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 return if calls_hooks_or_creates_jsx(params, body) {
1132 Some(ReactFunctionType::Hook)
1133 } else {
1134 None
1135 };
1136 }
1137 }
1138
1139 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
1153fn 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
1180fn 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
1204fn 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
1242fn 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
1263fn 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
1281fn 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
1314fn log_error(
1316 err: &CompilerError,
1317 fn_ast_loc: Option<&react_compiler_ast::common::SourceLocation>,
1318 context: &mut ProgramContext,
1319) {
1320 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 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 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
1382fn handle_error(
1386 err: &CompilerError,
1387 fn_ast_loc: Option<&react_compiler_ast::common::SourceLocation>,
1388 context: &mut ProgramContext,
1389) -> Option<CompileResult> {
1390 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 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 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 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
1449fn 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
1490fn 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 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 err.is_thrown = false;
1514 return Err(err);
1515 }
1516 }
1517
1518 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
1530fn 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 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 let opt_in = match opt_in_result {
1549 Ok(d) => d,
1550 Err(err) => {
1551 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 let compile_result = try_compile_function(source, scope_info, output_mode, env_config, context);
1561
1562 match compile_result {
1563 Err(err) => {
1564 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 log_error(&err, source.fn_ast_loc.as_ref(), context);
1581 } else {
1582 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 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 return Ok(None);
1606 }
1607
1608 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 if context.has_module_scope_opt_out {
1625 return Ok(None);
1626 }
1627
1628 if output_mode == CompilerOutputMode::Lint {
1630 return Ok(None);
1631 }
1632
1633 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
1643fn 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
1670fn 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
1676struct 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 is_component_declaration: bool,
1691 is_hook_declaration: bool,
1693}
1694
1695fn 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
1710fn 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
1729fn 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
1754fn try_make_compile_source<'a>(
1756 info: FunctionInfo<'a>,
1757 opts: &PluginOptions,
1758 context: &mut ProgramContext,
1759) -> Option<CompileSource<'a>> {
1760 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 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
1798fn get_declarator_name(decl: &VariableDeclarator) -> Option<String> {
1800 match &decl.id {
1801 PatternLike::Identifier(id) => Some(id.name.clone()),
1802 _ => None,
1803 }
1804}
1805
1806struct FunctionDiscoveryVisitor<'a, 'ast> {
1830 opts: &'a PluginOptions,
1831 context: &'a mut ProgramContext,
1832 queue: Vec<CompileSource<'ast>>,
1833 current_declarator_name: Option<String>,
1835 parent_callee_stack: Vec<Option<String>>,
1839 loop_expression_depth: usize,
1842 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 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 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 !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 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 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 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 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
2015fn 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
2041struct 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#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2057enum OriginalFnKind {
2058 FunctionDeclaration,
2059 FunctionExpression,
2060 ArrowFunctionExpression,
2061}
2062
2063struct CompiledFnForReplacement {
2066 fn_start: Option<u32>,
2068 fn_node_id: Option<u32>,
2070 original_kind: OriginalFnKind,
2072 codegen_fn: CodegenFunction,
2074 #[allow(dead_code)]
2076 source_kind: CompileSourceKind,
2077 fn_name: Option<String>,
2079 gating: Option<GatingConfig>,
2081}
2082
2083fn get_functions_referenced_before_declaration(
2087 program: &Program,
2088 compiled_fns: &[CompiledFnForReplacement],
2089) -> HashSet<u32> {
2090 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 for stmt in &program.body {
2111 if let Statement::FunctionDeclaration(f) = stmt {
2113 if let Some(ref id) = f.id {
2114 fn_names.remove(&id.name);
2115 }
2116 }
2117 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
2129fn stmt_references_identifier_at_top_level(stmt: &Statement, name: &str) -> bool {
2131 match stmt {
2132 Statement::FunctionDeclaration(_) => {
2133 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.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 Statement::Unknown(unknown) => {
2185 raw_node_references_identifier(&unknown.raw().parse_value(), name)
2186 }
2187 _ => false,
2188 }
2189}
2190
2191fn 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
2210fn 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 Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) => false,
2239 _ => false,
2240 }
2241}
2242
2243fn 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
2258fn 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 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
2485fn 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
2558fn 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
2583fn 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 let has_gating = compiled_fns.iter().any(|cf| cf.gating.is_some());
2596
2597 let referenced_before_decl = if has_gating {
2599 get_functions_referenced_before_declaration(program, compiled_fns)
2600 } else {
2601 HashSet::new()
2602 };
2603
2604 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 let mut outlined_decls: Vec<(Option<u32>, OriginalFnKind, FunctionDeclaration)> = Vec::new(); for (idx, compiled) in compiled_fns.iter().enumerate() {
2636 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 apply_gated_function_hoisted(program, compiled, gating_config, context);
2663 } else {
2664 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 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 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 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 add_imports_to_program(program, context);
2727}
2728
2729fn 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 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 let compiled_expr =
2768 build_compiled_expression_matching_kind(&compiled.codegen_fn, compiled.original_kind);
2769
2770 let original_expr = match original_expr {
2772 Some(e) => e,
2773 None => return, };
2775
2776 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 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 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
2841struct 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 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 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 }
2914
2915 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
2962fn 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 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 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 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 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 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 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 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 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 program.body.insert(fn_idx + 1, dispatcher_fn);
3240
3241 program
3243 .body
3244 .insert(fn_idx, Statement::FunctionDeclaration(compiled_fn_decl));
3245
3246 program.body.insert(fn_idx, gating_result_stmt);
3248}
3249
3250fn insert_after_fn_recursive(
3254 stmts: &mut Vec<Statement>,
3255 node_id: u32,
3256 new_stmt: Statement,
3257) -> bool {
3258 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 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
3461fn 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 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
3603fn 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 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
3617struct 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
3707struct 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
3722pub fn compile_program(mut file: File, scope: ScopeInfo, options: PluginOptions) -> CompileResult {
3736 let output_mode = CompilerOutputMode::from_opts(&options);
3738
3739 let early_events: Vec<LoggerEvent> = Vec::new();
3741 let mut early_ordered_log: Vec<OrderedLogItem> = Vec::new();
3742
3743 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 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 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 let restricted_imports = options.environment.validate_blocklisted_imports.clone();
3779
3780 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 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 let suppressions = find_program_suppressions(
3800 &file.comments,
3801 eslint_rules.as_deref(),
3802 options.flow_suppressions,
3803 );
3804
3805 let has_module_scope_opt_out =
3807 find_directive_disabling_memoization(&program.directives, &options).is_some();
3808
3809 let mut context = ProgramContext::new(
3811 options.clone(),
3812 options.filename.clone(),
3813 options.source_code.clone(),
3815 suppressions,
3816 has_module_scope_opt_out,
3817 );
3818
3819 let source_filename = program
3823 .base
3824 .loc
3825 .as_ref()
3826 .and_then(|loc| loc.filename.clone())
3827 .or_else(|| {
3828 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 context.init_from_scope(&scope);
3843
3844 context.ordered_log.extend(early_ordered_log);
3846
3847 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 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 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 let queue = find_functions_to_compile(program, &options, &mut context, &scope);
3901
3902 let env_config = options.environment.clone();
3905
3906 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 }
3921 Err(fatal_result) => {
3922 return fatal_result;
3923 }
3924 }
3925 }
3926
3927 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 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 let function_gating_config = options.gating.clone();
3970
3971 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 let gating = if cf.kind == CompileSourceKind::Original {
3984 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);
4007
4008 if replacements.is_empty() {
4009 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 apply_compiled_functions(&replacements, &mut file.program, &mut context);
4024
4025 let timing_entries = context.timing.into_entries();
4026
4027 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
4038fn 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")); 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(¶ms));
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(¶ms));
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(¶ms));
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}