494
): Array<CompileSource> {
495
const queue: Array<CompileSource> = [];
496
const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => {
497
+ // In 'all' mode, compile only top level functions
498
+ if (
499
+ pass.opts.compilationMode === 'all' &&
500
+ fn.scope.getProgramParent() !== fn.scope.parent
501
+ ) {
502
+ return;
503
+ }
504
+
505
const fnType = getReactFunctionType(fn, pass);
506
+
507
+ if (pass.opts.environment.validateNoDynamicallyCreatedComponentsOrHooks) {
508
+ validateNoDynamicallyCreatedComponentsOrHooks(fn, pass, programContext);
509
+ }
510
+
511
if (fnType === null || programContext.alreadyCompiled.has(fn.node)) {
512
return;
513
}
852
return false;
853
}
854
855
+/**
856
+ * Validates that Components/Hooks are always defined at module level. This prevents scope reference
857
+ * errors that occur when the compiler attempts to optimize the nested component/hook while its
858
+ * parent function remains uncompiled.
859
+ */
860
+function validateNoDynamicallyCreatedComponentsOrHooks(
861
+ fn: BabelFn,
862
+ pass: CompilerPass,
863
+ programContext: ProgramContext,
864
+): void {
865
+ const parentNameExpr = getFunctionName(fn);
866
+ const parentName =
867
+ parentNameExpr !== null && parentNameExpr.isIdentifier()
868
+ ? parentNameExpr.node.name
869
+ : '<anonymous>';
870
+
871
+ const validateNestedFunction = (
872
+ nestedFn: NodePath<
873
+ t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression
874
+ >,
875
+ ): void => {
876
+ if (
877
+ nestedFn.node === fn.node ||
878
+ programContext.alreadyCompiled.has(nestedFn.node)
879
+ ) {
880
+ return;
881
+ }
882
+
883
+ if (nestedFn.scope.getProgramParent() !== nestedFn.scope.parent) {
884
+ const nestedFnType = getReactFunctionType(nestedFn as BabelFn, pass);
885
+ const nestedFnNameExpr = getFunctionName(nestedFn as BabelFn);
886
+ const nestedName =
887
+ nestedFnNameExpr !== null && nestedFnNameExpr.isIdentifier()
888
+ ? nestedFnNameExpr.node.name
889
+ : '<anonymous>';
890
+ if (nestedFnType === 'Component' || nestedFnType === 'Hook') {
891
+ CompilerError.throwDiagnostic({
892
+ category: ErrorCategory.Factories,
893
+ severity: ErrorSeverity.InvalidReact,
894
+ reason: `Components and hooks cannot be created dynamically`,
895
+ 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`,
896
+ details: [
897
+ {
898
+ kind: 'error',
899
+ message: 'this function dynamically created a component/hook',
900
+ loc: parentNameExpr?.node.loc ?? fn.node.loc ?? null,
901
+ },
902
+ {
903
+ kind: 'error',
904
+ message: 'the component is created here',
905
+ loc: nestedFnNameExpr?.node.loc ?? nestedFn.node.loc ?? null,
906
+ },
907
+ ],
908
+ });
909
+ }
910
+ }
911
+
912
+ nestedFn.skip();
913
+ };
914
+
915
+ fn.traverse({
916
+ FunctionDeclaration: validateNestedFunction,
917
+ FunctionExpression: validateNestedFunction,
918
+ ArrowFunctionExpression: validateNestedFunction,
919
+ });
920
+}
921
+
922
function getReactFunctionType(
923
fn: BabelFn,
924
pass: CompilerPass,
957
return componentSyntaxType;
958
}
959
case 'all': {
880
- // Compile only top level functions
881
- if (fn.scope.getProgramParent() !== fn.scope.parent) {
882
- return null;
883
- }
884
-
960
return getComponentOrHookLike(fn, hookPattern) ?? 'Other';
961
}
962
default: {