Repository: facebook/react
File: compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts (TypeScript)

Code before:
      t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression
    >,
  ): void => {
    if (
      nestedFn.node === fn.node ||
      programContext.alreadyCompiled.has(nestedFn.node)
    ) {
      return;
    }

    const nestedFnType = getReactFunctionType(nestedFn as BabelFn, pass);
    if (nestedFnType === 'Component' || nestedFnType === 'Hook') {
      const nestedFnNameExpr = getFunctionName(nestedFn as BabelFn);
      const nestedName =
        nestedFnNameExpr !== null && nestedFnNameExpr.isIdentifier()
          ? nestedFnNameExpr.node.name
          : '<anonymous>';

      CompilerError.throwInvalidReact({
        category: ErrorCategory.Factories,
        reason: `Cannot compile nested ${nestedFnType.toLowerCase()} inside a non-React function`,
        description:
          `The function \`${nestedName}\` appears to be a React ${nestedFnType.toLowerCase()}, ` +
          `but it's defined inside \`${parentName}\`, which is not a React component or hook. ` +
          `The compiler cannot optimize nested React functions when the parent function is not compiled, ` +
          `as this leads to scope reference errors. Either move \`${nestedName}\` to the module level, ` +
          `or ensure the parent function follows React naming conventions (PascalCase for components, ` +
          `"use" prefix for hooks)`,
        loc: nestedFn.node.loc ?? null,
      });
    }

    nestedFn.skip();
  };

  fn.traverse({
    FunctionDeclaration: validateNestedFunction,
    FunctionExpression: validateNestedFunction,
    ArrowFunctionExpression: validateNestedFunction,
  });
}

function getReactFunctionType(
  fn: BabelFn,
  pass: CompilerPass,
): ReactFunctionType | null {
  const hookPattern = pass.opts.environment.hookPattern;
  if (fn.node.body.type === 'BlockStatement') {
    const optInDirectives = tryFindDirectiveEnablingMemoization(
      fn.node.body.directives,
      pass.opts,

Review comment (suggestion):
Related, this describes compiler implementation details that aren't relevant to the user. If we think we should support factories then we can make this a todo, but otherwise let's focus on why the pattern is bad.

Code after:
      return;
    }

    if (nestedFn.scope.getProgramParent() !== nestedFn.scope.parent) {
      const nestedFnType = getReactFunctionType(nestedFn as BabelFn, pass);
      const nestedFnNameExpr = getFunctionName(nestedFn as BabelFn);
      const nestedName =
        nestedFnNameExpr !== null && nestedFnNameExpr.isIdentifier()
          ? nestedFnNameExpr.node.name
          : '<anonymous>';
      if (nestedFnType === 'Component' || nestedFnType === 'Hook') {
        CompilerError.throwDiagnostic({
          category: ErrorCategory.Factories,
          severity: ErrorSeverity.InvalidReact,
          reason: `Components and hooks cannot be created dynamically`,
          description: `The function \`${nestedName}\` appears to be a React ${nestedFnType.toLowerCase()}, but it's defined inside \`${parentName}\`. Components and Hooks should always be declared at module scope`,
          details: [
            {
              kind: 'error',
              message: 'this function dynamically created a component/hook',
              loc: parentNameExpr?.node.loc ?? fn.node.loc ?? null,
            },
            {
              kind: 'error',
              message: 'the component is created here',
              loc: nestedFnNameExpr?.node.loc ?? nestedFn.node.loc ?? null,
            },
          ],
        });
      }
    }

    nestedFn.skip();
  };

  fn.traverse({
    FunctionDeclaration: validateNestedFunction,
    FunctionExpression: validateNestedFunction,
    ArrowFunctionExpression: validateNestedFunction,
  });
}

function getReactFunctionType(
  fn: BabelFn,
  pass: CompilerPass,
): ReactFunctionType | null {
  const hookPattern = pass.opts.environment.hookPattern;
  if (fn.node.body.type === 'BlockStatement') {
    const optInDirectives = tryFindDirectiveEnablingMemoization(
      fn.node.body.directives,
      pass.opts,