Repository: facebook/react
Pull request: [compiler] Patch error reporting for blocklisted imports

Stack from [ghstack](https://github.com/ezyang/ghstack) (oldest at bottom):
* __->__ #30652

---
Call `handleError` to log (and throw, depending on `panicThreshold`) instead of always throwing an exception. Discovered when [syncing internally](https://fburl.com/diff/2lu1zuhp) and failing the babel build

Also added a comment to `compileProgram` for future reference (pasted below)
`compileProgram` is directly invoked by the react-compiler babel plugin, so exceptions thrown by this function will fail the babel build.
- call `handleError` if your error is recoverable.
  Unless the error is a warning / info diagnostic, compilation of a function / entire file should also be skipped.
- throw an exception if the error is fatal / not recoverable.
  Examples of this are invalid compiler configs or failure to codegen outlined functions *after* already emitting optimized components / hooks that invoke the outlined functions.

---
It looks like this wasn't caught in #30643 because we run snap tests with `panicThreshold: 'all_errors'`. Thoughts on switching to `panicThreshold: 'none'` and explicitly labeling fatal errors vs compilation skipped?
  

Changed files:
filename: compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts
patch: @@ -7,32 +7,39 @@
 
 import {NodePath} from '@babel/core';
 import * as t from '@babel/types';
-import {CompilerError} from '../CompilerError';
+import {CompilerError, ErrorSeverity} from '../CompilerError';
 import {EnvironmentConfig, ExternalFunction, GeneratedSource} from '../HIR';
 import {getOrInsertDefault} from '../Utils/utils';
 
 export function validateRestrictedImports(
   path: NodePath<t.Program>,
   {validateBlocklistedImports}: EnvironmentConfig,
-): void {
+): CompilerError | null {
   if (
     validateBlocklistedImports == null ||
     validateBlocklistedImports.length === 0
   ) {
-    return;
+    return null;
   }
+  const error = new CompilerError();
   const restrictedImports = new Set(validateBlocklistedImports);
   path.traverse({
     ImportDeclaration(importDeclPath) {
       if (restrictedImports.has(importDeclPath.node.source.value)) {
-        CompilerError.throwTodo({
+        error.push({
+          severity: ErrorSeverity.Todo,
           reason: 'Bailing out due to blocklisted import',
           description: `Import from module ${importDeclPath.node.source.value}`,
           loc: importDeclPath.node.loc ?? null,
         });
       }
     },
   });
+  if (error.hasErrors()) {
+    return error;
+  } else {
+    return null;
+  }
 }
 
 export function addImportsToProgram(

filename: compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts
patch: @@ -277,6 +277,17 @@ function isFilePartOfSources(
   return false;
 }
 
+/**
+ * `compileProgram` is directly invoked by the react-compiler babel plugin, so
+ * exceptions thrown by this function will fail the babel build.
+ * - call `handleError` if your error is recoverable.
+ *   Unless the error is a warning / info diagnostic, compilation of a function
+ *   / entire file should also be skipped.
+ * - throw an exception if the error is fatal / not recoverable.
+ *   Examples of this are invalid compiler configs or failure to codegen outlined
+ *   functions *after* already emitting optimized components / hooks that invoke
+ *   the outlined functions.
+ */
 export function compileProgram(
   program: NodePath<t.Program>,
   pass: CompilerPass,
@@ -300,7 +311,11 @@ export function compileProgram(
     });
   }
   const environment = environmentResult.unwrap();
-  validateRestrictedImports(program, environment);
+  const restrictedImportsErr = validateRestrictedImports(program, environment);
+  if (restrictedImportsErr) {
+    handleError(restrictedImportsErr, pass, null);
+    return;
+  }
   const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c');
   const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime';