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 2a6408a4fad..7d6a5542666 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -13,6 +13,7 @@ import { ErrorSeverity, } from '../CompilerError'; import { + EnvironmentConfig, ExternalFunction, ReactFunctionType, parseEnvironmentConfig, @@ -276,41 +277,27 @@ export function compileProgram( program: NodePath, pass: CompilerPass, ): void { - if (pass.opts.sources) { - if (pass.filename === null) { - const error = new CompilerError(); - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: `Expected a filename but found none.`, - description: - "When the 'sources' config options is specified, the React compiler will only compile files with a name", - severity: ErrorSeverity.InvalidConfig, - loc: null, - }), - ); - handleError(error, pass, null); - return; - } - - if (!isFilePartOfSources(pass.opts.sources, pass.filename)) { - return; - } - } - - // Top level "use no forget", skip this file entirely - if ( - findDirectiveDisablingMemoization(program.node.directives, pass.opts) != - null - ) { + if (shouldSkipCompilation(program, pass)) { return; } - const environment = parseEnvironmentConfig(pass.opts.environment ?? {}); + /* + * TODO(lauren): Remove pass.opts.environment nullcheck once PluginOptions + * is validated + */ + const environmentResult = parseEnvironmentConfig(pass.opts.environment ?? {}); + if (environmentResult.isErr()) { + CompilerError.throwInvalidConfig({ + reason: + 'Error in validating environment config. This is an advanced setting and not meant to be used directly', + description: environmentResult.unwrapErr().toString(), + suggestions: null, + loc: null, + }); + } + const environment = environmentResult.unwrap(); const useMemoCacheIdentifier = program.scope.generateUidIdentifier('c'); const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime'; - if (hasMemoCacheFunctionImport(program, moduleName)) { - return; - } /* * Record lint errors and critical errors as depending on Forget's config, @@ -332,7 +319,7 @@ export function compileProgram( const compiledFns: Array = []; const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => { - const fnType = getReactFunctionType(fn, pass); + const fnType = getReactFunctionType(fn, pass, environment); if (fnType === null || ALREADY_COMPILED.has(fn.node)) { return; } @@ -403,24 +390,9 @@ export function compileProgram( let compiledFn: CodegenFunction; try { - /* - * TODO(lauren): Remove pass.opts.environment nullcheck once PluginOptions - * is validated - */ - if (environment.isErr()) { - CompilerError.throwInvalidConfig({ - reason: - 'Error in validating environment config. This is an advanced setting and not meant to be used directly', - description: environment.unwrapErr().toString(), - suggestions: null, - loc: null, - }); - } - const config = environment.unwrap(); - compiledFn = compileFn( fn, - config, + environment, fnType, useMemoCacheIdentifier.name, pass.opts.logger, @@ -514,43 +486,29 @@ export function compileProgram( externalFunctions.push(gating); } - const lowerContextAccess = pass.opts.environment?.lowerContextAccess; + const lowerContextAccess = environment.lowerContextAccess; if (lowerContextAccess && hasLoweredContextAccess) { - externalFunctions.push(tryParseExternalFunction(lowerContextAccess)); + externalFunctions.push(lowerContextAccess); } - const enableEmitInstrumentForget = - pass.opts.environment?.enableEmitInstrumentForget; + const enableEmitInstrumentForget = environment.enableEmitInstrumentForget; if (enableEmitInstrumentForget != null) { - externalFunctions.push( - tryParseExternalFunction(enableEmitInstrumentForget.fn), - ); + externalFunctions.push(enableEmitInstrumentForget.fn); if (enableEmitInstrumentForget.gating != null) { - externalFunctions.push( - tryParseExternalFunction(enableEmitInstrumentForget.gating), - ); + externalFunctions.push(enableEmitInstrumentForget.gating); } } - if (pass.opts.environment?.enableEmitFreeze != null) { - const enableEmitFreeze = tryParseExternalFunction( - pass.opts.environment.enableEmitFreeze, - ); - externalFunctions.push(enableEmitFreeze); + if (environment.enableEmitFreeze != null) { + externalFunctions.push(environment.enableEmitFreeze); } - if (pass.opts.environment?.enableEmitHookGuards != null) { - const enableEmitHookGuards = tryParseExternalFunction( - pass.opts.environment.enableEmitHookGuards, - ); - externalFunctions.push(enableEmitHookGuards); + if (environment.enableEmitHookGuards != null) { + externalFunctions.push(environment.enableEmitHookGuards); } - if (pass.opts.environment?.enableChangeDetectionForDebugging != null) { - const enableChangeDetectionForDebugging = tryParseExternalFunction( - pass.opts.environment.enableChangeDetectionForDebugging, - ); - externalFunctions.push(enableChangeDetectionForDebugging); + if (environment.enableChangeDetectionForDebugging != null) { + externalFunctions.push(environment.enableChangeDetectionForDebugging); } } catch (err) { handleError(err, pass, null); @@ -593,11 +551,65 @@ export function compileProgram( } } +function shouldSkipCompilation( + program: NodePath, + pass: CompilerPass, +): boolean { + if (pass.opts.sources) { + if (pass.filename === null) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: `Expected a filename but found none.`, + description: + "When the 'sources' config options is specified, the React compiler will only compile files with a name", + severity: ErrorSeverity.InvalidConfig, + loc: null, + }), + ); + handleError(error, pass, null); + return true; + } + + if (!isFilePartOfSources(pass.opts.sources, pass.filename)) { + return true; + } + } + + // Top level "use no forget", skip this file entirely + const useNoForget = findDirectiveDisablingMemoization( + program.node.directives, + pass.opts, + ); + if (useNoForget != null) { + pass.opts.logger?.logEvent(pass.filename, { + kind: 'CompileError', + fnLoc: null, + detail: { + severity: ErrorSeverity.Todo, + reason: 'Skipped due to "use no forget" directive.', + loc: useNoForget.loc ?? null, + suggestions: null, + }, + }); + return true; + } + const moduleName = pass.opts.runtimeModule ?? 'react/compiler-runtime'; + if (hasMemoCacheFunctionImport(program, moduleName)) { + return true; + } + return false; +} + function getReactFunctionType( fn: BabelFn, pass: CompilerPass, + /** + * TODO(mofeiZ): remove once we validate PluginOptions with Zod + */ + environment: EnvironmentConfig, ): ReactFunctionType | null { - const hookPattern = pass.opts.environment?.hookPattern ?? null; + const hookPattern = environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { // Opt-outs disable compilation regardless of mode const useNoForget = findDirectiveDisablingMemoization(