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;
46use regex::Regex;
47
48use super::compile_result::BindingRenameInfo;
49use super::compile_result::CodegenFunction;
50use super::compile_result::CompileResult;
51use super::compile_result::CompilerErrorDetailInfo;
52use super::compile_result::CompilerErrorInfo;
53use super::compile_result::CompilerErrorItemInfo;
54use super::compile_result::DebugLogEntry;
55use super::compile_result::LoggerEvent;
56use super::compile_result::LoggerPosition;
57use super::compile_result::LoggerSourceLocation;
58use super::compile_result::LoggerSuggestionInfo;
59use super::compile_result::LoggerSuggestionOp;
60use super::compile_result::OrderedLogItem;
61use super::imports::ProgramContext;
62use super::imports::add_imports_to_program;
63use super::imports::get_react_compiler_runtime_module;
64use super::imports::validate_restricted_imports;
65use super::pipeline;
66use super::plugin_options::CompilerOutputMode;
67use super::plugin_options::GatingConfig;
68use super::plugin_options::PluginOptions;
69use super::suppression::SuppressionRange;
70use super::suppression::filter_suppressions_that_affect_function;
71use super::suppression::find_program_suppressions;
72use super::suppression::suppressions_to_compiler_error;
73
74const DEFAULT_ESLINT_SUPPRESSIONS: &[&str] =
79 &["react-hooks/exhaustive-deps", "react-hooks/rules-of-hooks"];
80
81const OPT_IN_DIRECTIVES: &[&str] = &["use forget", "use memo"];
83
84const OPT_OUT_DIRECTIVES: &[&str] = &["use no forget", "use no memo"];
86
87#[allow(dead_code)]
93struct CompileSource<'a> {
94 kind: CompileSourceKind,
95 fn_node: FunctionNode<'a>,
96 fn_name: Option<String>,
98 fn_loc: Option<SourceLocation>,
99 fn_ast_loc: Option<react_compiler_ast::common::SourceLocation>,
101 fn_start: Option<u32>,
102 fn_end: Option<u32>,
103 fn_node_id: Option<u32>,
104 fn_type: ReactFunctionType,
105 body_directives: Vec<Directive>,
107}
108
109#[derive(Debug, Clone, Copy, PartialEq, Eq)]
110enum CompileSourceKind {
111 Original,
112 #[allow(dead_code)]
113 Outlined,
114}
115
116fn try_find_directive_enabling_memoization<'a>(
125 directives: &'a [Directive],
126 opts: &PluginOptions,
127) -> Result<Option<&'a Directive>, CompilerError> {
128 let opt_in = directives
130 .iter()
131 .find(|d| OPT_IN_DIRECTIVES.contains(&d.value.value.as_str()));
132 if let Some(directive) = opt_in {
133 return Ok(Some(directive));
134 }
135
136 match find_directives_dynamic_gating(directives, opts) {
138 Ok(Some(result)) => Ok(Some(result.directive)),
139 Ok(None) => Ok(None),
140 Err(e) => Err(e),
141 }
142}
143
144fn find_directive_disabling_memoization<'a>(
146 directives: &'a [Directive],
147 opts: &PluginOptions,
148) -> Option<&'a Directive> {
149 if let Some(ref custom_directives) = opts.custom_opt_out_directives {
150 directives
151 .iter()
152 .find(|d| custom_directives.contains(&d.value.value))
153 } else {
154 directives
155 .iter()
156 .find(|d| OPT_OUT_DIRECTIVES.contains(&d.value.value.as_str()))
157 }
158}
159
160struct DynamicGatingResult<'a> {
162 #[allow(dead_code)]
163 directive: &'a Directive,
164 gating: GatingConfig,
165}
166
167fn find_directives_dynamic_gating<'a>(
170 directives: &'a [Directive],
171 opts: &PluginOptions,
172) -> Result<Option<DynamicGatingResult<'a>>, CompilerError> {
173 let dynamic_gating = match &opts.dynamic_gating {
174 Some(dg) => dg,
175 None => return Ok(None),
176 };
177
178 let pattern = Regex::new(r"^use memo if\(([^\)]*)\)$").expect("Invalid dynamic gating regex");
179
180 let mut errors: Vec<CompilerErrorDetail> = Vec::new();
181 let mut matches: Vec<(&'a Directive, String)> = Vec::new();
182
183 for directive in directives {
184 if let Some(caps) = pattern.captures(&directive.value.value) {
185 if let Some(m) = caps.get(1) {
186 let ident = m.as_str();
187 if is_valid_identifier(ident) {
188 matches.push((directive, ident.to_string()));
189 } else {
190 let mut detail = CompilerErrorDetail::new(
191 ErrorCategory::Gating,
192 "Dynamic gating directive is not a valid JavaScript identifier",
193 )
194 .with_description(format!("Found '{}'", directive.value.value));
195 detail.loc = directive.base.loc.as_ref().map(convert_loc);
196 errors.push(detail);
197 }
198 }
199 }
200 }
201
202 if !errors.is_empty() {
203 let mut err = CompilerError::new();
204 for e in errors {
205 err.push_error_detail(e);
206 }
207 return Err(err);
208 }
209
210 if matches.len() > 1 {
211 let names: Vec<String> = matches.iter().map(|(d, _)| d.value.value.clone()).collect();
212 let mut err = CompilerError::new();
213 let mut detail = CompilerErrorDetail::new(
214 ErrorCategory::Gating,
215 "Multiple dynamic gating directives found",
216 )
217 .with_description(format!(
218 "Expected a single directive but found [{}]",
219 names.join(", ")
220 ));
221 detail.loc = matches[0].0.base.loc.as_ref().map(convert_loc);
222 err.push_error_detail(detail);
223 return Err(err);
224 }
225
226 if matches.len() == 1 {
227 Ok(Some(DynamicGatingResult {
228 directive: matches[0].0,
229 gating: GatingConfig {
230 source: dynamic_gating.source.clone(),
231 import_specifier_name: matches[0].1.clone(),
232 },
233 }))
234 } else {
235 Ok(None)
236 }
237}
238
239fn is_valid_identifier(s: &str) -> bool {
242 if s.is_empty() {
243 return false;
244 }
245 let mut chars = s.chars();
246 let first = chars.next().unwrap();
247 if !first.is_alphabetic() && first != '_' && first != '$' {
248 return false;
249 }
250 if !chars.all(|c| c.is_alphanumeric() || c == '_' || c == '$') {
251 return false;
252 }
253 !matches!(
255 s,
256 "break"
257 | "case"
258 | "catch"
259 | "continue"
260 | "debugger"
261 | "default"
262 | "do"
263 | "else"
264 | "finally"
265 | "for"
266 | "function"
267 | "if"
268 | "in"
269 | "instanceof"
270 | "new"
271 | "return"
272 | "switch"
273 | "this"
274 | "throw"
275 | "try"
276 | "typeof"
277 | "var"
278 | "void"
279 | "while"
280 | "with"
281 | "class"
282 | "const"
283 | "enum"
284 | "export"
285 | "extends"
286 | "import"
287 | "super"
288 | "implements"
289 | "interface"
290 | "let"
291 | "package"
292 | "private"
293 | "protected"
294 | "public"
295 | "static"
296 | "yield"
297 | "null"
298 | "true"
299 | "false"
300 | "delete"
301 )
302}
303
304fn is_hook_name(s: &str) -> bool {
310 let bytes = s.as_bytes();
311 bytes.len() >= 4
312 && bytes[0] == b'u'
313 && bytes[1] == b's'
314 && bytes[2] == b'e'
315 && bytes
316 .get(3)
317 .map_or(false, |c| c.is_ascii_uppercase() || c.is_ascii_digit())
318}
319
320fn is_component_name(name: &str) -> bool {
322 name.chars()
323 .next()
324 .map_or(false, |c| c.is_ascii_uppercase())
325}
326
327fn expr_is_hook(expr: &Expression) -> bool {
330 match expr {
331 Expression::Identifier(id) => is_hook_name(&id.name),
332 Expression::MemberExpression(member) => {
333 if member.computed {
334 return false;
335 }
336 if !expr_is_hook(&member.property) {
338 return false;
339 }
340 if let Expression::Identifier(obj) = member.object.as_ref() {
342 obj.name
343 .chars()
344 .next()
345 .map_or(false, |c| c.is_ascii_uppercase())
346 } else {
347 false
348 }
349 }
350 _ => false,
351 }
352}
353
354#[allow(dead_code)]
356fn is_react_api(expr: &Expression, function_name: &str) -> bool {
357 match expr {
358 Expression::Identifier(id) => id.name == function_name,
359 Expression::MemberExpression(member) => {
360 if let Expression::Identifier(obj) = member.object.as_ref() {
361 if obj.name == "React" {
362 if let Expression::Identifier(prop) = member.property.as_ref() {
363 return prop.name == function_name;
364 }
365 }
366 }
367 false
368 }
369 _ => false,
370 }
371}
372
373fn get_function_name_from_id(id: Option<&Identifier>) -> Option<String> {
379 id.map(|id| id.name.clone())
380}
381
382fn is_non_node(expr: &Expression) -> bool {
389 matches!(
390 expr,
391 Expression::ObjectExpression(_)
392 | Expression::ArrowFunctionExpression(_)
393 | Expression::FunctionExpression(_)
394 | Expression::BigIntLiteral(_)
395 | Expression::ClassExpression(_)
396 | Expression::NewExpression(_)
397 )
398}
399
400fn returns_non_node_in_stmts(stmts: &[Statement]) -> bool {
405 let mut result = false;
406 for stmt in stmts {
407 returns_non_node_in_stmt(stmt, &mut result);
408 }
409 result
410}
411
412fn returns_non_node_in_stmt(stmt: &Statement, result: &mut bool) {
413 match stmt {
414 Statement::ReturnStatement(ret) => {
415 *result = match &ret.argument {
416 Some(arg) => is_non_node(arg),
417 None => true, };
419 }
420 Statement::BlockStatement(block) => {
421 for s in &block.body {
422 returns_non_node_in_stmt(s, result);
423 }
424 }
425 Statement::IfStatement(if_stmt) => {
426 returns_non_node_in_stmt(&if_stmt.consequent, result);
427 if let Some(ref alt) = if_stmt.alternate {
428 returns_non_node_in_stmt(alt, result);
429 }
430 }
431 Statement::ForStatement(for_stmt) => returns_non_node_in_stmt(&for_stmt.body, result),
432 Statement::WhileStatement(while_stmt) => returns_non_node_in_stmt(&while_stmt.body, result),
433 Statement::DoWhileStatement(do_while) => returns_non_node_in_stmt(&do_while.body, result),
434 Statement::ForInStatement(for_in) => returns_non_node_in_stmt(&for_in.body, result),
435 Statement::ForOfStatement(for_of) => returns_non_node_in_stmt(&for_of.body, result),
436 Statement::SwitchStatement(switch) => {
437 for case in &switch.cases {
438 for s in &case.consequent {
439 returns_non_node_in_stmt(s, result);
440 }
441 }
442 }
443 Statement::TryStatement(try_stmt) => {
444 for s in &try_stmt.block.body {
445 returns_non_node_in_stmt(s, result);
446 }
447 if let Some(ref handler) = try_stmt.handler {
448 for s in &handler.body.body {
449 returns_non_node_in_stmt(s, result);
450 }
451 }
452 if let Some(ref finalizer) = try_stmt.finalizer {
453 for s in &finalizer.body {
454 returns_non_node_in_stmt(s, result);
455 }
456 }
457 }
458 Statement::LabeledStatement(labeled) => returns_non_node_in_stmt(&labeled.body, result),
459 Statement::WithStatement(with) => returns_non_node_in_stmt(&with.body, result),
460 Statement::FunctionDeclaration(_) | Statement::ClassDeclaration(_) => {}
462 Statement::Unknown(_) => {}
465 _ => {}
466 }
467}
468
469fn returns_non_node_fn(params: &[PatternLike], body: &FunctionBody) -> bool {
473 let _ = params;
474 match body {
475 FunctionBody::Block(block) => returns_non_node_in_stmts(&block.body),
476 FunctionBody::Expression(expr) => is_non_node(expr),
477 }
478}
479
480fn calls_hooks_or_creates_jsx_in_stmts(stmts: &[Statement]) -> bool {
485 for stmt in stmts {
486 if calls_hooks_or_creates_jsx_in_stmt(stmt) {
487 return true;
488 }
489 }
490 false
491}
492
493fn calls_hooks_or_creates_jsx_in_stmt(stmt: &Statement) -> bool {
494 match stmt {
495 Statement::ExpressionStatement(expr_stmt) => {
496 calls_hooks_or_creates_jsx_in_expr(&expr_stmt.expression)
497 }
498 Statement::ReturnStatement(ret) => {
499 if let Some(ref arg) = ret.argument {
500 calls_hooks_or_creates_jsx_in_expr(arg)
501 } else {
502 false
503 }
504 }
505 Statement::VariableDeclaration(var_decl) => {
506 for decl in &var_decl.declarations {
507 if let Some(ref init) = decl.init {
508 if calls_hooks_or_creates_jsx_in_expr(init) {
509 return true;
510 }
511 }
512 }
513 false
514 }
515 Statement::BlockStatement(block) => calls_hooks_or_creates_jsx_in_stmts(&block.body),
516 Statement::IfStatement(if_stmt) => {
517 calls_hooks_or_creates_jsx_in_expr(&if_stmt.test)
518 || calls_hooks_or_creates_jsx_in_stmt(&if_stmt.consequent)
519 || if_stmt
520 .alternate
521 .as_ref()
522 .map_or(false, |alt| calls_hooks_or_creates_jsx_in_stmt(alt))
523 }
524 Statement::ForStatement(for_stmt) => {
525 if let Some(ref init) = for_stmt.init {
526 match init.as_ref() {
527 ForInit::Expression(expr) => {
528 if calls_hooks_or_creates_jsx_in_expr(expr) {
529 return true;
530 }
531 }
532 ForInit::VariableDeclaration(var_decl) => {
533 for decl in &var_decl.declarations {
534 if let Some(ref init) = decl.init {
535 if calls_hooks_or_creates_jsx_in_expr(init) {
536 return true;
537 }
538 }
539 }
540 }
541 }
542 }
543 if let Some(ref test) = for_stmt.test {
544 if calls_hooks_or_creates_jsx_in_expr(test) {
545 return true;
546 }
547 }
548 if let Some(ref update) = for_stmt.update {
549 if calls_hooks_or_creates_jsx_in_expr(update) {
550 return true;
551 }
552 }
553 calls_hooks_or_creates_jsx_in_stmt(&for_stmt.body)
554 }
555 Statement::WhileStatement(while_stmt) => {
556 calls_hooks_or_creates_jsx_in_expr(&while_stmt.test)
557 || calls_hooks_or_creates_jsx_in_stmt(&while_stmt.body)
558 }
559 Statement::DoWhileStatement(do_while) => {
560 calls_hooks_or_creates_jsx_in_stmt(&do_while.body)
561 || calls_hooks_or_creates_jsx_in_expr(&do_while.test)
562 }
563 Statement::ForInStatement(for_in) => {
564 calls_hooks_or_creates_jsx_in_expr(&for_in.right)
565 || calls_hooks_or_creates_jsx_in_stmt(&for_in.body)
566 }
567 Statement::ForOfStatement(for_of) => {
568 calls_hooks_or_creates_jsx_in_expr(&for_of.right)
569 || calls_hooks_or_creates_jsx_in_stmt(&for_of.body)
570 }
571 Statement::SwitchStatement(switch) => {
572 if calls_hooks_or_creates_jsx_in_expr(&switch.discriminant) {
573 return true;
574 }
575 for case in &switch.cases {
576 if let Some(ref test) = case.test {
577 if calls_hooks_or_creates_jsx_in_expr(test) {
578 return true;
579 }
580 }
581 if calls_hooks_or_creates_jsx_in_stmts(&case.consequent) {
582 return true;
583 }
584 }
585 false
586 }
587 Statement::ThrowStatement(throw) => calls_hooks_or_creates_jsx_in_expr(&throw.argument),
588 Statement::TryStatement(try_stmt) => {
589 if calls_hooks_or_creates_jsx_in_stmts(&try_stmt.block.body) {
590 return true;
591 }
592 if let Some(ref handler) = try_stmt.handler {
593 if calls_hooks_or_creates_jsx_in_stmts(&handler.body.body) {
594 return true;
595 }
596 }
597 if let Some(ref finalizer) = try_stmt.finalizer {
598 if calls_hooks_or_creates_jsx_in_stmts(&finalizer.body) {
599 return true;
600 }
601 }
602 false
603 }
604 Statement::LabeledStatement(labeled) => calls_hooks_or_creates_jsx_in_stmt(&labeled.body),
605 Statement::WithStatement(with) => {
606 calls_hooks_or_creates_jsx_in_expr(&with.object)
607 || calls_hooks_or_creates_jsx_in_stmt(&with.body)
608 }
609 Statement::FunctionDeclaration(_) => false,
612 Statement::ClassDeclaration(class) => calls_hooks_or_creates_jsx_in_class_body(&class.body),
613 Statement::Unknown(_) => false,
616 _ => false,
617 }
618}
619
620fn calls_hooks_or_creates_jsx_in_expr(expr: &Expression) -> bool {
621 match expr {
622 Expression::JSXElement(_) | Expression::JSXFragment(_) => true,
624
625 Expression::CallExpression(call) => {
627 if expr_is_hook(&call.callee) {
628 return true;
629 }
630 if calls_hooks_or_creates_jsx_in_expr(&call.callee) {
632 return true;
633 }
634 for arg in &call.arguments {
635 if matches!(
637 arg,
638 Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_)
639 ) {
640 continue;
641 }
642 if calls_hooks_or_creates_jsx_in_expr(arg) {
643 return true;
644 }
645 }
646 false
647 }
648 Expression::OptionalCallExpression(call) => {
649 if calls_hooks_or_creates_jsx_in_expr(&call.callee) {
655 return true;
656 }
657 for arg in &call.arguments {
658 if matches!(
659 arg,
660 Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_)
661 ) {
662 continue;
663 }
664 if calls_hooks_or_creates_jsx_in_expr(arg) {
665 return true;
666 }
667 }
668 false
669 }
670
671 Expression::BinaryExpression(bin) => {
673 calls_hooks_or_creates_jsx_in_expr(&bin.left)
674 || calls_hooks_or_creates_jsx_in_expr(&bin.right)
675 }
676 Expression::LogicalExpression(log) => {
677 calls_hooks_or_creates_jsx_in_expr(&log.left)
678 || calls_hooks_or_creates_jsx_in_expr(&log.right)
679 }
680 Expression::ConditionalExpression(cond) => {
681 calls_hooks_or_creates_jsx_in_expr(&cond.test)
682 || calls_hooks_or_creates_jsx_in_expr(&cond.consequent)
683 || calls_hooks_or_creates_jsx_in_expr(&cond.alternate)
684 }
685 Expression::AssignmentExpression(assign) => {
686 calls_hooks_or_creates_jsx_in_expr(&assign.right)
687 }
688 Expression::SequenceExpression(seq) => seq
689 .expressions
690 .iter()
691 .any(|e| calls_hooks_or_creates_jsx_in_expr(e)),
692 Expression::UnaryExpression(unary) => calls_hooks_or_creates_jsx_in_expr(&unary.argument),
693 Expression::UpdateExpression(update) => {
694 calls_hooks_or_creates_jsx_in_expr(&update.argument)
695 }
696 Expression::MemberExpression(member) => {
697 calls_hooks_or_creates_jsx_in_expr(&member.object)
698 || calls_hooks_or_creates_jsx_in_expr(&member.property)
699 }
700 Expression::OptionalMemberExpression(member) => {
701 calls_hooks_or_creates_jsx_in_expr(&member.object)
702 || calls_hooks_or_creates_jsx_in_expr(&member.property)
703 }
704 Expression::SpreadElement(spread) => calls_hooks_or_creates_jsx_in_expr(&spread.argument),
705 Expression::AwaitExpression(await_expr) => {
706 calls_hooks_or_creates_jsx_in_expr(&await_expr.argument)
707 }
708 Expression::YieldExpression(yield_expr) => yield_expr
709 .argument
710 .as_ref()
711 .map_or(false, |arg| calls_hooks_or_creates_jsx_in_expr(arg)),
712 Expression::TaggedTemplateExpression(tagged) => {
713 calls_hooks_or_creates_jsx_in_expr(&tagged.tag)
714 }
715 Expression::TemplateLiteral(tl) => tl
716 .expressions
717 .iter()
718 .any(|e| calls_hooks_or_creates_jsx_in_expr(e)),
719 Expression::ArrayExpression(arr) => arr.elements.iter().any(|e| {
720 e.as_ref()
721 .map_or(false, |e| calls_hooks_or_creates_jsx_in_expr(e))
722 }),
723 Expression::ObjectExpression(obj) => obj.properties.iter().any(|prop| match prop {
724 ObjectExpressionProperty::ObjectProperty(p) => {
725 calls_hooks_or_creates_jsx_in_expr(&p.value)
726 }
727 ObjectExpressionProperty::SpreadElement(s) => {
728 calls_hooks_or_creates_jsx_in_expr(&s.argument)
729 }
730 ObjectExpressionProperty::ObjectMethod(m) => {
735 calls_hooks_or_creates_jsx_in_stmts(&m.body.body)
736 }
737 }),
738 Expression::ParenthesizedExpression(paren) => {
739 calls_hooks_or_creates_jsx_in_expr(&paren.expression)
740 }
741 Expression::TSAsExpression(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression),
742 Expression::TSSatisfiesExpression(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression),
743 Expression::TSNonNullExpression(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression),
744 Expression::TSTypeAssertion(ts) => calls_hooks_or_creates_jsx_in_expr(&ts.expression),
745 Expression::TSInstantiationExpression(ts) => {
746 calls_hooks_or_creates_jsx_in_expr(&ts.expression)
747 }
748 Expression::TypeCastExpression(tc) => calls_hooks_or_creates_jsx_in_expr(&tc.expression),
749 Expression::NewExpression(new) => {
750 if calls_hooks_or_creates_jsx_in_expr(&new.callee) {
751 return true;
752 }
753 new.arguments.iter().any(|a| {
754 if matches!(
755 a,
756 Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_)
757 ) {
758 return false;
759 }
760 calls_hooks_or_creates_jsx_in_expr(a)
761 })
762 }
763
764 Expression::ArrowFunctionExpression(_) | Expression::FunctionExpression(_) => false,
766
767 Expression::ClassExpression(class) => calls_hooks_or_creates_jsx_in_class_body(&class.body),
769
770 _ => false,
772 }
773}
774
775fn calls_hooks_or_creates_jsx_in_class_body(
781 body: &react_compiler_ast::expressions::ClassBody,
782) -> bool {
783 body.body
784 .iter()
785 .any(|member| calls_hooks_or_creates_jsx_in_json(member))
786}
787
788fn calls_hooks_or_creates_jsx_in_json(value: &serde_json::Value) -> bool {
789 match value {
790 serde_json::Value::Object(obj) => {
791 if let Some(serde_json::Value::String(node_type)) = obj.get("type") {
793 match node_type.as_str() {
794 "JSXElement" | "JSXFragment" => return true,
796 "ArrowFunctionExpression" | "FunctionExpression" | "FunctionDeclaration" => {
798 return false;
799 }
800 "CallExpression" => {
802 if let Some(callee) = obj.get("callee") {
803 if json_expr_is_hook(callee) {
804 return true;
805 }
806 }
807 }
808 _ => {}
809 }
810 }
811 obj.values().any(|v| calls_hooks_or_creates_jsx_in_json(v))
813 }
814 serde_json::Value::Array(arr) => arr.iter().any(|v| calls_hooks_or_creates_jsx_in_json(v)),
815 _ => false,
816 }
817}
818
819fn json_expr_is_hook(callee: &serde_json::Value) -> bool {
824 if let serde_json::Value::Object(obj) = callee {
825 if let Some(serde_json::Value::String(node_type)) = obj.get("type") {
826 if node_type == "Identifier" {
827 if let Some(serde_json::Value::String(name)) = obj.get("name") {
828 return is_hook_name(name);
829 }
830 } else if node_type == "MemberExpression" {
831 let computed = obj
833 .get("computed")
834 .and_then(|v| v.as_bool())
835 .unwrap_or(false);
836 if computed {
837 return false;
838 }
839 if let Some(serde_json::Value::Object(prop)) = obj.get("property") {
841 if prop.get("type").and_then(|v| v.as_str()) == Some("Identifier") {
842 if let Some(name) = prop.get("name").and_then(|v| v.as_str()) {
843 if !is_hook_name(name) {
844 return false;
845 }
846 if let Some(serde_json::Value::Object(obj_node)) = obj.get("object") {
848 if obj_node.get("type").and_then(|v| v.as_str())
849 == Some("Identifier")
850 {
851 if let Some(obj_name) =
852 obj_node.get("name").and_then(|v| v.as_str())
853 {
854 return is_component_name(obj_name);
855 }
856 }
857 }
858 }
859 }
860 }
861 }
862 }
863 }
864 false
865}
866
867fn calls_hooks_or_creates_jsx(params: &[PatternLike], body: &FunctionBody) -> bool {
869 if calls_hooks_or_creates_jsx_in_params(params) {
871 return true;
872 }
873 match body {
874 FunctionBody::Block(block) => calls_hooks_or_creates_jsx_in_stmts(&block.body),
875 FunctionBody::Expression(expr) => calls_hooks_or_creates_jsx_in_expr(expr),
876 }
877}
878
879fn calls_hooks_or_creates_jsx_in_params(params: &[PatternLike]) -> bool {
881 for param in params {
882 if calls_hooks_or_creates_jsx_in_pattern(param) {
883 return true;
884 }
885 }
886 false
887}
888
889fn calls_hooks_or_creates_jsx_in_pattern(pattern: &PatternLike) -> bool {
890 match pattern {
891 PatternLike::AssignmentPattern(assign) => {
892 calls_hooks_or_creates_jsx_in_expr(&assign.right)
894 || calls_hooks_or_creates_jsx_in_pattern(&assign.left)
895 }
896 PatternLike::ObjectPattern(obj) => obj.properties.iter().any(|prop| match prop {
897 react_compiler_ast::patterns::ObjectPatternProperty::ObjectProperty(p) => {
898 calls_hooks_or_creates_jsx_in_pattern(&p.value)
899 }
900 react_compiler_ast::patterns::ObjectPatternProperty::RestElement(rest) => {
901 calls_hooks_or_creates_jsx_in_pattern(&rest.argument)
902 }
903 }),
904 PatternLike::ArrayPattern(arr) => arr.elements.iter().any(|elem| {
905 elem.as_ref()
906 .map_or(false, |e| calls_hooks_or_creates_jsx_in_pattern(e))
907 }),
908 PatternLike::RestElement(rest) => calls_hooks_or_creates_jsx_in_pattern(&rest.argument),
909 PatternLike::Identifier(_)
910 | PatternLike::MemberExpression(_)
911 | PatternLike::TSAsExpression(_)
912 | PatternLike::TSSatisfiesExpression(_)
913 | PatternLike::TSNonNullExpression(_)
914 | PatternLike::TSTypeAssertion(_)
915 | PatternLike::TypeCastExpression(_) => false,
916 }
917}
918
919fn is_valid_props_annotation(param: &PatternLike) -> bool {
924 let type_annotation = match param {
925 PatternLike::Identifier(id) => id.type_annotation.as_deref(),
926 PatternLike::ObjectPattern(op) => op.type_annotation.as_deref(),
927 PatternLike::ArrayPattern(ap) => ap.type_annotation.as_deref(),
928 PatternLike::AssignmentPattern(ap) => ap.type_annotation.as_deref(),
929 PatternLike::RestElement(re) => re.type_annotation.as_deref(),
930 PatternLike::MemberExpression(_)
931 | PatternLike::TSAsExpression(_)
932 | PatternLike::TSSatisfiesExpression(_)
933 | PatternLike::TSNonNullExpression(_)
934 | PatternLike::TSTypeAssertion(_)
935 | PatternLike::TypeCastExpression(_) => None,
936 };
937 let annot = match type_annotation {
938 Some(val) => val,
939 None => return true, };
941 let annot_type = match annot.get("type").and_then(|v| v.as_str()) {
942 Some(t) => t,
943 None => return true,
944 };
945 match annot_type {
946 "TSTypeAnnotation" => {
947 let inner_type = annot
948 .get("typeAnnotation")
949 .and_then(|v| v.get("type"))
950 .and_then(|v| v.as_str())
951 .unwrap_or("");
952 !matches!(
953 inner_type,
954 "TSArrayType"
955 | "TSBigIntKeyword"
956 | "TSBooleanKeyword"
957 | "TSConstructorType"
958 | "TSFunctionType"
959 | "TSLiteralType"
960 | "TSNeverKeyword"
961 | "TSNumberKeyword"
962 | "TSStringKeyword"
963 | "TSSymbolKeyword"
964 | "TSTupleType"
965 )
966 }
967 "TypeAnnotation" => {
968 let inner_type = annot
969 .get("typeAnnotation")
970 .and_then(|v| v.get("type"))
971 .and_then(|v| v.as_str())
972 .unwrap_or("");
973 !matches!(
974 inner_type,
975 "ArrayTypeAnnotation"
976 | "BooleanLiteralTypeAnnotation"
977 | "BooleanTypeAnnotation"
978 | "EmptyTypeAnnotation"
979 | "FunctionTypeAnnotation"
980 | "NullLiteralTypeAnnotation"
981 | "NumberLiteralTypeAnnotation"
982 | "NumberTypeAnnotation"
983 | "StringLiteralTypeAnnotation"
984 | "StringTypeAnnotation"
985 | "SymbolTypeAnnotation"
986 | "ThisTypeAnnotation"
987 | "TupleTypeAnnotation"
988 )
989 }
990 "Noop" => true,
991 _ => true,
992 }
993}
994
995fn is_valid_component_params(params: &[PatternLike]) -> bool {
996 if params.is_empty() {
997 return true;
998 }
999 if params.len() > 2 {
1000 return false;
1001 }
1002 if matches!(params[0], PatternLike::RestElement(_)) {
1004 return false;
1005 }
1006 if !is_valid_props_annotation(¶ms[0]) {
1008 return false;
1009 }
1010 if params.len() == 1 {
1011 return true;
1012 }
1013 if let PatternLike::Identifier(ref id) = params[1] {
1015 id.name.contains("ref") || id.name.contains("Ref")
1016 } else {
1017 false
1018 }
1019}
1020
1021enum FunctionBody<'a> {
1027 Block(&'a BlockStatement),
1028 Expression(&'a Expression),
1029}
1030
1031fn get_react_function_type(
1040 name: Option<&str>,
1041 params: &[PatternLike],
1042 body: &FunctionBody,
1043 body_directives: &[Directive],
1044 is_declaration: bool,
1045 parent_callee_name: Option<&str>,
1046 opts: &PluginOptions,
1047 is_component_declaration: bool,
1048 is_hook_declaration: bool,
1049) -> Option<ReactFunctionType> {
1050 if let FunctionBody::Block(_) = body {
1052 let opt_in = try_find_directive_enabling_memoization(body_directives, opts);
1053 if let Ok(Some(_)) = opt_in {
1054 return Some(
1056 get_component_or_hook_like(name, params, body, parent_callee_name)
1057 .unwrap_or(ReactFunctionType::Other),
1058 );
1059 }
1060 }
1061
1062 let component_syntax_type = if is_declaration {
1066 if is_component_declaration {
1067 Some(ReactFunctionType::Component)
1068 } else if is_hook_declaration {
1069 Some(ReactFunctionType::Hook)
1070 } else {
1071 None
1072 }
1073 } else {
1074 None
1075 };
1076
1077 match opts.compilation_mode.as_str() {
1078 "annotation" => {
1079 None
1081 }
1082 "infer" => {
1083 component_syntax_type
1085 .or_else(|| get_component_or_hook_like(name, params, body, parent_callee_name))
1086 }
1087 "syntax" => {
1088 component_syntax_type
1090 }
1091 "all" => Some(
1092 get_component_or_hook_like(name, params, body, parent_callee_name)
1093 .unwrap_or(ReactFunctionType::Other),
1094 ),
1095 _ => None,
1096 }
1097}
1098
1099fn get_component_or_hook_like(
1105 name: Option<&str>,
1106 params: &[PatternLike],
1107 body: &FunctionBody,
1108 parent_callee_name: Option<&str>,
1109) -> Option<ReactFunctionType> {
1110 if let Some(fn_name) = name {
1111 if is_component_name(fn_name) {
1112 let is_component = calls_hooks_or_creates_jsx(params, body)
1114 && is_valid_component_params(params)
1115 && !returns_non_node_fn(params, body);
1116 return if is_component {
1117 Some(ReactFunctionType::Component)
1118 } else {
1119 None
1120 };
1121 } else if is_hook_name(fn_name) {
1122 return if calls_hooks_or_creates_jsx(params, body) {
1124 Some(ReactFunctionType::Hook)
1125 } else {
1126 None
1127 };
1128 }
1129 }
1130
1131 if let Some(callee_name) = parent_callee_name {
1133 if callee_name == "forwardRef" || callee_name == "memo" {
1134 return if calls_hooks_or_creates_jsx(params, body) {
1135 Some(ReactFunctionType::Component)
1136 } else {
1137 None
1138 };
1139 }
1140 }
1141
1142 None
1143}
1144
1145fn get_callee_name_if_react_api(callee: &Expression) -> Option<&str> {
1148 match callee {
1149 Expression::Identifier(id) => {
1150 if id.name == "forwardRef" || id.name == "memo" {
1151 Some(&id.name)
1152 } else {
1153 None
1154 }
1155 }
1156 Expression::MemberExpression(member) => {
1157 if let Expression::Identifier(obj) = member.object.as_ref() {
1158 if obj.name == "React" {
1159 if let Expression::Identifier(prop) = member.property.as_ref() {
1160 if prop.name == "forwardRef" || prop.name == "memo" {
1161 return Some(&prop.name);
1162 }
1163 }
1164 }
1165 }
1166 None
1167 }
1168 _ => None,
1169 }
1170}
1171
1172fn convert_loc(loc: &react_compiler_ast::common::SourceLocation) -> SourceLocation {
1178 SourceLocation {
1179 start: react_compiler_diagnostics::Position {
1180 line: loc.start.line,
1181 column: loc.start.column,
1182 index: loc.start.index,
1183 },
1184 end: react_compiler_diagnostics::Position {
1185 line: loc.end.line,
1186 column: loc.end.column,
1187 index: loc.end.index,
1188 },
1189 }
1190}
1191
1192fn base_node_loc(base: &BaseNode) -> Option<SourceLocation> {
1193 base.loc.as_ref().map(convert_loc)
1194}
1195
1196fn diagnostic_details_to_items(
1202 d: &react_compiler_diagnostics::CompilerDiagnostic,
1203 filename: Option<&str>,
1204) -> Option<Vec<CompilerErrorItemInfo>> {
1205 let items: Vec<CompilerErrorItemInfo> = d
1206 .details
1207 .iter()
1208 .map(|item| match item {
1209 react_compiler_diagnostics::CompilerDiagnosticDetail::Error {
1210 loc,
1211 message,
1212 identifier_name,
1213 } => CompilerErrorItemInfo {
1214 kind: "error".to_string(),
1215 loc: loc.as_ref().map(|l| {
1216 let mut logger_loc = diag_loc_to_logger_loc(l, filename);
1217 logger_loc.identifier_name = identifier_name.clone();
1218 logger_loc
1219 }),
1220 message: message.clone(),
1221 },
1222 react_compiler_diagnostics::CompilerDiagnosticDetail::Hint { message } => {
1223 CompilerErrorItemInfo {
1224 kind: "hint".to_string(),
1225 loc: None,
1226 message: Some(message.clone()),
1227 }
1228 }
1229 })
1230 .collect();
1231 if items.is_empty() { None } else { Some(items) }
1232}
1233
1234fn to_logger_loc(
1236 ast_loc: Option<&react_compiler_ast::common::SourceLocation>,
1237 filename: Option<&str>,
1238) -> Option<LoggerSourceLocation> {
1239 ast_loc.map(|loc| LoggerSourceLocation {
1240 start: LoggerPosition {
1241 line: loc.start.line,
1242 column: loc.start.column,
1243 index: loc.start.index,
1244 },
1245 end: LoggerPosition {
1246 line: loc.end.line,
1247 column: loc.end.column,
1248 index: loc.end.index,
1249 },
1250 filename: filename.map(|s| s.to_string()),
1251 identifier_name: loc.identifier_name.clone(),
1252 })
1253}
1254
1255fn diag_loc_to_logger_loc(loc: &SourceLocation, filename: Option<&str>) -> LoggerSourceLocation {
1257 LoggerSourceLocation {
1258 start: LoggerPosition {
1259 line: loc.start.line,
1260 column: loc.start.column,
1261 index: loc.start.index,
1262 },
1263 end: LoggerPosition {
1264 line: loc.end.line,
1265 column: loc.end.column,
1266 index: loc.end.index,
1267 },
1268 filename: filename.map(|s| s.to_string()),
1269 identifier_name: None,
1270 }
1271}
1272
1273fn suggestions_to_logger(
1275 suggestions: &Option<Vec<react_compiler_diagnostics::CompilerSuggestion>>,
1276) -> Option<Vec<LoggerSuggestionInfo>> {
1277 suggestions.as_ref().map(|suggestions| {
1278 suggestions
1279 .iter()
1280 .map(|s| {
1281 let op = match s.op {
1282 react_compiler_diagnostics::CompilerSuggestionOperation::InsertBefore => {
1283 LoggerSuggestionOp::InsertBefore
1284 }
1285 react_compiler_diagnostics::CompilerSuggestionOperation::InsertAfter => {
1286 LoggerSuggestionOp::InsertAfter
1287 }
1288 react_compiler_diagnostics::CompilerSuggestionOperation::Remove => {
1289 LoggerSuggestionOp::Remove
1290 }
1291 react_compiler_diagnostics::CompilerSuggestionOperation::Replace => {
1292 LoggerSuggestionOp::Replace
1293 }
1294 };
1295 LoggerSuggestionInfo {
1296 description: s.description.clone(),
1297 op,
1298 range: s.range,
1299 text: s.text.clone(),
1300 }
1301 })
1302 .collect()
1303 })
1304}
1305
1306fn log_error(
1308 err: &CompilerError,
1309 fn_ast_loc: Option<&react_compiler_ast::common::SourceLocation>,
1310 context: &mut ProgramContext,
1311) {
1312 let source_filename = fn_ast_loc.and_then(|loc| loc.filename.as_deref());
1315 let fn_loc = to_logger_loc(fn_ast_loc, source_filename);
1316
1317 let is_simulated_unknown = err.details.len() == 1
1321 && err.details.iter().all(|d| match d {
1322 CompilerErrorOrDiagnostic::ErrorDetail(d) => {
1323 d.category == ErrorCategory::Invariant && d.reason == "unexpected error"
1324 }
1325 _ => false,
1326 });
1327 if is_simulated_unknown {
1328 context.log_event(LoggerEvent::PipelineError {
1329 fn_loc: fn_loc.clone(),
1330 data: "Error: unexpected error".to_string(),
1331 });
1332 return;
1333 }
1334
1335 for detail in &err.details {
1336 let detail_info = match detail {
1337 CompilerErrorOrDiagnostic::Diagnostic(d) => CompilerErrorDetailInfo {
1338 category: format!("{:?}", d.category),
1339 reason: d.reason.clone(),
1340 description: d.description.clone(),
1341 severity: format!("{:?}", d.logged_severity()),
1342 suggestions: suggestions_to_logger(&d.suggestions),
1343 details: diagnostic_details_to_items(d, source_filename),
1344 loc: None,
1345 },
1346 CompilerErrorOrDiagnostic::ErrorDetail(d) => CompilerErrorDetailInfo {
1347 category: format!("{:?}", d.category),
1348 reason: d.reason.clone(),
1349 description: d.description.clone(),
1350 severity: format!("{:?}", d.logged_severity()),
1351 suggestions: suggestions_to_logger(&d.suggestions),
1352 details: None,
1353 loc: d
1354 .loc
1355 .as_ref()
1356 .map(|l| diag_loc_to_logger_loc(l, source_filename)),
1357 },
1358 };
1359 if let Some(ref loc) = fn_loc {
1361 context.log_event(LoggerEvent::CompileErrorWithLoc {
1362 fn_loc: loc.clone(),
1363 detail: detail_info,
1364 });
1365 } else {
1366 context.log_event(LoggerEvent::CompileError {
1367 fn_loc: None,
1368 detail: detail_info,
1369 });
1370 }
1371 }
1372}
1373
1374fn handle_error(
1378 err: &CompilerError,
1379 fn_ast_loc: Option<&react_compiler_ast::common::SourceLocation>,
1380 context: &mut ProgramContext,
1381) -> Option<CompileResult> {
1382 log_error(err, fn_ast_loc, context);
1384
1385 let should_panic = match context.opts.panic_threshold.as_str() {
1386 "all_errors" => true,
1387 "critical_errors" => err.has_errors(),
1388 _ => false,
1389 };
1390
1391 let is_config_error = err.details.iter().any(|d| match d {
1393 CompilerErrorOrDiagnostic::Diagnostic(d) => d.category == ErrorCategory::Config,
1394 CompilerErrorOrDiagnostic::ErrorDetail(d) => d.category == ErrorCategory::Config,
1395 });
1396
1397 if should_panic || is_config_error {
1398 let source_fn = context.source_filename();
1399 let mut error_info = compiler_error_to_info(err, source_fn.as_deref());
1400
1401 let is_simulated_unknown = err.details.len() == 1
1406 && err.details.iter().all(|d| match d {
1407 CompilerErrorOrDiagnostic::ErrorDetail(d) => {
1408 d.category == ErrorCategory::Invariant && d.reason == "unexpected error"
1409 }
1410 _ => false,
1411 });
1412 if is_simulated_unknown {
1413 error_info.raw_message = Some("unexpected error".to_string());
1414 }
1415
1416 if error_info.raw_message.is_none() {
1419 if let Some(ref source) = context.code {
1420 error_info.formatted_message = Some(
1421 react_compiler_diagnostics::code_frame::format_compiler_error(
1422 err,
1423 source,
1424 source_fn.as_deref(),
1425 ),
1426 );
1427 }
1428 }
1429
1430 Some(CompileResult::Error {
1431 error: error_info,
1432 events: context.events.clone(),
1433 ordered_log: context.ordered_log.clone(),
1434 timing: Vec::new(),
1435 })
1436 } else {
1437 None
1438 }
1439}
1440
1441fn compiler_error_to_info(err: &CompilerError, filename: Option<&str>) -> CompilerErrorInfo {
1443 let details: Vec<CompilerErrorDetailInfo> = err
1444 .details
1445 .iter()
1446 .map(|d| match d {
1447 CompilerErrorOrDiagnostic::Diagnostic(d) => CompilerErrorDetailInfo {
1448 category: format!("{:?}", d.category),
1449 reason: d.reason.clone(),
1450 description: d.description.clone(),
1451 severity: format!("{:?}", d.severity()),
1452 suggestions: suggestions_to_logger(&d.suggestions),
1453 details: diagnostic_details_to_items(d, filename),
1454 loc: None,
1455 },
1456 CompilerErrorOrDiagnostic::ErrorDetail(d) => CompilerErrorDetailInfo {
1457 category: format!("{:?}", d.category),
1458 reason: d.reason.clone(),
1459 description: d.description.clone(),
1460 severity: format!("{:?}", d.severity()),
1461 suggestions: suggestions_to_logger(&d.suggestions),
1462 details: None,
1463 loc: d.loc.as_ref().map(|l| diag_loc_to_logger_loc(l, filename)),
1464 },
1465 })
1466 .collect();
1467
1468 let (reason, description) = details
1469 .first()
1470 .map(|d| (d.reason.clone(), d.description.clone()))
1471 .unwrap_or_else(|| ("Unknown error".to_string(), None));
1472
1473 CompilerErrorInfo {
1474 reason,
1475 description,
1476 details,
1477 raw_message: None,
1478 formatted_message: None,
1479 }
1480}
1481
1482fn try_compile_function(
1491 source: &CompileSource<'_>,
1492 scope_info: &ScopeInfo,
1493 output_mode: CompilerOutputMode,
1494 env_config: &EnvironmentConfig,
1495 context: &mut ProgramContext,
1496) -> Result<CodegenFunction, CompilerError> {
1497 if let (Some(start), Some(end)) = (source.fn_start, source.fn_end) {
1499 let affecting = filter_suppressions_that_affect_function(&context.suppressions, start, end);
1500 if !affecting.is_empty() {
1501 let owned: Vec<SuppressionRange> = affecting.into_iter().cloned().collect();
1502 let mut err = suppressions_to_compiler_error(&owned);
1503 err.is_thrown = false;
1506 return Err(err);
1507 }
1508 }
1509
1510 pipeline::compile_fn(
1512 &source.fn_node,
1513 source.fn_name.as_deref(),
1514 scope_info,
1515 source.fn_type,
1516 output_mode,
1517 env_config,
1518 context,
1519 )
1520}
1521
1522fn process_fn(
1528 source: &CompileSource<'_>,
1529 scope_info: &ScopeInfo,
1530 output_mode: CompilerOutputMode,
1531 env_config: &EnvironmentConfig,
1532 context: &mut ProgramContext,
1533) -> Result<Option<CodegenFunction>, CompileResult> {
1534 let opt_in_result =
1536 try_find_directive_enabling_memoization(&source.body_directives, &context.opts);
1537 let opt_out = find_directive_disabling_memoization(&source.body_directives, &context.opts);
1538
1539 let opt_in = match opt_in_result {
1541 Ok(d) => d,
1542 Err(err) => {
1543 if let Some(result) = handle_error(&err, source.fn_ast_loc.as_ref(), context) {
1545 return Err(result);
1546 }
1547 return Ok(None);
1548 }
1549 };
1550
1551 let compile_result = try_compile_function(source, scope_info, output_mode, env_config, context);
1553
1554 match compile_result {
1555 Err(err) => {
1556 if err.is_thrown && err.is_all_non_invariant() {
1560 let source_filename = source
1561 .fn_ast_loc
1562 .as_ref()
1563 .and_then(|loc| loc.filename.as_deref());
1564 context.log_event(LoggerEvent::CompileUnexpectedThrow {
1565 fn_loc: to_logger_loc(source.fn_ast_loc.as_ref(), source_filename),
1566 data: err.to_string_for_event(),
1567 });
1568 }
1569
1570 if opt_out.is_some() {
1571 log_error(&err, source.fn_ast_loc.as_ref(), context);
1573 } else {
1574 if let Some(result) = handle_error(&err, source.fn_ast_loc.as_ref(), context) {
1576 return Err(result);
1577 }
1578 }
1579 Ok(None)
1580 }
1581 Ok(codegen_fn) => {
1582 if !context.opts.ignore_use_no_forget && opt_out.is_some() {
1584 let opt_out_value = &opt_out.unwrap().value.value;
1585 let source_filename = source
1586 .fn_ast_loc
1587 .as_ref()
1588 .and_then(|loc| loc.filename.as_deref());
1589 context.log_event(LoggerEvent::CompileSkip {
1590 fn_loc: to_logger_loc(source.fn_ast_loc.as_ref(), source_filename),
1591 reason: format!("Skipped due to '{}' directive.", opt_out_value),
1592 loc: opt_out.and_then(|d| to_logger_loc(d.base.loc.as_ref(), source_filename)),
1593 });
1594 return Ok(None);
1598 }
1599
1600 let source_filename = source
1602 .fn_ast_loc
1603 .as_ref()
1604 .and_then(|loc| loc.filename.as_deref());
1605 context.log_event(LoggerEvent::CompileSuccess {
1606 fn_loc: to_logger_loc(source.fn_ast_loc.as_ref(), source_filename),
1607 fn_name: codegen_fn.id.as_ref().map(|id| id.name.clone()),
1608 memo_slots: codegen_fn.memo_slots_used,
1609 memo_blocks: codegen_fn.memo_blocks,
1610 memo_values: codegen_fn.memo_values,
1611 pruned_memo_blocks: codegen_fn.pruned_memo_blocks,
1612 pruned_memo_values: codegen_fn.pruned_memo_values,
1613 });
1614
1615 if context.has_module_scope_opt_out {
1617 return Ok(None);
1618 }
1619
1620 if output_mode == CompilerOutputMode::Lint {
1622 return Ok(None);
1623 }
1624
1625 if context.opts.compilation_mode == "annotation" && opt_in.is_none() {
1627 return Ok(None);
1628 }
1629
1630 Ok(Some(codegen_fn))
1631 }
1632 }
1633}
1634
1635fn has_memo_cache_function_import(program: &Program, module_name: &str) -> bool {
1642 for stmt in &program.body {
1643 if let Statement::ImportDeclaration(import) = stmt {
1644 if import.source.value == module_name {
1645 for specifier in &import.specifiers {
1646 if let ImportSpecifier::ImportSpecifier(data) = specifier {
1647 let imported_name = match &data.imported {
1648 ModuleExportName::Identifier(id) => &id.name,
1649 ModuleExportName::StringLiteral(s) => &s.value,
1650 };
1651 if imported_name == "c" {
1652 return true;
1653 }
1654 }
1655 }
1656 }
1657 }
1658 }
1659 false
1660}
1661
1662fn should_skip_compilation(program: &Program, options: &PluginOptions) -> bool {
1664 let runtime_module = get_react_compiler_runtime_module(&options.target);
1665 has_memo_cache_function_import(program, &runtime_module)
1666}
1667
1668struct FunctionInfo<'a> {
1674 name: Option<String>,
1675 fn_node: FunctionNode<'a>,
1676 params: &'a [PatternLike],
1677 body: FunctionBody<'a>,
1678 body_directives: Vec<Directive>,
1679 base: &'a BaseNode,
1680 parent_callee_name: Option<String>,
1681 is_component_declaration: bool,
1683 is_hook_declaration: bool,
1685}
1686
1687fn fn_info_from_decl(decl: &FunctionDeclaration) -> FunctionInfo<'_> {
1689 FunctionInfo {
1690 name: get_function_name_from_id(decl.id.as_ref()),
1691 fn_node: FunctionNode::FunctionDeclaration(decl),
1692 params: &decl.params,
1693 body: FunctionBody::Block(&decl.body),
1694 body_directives: decl.body.directives.clone(),
1695 base: &decl.base,
1696 parent_callee_name: None,
1697 is_component_declaration: decl.component_declaration,
1698 is_hook_declaration: decl.hook_declaration,
1699 }
1700}
1701
1702fn fn_info_from_func_expr<'a>(
1704 expr: &'a FunctionExpression,
1705 inferred_name: Option<String>,
1706 parent_callee_name: Option<String>,
1707) -> FunctionInfo<'a> {
1708 FunctionInfo {
1709 name: inferred_name,
1710 fn_node: FunctionNode::FunctionExpression(expr),
1711 params: &expr.params,
1712 body: FunctionBody::Block(&expr.body),
1713 body_directives: expr.body.directives.clone(),
1714 base: &expr.base,
1715 parent_callee_name,
1716 is_component_declaration: false,
1717 is_hook_declaration: false,
1718 }
1719}
1720
1721fn fn_info_from_arrow<'a>(
1723 expr: &'a ArrowFunctionExpression,
1724 inferred_name: Option<String>,
1725 parent_callee_name: Option<String>,
1726) -> FunctionInfo<'a> {
1727 let (body, directives) = match expr.body.as_ref() {
1728 ArrowFunctionBody::BlockStatement(block) => {
1729 (FunctionBody::Block(block), block.directives.clone())
1730 }
1731 ArrowFunctionBody::Expression(e) => (FunctionBody::Expression(e), Vec::new()),
1732 };
1733 FunctionInfo {
1734 name: inferred_name,
1735 fn_node: FunctionNode::ArrowFunctionExpression(expr),
1736 params: &expr.params,
1737 body,
1738 body_directives: directives,
1739 base: &expr.base,
1740 parent_callee_name,
1741 is_component_declaration: false,
1742 is_hook_declaration: false,
1743 }
1744}
1745
1746fn try_make_compile_source<'a>(
1748 info: FunctionInfo<'a>,
1749 opts: &PluginOptions,
1750 context: &mut ProgramContext,
1751) -> Option<CompileSource<'a>> {
1752 if let Some(nid) = info.base.node_id {
1754 if context.is_already_compiled(nid) {
1755 return None;
1756 }
1757 }
1758
1759 let fn_type = get_react_function_type(
1760 info.name.as_deref(),
1761 info.params,
1762 &info.body,
1763 &info.body_directives,
1764 info.is_component_declaration || info.is_hook_declaration,
1765 info.parent_callee_name.as_deref(),
1766 opts,
1767 info.is_component_declaration,
1768 info.is_hook_declaration,
1769 )?;
1770
1771 if let Some(nid) = info.base.node_id {
1773 context.mark_compiled(nid);
1774 }
1775
1776 Some(CompileSource {
1777 kind: CompileSourceKind::Original,
1778 fn_node: info.fn_node,
1779 fn_name: info.name,
1780 fn_loc: base_node_loc(info.base),
1781 fn_ast_loc: info.base.loc.clone(),
1782 fn_start: info.base.start,
1783 fn_end: info.base.end,
1784 fn_node_id: info.base.node_id,
1785 fn_type,
1786 body_directives: info.body_directives,
1787 })
1788}
1789
1790fn get_declarator_name(decl: &VariableDeclarator) -> Option<String> {
1792 match &decl.id {
1793 PatternLike::Identifier(id) => Some(id.name.clone()),
1794 _ => None,
1795 }
1796}
1797
1798struct FunctionDiscoveryVisitor<'a, 'ast> {
1822 opts: &'a PluginOptions,
1823 context: &'a mut ProgramContext,
1824 queue: Vec<CompileSource<'ast>>,
1825 current_declarator_name: Option<String>,
1827 parent_callee_stack: Vec<Option<String>>,
1831 loop_expression_depth: usize,
1834 skip_body: bool,
1838}
1839
1840impl<'a, 'ast> FunctionDiscoveryVisitor<'a, 'ast> {
1841 fn new(opts: &'a PluginOptions, context: &'a mut ProgramContext) -> Self {
1842 Self {
1843 opts,
1844 context,
1845 queue: Vec::new(),
1846 current_declarator_name: None,
1847 parent_callee_stack: Vec::new(),
1848 loop_expression_depth: 0,
1849 skip_body: false,
1850 }
1851 }
1852
1853 fn is_rejected_by_scope_check(&self, scope_stack: &[ScopeId]) -> bool {
1861 self.opts.compilation_mode == "all"
1862 && (scope_stack.len() > 2 || self.loop_expression_depth > 0)
1863 }
1864
1865 fn current_parent_callee(&self) -> Option<String> {
1867 self.parent_callee_stack.last().and_then(|opt| opt.clone())
1868 }
1869}
1870
1871impl<'a, 'ast> Visitor<'ast> for FunctionDiscoveryVisitor<'a, 'ast> {
1872 fn traverse_function_bodies(&self) -> bool {
1873 !self.skip_body
1877 }
1878
1879 fn enter_loop_expression(&mut self) {
1880 self.loop_expression_depth += 1;
1881 }
1882
1883 fn leave_loop_expression(&mut self) {
1884 self.loop_expression_depth -= 1;
1885 }
1886
1887 fn enter_variable_declarator(
1888 &mut self,
1889 node: &'ast VariableDeclarator,
1890 _scope_stack: &[ScopeId],
1891 ) {
1892 if let Some(ref init) = node.init {
1898 match init.as_ref() {
1899 Expression::FunctionExpression(_)
1900 | Expression::ArrowFunctionExpression(_)
1901 | Expression::CallExpression(_) => {
1902 self.current_declarator_name = get_declarator_name(node);
1903 }
1904 _ => {}
1905 }
1906 }
1907 }
1908
1909 fn leave_variable_declarator(
1910 &mut self,
1911 _node: &'ast VariableDeclarator,
1912 _scope_stack: &[ScopeId],
1913 ) {
1914 self.current_declarator_name = None;
1915 }
1916
1917 fn enter_call_expression(&mut self, node: &'ast CallExpression, _scope_stack: &[ScopeId]) {
1918 let callee_name = get_callee_name_if_react_api(&node.callee).map(|s| s.to_string());
1919 if callee_name.is_none() {
1923 self.current_declarator_name = None;
1924 }
1925 self.parent_callee_stack.push(callee_name);
1926 }
1927
1928 fn leave_call_expression(&mut self, _node: &'ast CallExpression, _scope_stack: &[ScopeId]) {
1929 let was_react_api = self
1930 .parent_callee_stack
1931 .pop()
1932 .and_then(|name| name)
1933 .is_some();
1934 if was_react_api {
1939 self.current_declarator_name = None;
1940 }
1941 }
1942
1943 fn enter_function_declaration(
1944 &mut self,
1945 node: &'ast FunctionDeclaration,
1946 scope_stack: &[ScopeId],
1947 ) {
1948 self.skip_body = false;
1949 if self.is_rejected_by_scope_check(scope_stack) {
1950 return;
1951 }
1952 let info = fn_info_from_decl(node);
1953 if let Some(source) = try_make_compile_source(info, self.opts, self.context) {
1954 self.queue.push(source);
1955 self.skip_body = true;
1956 }
1957 }
1958
1959 fn enter_function_expression(
1960 &mut self,
1961 node: &'ast FunctionExpression,
1962 scope_stack: &[ScopeId],
1963 ) {
1964 self.skip_body = false;
1965 if self.is_rejected_by_scope_check(scope_stack) {
1966 return;
1967 }
1968 let inferred_name = self.current_declarator_name.take();
1972 let parent_callee = self.current_parent_callee();
1973 let info = fn_info_from_func_expr(node, inferred_name, parent_callee);
1974 if let Some(source) = try_make_compile_source(info, self.opts, self.context) {
1975 self.queue.push(source);
1976 self.skip_body = true;
1977 }
1978 }
1979
1980 fn enter_arrow_function_expression(
1981 &mut self,
1982 node: &'ast ArrowFunctionExpression,
1983 scope_stack: &[ScopeId],
1984 ) {
1985 self.skip_body = false;
1986 if self.is_rejected_by_scope_check(scope_stack) {
1987 return;
1988 }
1989 let inferred_name = self.current_declarator_name.take();
1990 let parent_callee = self.current_parent_callee();
1991 let info = fn_info_from_arrow(node, inferred_name, parent_callee);
1992 if let Some(source) = try_make_compile_source(info, self.opts, self.context) {
1993 self.queue.push(source);
1994 self.skip_body = true;
1995 }
1996 }
1997
1998 fn enter_object_method(
1999 &mut self,
2000 _node: &'ast react_compiler_ast::expressions::ObjectMethod,
2001 _scope_stack: &[ScopeId],
2002 ) {
2003 self.skip_body = false;
2004 }
2005}
2006
2007fn find_functions_to_compile<'a>(
2022 program: &'a Program,
2023 opts: &PluginOptions,
2024 context: &mut ProgramContext,
2025 scope: &ScopeInfo,
2026) -> Vec<CompileSource<'a>> {
2027 let mut visitor = FunctionDiscoveryVisitor::new(opts, context);
2028 let mut walker = AstWalker::new(scope);
2029 walker.walk_program(&mut visitor, program);
2030 visitor.queue
2031}
2032
2033struct CompiledFunction<'a> {
2039 #[allow(dead_code)]
2040 kind: CompileSourceKind,
2041 #[allow(dead_code)]
2042 source: &'a CompileSource<'a>,
2043 codegen_fn: CodegenFunction,
2044}
2045
2046#[derive(Debug, Clone, Copy, PartialEq, Eq)]
2049enum OriginalFnKind {
2050 FunctionDeclaration,
2051 FunctionExpression,
2052 ArrowFunctionExpression,
2053}
2054
2055struct CompiledFnForReplacement {
2058 fn_start: Option<u32>,
2060 fn_node_id: Option<u32>,
2062 original_kind: OriginalFnKind,
2064 codegen_fn: CodegenFunction,
2066 #[allow(dead_code)]
2068 source_kind: CompileSourceKind,
2069 fn_name: Option<String>,
2071 gating: Option<GatingConfig>,
2073}
2074
2075fn get_functions_referenced_before_declaration(
2079 program: &Program,
2080 compiled_fns: &[CompiledFnForReplacement],
2081) -> HashSet<u32> {
2082 let mut fn_names: HashMap<String, u32> = HashMap::new();
2084 for compiled in compiled_fns {
2085 if compiled.original_kind == OriginalFnKind::FunctionDeclaration {
2086 if let Some(ref name) = compiled.fn_name {
2087 if let Some(nid) = compiled.fn_node_id {
2088 fn_names.insert(name.clone(), nid);
2089 }
2090 }
2091 }
2092 }
2093
2094 if fn_names.is_empty() {
2095 return HashSet::new();
2096 }
2097
2098 let mut referenced_before_decl: HashSet<u32> = HashSet::new();
2099
2100 for stmt in &program.body {
2103 if let Statement::FunctionDeclaration(f) = stmt {
2105 if let Some(ref id) = f.id {
2106 fn_names.remove(&id.name);
2107 }
2108 }
2109 for (_name, nid) in &fn_names {
2112 if stmt_references_identifier_at_top_level(stmt, _name) {
2113 referenced_before_decl.insert(*nid);
2114 }
2115 }
2116 }
2117
2118 referenced_before_decl
2119}
2120
2121fn stmt_references_identifier_at_top_level(stmt: &Statement, name: &str) -> bool {
2123 match stmt {
2124 Statement::FunctionDeclaration(_) => {
2125 false
2127 }
2128 Statement::ExportDefaultDeclaration(export) => match export.declaration.as_ref() {
2129 ExportDefaultDecl::Expression(e) => expr_references_identifier_at_top_level(e, name),
2130 _ => false,
2131 },
2132 Statement::ExportNamedDeclaration(export) => {
2133 if let Some(ref decl) = export.declaration {
2134 match decl.as_ref() {
2135 Declaration::VariableDeclaration(var_decl) => {
2136 var_decl.declarations.iter().any(|d| {
2137 d.init
2138 .as_ref()
2139 .map_or(false, |e| expr_references_identifier_at_top_level(e, name))
2140 })
2141 }
2142 _ => false,
2143 }
2144 } else {
2145 export.specifiers.iter().any(|s| {
2147 if let react_compiler_ast::declarations::ExportSpecifier::ExportSpecifier(
2148 spec,
2149 ) = s
2150 {
2151 match &spec.local {
2152 ModuleExportName::Identifier(id) => id.name == name,
2153 _ => false,
2154 }
2155 } else {
2156 false
2157 }
2158 })
2159 }
2160 }
2161 Statement::VariableDeclaration(var_decl) => var_decl.declarations.iter().any(|d| {
2162 d.init
2163 .as_ref()
2164 .map_or(false, |e| expr_references_identifier_at_top_level(e, name))
2165 }),
2166 Statement::ExpressionStatement(expr_stmt) => {
2167 expr_references_identifier_at_top_level(&expr_stmt.expression, name)
2168 }
2169 Statement::ReturnStatement(ret) => ret
2170 .argument
2171 .as_ref()
2172 .map_or(false, |e| expr_references_identifier_at_top_level(e, name)),
2173 Statement::Unknown(unknown) => raw_node_references_identifier(unknown.raw(), name),
2177 _ => false,
2178 }
2179}
2180
2181fn raw_node_references_identifier(value: &serde_json::Value, name: &str) -> bool {
2184 match value {
2185 serde_json::Value::Object(map) => {
2186 if map.get("type").and_then(serde_json::Value::as_str) == Some("Identifier")
2187 && map.get("name").and_then(serde_json::Value::as_str) == Some(name)
2188 {
2189 return true;
2190 }
2191 map.values().any(|v| raw_node_references_identifier(v, name))
2192 }
2193 serde_json::Value::Array(items) => {
2194 items.iter().any(|v| raw_node_references_identifier(v, name))
2195 }
2196 _ => false,
2197 }
2198}
2199
2200fn expr_references_identifier_at_top_level(expr: &Expression, name: &str) -> bool {
2202 match expr {
2203 Expression::Identifier(id) => id.name == name,
2204 Expression::CallExpression(call) => {
2205 expr_references_identifier_at_top_level(&call.callee, name)
2206 || call
2207 .arguments
2208 .iter()
2209 .any(|a| expr_references_identifier_at_top_level(a, name))
2210 }
2211 Expression::MemberExpression(member) => {
2212 expr_references_identifier_at_top_level(&member.object, name)
2213 }
2214 Expression::ConditionalExpression(cond) => {
2215 expr_references_identifier_at_top_level(&cond.test, name)
2216 || expr_references_identifier_at_top_level(&cond.consequent, name)
2217 || expr_references_identifier_at_top_level(&cond.alternate, name)
2218 }
2219 Expression::BinaryExpression(bin) => {
2220 expr_references_identifier_at_top_level(&bin.left, name)
2221 || expr_references_identifier_at_top_level(&bin.right, name)
2222 }
2223 Expression::LogicalExpression(log) => {
2224 expr_references_identifier_at_top_level(&log.left, name)
2225 || expr_references_identifier_at_top_level(&log.right, name)
2226 }
2227 Expression::FunctionExpression(_) | Expression::ArrowFunctionExpression(_) => false,
2229 _ => false,
2230 }
2231}
2232
2233fn build_compiled_function_expression(codegen: &CodegenFunction) -> Expression {
2235 Expression::FunctionExpression(FunctionExpression {
2236 base: BaseNode::typed("FunctionExpression"),
2237 id: codegen.id.clone(),
2238 params: codegen.params.clone(),
2239 body: codegen.body.clone(),
2240 generator: codegen.generator,
2241 is_async: codegen.is_async,
2242 return_type: None,
2243 type_parameters: None,
2244 predicate: None,
2245 })
2246}
2247
2248fn clone_original_fn_as_expression(stmt: &Statement, node_id: u32) -> Option<Expression> {
2252 match stmt {
2253 Statement::FunctionDeclaration(f) => {
2254 if f.base.node_id == Some(node_id) {
2255 return Some(Expression::FunctionExpression(FunctionExpression {
2256 base: BaseNode::typed("FunctionExpression"),
2257 id: f.id.clone(),
2258 params: f.params.clone(),
2259 body: f.body.clone(),
2260 generator: f.generator,
2261 is_async: f.is_async,
2262 return_type: None,
2263 type_parameters: None,
2264 predicate: None,
2265 }));
2266 }
2267 None
2268 }
2269 Statement::VariableDeclaration(var_decl) => {
2270 for d in &var_decl.declarations {
2271 if let Some(ref init) = d.init {
2272 if let Some(e) = clone_original_expr_as_expression(init, node_id) {
2273 return Some(e);
2274 }
2275 }
2276 }
2277 None
2278 }
2279 Statement::ExportDefaultDeclaration(export) => match export.declaration.as_ref() {
2280 ExportDefaultDecl::FunctionDeclaration(f) => {
2281 if f.base.node_id == Some(node_id) {
2282 return Some(Expression::FunctionExpression(FunctionExpression {
2283 base: BaseNode::typed("FunctionExpression"),
2284 id: f.id.clone(),
2285 params: f.params.clone(),
2286 body: f.body.clone(),
2287 generator: f.generator,
2288 is_async: f.is_async,
2289 return_type: None,
2290 type_parameters: None,
2291 predicate: None,
2292 }));
2293 }
2294 None
2295 }
2296 ExportDefaultDecl::Expression(e) => clone_original_expr_as_expression(e, node_id),
2297 _ => None,
2298 },
2299 Statement::ExportNamedDeclaration(export) => {
2300 if let Some(ref decl) = export.declaration {
2301 match decl.as_ref() {
2302 Declaration::FunctionDeclaration(f) => {
2303 if f.base.node_id == Some(node_id) {
2304 return Some(Expression::FunctionExpression(FunctionExpression {
2305 base: BaseNode::typed("FunctionExpression"),
2306 id: f.id.clone(),
2307 params: f.params.clone(),
2308 body: f.body.clone(),
2309 generator: f.generator,
2310 is_async: f.is_async,
2311 return_type: None,
2312 type_parameters: None,
2313 predicate: None,
2314 }));
2315 }
2316 None
2317 }
2318 Declaration::VariableDeclaration(var_decl) => {
2319 for d in &var_decl.declarations {
2320 if let Some(ref init) = d.init {
2321 if let Some(e) = clone_original_expr_as_expression(init, node_id) {
2322 return Some(e);
2323 }
2324 }
2325 }
2326 None
2327 }
2328 _ => None,
2329 }
2330 } else {
2331 None
2332 }
2333 }
2334 Statement::ExpressionStatement(expr_stmt) => {
2335 clone_original_expr_as_expression(&expr_stmt.expression, node_id)
2336 }
2337 Statement::BlockStatement(block) => {
2339 for s in &block.body {
2340 if let Some(e) = clone_original_fn_as_expression(s, node_id) {
2341 return Some(e);
2342 }
2343 }
2344 None
2345 }
2346 Statement::IfStatement(if_stmt) => {
2347 if let Some(e) = clone_original_expr_as_expression(&if_stmt.test, node_id) {
2348 return Some(e);
2349 }
2350 if let Some(e) = clone_original_fn_as_expression(&if_stmt.consequent, node_id) {
2351 return Some(e);
2352 }
2353 if let Some(ref alt) = if_stmt.alternate {
2354 if let Some(e) = clone_original_fn_as_expression(alt, node_id) {
2355 return Some(e);
2356 }
2357 }
2358 None
2359 }
2360 Statement::TryStatement(try_stmt) => {
2361 for s in &try_stmt.block.body {
2362 if let Some(e) = clone_original_fn_as_expression(s, node_id) {
2363 return Some(e);
2364 }
2365 }
2366 if let Some(ref handler) = try_stmt.handler {
2367 for s in &handler.body.body {
2368 if let Some(e) = clone_original_fn_as_expression(s, node_id) {
2369 return Some(e);
2370 }
2371 }
2372 }
2373 if let Some(ref finalizer) = try_stmt.finalizer {
2374 for s in &finalizer.body {
2375 if let Some(e) = clone_original_fn_as_expression(s, node_id) {
2376 return Some(e);
2377 }
2378 }
2379 }
2380 None
2381 }
2382 Statement::SwitchStatement(switch_stmt) => {
2383 if let Some(e) = clone_original_expr_as_expression(&switch_stmt.discriminant, node_id) {
2384 return Some(e);
2385 }
2386 for case in &switch_stmt.cases {
2387 for s in &case.consequent {
2388 if let Some(e) = clone_original_fn_as_expression(s, node_id) {
2389 return Some(e);
2390 }
2391 }
2392 }
2393 None
2394 }
2395 Statement::LabeledStatement(labeled) => {
2396 clone_original_fn_as_expression(&labeled.body, node_id)
2397 }
2398 Statement::ForStatement(for_stmt) => {
2399 if let Some(ref init) = for_stmt.init {
2400 match init.as_ref() {
2401 ForInit::VariableDeclaration(var_decl) => {
2402 for d in &var_decl.declarations {
2403 if let Some(ref init_expr) = d.init {
2404 if let Some(e) =
2405 clone_original_expr_as_expression(init_expr, node_id)
2406 {
2407 return Some(e);
2408 }
2409 }
2410 }
2411 }
2412 ForInit::Expression(expr) => {
2413 if let Some(e) = clone_original_expr_as_expression(expr, node_id) {
2414 return Some(e);
2415 }
2416 }
2417 }
2418 }
2419 if let Some(ref test) = for_stmt.test {
2420 if let Some(e) = clone_original_expr_as_expression(test, node_id) {
2421 return Some(e);
2422 }
2423 }
2424 if let Some(ref update) = for_stmt.update {
2425 if let Some(e) = clone_original_expr_as_expression(update, node_id) {
2426 return Some(e);
2427 }
2428 }
2429 clone_original_fn_as_expression(&for_stmt.body, node_id)
2430 }
2431 Statement::WhileStatement(while_stmt) => {
2432 if let Some(e) = clone_original_expr_as_expression(&while_stmt.test, node_id) {
2433 return Some(e);
2434 }
2435 clone_original_fn_as_expression(&while_stmt.body, node_id)
2436 }
2437 Statement::DoWhileStatement(do_while) => {
2438 if let Some(e) = clone_original_expr_as_expression(&do_while.test, node_id) {
2439 return Some(e);
2440 }
2441 clone_original_fn_as_expression(&do_while.body, node_id)
2442 }
2443 Statement::ForInStatement(for_in) => {
2444 if let Some(e) = clone_original_expr_as_expression(&for_in.right, node_id) {
2445 return Some(e);
2446 }
2447 clone_original_fn_as_expression(&for_in.body, node_id)
2448 }
2449 Statement::ForOfStatement(for_of) => {
2450 if let Some(e) = clone_original_expr_as_expression(&for_of.right, node_id) {
2451 return Some(e);
2452 }
2453 clone_original_fn_as_expression(&for_of.body, node_id)
2454 }
2455 Statement::WithStatement(with_stmt) => {
2456 if let Some(e) = clone_original_expr_as_expression(&with_stmt.object, node_id) {
2457 return Some(e);
2458 }
2459 clone_original_fn_as_expression(&with_stmt.body, node_id)
2460 }
2461 Statement::ReturnStatement(ret) => {
2462 if let Some(ref arg) = ret.argument {
2463 clone_original_expr_as_expression(arg, node_id)
2464 } else {
2465 None
2466 }
2467 }
2468 Statement::ThrowStatement(throw_stmt) => {
2469 clone_original_expr_as_expression(&throw_stmt.argument, node_id)
2470 }
2471 _ => None,
2472 }
2473}
2474
2475fn clone_original_expr_as_expression(expr: &Expression, node_id: u32) -> Option<Expression> {
2477 match expr {
2478 Expression::FunctionExpression(f) => {
2479 if f.base.node_id == Some(node_id) {
2480 return Some(Expression::FunctionExpression(f.clone()));
2481 }
2482 None
2483 }
2484 Expression::ArrowFunctionExpression(f) => {
2485 if f.base.node_id == Some(node_id) {
2486 return Some(Expression::ArrowFunctionExpression(f.clone()));
2487 }
2488 None
2489 }
2490 Expression::CallExpression(call) => {
2491 for arg in &call.arguments {
2492 if let Some(e) = clone_original_expr_as_expression(arg, node_id) {
2493 return Some(e);
2494 }
2495 }
2496 None
2497 }
2498 Expression::ObjectExpression(obj) => {
2499 for prop in &obj.properties {
2500 match prop {
2501 ObjectExpressionProperty::ObjectProperty(p) => {
2502 if let Some(e) = clone_original_expr_as_expression(&p.value, node_id) {
2503 return Some(e);
2504 }
2505 }
2506 ObjectExpressionProperty::SpreadElement(s) => {
2507 if let Some(e) = clone_original_expr_as_expression(&s.argument, node_id) {
2508 return Some(e);
2509 }
2510 }
2511 _ => {}
2512 }
2513 }
2514 None
2515 }
2516 Expression::ArrayExpression(arr) => {
2517 for elem in arr.elements.iter().flatten() {
2518 if let Some(e) = clone_original_expr_as_expression(elem, node_id) {
2519 return Some(e);
2520 }
2521 }
2522 None
2523 }
2524 Expression::AssignmentExpression(assign) => {
2525 clone_original_expr_as_expression(&assign.right, node_id)
2526 }
2527 Expression::SequenceExpression(seq) => {
2528 for e in &seq.expressions {
2529 if let Some(e) = clone_original_expr_as_expression(e, node_id) {
2530 return Some(e);
2531 }
2532 }
2533 None
2534 }
2535 Expression::ConditionalExpression(cond) => {
2536 if let Some(e) = clone_original_expr_as_expression(&cond.consequent, node_id) {
2537 return Some(e);
2538 }
2539 clone_original_expr_as_expression(&cond.alternate, node_id)
2540 }
2541 Expression::ParenthesizedExpression(paren) => {
2542 clone_original_expr_as_expression(&paren.expression, node_id)
2543 }
2544 _ => None,
2545 }
2546}
2547
2548fn build_compiled_expression_matching_kind(
2551 codegen: &CodegenFunction,
2552 original_kind: OriginalFnKind,
2553) -> Expression {
2554 match original_kind {
2555 OriginalFnKind::ArrowFunctionExpression => {
2556 Expression::ArrowFunctionExpression(ArrowFunctionExpression {
2557 base: BaseNode::typed("ArrowFunctionExpression"),
2558 params: codegen.params.clone(),
2559 body: Box::new(ArrowFunctionBody::BlockStatement(codegen.body.clone())),
2560 id: None,
2561 generator: codegen.generator,
2562 is_async: codegen.is_async,
2563 expression: Some(false),
2564 return_type: None,
2565 type_parameters: None,
2566 predicate: None,
2567 })
2568 }
2569 _ => build_compiled_function_expression(codegen),
2570 }
2571}
2572
2573fn apply_compiled_functions(
2576 compiled_fns: &[CompiledFnForReplacement],
2577 program: &mut Program,
2578 context: &mut ProgramContext,
2579) {
2580 if compiled_fns.is_empty() {
2581 return;
2582 }
2583
2584 let has_gating = compiled_fns.iter().any(|cf| cf.gating.is_some());
2586
2587 let referenced_before_decl = if has_gating {
2589 get_functions_referenced_before_declaration(program, compiled_fns)
2590 } else {
2591 HashSet::new()
2592 };
2593
2594 let original_expressions: Vec<Option<Expression>> = if has_gating {
2597 compiled_fns
2598 .iter()
2599 .map(|compiled| {
2600 if compiled.gating.is_some() {
2601 if let Some(node_id) = compiled.fn_node_id {
2602 for stmt in program.body.iter() {
2603 if let Some(expr) = clone_original_fn_as_expression(stmt, node_id) {
2604 return Some(expr);
2605 }
2606 }
2607 }
2608 None
2609 } else {
2610 None
2611 }
2612 })
2613 .collect()
2614 } else {
2615 compiled_fns.iter().map(|_| None).collect()
2616 };
2617
2618 let mut outlined_decls: Vec<(Option<u32>, OriginalFnKind, FunctionDeclaration)> = Vec::new(); for (idx, compiled) in compiled_fns.iter().enumerate() {
2626 for outlined in &compiled.codegen_fn.outlined {
2628 let outlined_decl = FunctionDeclaration {
2629 base: BaseNode::typed("FunctionDeclaration"),
2630 id: outlined.func.id.clone(),
2631 params: outlined.func.params.clone(),
2632 body: outlined.func.body.clone(),
2633 generator: outlined.func.generator,
2634 is_async: outlined.func.is_async,
2635 declare: None,
2636 return_type: None,
2637 type_parameters: None,
2638 predicate: None,
2639 component_declaration: false,
2640 hook_declaration: false,
2641 };
2642 outlined_decls.push((compiled.fn_node_id, compiled.original_kind, outlined_decl));
2643 }
2644
2645 if let Some(ref gating_config) = compiled.gating {
2646 let is_ref_before_decl = compiled
2647 .fn_node_id
2648 .map_or(false, |nid| referenced_before_decl.contains(&nid));
2649
2650 if is_ref_before_decl && compiled.original_kind == OriginalFnKind::FunctionDeclaration {
2651 apply_gated_function_hoisted(program, compiled, gating_config, context);
2653 } else {
2654 let original_expr = original_expressions[idx].clone();
2656 apply_gated_function_conditional(
2657 program,
2658 compiled,
2659 gating_config,
2660 original_expr,
2661 context,
2662 );
2663 }
2664 } else {
2665 if let Some(node_id) = compiled.fn_node_id {
2667 let mut visitor = ReplaceFnVisitor { node_id, compiled };
2668 walk_program_mut(&mut visitor, program);
2669 }
2670 }
2671 }
2672
2673 for (parent_node_id, original_kind, outlined_decl) in outlined_decls {
2681 let outlined_stmt = Statement::FunctionDeclaration(outlined_decl);
2682 match original_kind {
2683 OriginalFnKind::FunctionDeclaration => {
2684 if let Some(nid) = parent_node_id {
2685 if !insert_after_fn_recursive(&mut program.body, nid, outlined_stmt.clone()) {
2686 program.body.push(outlined_stmt);
2687 }
2688 } else {
2689 program.body.push(outlined_stmt);
2690 }
2691 }
2692 OriginalFnKind::FunctionExpression | OriginalFnKind::ArrowFunctionExpression => {
2693 program.body.push(outlined_stmt);
2694 }
2695 }
2696 }
2697
2698 let needs_memo_import = compiled_fns
2700 .iter()
2701 .any(|cf| cf.codegen_fn.memo_slots_used > 0);
2702 if needs_memo_import {
2703 let import_spec = context.add_memo_cache_import();
2704 let local_name = import_spec.name;
2705 let mut visitor = RenameIdentifierVisitor {
2706 old_name: "useMemoCache",
2707 new_name: &local_name,
2708 };
2709 walk_program_mut(&mut visitor, program);
2710 }
2711
2712 add_imports_to_program(program, context);
2717}
2718
2719fn apply_gated_function_conditional(
2733 program: &mut Program,
2734 compiled: &CompiledFnForReplacement,
2735 gating_config: &GatingConfig,
2736 original_expr: Option<Expression>,
2737 context: &mut ProgramContext,
2738) {
2739 let _start = match compiled.fn_start {
2740 Some(s) => s,
2741 None => return,
2742 };
2743 let node_id = match compiled.fn_node_id {
2744 Some(nid) => nid,
2745 None => return,
2746 };
2747
2748 let gating_import = context.add_import_specifier(
2750 &gating_config.source,
2751 &gating_config.import_specifier_name,
2752 None,
2753 );
2754 let gating_callee_name = gating_import.name;
2755
2756 let compiled_expr =
2758 build_compiled_expression_matching_kind(&compiled.codegen_fn, compiled.original_kind);
2759
2760 let original_expr = match original_expr {
2762 Some(e) => e,
2763 None => return, };
2765
2766 let gating_expression = Expression::ConditionalExpression(ConditionalExpression {
2768 base: BaseNode::typed("ConditionalExpression"),
2769 test: Box::new(Expression::CallExpression(CallExpression {
2770 base: BaseNode::typed("CallExpression"),
2771 callee: Box::new(Expression::Identifier(Identifier {
2772 base: BaseNode::typed("Identifier"),
2773 name: gating_callee_name,
2774 type_annotation: None,
2775 optional: None,
2776 decorators: None,
2777 })),
2778 arguments: vec![],
2779 type_parameters: None,
2780 type_arguments: None,
2781 optional: None,
2782 })),
2783 consequent: Box::new(compiled_expr),
2784 alternate: Box::new(original_expr),
2785 });
2786
2787 let mut export_default_name: Option<(usize, String)> = None;
2791
2792 for (idx, stmt) in program.body.iter().enumerate() {
2793 if let Statement::ExportDefaultDeclaration(export) = stmt {
2794 if let ExportDefaultDecl::FunctionDeclaration(f) = export.declaration.as_ref() {
2795 if f.base.node_id == Some(node_id) {
2796 if let Some(ref fn_id) = f.id {
2797 export_default_name = Some((idx, fn_id.name.clone()));
2798 }
2799 }
2800 }
2801 }
2802 }
2803
2804 let mut visitor = ReplaceWithGatedVisitor {
2805 node_id,
2806 gating_expression: &gating_expression,
2807 };
2808 walk_program_mut(&mut visitor, program);
2809
2810 if let Some((idx, name)) = export_default_name {
2812 program.body.insert(
2813 idx + 1,
2814 Statement::ExportDefaultDeclaration(ExportDefaultDeclaration {
2815 base: BaseNode::typed("ExportDefaultDeclaration"),
2816 declaration: Box::new(ExportDefaultDecl::Expression(Box::new(
2817 Expression::Identifier(Identifier {
2818 base: BaseNode::typed("Identifier"),
2819 name,
2820 type_annotation: None,
2821 optional: None,
2822 decorators: None,
2823 }),
2824 ))),
2825 export_kind: None,
2826 }),
2827 );
2828 }
2829}
2830
2831struct ReplaceWithGatedVisitor<'a> {
2833 node_id: u32,
2834 gating_expression: &'a Expression,
2835}
2836
2837impl MutVisitor for ReplaceWithGatedVisitor<'_> {
2838 fn visit_statement(&mut self, stmt: &mut Statement) -> VisitResult {
2839 if let Statement::FunctionDeclaration(f) = &*stmt {
2841 if f.base.node_id == Some(self.node_id) {
2842 let fn_name = f.id.clone().unwrap_or_else(|| Identifier {
2843 base: BaseNode::typed("Identifier"),
2844 name: "anonymous".to_string(),
2845 type_annotation: None,
2846 optional: None,
2847 decorators: None,
2848 });
2849 let mut base = BaseNode::typed("VariableDeclaration");
2850 base.leading_comments = f.base.leading_comments.clone();
2851 base.trailing_comments = f.base.trailing_comments.clone();
2852 base.inner_comments = f.base.inner_comments.clone();
2853 *stmt = Statement::VariableDeclaration(VariableDeclaration {
2854 base,
2855 kind: VariableDeclarationKind::Const,
2856 declarations: vec![VariableDeclarator {
2857 base: BaseNode::typed("VariableDeclarator"),
2858 id: PatternLike::Identifier(fn_name),
2859 init: Some(Box::new(self.gating_expression.clone())),
2860 definite: None,
2861 }],
2862 declare: None,
2863 });
2864 return VisitResult::Stop;
2865 }
2866 }
2867
2868 if let Statement::ExportDefaultDeclaration(export) = stmt {
2870 let is_fn_decl_match = matches!(
2871 export.declaration.as_ref(),
2872 ExportDefaultDecl::FunctionDeclaration(f) if f.base.node_id == Some(self.node_id)
2873 );
2874 if is_fn_decl_match {
2875 if let ExportDefaultDecl::FunctionDeclaration(f) = export.declaration.as_ref() {
2876 let fn_name = f.id.clone();
2877 if let Some(fn_id) = fn_name {
2878 let mut base = BaseNode::typed("VariableDeclaration");
2879 base.leading_comments = export.base.leading_comments.clone();
2880 base.trailing_comments = export.base.trailing_comments.clone();
2881 base.inner_comments = export.base.inner_comments.clone();
2882 *stmt = Statement::VariableDeclaration(VariableDeclaration {
2883 base,
2884 kind: VariableDeclarationKind::Const,
2885 declarations: vec![VariableDeclarator {
2886 base: BaseNode::typed("VariableDeclarator"),
2887 id: PatternLike::Identifier(fn_id),
2888 init: Some(Box::new(self.gating_expression.clone())),
2889 definite: None,
2890 }],
2891 declare: None,
2892 });
2893 return VisitResult::Stop;
2894 } else {
2895 export.declaration = Box::new(ExportDefaultDecl::Expression(Box::new(
2896 self.gating_expression.clone(),
2897 )));
2898 return VisitResult::Stop;
2899 }
2900 }
2901 }
2902 }
2904
2905 if let Statement::ExportNamedDeclaration(export) = stmt {
2907 if let Some(ref mut decl) = export.declaration {
2908 if let Declaration::FunctionDeclaration(f) = decl.as_mut() {
2909 if f.base.node_id == Some(self.node_id) {
2910 let fn_name = f.id.clone().unwrap_or_else(|| Identifier {
2911 base: BaseNode::typed("Identifier"),
2912 name: "anonymous".to_string(),
2913 type_annotation: None,
2914 optional: None,
2915 decorators: None,
2916 });
2917 *decl = Box::new(Declaration::VariableDeclaration(VariableDeclaration {
2918 base: BaseNode::typed("VariableDeclaration"),
2919 kind: VariableDeclarationKind::Const,
2920 declarations: vec![VariableDeclarator {
2921 base: BaseNode::typed("VariableDeclarator"),
2922 id: PatternLike::Identifier(fn_name),
2923 init: Some(Box::new(self.gating_expression.clone())),
2924 definite: None,
2925 }],
2926 declare: None,
2927 }));
2928 return VisitResult::Stop;
2929 }
2930 }
2931 }
2932 }
2933
2934 VisitResult::Continue
2935 }
2936
2937 fn visit_expression(&mut self, expr: &mut Expression) -> VisitResult {
2938 match expr {
2939 Expression::FunctionExpression(f) if f.base.node_id == Some(self.node_id) => {
2940 *expr = self.gating_expression.clone();
2941 VisitResult::Stop
2942 }
2943 Expression::ArrowFunctionExpression(f) if f.base.node_id == Some(self.node_id) => {
2944 *expr = self.gating_expression.clone();
2945 VisitResult::Stop
2946 }
2947 _ => VisitResult::Continue,
2948 }
2949 }
2950}
2951
2952fn apply_gated_function_hoisted(
2961 program: &mut Program,
2962 compiled: &CompiledFnForReplacement,
2963 gating_config: &GatingConfig,
2964 context: &mut ProgramContext,
2965) {
2966 let _start = match compiled.fn_start {
2967 Some(s) => s,
2968 None => return,
2969 };
2970 let node_id = match compiled.fn_node_id {
2971 Some(nid) => nid,
2972 None => return,
2973 };
2974
2975 let original_fn_name = match &compiled.fn_name {
2976 Some(name) => name.clone(),
2977 None => return,
2978 };
2979
2980 let gating_import = context.add_import_specifier(
2982 &gating_config.source,
2983 &gating_config.import_specifier_name,
2984 None,
2985 );
2986 let gating_callee_name = gating_import.name.clone();
2987
2988 let gating_result_name = context.new_uid(&format!("{}_result", gating_callee_name));
2990 let unoptimized_name = context.new_uid(&format!("{}_unoptimized", original_fn_name));
2991 let optimized_name = context.new_uid(&format!("{}_optimized", original_fn_name));
2992
2993 let mut original_params: Vec<PatternLike> = Vec::new();
2995 let mut fn_stmt_idx: Option<usize> = None;
2996
2997 for (idx, stmt) in program.body.iter().enumerate() {
2998 if let Statement::FunctionDeclaration(f) = stmt {
2999 if f.base.node_id == Some(node_id) {
3000 original_params = f.params.clone();
3001 fn_stmt_idx = Some(idx);
3002 break;
3003 }
3004 }
3005 }
3006
3007 let fn_idx = match fn_stmt_idx {
3008 Some(idx) => idx,
3009 None => return,
3010 };
3011
3012 if let Statement::FunctionDeclaration(f) = &mut program.body[fn_idx] {
3014 if let Some(ref mut id) = f.id {
3015 id.name = unoptimized_name.clone();
3016 }
3017 }
3018
3019 let compiled_fn_decl = FunctionDeclaration {
3021 base: BaseNode::typed("FunctionDeclaration"),
3022 id: Some(Identifier {
3023 base: BaseNode::typed("Identifier"),
3024 name: optimized_name.clone(),
3025 type_annotation: None,
3026 optional: None,
3027 decorators: None,
3028 }),
3029 params: compiled.codegen_fn.params.clone(),
3030 body: compiled.codegen_fn.body.clone(),
3031 generator: compiled.codegen_fn.generator,
3032 is_async: compiled.codegen_fn.is_async,
3033 declare: None,
3034 return_type: None,
3035 type_parameters: None,
3036 predicate: None,
3037 component_declaration: false,
3038 hook_declaration: false,
3039 };
3040
3041 let gating_result_stmt = Statement::VariableDeclaration(VariableDeclaration {
3043 base: BaseNode::typed("VariableDeclaration"),
3044 kind: VariableDeclarationKind::Const,
3045 declarations: vec![VariableDeclarator {
3046 base: BaseNode::typed("VariableDeclarator"),
3047 id: PatternLike::Identifier(Identifier {
3048 base: BaseNode::typed("Identifier"),
3049 name: gating_result_name.clone(),
3050 type_annotation: None,
3051 optional: None,
3052 decorators: None,
3053 }),
3054 init: Some(Box::new(Expression::CallExpression(CallExpression {
3055 base: BaseNode::typed("CallExpression"),
3056 callee: Box::new(Expression::Identifier(Identifier {
3057 base: BaseNode::typed("Identifier"),
3058 name: gating_callee_name,
3059 type_annotation: None,
3060 optional: None,
3061 decorators: None,
3062 })),
3063 arguments: vec![],
3064 type_parameters: None,
3065 type_arguments: None,
3066 optional: None,
3067 }))),
3068 definite: None,
3069 }],
3070 declare: None,
3071 });
3072
3073 let num_params = original_params.len();
3075 let mut new_params: Vec<PatternLike> = Vec::new();
3076 let mut optimized_args: Vec<Expression> = Vec::new();
3077 let mut unoptimized_args: Vec<Expression> = Vec::new();
3078
3079 for i in 0..num_params {
3080 let arg_name = format!("arg{}", i);
3081 let is_rest = matches!(&original_params[i], PatternLike::RestElement(_));
3082
3083 if is_rest {
3084 new_params.push(PatternLike::RestElement(
3085 react_compiler_ast::patterns::RestElement {
3086 base: BaseNode::typed("RestElement"),
3087 argument: Box::new(PatternLike::Identifier(Identifier {
3088 base: BaseNode::typed("Identifier"),
3089 name: arg_name.clone(),
3090 type_annotation: None,
3091 optional: None,
3092 decorators: None,
3093 })),
3094 type_annotation: None,
3095 decorators: None,
3096 },
3097 ));
3098 optimized_args.push(Expression::SpreadElement(SpreadElement {
3099 base: BaseNode::typed("SpreadElement"),
3100 argument: Box::new(Expression::Identifier(Identifier {
3101 base: BaseNode::typed("Identifier"),
3102 name: arg_name.clone(),
3103 type_annotation: None,
3104 optional: None,
3105 decorators: None,
3106 })),
3107 }));
3108 unoptimized_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,
3113 type_annotation: None,
3114 optional: None,
3115 decorators: None,
3116 })),
3117 }));
3118 } else {
3119 new_params.push(PatternLike::Identifier(Identifier {
3120 base: BaseNode::typed("Identifier"),
3121 name: arg_name.clone(),
3122 type_annotation: None,
3123 optional: None,
3124 decorators: None,
3125 }));
3126 optimized_args.push(Expression::Identifier(Identifier {
3127 base: BaseNode::typed("Identifier"),
3128 name: arg_name.clone(),
3129 type_annotation: None,
3130 optional: None,
3131 decorators: None,
3132 }));
3133 unoptimized_args.push(Expression::Identifier(Identifier {
3134 base: BaseNode::typed("Identifier"),
3135 name: arg_name,
3136 type_annotation: None,
3137 optional: None,
3138 decorators: None,
3139 }));
3140 }
3141 }
3142
3143 let dispatcher_fn = Statement::FunctionDeclaration(FunctionDeclaration {
3149 base: BaseNode::typed("FunctionDeclaration"),
3150 id: Some(Identifier {
3151 base: BaseNode::typed("Identifier"),
3152 name: original_fn_name,
3153 type_annotation: None,
3154 optional: None,
3155 decorators: None,
3156 }),
3157 params: new_params,
3158 body: BlockStatement {
3159 base: BaseNode::typed("BlockStatement"),
3160 body: vec![Statement::IfStatement(IfStatement {
3161 base: BaseNode::typed("IfStatement"),
3162 test: Box::new(Expression::Identifier(Identifier {
3163 base: BaseNode::typed("Identifier"),
3164 name: gating_result_name,
3165 type_annotation: None,
3166 optional: None,
3167 decorators: None,
3168 })),
3169 consequent: Box::new(Statement::ReturnStatement(ReturnStatement {
3170 base: BaseNode::typed("ReturnStatement"),
3171 argument: Some(Box::new(Expression::CallExpression(CallExpression {
3172 base: BaseNode::typed("CallExpression"),
3173 callee: Box::new(Expression::Identifier(Identifier {
3174 base: BaseNode::typed("Identifier"),
3175 name: optimized_name.clone(),
3176 type_annotation: None,
3177 optional: None,
3178 decorators: None,
3179 })),
3180 arguments: optimized_args,
3181 type_parameters: None,
3182 type_arguments: None,
3183 optional: None,
3184 }))),
3185 })),
3186 alternate: Some(Box::new(Statement::ReturnStatement(ReturnStatement {
3187 base: BaseNode::typed("ReturnStatement"),
3188 argument: Some(Box::new(Expression::CallExpression(CallExpression {
3189 base: BaseNode::typed("CallExpression"),
3190 callee: Box::new(Expression::Identifier(Identifier {
3191 base: BaseNode::typed("Identifier"),
3192 name: unoptimized_name,
3193 type_annotation: None,
3194 optional: None,
3195 decorators: None,
3196 })),
3197 arguments: unoptimized_args,
3198 type_parameters: None,
3199 type_arguments: None,
3200 optional: None,
3201 }))),
3202 }))),
3203 })],
3204 directives: vec![],
3205 },
3206 generator: false,
3207 is_async: false,
3208 declare: None,
3209 return_type: None,
3210 type_parameters: None,
3211 predicate: None,
3212 component_declaration: false,
3213 hook_declaration: false,
3214 });
3215
3216 program.body.insert(fn_idx + 1, dispatcher_fn);
3230
3231 program
3233 .body
3234 .insert(fn_idx, Statement::FunctionDeclaration(compiled_fn_decl));
3235
3236 program.body.insert(fn_idx, gating_result_stmt);
3238}
3239
3240fn insert_after_fn_recursive(
3244 stmts: &mut Vec<Statement>,
3245 node_id: u32,
3246 new_stmt: Statement,
3247) -> bool {
3248 if let Some(pos) = stmts
3250 .iter()
3251 .position(|s| stmt_has_fn_with_node_id(s, node_id))
3252 {
3253 stmts.insert(pos + 1, new_stmt);
3254 return true;
3255 }
3256 for stmt in stmts.iter_mut() {
3258 if insert_after_fn_in_stmt(stmt, node_id, &new_stmt) {
3259 return true;
3260 }
3261 }
3262 false
3263}
3264
3265fn insert_after_fn_in_stmt(stmt: &mut Statement, node_id: u32, new_stmt: &Statement) -> bool {
3266 match stmt {
3267 Statement::FunctionDeclaration(f) => {
3268 insert_after_fn_in_block(&mut f.body, node_id, new_stmt)
3269 }
3270 Statement::BlockStatement(b) => insert_after_fn_in_block(b, node_id, new_stmt),
3271 Statement::ExpressionStatement(e) => {
3272 insert_after_fn_in_expr(&mut e.expression, node_id, new_stmt)
3273 }
3274 Statement::ReturnStatement(r) => {
3275 if let Some(arg) = &mut r.argument {
3276 insert_after_fn_in_expr(arg, node_id, new_stmt)
3277 } else {
3278 false
3279 }
3280 }
3281 Statement::VariableDeclaration(v) => {
3282 for decl in &mut v.declarations {
3283 if let Some(init) = &mut decl.init {
3284 if insert_after_fn_in_expr(init, node_id, new_stmt) {
3285 return true;
3286 }
3287 }
3288 }
3289 false
3290 }
3291 Statement::ExportDefaultDeclaration(e) => match e.declaration.as_mut() {
3292 ExportDefaultDecl::FunctionDeclaration(f) => {
3293 insert_after_fn_in_block(&mut f.body, node_id, new_stmt)
3294 }
3295 ExportDefaultDecl::Expression(expr) => insert_after_fn_in_expr(expr, node_id, new_stmt),
3296 _ => false,
3297 },
3298 Statement::ExportNamedDeclaration(e) => {
3299 if let Some(decl) = &mut e.declaration {
3300 match decl.as_mut() {
3301 Declaration::FunctionDeclaration(f) => {
3302 insert_after_fn_in_block(&mut f.body, node_id, new_stmt)
3303 }
3304 Declaration::VariableDeclaration(v) => {
3305 for d in &mut v.declarations {
3306 if let Some(init) = &mut d.init {
3307 if insert_after_fn_in_expr(init, node_id, new_stmt) {
3308 return true;
3309 }
3310 }
3311 }
3312 false
3313 }
3314 _ => false,
3315 }
3316 } else {
3317 false
3318 }
3319 }
3320 Statement::IfStatement(i) => {
3321 insert_after_fn_in_stmt(&mut i.consequent, node_id, new_stmt)
3322 || i.alternate
3323 .as_mut()
3324 .map_or(false, |a| insert_after_fn_in_stmt(a, node_id, new_stmt))
3325 }
3326 Statement::ForStatement(f) => insert_after_fn_in_stmt(&mut f.body, node_id, new_stmt),
3327 Statement::WhileStatement(w) => insert_after_fn_in_stmt(&mut w.body, node_id, new_stmt),
3328 Statement::TryStatement(t) => {
3329 if insert_after_fn_in_block(&mut t.block, node_id, new_stmt) {
3330 return true;
3331 }
3332 if let Some(h) = &mut t.handler {
3333 if insert_after_fn_in_block(&mut h.body, node_id, new_stmt) {
3334 return true;
3335 }
3336 }
3337 if let Some(f) = &mut t.finalizer {
3338 if insert_after_fn_in_block(f, node_id, new_stmt) {
3339 return true;
3340 }
3341 }
3342 false
3343 }
3344 _ => false,
3345 }
3346}
3347
3348fn insert_after_fn_in_block(
3349 block: &mut react_compiler_ast::statements::BlockStatement,
3350 node_id: u32,
3351 new_stmt: &Statement,
3352) -> bool {
3353 if let Some(pos) = block
3354 .body
3355 .iter()
3356 .position(|s| stmt_has_fn_with_node_id(s, node_id))
3357 {
3358 block.body.insert(pos + 1, new_stmt.clone());
3359 return true;
3360 }
3361 for stmt in block.body.iter_mut() {
3362 if insert_after_fn_in_stmt(stmt, node_id, new_stmt) {
3363 return true;
3364 }
3365 }
3366 false
3367}
3368
3369fn insert_after_fn_in_expr(
3370 expr: &mut react_compiler_ast::expressions::Expression,
3371 node_id: u32,
3372 new_stmt: &Statement,
3373) -> bool {
3374 use react_compiler_ast::expressions::Expression;
3375 match expr {
3376 Expression::ObjectExpression(obj) => {
3377 for prop in &mut obj.properties {
3378 match prop {
3379 react_compiler_ast::expressions::ObjectExpressionProperty::ObjectMethod(m) => {
3380 if insert_after_fn_in_block(&mut m.body, node_id, new_stmt) {
3381 return true;
3382 }
3383 }
3384 react_compiler_ast::expressions::ObjectExpressionProperty::ObjectProperty(
3385 p,
3386 ) => {
3387 if insert_after_fn_in_expr(&mut p.value, node_id, new_stmt) {
3388 return true;
3389 }
3390 }
3391 _ => {}
3392 }
3393 }
3394 false
3395 }
3396 Expression::ArrayExpression(arr) => {
3397 for elem in arr.elements.iter_mut().flatten() {
3398 if insert_after_fn_in_expr(elem, node_id, new_stmt) {
3399 return true;
3400 }
3401 }
3402 false
3403 }
3404 Expression::ArrowFunctionExpression(arrow) => match arrow.body.as_mut() {
3405 react_compiler_ast::expressions::ArrowFunctionBody::BlockStatement(block) => {
3406 insert_after_fn_in_block(block, node_id, new_stmt)
3407 }
3408 react_compiler_ast::expressions::ArrowFunctionBody::Expression(e) => {
3409 insert_after_fn_in_expr(e, node_id, new_stmt)
3410 }
3411 },
3412 Expression::FunctionExpression(f) => {
3413 insert_after_fn_in_block(&mut f.body, node_id, new_stmt)
3414 }
3415 Expression::CallExpression(c) => {
3416 for arg in &mut c.arguments {
3417 if insert_after_fn_in_expr(arg, node_id, new_stmt) {
3418 return true;
3419 }
3420 }
3421 insert_after_fn_in_expr(&mut c.callee, node_id, new_stmt)
3422 }
3423 Expression::ConditionalExpression(c) => {
3424 insert_after_fn_in_expr(&mut c.consequent, node_id, new_stmt)
3425 || insert_after_fn_in_expr(&mut c.alternate, node_id, new_stmt)
3426 }
3427 Expression::AssignmentExpression(a) => {
3428 insert_after_fn_in_expr(&mut a.right, node_id, new_stmt)
3429 }
3430 Expression::TypeCastExpression(tc) => {
3431 insert_after_fn_in_expr(&mut tc.expression, node_id, new_stmt)
3432 }
3433 Expression::ParenthesizedExpression(p) => {
3434 insert_after_fn_in_expr(&mut p.expression, node_id, new_stmt)
3435 }
3436 Expression::TSAsExpression(ts) => {
3437 insert_after_fn_in_expr(&mut ts.expression, node_id, new_stmt)
3438 }
3439 Expression::SequenceExpression(s) => {
3440 for expr in &mut s.expressions {
3441 if insert_after_fn_in_expr(expr, node_id, new_stmt) {
3442 return true;
3443 }
3444 }
3445 false
3446 }
3447 _ => false,
3448 }
3449}
3450
3451fn stmt_has_fn_with_node_id(stmt: &Statement, node_id: u32) -> bool {
3453 match stmt {
3454 Statement::FunctionDeclaration(f) => f.base.node_id == Some(node_id),
3455 Statement::VariableDeclaration(var_decl) => var_decl.declarations.iter().any(|decl| {
3456 if let Some(ref init) = decl.init {
3457 expr_has_fn_with_node_id(init, node_id)
3458 } else {
3459 false
3460 }
3461 }),
3462 Statement::ExportDefaultDeclaration(export) => match export.declaration.as_ref() {
3463 ExportDefaultDecl::FunctionDeclaration(f) => f.base.node_id == Some(node_id),
3464 ExportDefaultDecl::Expression(e) => expr_has_fn_with_node_id(e, node_id),
3465 _ => false,
3466 },
3467 Statement::ExportNamedDeclaration(export) => {
3468 if let Some(ref decl) = export.declaration {
3469 match decl.as_ref() {
3470 Declaration::FunctionDeclaration(f) => f.base.node_id == Some(node_id),
3471 Declaration::VariableDeclaration(var_decl) => {
3472 var_decl.declarations.iter().any(|d| {
3473 if let Some(ref init) = d.init {
3474 expr_has_fn_with_node_id(init, node_id)
3475 } else {
3476 false
3477 }
3478 })
3479 }
3480 _ => false,
3481 }
3482 } else {
3483 false
3484 }
3485 }
3486 Statement::ExpressionStatement(expr_stmt) => {
3487 expr_has_fn_with_node_id(&expr_stmt.expression, node_id)
3488 }
3489 Statement::BlockStatement(block) => block
3491 .body
3492 .iter()
3493 .any(|s| stmt_has_fn_with_node_id(s, node_id)),
3494 Statement::IfStatement(if_stmt) => {
3495 expr_has_fn_with_node_id(&if_stmt.test, node_id)
3496 || stmt_has_fn_with_node_id(&if_stmt.consequent, node_id)
3497 || if_stmt
3498 .alternate
3499 .as_ref()
3500 .map_or(false, |alt| stmt_has_fn_with_node_id(alt, node_id))
3501 }
3502 Statement::TryStatement(try_stmt) => {
3503 try_stmt
3504 .block
3505 .body
3506 .iter()
3507 .any(|s| stmt_has_fn_with_node_id(s, node_id))
3508 || try_stmt.handler.as_ref().map_or(false, |h| {
3509 h.body
3510 .body
3511 .iter()
3512 .any(|s| stmt_has_fn_with_node_id(s, node_id))
3513 })
3514 || try_stmt.finalizer.as_ref().map_or(false, |f| {
3515 f.body.iter().any(|s| stmt_has_fn_with_node_id(s, node_id))
3516 })
3517 }
3518 Statement::SwitchStatement(switch_stmt) => {
3519 expr_has_fn_with_node_id(&switch_stmt.discriminant, node_id)
3520 || switch_stmt.cases.iter().any(|case| {
3521 case.consequent
3522 .iter()
3523 .any(|s| stmt_has_fn_with_node_id(s, node_id))
3524 })
3525 }
3526 Statement::LabeledStatement(labeled) => stmt_has_fn_with_node_id(&labeled.body, node_id),
3527 Statement::ForStatement(for_stmt) => {
3528 if let Some(ref init) = for_stmt.init {
3529 match init.as_ref() {
3530 ForInit::VariableDeclaration(var_decl) => {
3531 if var_decl.declarations.iter().any(|d| {
3532 d.init
3533 .as_ref()
3534 .map_or(false, |e| expr_has_fn_with_node_id(e, node_id))
3535 }) {
3536 return true;
3537 }
3538 }
3539 ForInit::Expression(expr) => {
3540 if expr_has_fn_with_node_id(expr, node_id) {
3541 return true;
3542 }
3543 }
3544 }
3545 }
3546 if for_stmt
3547 .test
3548 .as_ref()
3549 .map_or(false, |t| expr_has_fn_with_node_id(t, node_id))
3550 {
3551 return true;
3552 }
3553 if for_stmt
3554 .update
3555 .as_ref()
3556 .map_or(false, |u| expr_has_fn_with_node_id(u, node_id))
3557 {
3558 return true;
3559 }
3560 stmt_has_fn_with_node_id(&for_stmt.body, node_id)
3561 }
3562 Statement::WhileStatement(while_stmt) => {
3563 expr_has_fn_with_node_id(&while_stmt.test, node_id)
3564 || stmt_has_fn_with_node_id(&while_stmt.body, node_id)
3565 }
3566 Statement::DoWhileStatement(do_while) => {
3567 expr_has_fn_with_node_id(&do_while.test, node_id)
3568 || stmt_has_fn_with_node_id(&do_while.body, node_id)
3569 }
3570 Statement::ForInStatement(for_in) => {
3571 expr_has_fn_with_node_id(&for_in.right, node_id)
3572 || stmt_has_fn_with_node_id(&for_in.body, node_id)
3573 }
3574 Statement::ForOfStatement(for_of) => {
3575 expr_has_fn_with_node_id(&for_of.right, node_id)
3576 || stmt_has_fn_with_node_id(&for_of.body, node_id)
3577 }
3578 Statement::WithStatement(with_stmt) => {
3579 expr_has_fn_with_node_id(&with_stmt.object, node_id)
3580 || stmt_has_fn_with_node_id(&with_stmt.body, node_id)
3581 }
3582 Statement::ReturnStatement(ret) => ret
3583 .argument
3584 .as_ref()
3585 .map_or(false, |arg| expr_has_fn_with_node_id(arg, node_id)),
3586 Statement::ThrowStatement(throw_stmt) => {
3587 expr_has_fn_with_node_id(&throw_stmt.argument, node_id)
3588 }
3589 _ => false,
3590 }
3591}
3592
3593fn expr_has_fn_with_node_id(expr: &Expression, node_id: u32) -> bool {
3595 match expr {
3596 Expression::FunctionExpression(f) => f.base.node_id == Some(node_id),
3597 Expression::ArrowFunctionExpression(f) => f.base.node_id == Some(node_id),
3598 Expression::CallExpression(call) => call
3600 .arguments
3601 .iter()
3602 .any(|arg| expr_has_fn_with_node_id(arg, node_id)),
3603 _ => false,
3604 }
3605}
3606
3607struct ReplaceFnVisitor<'a> {
3609 node_id: u32,
3610 compiled: &'a CompiledFnForReplacement,
3611}
3612
3613impl MutVisitor for ReplaceFnVisitor<'_> {
3614 fn visit_statement(&mut self, stmt: &mut Statement) -> VisitResult {
3615 match stmt {
3616 Statement::FunctionDeclaration(f) if f.base.node_id == Some(self.node_id) => {
3617 f.id = self.compiled.codegen_fn.id.clone();
3618 f.params = self.compiled.codegen_fn.params.clone();
3619 f.body = self.compiled.codegen_fn.body.clone();
3620 f.generator = self.compiled.codegen_fn.generator;
3621 f.is_async = self.compiled.codegen_fn.is_async;
3622 f.return_type = None;
3623 f.type_parameters = None;
3624 f.predicate = None;
3625 f.declare = None;
3626 return VisitResult::Stop;
3627 }
3628 Statement::ExportDefaultDeclaration(export) => {
3629 if let ExportDefaultDecl::FunctionDeclaration(f) = export.declaration.as_mut() {
3630 if f.base.node_id == Some(self.node_id) {
3631 f.id = self.compiled.codegen_fn.id.clone();
3632 f.params = self.compiled.codegen_fn.params.clone();
3633 f.body = self.compiled.codegen_fn.body.clone();
3634 f.generator = self.compiled.codegen_fn.generator;
3635 f.is_async = self.compiled.codegen_fn.is_async;
3636 f.return_type = None;
3637 f.type_parameters = None;
3638 f.predicate = None;
3639 f.declare = None;
3640 return VisitResult::Stop;
3641 }
3642 }
3643 }
3644 Statement::ExportNamedDeclaration(export) => {
3645 if let Some(ref mut decl) = export.declaration {
3646 if let Declaration::FunctionDeclaration(f) = decl.as_mut() {
3647 if f.base.node_id == Some(self.node_id) {
3648 f.id = self.compiled.codegen_fn.id.clone();
3649 f.params = self.compiled.codegen_fn.params.clone();
3650 f.body = self.compiled.codegen_fn.body.clone();
3651 f.generator = self.compiled.codegen_fn.generator;
3652 f.is_async = self.compiled.codegen_fn.is_async;
3653 f.return_type = None;
3654 f.type_parameters = None;
3655 f.predicate = None;
3656 f.declare = None;
3657 return VisitResult::Stop;
3658 }
3659 }
3660 }
3661 }
3662 _ => {}
3663 }
3664 VisitResult::Continue
3665 }
3666
3667 fn visit_expression(&mut self, expr: &mut Expression) -> VisitResult {
3668 match expr {
3669 Expression::FunctionExpression(f) if f.base.node_id == Some(self.node_id) => {
3670 f.id = self.compiled.codegen_fn.id.clone();
3671 f.params = self.compiled.codegen_fn.params.clone();
3672 f.body = self.compiled.codegen_fn.body.clone();
3673 f.generator = self.compiled.codegen_fn.generator;
3674 f.is_async = self.compiled.codegen_fn.is_async;
3675 f.return_type = None;
3676 f.type_parameters = None;
3677 VisitResult::Stop
3678 }
3679 Expression::ArrowFunctionExpression(f) if f.base.node_id == Some(self.node_id) => {
3680 f.params = self.compiled.codegen_fn.params.clone();
3681 f.body = Box::new(ArrowFunctionBody::BlockStatement(
3682 self.compiled.codegen_fn.body.clone(),
3683 ));
3684 f.generator = self.compiled.codegen_fn.generator;
3685 f.is_async = self.compiled.codegen_fn.is_async;
3686 f.expression = Some(false);
3687 f.return_type = None;
3688 f.type_parameters = None;
3689 f.predicate = None;
3690 VisitResult::Stop
3691 }
3692 _ => VisitResult::Continue,
3693 }
3694 }
3695}
3696
3697struct RenameIdentifierVisitor<'a> {
3699 old_name: &'a str,
3700 new_name: &'a str,
3701}
3702
3703impl MutVisitor for RenameIdentifierVisitor<'_> {
3704 fn visit_identifier(&mut self, node: &mut Identifier) -> VisitResult {
3705 if node.name == self.old_name {
3706 node.name = self.new_name.to_string();
3707 }
3708 VisitResult::Continue
3709 }
3710}
3711
3712pub fn compile_program(mut file: File, scope: ScopeInfo, options: PluginOptions) -> CompileResult {
3726 let output_mode = CompilerOutputMode::from_opts(&options);
3728
3729 let early_events: Vec<LoggerEvent> = Vec::new();
3731 let mut early_ordered_log: Vec<OrderedLogItem> = Vec::new();
3732
3733 if options.debug {
3735 early_ordered_log.push(OrderedLogItem::Debug {
3736 entry: DebugLogEntry::new(
3737 "EnvironmentConfig",
3738 serde_json::to_string_pretty(&options.environment).unwrap_or_default(),
3739 ),
3740 });
3741 }
3742
3743 if !options.should_compile {
3745 return CompileResult::Success {
3746 ast: None,
3747 events: early_events,
3748 ordered_log: early_ordered_log,
3749 renames: Vec::new(),
3750 timing: Vec::new(),
3751 };
3752 }
3753
3754 let program = &file.program;
3755
3756 if should_skip_compilation(program, &options) {
3758 return CompileResult::Success {
3759 ast: None,
3760 events: early_events,
3761 ordered_log: early_ordered_log,
3762 renames: Vec::new(),
3763 timing: Vec::new(),
3764 };
3765 }
3766
3767 let restricted_imports = options.environment.validate_blocklisted_imports.clone();
3769
3770 let validate_exhaustive = options
3772 .environment
3773 .validate_exhaustive_memoization_dependencies;
3774 let validate_hooks = options.environment.validate_hooks_usage;
3775
3776 let eslint_rules: Option<Vec<String>> = if validate_exhaustive && validate_hooks {
3777 None
3779 } else {
3780 Some(options.eslint_suppression_rules.clone().unwrap_or_else(|| {
3781 DEFAULT_ESLINT_SUPPRESSIONS
3782 .iter()
3783 .map(|s| s.to_string())
3784 .collect()
3785 }))
3786 };
3787
3788 let suppressions = find_program_suppressions(
3790 &file.comments,
3791 eslint_rules.as_deref(),
3792 options.flow_suppressions,
3793 );
3794
3795 let has_module_scope_opt_out =
3797 find_directive_disabling_memoization(&program.directives, &options).is_some();
3798
3799 let mut context = ProgramContext::new(
3801 options.clone(),
3802 options.filename.clone(),
3803 options.source_code.clone(),
3805 suppressions,
3806 has_module_scope_opt_out,
3807 );
3808
3809 let source_filename = program
3813 .base
3814 .loc
3815 .as_ref()
3816 .and_then(|loc| loc.filename.clone())
3817 .or_else(|| {
3818 program.body.first().and_then(|stmt| {
3820 let base = match stmt {
3821 react_compiler_ast::statements::Statement::ExpressionStatement(s) => &s.base,
3822 react_compiler_ast::statements::Statement::VariableDeclaration(s) => &s.base,
3823 react_compiler_ast::statements::Statement::FunctionDeclaration(s) => &s.base,
3824 _ => return None,
3825 };
3826 base.loc.as_ref().and_then(|loc| loc.filename.clone())
3827 })
3828 });
3829 context.set_source_filename(source_filename);
3830
3831 context.init_from_scope(&scope);
3833
3834 context.ordered_log.extend(early_ordered_log);
3836
3837 if let Some(err) = validate_restricted_imports(program, &restricted_imports) {
3839 if let Some(result) = handle_error(&err, None, &mut context) {
3840 return result;
3841 }
3842 return CompileResult::Success {
3843 ast: None,
3844 events: context.events,
3845 ordered_log: context.ordered_log,
3846 renames: convert_renames(&context.renames),
3847 timing: Vec::new(),
3848 };
3849 }
3850
3851 let instrument_fn_name: Option<String>;
3854 let instrument_gating_name: Option<String>;
3855 let hook_guard_name: Option<String>;
3856
3857 if let Some(ref instrument_config) = options.environment.enable_emit_instrument_forget {
3858 let fn_spec = context.add_import_specifier(
3859 &instrument_config.fn_.source,
3860 &instrument_config.fn_.import_specifier_name,
3861 None,
3862 );
3863 instrument_fn_name = Some(fn_spec.name.clone());
3864 instrument_gating_name = instrument_config.gating.as_ref().map(|g| {
3865 let spec = context.add_import_specifier(&g.source, &g.import_specifier_name, None);
3866 spec.name.clone()
3867 });
3868 } else {
3869 instrument_fn_name = None;
3870 instrument_gating_name = None;
3871 }
3872
3873 if let Some(ref hook_guard_config) = options.environment.enable_emit_hook_guards {
3874 let spec = context.add_import_specifier(
3875 &hook_guard_config.source,
3876 &hook_guard_config.import_specifier_name,
3877 None,
3878 );
3879 hook_guard_name = Some(spec.name.clone());
3880 } else {
3881 hook_guard_name = None;
3882 }
3883
3884 context.instrument_fn_name = instrument_fn_name;
3886 context.instrument_gating_name = instrument_gating_name;
3887 context.hook_guard_name = hook_guard_name;
3888
3889 let queue = find_functions_to_compile(program, &options, &mut context, &scope);
3891
3892 let env_config = options.environment.clone();
3895
3896 let mut compiled_fns: Vec<CompiledFunction<'_>> = Vec::new();
3898
3899 for source in &queue {
3900 match process_fn(source, &scope, output_mode, &env_config, &mut context) {
3901 Ok(Some(codegen_fn)) => {
3902 compiled_fns.push(CompiledFunction {
3903 kind: source.kind,
3904 source,
3905 codegen_fn,
3906 });
3907 }
3908 Ok(None) => {
3909 }
3911 Err(fatal_result) => {
3912 return fatal_result;
3913 }
3914 }
3915 }
3916
3917 for compiled in &compiled_fns {
3922 for outlined in &compiled.codegen_fn.outlined {
3923 if outlined.fn_type.is_some() {
3924 context.log_event(LoggerEvent::CompileSuccess {
3925 fn_loc: None,
3926 fn_name: outlined.func.id.as_ref().map(|id| id.name.clone()),
3927 memo_slots: outlined.func.memo_slots_used,
3928 memo_blocks: outlined.func.memo_blocks,
3929 memo_values: outlined.func.memo_values,
3930 pruned_memo_blocks: outlined.func.pruned_memo_blocks,
3931 pruned_memo_values: outlined.func.pruned_memo_values,
3932 });
3933 }
3934 }
3935 }
3936
3937 if has_module_scope_opt_out {
3939 if !compiled_fns.is_empty() {
3940 let mut err = CompilerError::new();
3941 err.push_error_detail(CompilerErrorDetail::new(
3942 ErrorCategory::Invariant,
3943 "Unexpected compiled functions when module scope opt-out is present",
3944 ));
3945 handle_error(&err, None, &mut context);
3946 }
3947 return CompileResult::Success {
3948 ast: None,
3949 events: context.events,
3950 ordered_log: context.ordered_log,
3951 renames: convert_renames(&context.renames),
3952 timing: Vec::new(),
3953 };
3954 }
3955
3956 let function_gating_config = options.gating.clone();
3960
3961 let replacements: Vec<CompiledFnForReplacement> = compiled_fns
3964 .into_iter()
3965 .map(|cf| {
3966 let original_kind = match cf.source.fn_node {
3967 FunctionNode::FunctionDeclaration(_) => OriginalFnKind::FunctionDeclaration,
3968 FunctionNode::FunctionExpression(_) => OriginalFnKind::FunctionExpression,
3969 FunctionNode::ArrowFunctionExpression(_) => OriginalFnKind::ArrowFunctionExpression,
3970 };
3971 let gating = if cf.kind == CompileSourceKind::Original {
3974 let dynamic_gating =
3976 find_directives_dynamic_gating(&cf.source.body_directives, &options)
3977 .ok()
3978 .flatten()
3979 .map(|r| r.gating);
3980 dynamic_gating.or_else(|| function_gating_config.clone())
3981 } else {
3982 None
3983 };
3984 CompiledFnForReplacement {
3985 fn_start: cf.source.fn_start,
3986 fn_node_id: cf.source.fn_node_id,
3987 original_kind,
3988 codegen_fn: cf.codegen_fn,
3989 source_kind: cf.kind,
3990 fn_name: cf.source.fn_name.clone(),
3991 gating,
3992 }
3993 })
3994 .collect();
3995 drop(queue);
3997
3998 if replacements.is_empty() {
3999 return CompileResult::Success {
4004 ast: None,
4005 events: context.events,
4006 ordered_log: context.ordered_log,
4007 renames: convert_renames(&context.renames),
4008 timing: Vec::new(),
4009 };
4010 }
4011
4012 apply_compiled_functions(&replacements, &mut file.program, &mut context);
4014
4015 let timing_entries = context.timing.into_entries();
4016
4017 CompileResult::Success {
4020 ast: Some(file),
4021 events: context.events,
4022 ordered_log: context.ordered_log,
4023 renames: convert_renames(&context.renames),
4024 timing: timing_entries,
4025 }
4026}
4027
4028fn convert_renames(
4030 renames: &[react_compiler_hir::environment::BindingRename],
4031) -> Vec<BindingRenameInfo> {
4032 renames
4033 .iter()
4034 .map(|r| BindingRenameInfo {
4035 original: r.original.clone(),
4036 renamed: r.renamed.clone(),
4037 declaration_start: r.declaration_start,
4038 })
4039 .collect()
4040}
4041
4042#[cfg(test)]
4043mod tests {
4044 use super::*;
4045
4046 #[test]
4047 fn test_is_hook_name() {
4048 assert!(is_hook_name("useState"));
4049 assert!(is_hook_name("useEffect"));
4050 assert!(is_hook_name("use0Something"));
4051 assert!(!is_hook_name("use"));
4052 assert!(!is_hook_name("useless")); assert!(!is_hook_name("foo"));
4054 assert!(!is_hook_name(""));
4055 }
4056
4057 #[test]
4058 fn test_is_component_name() {
4059 assert!(is_component_name("MyComponent"));
4060 assert!(is_component_name("App"));
4061 assert!(!is_component_name("myComponent"));
4062 assert!(!is_component_name("app"));
4063 assert!(!is_component_name(""));
4064 }
4065
4066 #[test]
4067 fn test_is_valid_identifier() {
4068 assert!(is_valid_identifier("foo"));
4069 assert!(is_valid_identifier("_bar"));
4070 assert!(is_valid_identifier("$baz"));
4071 assert!(is_valid_identifier("foo123"));
4072 assert!(!is_valid_identifier(""));
4073 assert!(!is_valid_identifier("123foo"));
4074 assert!(!is_valid_identifier("foo bar"));
4075 }
4076
4077 #[test]
4078 fn test_is_valid_component_params_empty() {
4079 assert!(is_valid_component_params(&[]));
4080 }
4081
4082 #[test]
4083 fn test_is_valid_component_params_one_identifier() {
4084 let params = vec![PatternLike::Identifier(Identifier {
4085 base: BaseNode::default(),
4086 name: "props".to_string(),
4087 type_annotation: None,
4088 optional: None,
4089 decorators: None,
4090 })];
4091 assert!(is_valid_component_params(¶ms));
4092 }
4093
4094 #[test]
4095 fn test_is_valid_component_params_too_many() {
4096 let params = vec![
4097 PatternLike::Identifier(Identifier {
4098 base: BaseNode::default(),
4099 name: "a".to_string(),
4100 type_annotation: None,
4101 optional: None,
4102 decorators: None,
4103 }),
4104 PatternLike::Identifier(Identifier {
4105 base: BaseNode::default(),
4106 name: "b".to_string(),
4107 type_annotation: None,
4108 optional: None,
4109 decorators: None,
4110 }),
4111 PatternLike::Identifier(Identifier {
4112 base: BaseNode::default(),
4113 name: "c".to_string(),
4114 type_annotation: None,
4115 optional: None,
4116 decorators: None,
4117 }),
4118 ];
4119 assert!(!is_valid_component_params(¶ms));
4120 }
4121
4122 #[test]
4123 fn test_is_valid_component_params_with_ref() {
4124 let params = vec![
4125 PatternLike::Identifier(Identifier {
4126 base: BaseNode::default(),
4127 name: "props".to_string(),
4128 type_annotation: None,
4129 optional: None,
4130 decorators: None,
4131 }),
4132 PatternLike::Identifier(Identifier {
4133 base: BaseNode::default(),
4134 name: "ref".to_string(),
4135 type_annotation: None,
4136 optional: None,
4137 decorators: None,
4138 }),
4139 ];
4140 assert!(is_valid_component_params(¶ms));
4141 }
4142
4143 #[test]
4144 fn test_should_skip_compilation_no_import() {
4145 let program = Program {
4146 base: BaseNode::default(),
4147 body: vec![],
4148 directives: vec![],
4149 source_type: react_compiler_ast::SourceType::Module,
4150 interpreter: None,
4151 source_file: None,
4152 };
4153 let options = PluginOptions {
4154 should_compile: true,
4155 enable_reanimated: false,
4156 is_dev: false,
4157 filename: None,
4158 compilation_mode: "infer".to_string(),
4159 panic_threshold: "none".to_string(),
4160 target: super::super::plugin_options::CompilerTarget::Version("19".to_string()),
4161 gating: None,
4162 dynamic_gating: None,
4163 no_emit: false,
4164 output_mode: None,
4165 eslint_suppression_rules: None,
4166 flow_suppressions: true,
4167 ignore_use_no_forget: false,
4168 custom_opt_out_directives: None,
4169 environment: EnvironmentConfig::default(),
4170 source_code: None,
4171 profiling: false,
4172 debug: false,
4173 };
4174 assert!(!should_skip_compilation(&program, &options));
4175 }
4176}