diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index 0d7f2f4a4c..a4af9d1265 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -5,7 +5,9 @@ * LICENSE file in the root directory of this source tree. */ +// @ts-ignore import {NodePath} from '@babel/core'; +// @ts-ignore import * as t from '@babel/types'; import { CompilerError, @@ -265,7 +267,9 @@ export function createNewFunctionNode( break; } default: { + // @ts-ignore assertExhaustive( + // @ts-ignore originalFn.node, `Creating unhandled function: ${originalFn.node}`, ); @@ -321,7 +325,9 @@ function insertNewOutlinedFunctionNode( return insertedFuncDecl; } default: { + // @ts-ignore assertExhaustive( + // @ts-ignore originalFn, `Inserting unhandled function: ${originalFn}`, ); @@ -961,10 +967,13 @@ function getReactFunctionType( return componentSyntaxType; } case 'all': { + // @ts-ignore return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; } default: { + // @ts-ignore assertExhaustive( + // @ts-ignore pass.opts.compilationMode, `Unexpected compilationMode \`${pass.opts.compilationMode}\``, ); @@ -1125,6 +1134,7 @@ function isValidPropsAnnotation( } else if (annot.type === 'Noop') { return true; } else { + // @ts-ignore assertExhaustive(annot, `Unexpected annotation node \`${annot}\``); } } @@ -1398,6 +1408,23 @@ function getFunctionReferencedBeforeDeclarationAtTopLevel( */ if (scope === null && id.isReferencedIdentifier()) { referencedBeforeDeclaration.add(fn.fn); + } else if (scope !== null && id.isReferencedIdentifier()) { + // Check if this function call is within another function that's being compiled + // This handles the case where named functions call other named functions + // defined in a different order within the same component + const parentFunction = scope.getFunctionParent(); + if (parentFunction !== null) { + const parentFnName = getFunctionName(parentFunction); + if (parentFnName && parentFnName.isIdentifier()) { + const parentFn = fnNames.get(parentFnName.node.name); + if (parentFn) { + // This is a named function calling another named function + // Add the called function to referencedBeforeDeclaration to ensure + // proper ordering during compilation + referencedBeforeDeclaration.add(fn.fn); + } + } + } } }, }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/named-functions-calling-order.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/named-functions-calling-order.expect.md new file mode 100644 index 0000000000..c0d23eac3d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/named-functions-calling-order.expect.md @@ -0,0 +1,29 @@ +```js +// @enableNameAnonymousFunctions + +function Component(props) { + // This function calls another function that's defined later + function firstFunction() { + return secondFunction(); // This calls secondFunction before it's declared + } + + // This function is defined after firstFunction but is called by it + function secondFunction() { + return props.message || "Hello from second function"; + } + + return firstFunction(); +} + +export const TODO_FIXTURE_ENTRYPOINT = { + fn: Component, + params: [ + { + message: 'Hello from props', + thirdMessage: 'Hello from third props', + }, + ], +}; +``` + +Expected: The compiler should handle the function ordering correctly and not throw errors when named functions call other named functions defined later in the code. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/named-functions-calling-order.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/named-functions-calling-order.js new file mode 100644 index 0000000000..c7ffad0716 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/named-functions-calling-order.js @@ -0,0 +1,25 @@ +// @enableNameAnonymousFunctions + +function Component(props) { + // This function calls another function that's defined later + function firstFunction() { + return secondFunction(); // This calls secondFunction before it's declared + } + + // This function is defined after firstFunction but is called by it + function secondFunction() { + return props.message || "Hello from second function"; + } + + return firstFunction(); +} + +export const TODO_FIXTURE_ENTRYPOINT = { + fn: Component, + params: [ + { + message: 'Hello from props', + thirdMessage: 'Hello from third props', + }, + ], +}; diff --git a/flow-typed/npm/minimist_v1.x.x.js b/flow-typed/npm/minimist_v1.x.x.js index 9da29ffe60..30c3dcdde4 100644 --- a/flow-typed/npm/minimist_v1.x.x.js +++ b/flow-typed/npm/minimist_v1.x.x.js @@ -6,7 +6,7 @@ declare module 'minimist' { string?: string | Array, boolean?: boolean | string | Array, alias?: {[arg: string]: string | Array, ...}, - default?: {[arg: string]: any, ...}, + default?: {[arg: string]: any, }, stopEarly?: boolean, // TODO: Strings as keys don't work... // '--'? boolean,