diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt index 5633cf0b0f..0f35215e86 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt @@ -1,5 +1,5 @@ import { c as _c } from "react/compiler-runtime"; //  -        @compilationMode(all) +        @compilationMode:"all" function nonReactFn() {   const $ = _c(1);   let t0; diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt index b50c37fc4e..563a566a06 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-infer-output.txt @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function nonReactFn() {   return {}; } \ No newline at end of file diff --git a/compiler/apps/playground/__tests__/e2e/page.spec.ts b/compiler/apps/playground/__tests__/e2e/page.spec.ts index 48bb0eed4e..296f45a277 100644 --- a/compiler/apps/playground/__tests__/e2e/page.spec.ts +++ b/compiler/apps/playground/__tests__/e2e/page.spec.ts @@ -92,7 +92,7 @@ function useFoo(propVal: {+baz: number}) { }, { name: 'compilationMode-infer', - input: `// @compilationMode(infer) + input: `// @compilationMode:"infer" function nonReactFn() { return {}; } @@ -101,7 +101,7 @@ function nonReactFn() { }, { name: 'compilationMode-all', - input: `// @compilationMode(all) + input: `// @compilationMode:"all" function nonReactFn() { return {}; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts index 704f696b3b..24ce37cf72 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Imports.ts @@ -18,8 +18,9 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {ExternalFunction, isHookName} from '../HIR/Environment'; import {Err, Ok, Result} from '../Utils/Result'; -import {CompilerReactTarget} from './Options'; -import {getReactCompilerRuntimeModule} from './Program'; +import {LoggerEvent, PluginOptions} from './Options'; +import {BabelFn, getReactCompilerRuntimeModule} from './Program'; +import {SuppressionRange} from './Suppression'; export function validateRestrictedImports( path: NodePath, @@ -52,32 +53,65 @@ export function validateRestrictedImports( } } +type ProgramContextOptions = { + program: NodePath; + suppressions: Array; + opts: PluginOptions; + filename: string | null; + code: string | null; + hasModuleScopeOptOut: boolean; +}; export class ProgramContext { - /* Program and environment context */ + /** + * Program and environment context + */ scope: BabelScope; + opts: PluginOptions; + filename: string | null; + code: string | null; reactRuntimeModule: string; - hookPattern: string | null; + suppressions: Array; + hasModuleScopeOptOut: boolean; + /* + * This is a hack to work around what seems to be a Babel bug. Babel doesn't + * consistently respect the `skip()` function to avoid revisiting a node within + * a pass, so we use this set to track nodes that we have compiled. + */ + alreadyCompiled: WeakSet | Set = new (WeakSet ?? Set)(); // known generated or referenced identifiers in the program knownReferencedNames: Set = new Set(); // generated imports imports: Map> = new Map(); - constructor( - program: NodePath, - reactRuntimeModule: CompilerReactTarget, - hookPattern: string | null, - ) { - this.hookPattern = hookPattern; + /** + * Metadata from compilation + */ + retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; + inferredEffectLocations: Set = new Set(); + + constructor({ + program, + suppressions, + opts, + filename, + code, + hasModuleScopeOptOut, + }: ProgramContextOptions) { this.scope = program.scope; - this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule); + this.opts = opts; + this.filename = filename; + this.code = code; + this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target); + this.suppressions = suppressions; + this.hasModuleScopeOptOut = hasModuleScopeOptOut; } isHookName(name: string): boolean { - if (this.hookPattern == null) { + if (this.opts.environment.hookPattern == null) { return isHookName(name); } else { - const match = new RegExp(this.hookPattern).exec(name); + const match = new RegExp(this.opts.environment.hookPattern).exec(name); return ( match != null && typeof match[1] === 'string' && isHookName(match[1]) ); @@ -179,6 +213,12 @@ export class ProgramContext { }); return Err(error); } + + logEvent(event: LoggerEvent): void { + if (this.opts.logger != null) { + this.opts.logger.logEvent(this.filename, event); + } + } } function getExistingImports( diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 0adbf3077c..c732e16410 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -98,7 +98,7 @@ export type PluginOptions = { * provided rules will skip compilation. To disable this feature (never bailout of compilation * even if the default ESLint is suppressed), pass an empty array. */ - eslintSuppressionRules?: Array | null | undefined; + eslintSuppressionRules: Array | null | undefined; flowSuppressions: boolean; /* @@ -106,7 +106,7 @@ export type PluginOptions = { */ ignoreUseNoForget: boolean; - sources?: Array | ((filename: string) => boolean) | null; + sources: Array | ((filename: string) => boolean) | null; /** * The compiler has customized support for react-native-reanimated, intended as a temporary workaround. 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 34347f10b8..64abc110ea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -12,7 +12,7 @@ import { CompilerErrorDetail, ErrorSeverity, } from '../CompilerError'; -import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment'; +import {ReactFunctionType} from '../HIR/Environment'; import {CodegenFunction} from '../ReactiveScopes'; import {isComponentDeclaration} from '../Utils/ComponentDeclaration'; import {isHookDeclaration} from '../Utils/HookDeclaration'; @@ -43,17 +43,21 @@ export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']); export function findDirectiveEnablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_IN_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_IN_DIRECTIVES.has(directive.value.value), + ) ?? null ); } export function findDirectiveDisablingMemoization( directives: Array, -): Array { - return directives.filter(directive => - OPT_OUT_DIRECTIVES.has(directive.value.value), +): t.Directive | null { + return ( + directives.find(directive => + OPT_OUT_DIRECTIVES.has(directive.value.value), + ) ?? null ); } @@ -88,13 +92,16 @@ export type CompileResult = { function logError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - if (pass.opts.logger) { + if (context.opts.logger) { if (err instanceof CompilerError) { for (const detail of err.details) { - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'CompileError', fnLoc, detail: detail.options, @@ -108,7 +115,7 @@ function logError( stringifiedError = err?.toString() ?? '[ null ]'; } - pass.opts.logger.logEvent(pass.filename, { + context.opts.logger.logEvent(context.filename, { kind: 'PipelineError', fnLoc, data: stringifiedError, @@ -118,13 +125,17 @@ function logError( } function handleError( err: unknown, - pass: CompilerPass, + context: { + opts: PluginOptions; + filename: string | null; + }, fnLoc: t.SourceLocation | null, ): void { - logError(err, pass, fnLoc); + logError(err, context, fnLoc); if ( - pass.opts.panicThreshold === 'all_errors' || - (pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) || + context.opts.panicThreshold === 'all_errors' || + (context.opts.panicThreshold === 'critical_errors' && + isCriticalError(err)) || isConfigError(err) // Always throws regardless of panic threshold ) { throw err; @@ -187,7 +198,6 @@ export function createNewFunctionNode( } } // Avoid visiting the new transformed version - ALREADY_COMPILED.add(transformedFn); return transformedFn; } @@ -239,13 +249,6 @@ function insertNewOutlinedFunctionNode( } } -/* - * This is a hack to work around what seems to be a Babel bug. Babel doesn't - * consistently respect the `skip()` function to avoid revisiting a node within - * a pass, so we use this set to track nodes that we have compiled. - */ -const ALREADY_COMPILED: WeakSet | Set = new (WeakSet ?? Set)(); - const DEFAULT_ESLINT_SUPPRESSIONS = [ 'react-hooks/exhaustive-deps', 'react-hooks/rules-of-hooks', @@ -268,41 +271,43 @@ function isFilePartOfSources( return false; } -export type CompileProgramResult = { +export type CompileProgramMetadata = { retryErrors: Array<{fn: BabelFn; error: CompilerError}>; inferredEffectLocations: Set; }; /** - * `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. + * Main entrypoint for React Compiler. + * + * @param program The Babel program node to compile + * @param pass Compiler configuration and context + * @returns Compilation results or null if compilation was skipped */ export function compileProgram( program: NodePath, pass: CompilerPass, -): CompileProgramResult | null { +): CompileProgramMetadata | null { + /** + * This 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. + */ if (shouldSkipCompilation(program, pass)) { return null; } - - const environment = pass.opts.environment; - const restrictedImportsErr = validateRestrictedImports(program, environment); + const restrictedImportsErr = validateRestrictedImports( + program, + pass.opts.environment, + ); if (restrictedImportsErr) { handleError(restrictedImportsErr, pass, null); return null; } - - const programContext = new ProgramContext( - program, - pass.opts.target, - environment.hookPattern, - ); /* * Record lint errors and critical errors as depending on Forget's config, * we may still need to run Forget's analysis on every function (even if we @@ -313,16 +318,102 @@ export function compileProgram( pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS, pass.opts.flowSuppressions, ); - const queue: Array<{ - kind: 'original' | 'outlined'; - fn: BabelFn; - fnType: ReactFunctionType; - }> = []; + + const programContext = new ProgramContext({ + program: program, + opts: pass.opts, + filename: pass.filename, + code: pass.code, + suppressions, + hasModuleScopeOptOut: + findDirectiveDisablingMemoization(program.node.directives) != null, + }); + + const queue: Array = findFunctionsToCompile( + program, + pass, + programContext, + ); const compiledFns: Array = []; + while (queue.length !== 0) { + const current = queue.shift()!; + const compiled = processFn(current.fn, current.fnType, programContext); + + if (compiled != null) { + for (const outlined of compiled.outlined) { + CompilerError.invariant(outlined.fn.outlined.length === 0, { + reason: 'Unexpected nested outlined functions', + loc: outlined.fn.loc, + }); + const fn = insertNewOutlinedFunctionNode( + program, + current.fn, + outlined.fn, + ); + fn.skip(); + programContext.alreadyCompiled.add(fn.node); + if (outlined.type !== null) { + queue.push({ + kind: 'outlined', + fn, + fnType: outlined.type, + }); + } + } + compiledFns.push({ + kind: current.kind, + originalFn: current.fn, + compiledFn: compiled, + }); + } + } + + // Avoid modifying the program if we find a program level opt-out + if (programContext.hasModuleScopeOptOut) { + if (compiledFns.length > 0) { + const error = new CompilerError(); + error.pushErrorDetail( + new CompilerErrorDetail({ + reason: + 'Unexpected compiled functions when module scope opt-out is present', + severity: ErrorSeverity.Invariant, + loc: null, + }), + ); + handleError(error, programContext, null); + } + return null; + } + + // Insert React Compiler generated functions into the Babel AST + applyCompiledFunctions(program, compiledFns, pass, programContext); + + return { + retryErrors: programContext.retryErrors, + inferredEffectLocations: programContext.inferredEffectLocations, + }; +} + +type CompileSource = { + kind: 'original' | 'outlined'; + fn: BabelFn; + fnType: ReactFunctionType; +}; +/** + * Find all React components and hooks that need to be compiled + * + * @returns An array of React functions from @param program to transform + */ +function findFunctionsToCompile( + program: NodePath, + pass: CompilerPass, + programContext: ProgramContext, +): Array { + const queue: Array = []; const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => { - const fnType = getReactFunctionType(fn, pass, environment); - if (fnType === null || ALREADY_COMPILED.has(fn.node)) { + const fnType = getReactFunctionType(fn, pass); + if (fnType === null || programContext.alreadyCompiled.has(fn.node)) { return; } @@ -331,7 +422,7 @@ export function compileProgram( * traversal will loop infinitely. * Ensure we avoid visiting the original function again. */ - ALREADY_COMPILED.add(fn.node); + programContext.alreadyCompiled.add(fn.node); fn.skip(); queue.push({kind: 'original', fn, fnType}); @@ -346,7 +437,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, ClassExpression(node: NodePath) { @@ -355,7 +445,6 @@ export function compileProgram( * can reference `this` which is unsafe for compilation */ node.skip(); - return; }, FunctionDeclaration: traverseFunction, @@ -370,205 +459,206 @@ export function compileProgram( filename: pass.filename ?? null, }, ); - const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = []; - const inferredEffectLocations = new Set(); - const processFn = ( - fn: BabelFn, - fnType: ReactFunctionType, - ): null | CodegenFunction => { - let optInDirectives: Array = []; - let optOutDirectives: Array = []; - if (fn.node.body.type === 'BlockStatement') { - optInDirectives = findDirectiveEnablingMemoization( - fn.node.body.directives, - ); - optOutDirectives = findDirectiveDisablingMemoization( - fn.node.body.directives, - ); - } + return queue; +} - /** - * Note that Babel does not attach comment nodes to nodes; they are dangling off of the - * Program node itself. We need to figure out whether an eslint suppression range - * applies to this function first. - */ - const suppressionsInFunction = filterSuppressionsThatAffectFunction( - suppressions, - fn, - ); - let compileResult: - | {kind: 'compile'; compiledFn: CodegenFunction} - | {kind: 'error'; error: unknown}; - if (suppressionsInFunction.length > 0) { - compileResult = { - kind: 'error', - error: suppressionsToCompilerError(suppressionsInFunction), - }; +/** + * Try to compile a source function, taking into account all local suppressions, + * opt-ins, and opt-outs. + * + * Errors encountered during compilation are either logged (if recoverable) or + * thrown (if non-recoverable). + * + * @returns the compiled function or null if the function was skipped (due to + * config settings and/or outputs) + */ +function processFn( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): null | CodegenFunction { + let directives; + if (fn.node.body.type !== 'BlockStatement') { + directives = {optIn: null, optOut: null}; + } else { + directives = { + optIn: findDirectiveEnablingMemoization(fn.node.body.directives), + optOut: findDirectiveDisablingMemoization(fn.node.body.directives), + }; + } + + let compiledFn: CodegenFunction; + const compileResult = tryCompileFunction(fn, fnType, programContext); + if (compileResult.kind === 'error') { + if (directives.optOut != null) { + logError(compileResult.error, programContext, fn.node.loc ?? null); } else { - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'all_features', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - } catch (err) { - compileResult = {kind: 'error', error: err}; - } + handleError(compileResult.error, programContext, fn.node.loc ?? null); } - - if (compileResult.kind === 'error') { - /** - * If an opt out directive is present, log only instead of throwing and don't mark as - * containing a critical error. - */ - if (optOutDirectives.length > 0) { - logError(compileResult.error, pass, fn.node.loc ?? null); - } else { - handleError(compileResult.error, pass, fn.node.loc ?? null); - } - // If non-memoization features are enabled, retry regardless of error kind - if ( - !(environment.enableFire || environment.inferEffectDependencies != null) - ) { - return null; - } - try { - compileResult = { - kind: 'compile', - compiledFn: compileFn( - fn, - environment, - fnType, - 'no_inferred_memo', - programContext, - pass.opts.logger, - pass.filename, - pass.code, - ), - }; - if ( - !compileResult.compiledFn.hasFireRewrite && - !compileResult.compiledFn.hasInferredEffect - ) { - return null; - } - } catch (err) { - // TODO: we might want to log error here, but this will also result in duplicate logging - if (err instanceof CompilerError) { - retryErrors.push({fn, error: err}); - } - return null; - } - } - - /** - * Otherwise if 'use no forget/memo' is present, we still run the code through the compiler - * for validation but we don't mutate the babel AST. This allows us to flag if there is an - * unused 'use no forget/memo' directive. - */ - if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) { - for (const directive of optOutDirectives) { - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSkip', - fnLoc: fn.node.body.loc ?? null, - reason: `Skipped due to '${directive.value.value}' directive.`, - loc: directive.loc ?? null, - }); - } + const retryResult = retryCompileFunction(fn, fnType, programContext); + if (retryResult == null) { return null; } + compiledFn = retryResult; + } else { + compiledFn = compileResult.compiledFn; + } - pass.opts.logger?.logEvent(pass.filename, { - kind: 'CompileSuccess', - fnLoc: fn.node.loc ?? null, - fnName: compileResult.compiledFn.id?.name ?? null, - memoSlots: compileResult.compiledFn.memoSlotsUsed, - memoBlocks: compileResult.compiledFn.memoBlocks, - memoValues: compileResult.compiledFn.memoValues, - prunedMemoBlocks: compileResult.compiledFn.prunedMemoBlocks, - prunedMemoValues: compileResult.compiledFn.prunedMemoValues, + /** + * If 'use no forget/memo' is present and we still ran the code through the + * compiler for validation, log a skip event and don't mutate the babel AST. + * This allows us to flag if there is an unused 'use no forget/memo' + * directive. + */ + if ( + programContext.opts.ignoreUseNoForget === false && + directives.optOut != null + ) { + programContext.logEvent({ + kind: 'CompileSkip', + fnLoc: fn.node.body.loc ?? null, + reason: `Skipped due to '${directives.optOut.value}' directive.`, + loc: directives.optOut.loc ?? null, }); + return null; + } + programContext.logEvent({ + kind: 'CompileSuccess', + fnLoc: fn.node.loc ?? null, + fnName: compiledFn.id?.name ?? null, + memoSlots: compiledFn.memoSlotsUsed, + memoBlocks: compiledFn.memoBlocks, + memoValues: compiledFn.memoValues, + prunedMemoBlocks: compiledFn.prunedMemoBlocks, + prunedMemoValues: compiledFn.prunedMemoValues, + }); - /** - * Always compile functions with opt in directives. - */ - if (optInDirectives.length > 0) { - return compileResult.compiledFn; - } else if (pass.opts.compilationMode === 'annotation') { - /** - * No opt-in directive in annotation mode, so don't insert the compiled function. - */ - return null; - } - - if (!pass.opts.noEmit) { - return compileResult.compiledFn; - } + if (programContext.hasModuleScopeOptOut) { + return null; + } else if (programContext.opts.noEmit) { /** * inferEffectDependencies + noEmit is currently only used for linting. In * this mode, add source locations for where the compiler *can* infer effect * dependencies. */ - for (const loc of compileResult.compiledFn.inferredEffectLocations) { - if (loc !== GeneratedSource) inferredEffectLocations.add(loc); - } - return null; - }; - - while (queue.length !== 0) { - const current = queue.shift()!; - const compiled = processFn(current.fn, current.fnType); - if (compiled === null) { - continue; - } - for (const outlined of compiled.outlined) { - CompilerError.invariant(outlined.fn.outlined.length === 0, { - reason: 'Unexpected nested outlined functions', - loc: outlined.fn.loc, - }); - const fn = insertNewOutlinedFunctionNode( - program, - current.fn, - outlined.fn, - ); - fn.skip(); - ALREADY_COMPILED.add(fn.node); - if (outlined.type !== null) { - queue.push({ - kind: 'outlined', - fn, - fnType: outlined.type, - }); + for (const loc of compiledFn.inferredEffectLocations) { + if (loc !== GeneratedSource) { + programContext.inferredEffectLocations.add(loc); } } - compiledFns.push({ - kind: current.kind, - compiledFn: compiled, - originalFn: current.fn, - }); + return null; + } else if ( + programContext.opts.compilationMode === 'annotation' && + directives.optIn == null + ) { + /** + * If no opt-in directive is found and the compiler is configured in + * annotation mode, don't insert the compiled function. + */ + return null; + } else { + return compiledFn; + } +} + +function tryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): + | {kind: 'compile'; compiledFn: CodegenFunction} + | {kind: 'error'; error: unknown} { + /** + * Note that Babel does not attach comment nodes to nodes; they are dangling off of the + * Program node itself. We need to figure out whether an eslint suppression range + * applies to this function first. + */ + const suppressionsInFunction = filterSuppressionsThatAffectFunction( + programContext.suppressions, + fn, + ); + if (suppressionsInFunction.length > 0) { + return { + kind: 'error', + error: suppressionsToCompilerError(suppressionsInFunction), + }; } - /** - * Do not modify source if there is a module scope level opt out directive. - */ - const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization( - program.node.directives, - ); - if (moduleScopeOptOutDirectives.length > 0) { + try { + return { + kind: 'compile', + compiledFn: compileFn( + fn, + programContext.opts.environment, + fnType, + 'all_features', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ), + }; + } catch (err) { + return {kind: 'error', error: err}; + } +} + +/** + * If non-memo feature flags are enabled, retry compilation with a more minimal + * feature set. + * + * @returns a CodegenFunction if retry was successful + */ +function retryCompileFunction( + fn: BabelFn, + fnType: ReactFunctionType, + programContext: ProgramContext, +): CodegenFunction | null { + const environment = programContext.opts.environment; + if ( + !(environment.enableFire || environment.inferEffectDependencies != null) + ) { return null; } - /* - * Only insert Forget-ified functions if we have not encountered a critical - * error elsewhere in the file, regardless of bailout mode. + /** + * Note that function suppressions are not checked in the retry pipeline, as + * they only affect auto-memoization features. */ + try { + const retryResult = compileFn( + fn, + environment, + fnType, + 'no_inferred_memo', + programContext, + programContext.opts.logger, + programContext.filename, + programContext.code, + ); + + if (!retryResult.hasFireRewrite && !retryResult.hasInferredEffect) { + return null; + } + return retryResult; + } catch (err) { + // TODO: we might want to log error here, but this will also result in duplicate logging + if (err instanceof CompilerError) { + programContext.retryErrors.push({fn, error: err}); + } + return null; + } +} + +/** + * Applies React Compiler generated functions to the babel AST by replacing + * existing functions in place or inserting new declarations. + */ +function applyCompiledFunctions( + program: NodePath, + compiledFns: Array, + pass: CompilerPass, + programContext: ProgramContext, +): void { const referencedBeforeDeclared = pass.opts.gating != null ? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns) @@ -576,6 +666,7 @@ export function compileProgram( for (const result of compiledFns) { const {kind, originalFn, compiledFn} = result; const transformedFn = createNewFunctionNode(originalFn, compiledFn); + programContext.alreadyCompiled.add(transformedFn); if (referencedBeforeDeclared != null && kind === 'original') { CompilerError.invariant(pass.opts.gating != null, { @@ -598,7 +689,6 @@ export function compileProgram( if (compiledFns.length > 0) { addImportsToProgram(program, programContext); } - return {retryErrors, inferredEffectLocations}; } function shouldSkipCompilation( @@ -640,14 +730,10 @@ function shouldSkipCompilation( function getReactFunctionType( fn: BabelFn, pass: CompilerPass, - /** - * TODO(mofeiZ): remove once we validate PluginOptions with Zod - */ - environment: EnvironmentConfig, ): ReactFunctionType | null { - const hookPattern = environment.hookPattern; + const hookPattern = pass.opts.environment.hookPattern; if (fn.node.body.type === 'BlockStatement') { - if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0) + if (findDirectiveEnablingMemoization(fn.node.body.directives) != null) return getComponentOrHookLike(fn, hookPattern) ?? 'Other'; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index 5f6c6986e0..e288c227ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -18,7 +18,7 @@ import { import {getOrInsertWith} from '../Utils/utils'; import {Environment} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; -import {CompileProgramResult} from './Program'; +import {CompileProgramMetadata} from './Program'; function throwInvalidReact( options: Omit, @@ -109,7 +109,7 @@ export default function validateNoUntransformedReferences( filename: string | null, logger: Logger | null, env: EnvironmentConfig, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const moduleLoadChecks = new Map< string, @@ -236,7 +236,7 @@ function transformProgram( moduleLoadChecks: Map>, filename: string | null, logger: Logger | null, - compileResult: CompileProgramResult | null, + compileResult: CompileProgramMetadata | null, ): void { const traversalState: TraversalState = { shouldInvalidateScopes: true, diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index a487b5086c..6e6643cd1d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -9,15 +9,7 @@ import * as t from '@babel/types'; import {ZodError, z} from 'zod'; import {fromZodError} from 'zod-validation-error'; import {CompilerError} from '../CompilerError'; -import { - CompilationMode, - defaultOptions, - Logger, - PanicThresholdOptions, - parsePluginOptions, - PluginOptions, - ProgramContext, -} from '../Entrypoint'; +import {Logger, ProgramContext} from '../Entrypoint'; import {Err, Ok, Result} from '../Utils/Result'; import { DEFAULT_GLOBALS, @@ -158,7 +150,7 @@ export type Hook = z.infer; * missing some recursive Object / Function shapeIds */ -const EnvironmentConfigSchema = z.object({ +export const EnvironmentConfigSchema = z.object({ customHooks: z.map(z.string(), HookSchema).default(new Map()), /** @@ -640,191 +632,6 @@ const EnvironmentConfigSchema = z.object({ export type EnvironmentConfig = z.infer; -/** - * For test fixtures and playground only. - * - * Pragmas are straightforward to parse for boolean options (`:true` and - * `:false`). These are 'enabled' config values for non-boolean configs (i.e. - * what is used when parsing `:true`). - */ -const testComplexConfigDefaults: PartialEnvironmentConfig = { - validateNoCapitalizedCalls: [], - enableChangeDetectionForDebugging: { - source: 'react-compiler-runtime', - importSpecifierName: '$structuralCheck', - }, - enableEmitFreeze: { - source: 'react-compiler-runtime', - importSpecifierName: 'makeReadOnly', - }, - enableEmitInstrumentForget: { - fn: { - source: 'react-compiler-runtime', - importSpecifierName: 'useRenderCounter', - }, - gating: { - source: 'react-compiler-runtime', - importSpecifierName: 'shouldInstrument', - }, - globalGating: 'DEV', - }, - enableEmitHookGuards: { - source: 'react-compiler-runtime', - importSpecifierName: '$dispatcherGuard', - }, - inlineJsxTransform: { - elementSymbol: 'react.transitional.element', - globalDevVar: 'DEV', - }, - lowerContextAccess: { - source: 'react-compiler-runtime', - importSpecifierName: 'useContext_withSelector', - }, - inferEffectDependencies: [ - { - function: { - source: 'react', - importSpecifierName: 'useEffect', - }, - numRequiredArgs: 1, - }, - { - function: { - source: 'shared-runtime', - importSpecifierName: 'useSpecialEffect', - }, - numRequiredArgs: 2, - }, - { - function: { - source: 'useEffectWrapper', - importSpecifierName: 'default', - }, - numRequiredArgs: 1, - }, - ], -}; - -/** - * For snap test fixtures and playground only. - */ -function parseConfigPragmaEnvironmentForTest( - pragma: string, -): EnvironmentConfig { - const maybeConfig: any = {}; - // Get the defaults to programmatically check for boolean properties - const defaultConfig = EnvironmentConfigSchema.parse({}); - - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - const keyVal = token.slice(1); - let [key, val = undefined] = keyVal.split(':'); - const isSet = val === undefined || val === 'true'; - - if (isSet && key in testComplexConfigDefaults) { - maybeConfig[key] = - testComplexConfigDefaults[key as keyof PartialEnvironmentConfig]; - continue; - } - - if (key === 'customMacros' && val) { - const valSplit = val.split('.'); - if (valSplit.length > 0) { - const props = []; - for (const elt of valSplit.slice(1)) { - if (elt === '*') { - props.push({type: 'wildcard'}); - } else if (elt.length > 0) { - props.push({type: 'name', name: elt}); - } - } - maybeConfig[key] = [[valSplit[0], props]]; - } - continue; - } - - if ( - key !== 'enableResetCacheOnSourceFileChanges' && - typeof defaultConfig[key as keyof EnvironmentConfig] !== 'boolean' - ) { - // skip parsing non-boolean properties - continue; - } - if (val === undefined || val === 'true') { - maybeConfig[key] = true; - } else { - maybeConfig[key] = false; - } - } - const config = EnvironmentConfigSchema.safeParse(maybeConfig); - if (config.success) { - /** - * Unless explicitly enabled, do not insert HMR handling code - * in test fixtures or playground to reduce visual noise. - */ - if (config.data.enableResetCacheOnSourceFileChanges == null) { - config.data.enableResetCacheOnSourceFileChanges = false; - } - return config.data; - } - CompilerError.invariant(false, { - reason: 'Internal error, could not parse config from pragma string', - description: `${fromZodError(config.error)}`, - loc: null, - suggestions: null, - }); -} -export function parseConfigPragmaForTests( - pragma: string, - defaults: { - compilationMode: CompilationMode; - }, -): PluginOptions { - const environment = parseConfigPragmaEnvironmentForTest(pragma); - let compilationMode: CompilationMode = defaults.compilationMode; - let panicThreshold: PanicThresholdOptions = 'all_errors'; - let noEmit: boolean = defaultOptions.noEmit; - for (const token of pragma.split(' ')) { - if (!token.startsWith('@')) { - continue; - } - switch (token) { - case '@compilationMode(annotation)': { - compilationMode = 'annotation'; - break; - } - case '@compilationMode(infer)': { - compilationMode = 'infer'; - break; - } - case '@compilationMode(all)': { - compilationMode = 'all'; - break; - } - case '@compilationMode(syntax)': { - compilationMode = 'syntax'; - break; - } - case '@panicThreshold(none)': { - panicThreshold = 'none'; - break; - } - case '@noEmit': { - noEmit = true; - break; - } - } - } - return parsePluginOptions({ - environment, - compilationMode, - panicThreshold, - noEmit, - }); -} - export type PartialEnvironmentConfig = Partial; export type ReactFunctionType = 'Component' | 'Hook' | 'Other'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts index 579c525dfb..bbc9b325d4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/index.ts @@ -17,7 +17,6 @@ export {buildReactiveScopeTerminalsHIR} from './BuildReactiveScopeTerminalsHIR'; export {computeDominatorTree, computePostDominatorTree} from './Dominator'; export { Environment, - parseConfigPragmaForTests, validateEnvironmentConfig, type EnvironmentConfig, type ExternalFunction, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts b/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts index 07cd419230..4ad86abbe7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Optimization/ConstantPropagation.ts @@ -327,6 +327,23 @@ function evaluateInstruction( } return null; } + case '-': { + const operand = read(constants, value.value); + if ( + operand !== null && + operand.kind === 'Primitive' && + typeof operand.value === 'number' + ) { + const result: Primitive = { + kind: 'Primitive', + value: operand.value * -1, + loc: value.loc, + }; + instr.value = result; + return result; + } + return null; + } default: return null; } @@ -492,6 +509,73 @@ function evaluateInstruction( } return null; } + case 'TemplateLiteral': { + if (value.subexprs.length === 0) { + const result: InstructionValue = { + kind: 'Primitive', + value: value.quasis.map(q => q.cooked).join(''), + loc: value.loc, + }; + instr.value = result; + return result; + } + + if (value.subexprs.length !== value.quasis.length - 1) { + return null; + } + + if (value.quasis.some(q => q.cooked === undefined)) { + return null; + } + + let quasiIndex = 0; + let resultString = value.quasis[quasiIndex].cooked as string; + ++quasiIndex; + + for (const subExpr of value.subexprs) { + const subExprValue = read(constants, subExpr); + if (!subExprValue || subExprValue.kind !== 'Primitive') { + return null; + } + + const expressionValue = subExprValue.value; + if ( + typeof expressionValue !== 'number' && + typeof expressionValue !== 'string' && + typeof expressionValue !== 'boolean' && + !(typeof expressionValue === 'object' && expressionValue === null) + ) { + // value is not supported (function, object) or invalid (symbol), or something else + return null; + } + + const suffix = value.quasis[quasiIndex].cooked; + ++quasiIndex; + + if (suffix === undefined) { + return null; + } + + /* + * Spec states that concat calls ToString(argument) internally on its parameters + * -> we don't have to implement ToString(argument) ourselves and just use the engine implementation + * Refs: + * - https://tc39.es/ecma262/2024/#sec-tostring + * - https://tc39.es/ecma262/2024/#sec-string.prototype.concat + * - https://tc39.es/ecma262/2024/#sec-template-literals-runtime-semantics-evaluation + */ + resultString = resultString.concat(expressionValue as string, suffix); + } + + const result: InstructionValue = { + kind: 'Primitive', + value: resultString, + loc: value.loc, + }; + + instr.value = result; + return result; + } case 'LoadLocal': { const placeValue = read(constants, value.place); if (placeValue !== null) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts index 66b1e1ce15..ae3ff122a2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/ReactiveScopes/PruneHoistedContexts.ts @@ -5,10 +5,13 @@ * LICENSE file in the root directory of this source tree. */ +import {CompilerError} from '..'; import { convertHoistedLValueKind, IdentifierId, + InstructionId, InstructionKind, + Place, ReactiveFunction, ReactiveInstruction, ReactiveScopeBlock, @@ -24,15 +27,38 @@ import { /* * Prunes DeclareContexts lowered for HoistedConsts, and transforms any references back to its * original instruction kind. + * + * Also detects and bails out on context variables which are: + * - function declarations, which are hoisted by JS engines to the nearest block scope + * - referenced before they are defined (i.e. having a `DeclareContext HoistedConst`) + * - declared + * + * This is because React Compiler converts a `function foo()` function declaration to + * 1. a `let foo;` declaration before reactive memo blocks + * 2. a `foo = function foo() {}` assignment within the block + * + * This means references before the assignment are invalid (see fixture + * error.todo-functiondecl-hoisting) */ export function pruneHoistedContexts(fn: ReactiveFunction): void { visitReactiveFunction(fn, new Visitor(), { activeScopes: empty(), + uninitialized: new Map(), }); } type VisitorState = { activeScopes: Stack>; + uninitialized: Map< + IdentifierId, + | { + kind: 'unknown-kind'; + } + | { + kind: 'func'; + definition: Place | null; + } + >; }; class Visitor extends ReactiveFunctionTransform { @@ -40,15 +66,39 @@ class Visitor extends ReactiveFunctionTransform { state.activeScopes = state.activeScopes.push( new Set(scope.scope.declarations.keys()), ); + /** + * Add declared but not initialized / assigned variables. This may include + * function declarations that escape the memo block. + */ + for (const decl of scope.scope.declarations.values()) { + state.uninitialized.set(decl.identifier.id, {kind: 'unknown-kind'}); + } this.traverseScope(scope, state); state.activeScopes.pop(); + for (const decl of scope.scope.declarations.values()) { + state.uninitialized.delete(decl.identifier.id); + } + } + override visitPlace( + _id: InstructionId, + place: Place, + state: VisitorState, + ): void { + const maybeHoistedFn = state.uninitialized.get(place.identifier.id); + if ( + maybeHoistedFn?.kind === 'func' && + maybeHoistedFn.definition !== place + ) { + CompilerError.throwTodo({ + reason: '[PruneHoistedContexts] Rewrite hoisted function references', + loc: place.loc, + }); + } } override transformInstruction( instruction: ReactiveInstruction, state: VisitorState, ): Transformed { - this.visitInstruction(instruction, state); - /** * Remove hoisted declarations to preserve TDZ */ @@ -57,6 +107,18 @@ class Visitor extends ReactiveFunctionTransform { instruction.value.lvalue.kind, ); if (maybeNonHoisted != null) { + if ( + maybeNonHoisted === InstructionKind.Function && + state.uninitialized.has(instruction.value.lvalue.place.identifier.id) + ) { + state.uninitialized.set( + instruction.value.lvalue.place.identifier.id, + { + kind: 'func', + definition: null, + }, + ); + } return {kind: 'remove'}; } } @@ -65,7 +127,7 @@ class Visitor extends ReactiveFunctionTransform { instruction.value.lvalue.kind !== InstructionKind.Reassign ) { /** - * Rewrite StoreContexts let/const/functions that will be pre-declared in + * Rewrite StoreContexts let/const that will be pre-declared in * codegen to reassignments. */ const lvalueId = instruction.value.lvalue.place.identifier.id; @@ -73,10 +135,36 @@ class Visitor extends ReactiveFunctionTransform { scope.has(lvalueId), ); if (isDeclaredByScope) { - instruction.value.lvalue.kind = InstructionKind.Reassign; + if ( + instruction.value.lvalue.kind === InstructionKind.Let || + instruction.value.lvalue.kind === InstructionKind.Const + ) { + instruction.value.lvalue.kind = InstructionKind.Reassign; + } else if (instruction.value.lvalue.kind === InstructionKind.Function) { + const maybeHoistedFn = state.uninitialized.get(lvalueId); + if (maybeHoistedFn != null) { + CompilerError.invariant(maybeHoistedFn.kind === 'func', { + reason: '[PruneHoistedContexts] Unexpected hoisted function', + loc: instruction.loc, + }); + maybeHoistedFn.definition = instruction.value.lvalue.place; + /** + * References to hoisted functions are now "safe" as variable assignments + * have finished. + */ + state.uninitialized.delete(lvalueId); + } + } else { + CompilerError.throwTodo({ + reason: '[PruneHoistedContexts] Unexpected kind', + description: `(${instruction.value.lvalue.kind})`, + loc: instruction.loc, + }); + } } } + this.visitInstruction(instruction, state); return {kind: 'keep'}; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts new file mode 100644 index 0000000000..b4484331de --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/Utils/TestUtils.ts @@ -0,0 +1,210 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {fromZodError} from 'zod-validation-error'; +import {CompilerError} from '../CompilerError'; +import { + CompilationMode, + defaultOptions, + parsePluginOptions, + PluginOptions, +} from '../Entrypoint'; +import {EnvironmentConfig} from '..'; +import { + EnvironmentConfigSchema, + PartialEnvironmentConfig, +} from '../HIR/Environment'; +import {Err, Ok, Result} from './Result'; +import {hasOwnProperty} from './utils'; + +function tryParseTestPragmaValue(val: string): Result { + try { + let parsedVal: unknown; + const stringMatch = /^"([^"]*)"$/.exec(val); + if (stringMatch && stringMatch.length > 1) { + parsedVal = stringMatch[1]; + } else { + parsedVal = JSON.parse(val); + } + return Ok(parsedVal); + } catch (e) { + return Err(e); + } +} + +const testComplexConfigDefaults: PartialEnvironmentConfig = { + validateNoCapitalizedCalls: [], + enableChangeDetectionForDebugging: { + source: 'react-compiler-runtime', + importSpecifierName: '$structuralCheck', + }, + enableEmitFreeze: { + source: 'react-compiler-runtime', + importSpecifierName: 'makeReadOnly', + }, + enableEmitInstrumentForget: { + fn: { + source: 'react-compiler-runtime', + importSpecifierName: 'useRenderCounter', + }, + gating: { + source: 'react-compiler-runtime', + importSpecifierName: 'shouldInstrument', + }, + globalGating: 'DEV', + }, + enableEmitHookGuards: { + source: 'react-compiler-runtime', + importSpecifierName: '$dispatcherGuard', + }, + inlineJsxTransform: { + elementSymbol: 'react.transitional.element', + globalDevVar: 'DEV', + }, + lowerContextAccess: { + source: 'react-compiler-runtime', + importSpecifierName: 'useContext_withSelector', + }, + inferEffectDependencies: [ + { + function: { + source: 'react', + importSpecifierName: 'useEffect', + }, + numRequiredArgs: 1, + }, + { + function: { + source: 'shared-runtime', + importSpecifierName: 'useSpecialEffect', + }, + numRequiredArgs: 2, + }, + { + function: { + source: 'useEffectWrapper', + importSpecifierName: 'default', + }, + numRequiredArgs: 1, + }, + ], +}; +/** + * For snap test fixtures and playground only. + */ +function parseConfigPragmaEnvironmentForTest( + pragma: string, +): EnvironmentConfig { + const maybeConfig: Partial> = {}; + + for (const token of pragma.split(' ')) { + if (!token.startsWith('@')) { + continue; + } + const keyVal = token.slice(1); + const valIdx = keyVal.indexOf(':'); + const key = valIdx === -1 ? keyVal : keyVal.slice(0, valIdx); + const val = valIdx === -1 ? undefined : keyVal.slice(valIdx + 1); + const isSet = val === undefined || val === 'true'; + if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) { + continue; + } + + if (isSet && key in testComplexConfigDefaults) { + maybeConfig[key] = testComplexConfigDefaults[key]; + } else if (isSet) { + maybeConfig[key] = true; + } else if (val === 'false') { + maybeConfig[key] = false; + } else if (val) { + const parsedVal = tryParseTestPragmaValue(val).unwrap(); + if (key === 'customMacros' && typeof parsedVal === 'string') { + const valSplit = parsedVal.split('.'); + const props = []; + for (const elt of valSplit.slice(1)) { + if (elt === '*') { + props.push({type: 'wildcard'}); + } else if (elt.length > 0) { + props.push({type: 'name', name: elt}); + } + } + maybeConfig[key] = [[valSplit[0], props]]; + continue; + } + maybeConfig[key] = parsedVal; + } + } + const config = EnvironmentConfigSchema.safeParse(maybeConfig); + if (config.success) { + /** + * Unless explicitly enabled, do not insert HMR handling code + * in test fixtures or playground to reduce visual noise. + */ + if (config.data.enableResetCacheOnSourceFileChanges == null) { + config.data.enableResetCacheOnSourceFileChanges = false; + } + return config.data; + } + CompilerError.invariant(false, { + reason: 'Internal error, could not parse config from pragma string', + description: `${fromZodError(config.error)}`, + loc: null, + suggestions: null, + }); +} + +const testComplexPluginOptionDefaults: Partial = { + gating: { + source: 'ReactForgetFeatureFlag', + importSpecifierName: 'isForgetEnabled_Fixtures', + }, +}; +export function parseConfigPragmaForTests( + pragma: string, + defaults: { + compilationMode: CompilationMode; + }, +): PluginOptions { + const environment = parseConfigPragmaEnvironmentForTest(pragma); + const options: Record = { + ...defaultOptions, + panicThreshold: 'all_errors', + compilationMode: defaults.compilationMode, + environment, + }; + for (const token of pragma.split(' ')) { + if (!token.startsWith('@')) { + continue; + } + const keyVal = token.slice(1); + const idx = keyVal.indexOf(':'); + const key = idx === -1 ? keyVal : keyVal.slice(0, idx); + const val = idx === -1 ? undefined : keyVal.slice(idx + 1); + if (!hasOwnProperty(defaultOptions, key)) { + continue; + } + const isSet = val === undefined || val === 'true'; + if (isSet && key in testComplexPluginOptionDefaults) { + options[key] = testComplexPluginOptionDefaults[key]; + } else if (isSet) { + options[key] = true; + } else if (val === 'false') { + options[key] = false; + } else if (val != null) { + const parsedVal = tryParseTestPragmaValue(val).unwrap(); + if (key === 'target' && parsedVal === 'donotuse_meta_internal') { + options[key] = { + kind: parsedVal, + runtimeModule: 'react', + }; + } else { + options[key] = parsedVal; + } + } + } + return parsePluginOptions(options); +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md index 244e70bbba..d50d9d58cc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" const Test = () =>
; export const FIXTURE_ENTRYPOINT = { @@ -15,7 +15,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" const Test = () => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js index d2b29b1889..03195fd7d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/arrow-function-with-implicit-return.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" const Test = () =>
; export const FIXTURE_ENTRYPOINT = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md deleted file mode 100644 index f8712ed728..0000000000 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.expect.md +++ /dev/null @@ -1,79 +0,0 @@ - -## Input - -```javascript -import {Stringify} from 'shared-runtime'; - -/** - * Fixture currently fails with - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
- * Forget: - * (kind: exception) bar is not a function - */ -function Foo({value}) { - const result = bar(); - function bar() { - return {value}; - } - return ; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{value: 2}], -}; - -``` - -## Code - -```javascript -import { c as _c } from "react/compiler-runtime"; -import { Stringify } from "shared-runtime"; - -/** - * Fixture currently fails with - * Found differences in evaluator results - * Non-forget (expected): - * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
- * Forget: - * (kind: exception) bar is not a function - */ -function Foo(t0) { - const $ = _c(6); - const { value } = t0; - let bar; - let result; - if ($[0] !== value) { - result = bar(); - bar = function bar() { - return { value }; - }; - $[0] = value; - $[1] = bar; - $[2] = result; - } else { - bar = $[1]; - result = $[2]; - } - let t1; - if ($[3] !== bar || $[4] !== result) { - t1 = ; - $[3] = bar; - $[4] = result; - $[5] = t1; - } else { - t1 = $[5]; - } - return t1; -} - -export const FIXTURE_ENTRYPOINT = { - fn: Foo, - params: [{ value: 2 }], -}; - -``` - \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md index 11abaa66ee..093a88b1de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" class Component { _renderMessage = () => { const Message = () => { @@ -22,7 +22,7 @@ class Component { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" class Component { _renderMessage = () => { const Message = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js index 90cba5b265..0786c82d6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/class-component-with-render-helper.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" class Component { _renderMessage = () => { const Message = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md index 8c2f0b94ef..edabebecfc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" function Bar(props) { 'use forget'; @@ -24,7 +24,7 @@ function Foo(props) { ```javascript import { shouldInstrument, useRenderCounter } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode:"annotation" function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js index 2aef527e6b..cc5e471054 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/codegen-instrument-forget-test.js @@ -1,4 +1,4 @@ -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md index 12184dafbb..15cba5b29a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default component Foo(bar: number) { return ; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js index d29777d4f0..04d419c7ce 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/component-declaration-basic.flow.js @@ -1,4 +1,4 @@ -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default component Foo(bar: number) { return ; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md index a2df134b2a..243c415467 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" import {identity} from 'shared-runtime'; @@ -35,7 +35,7 @@ import { shouldInstrument as _shouldInstrument3, useRenderCounter, } from "react-compiler-runtime"; -import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode(annotation) +import { c as _c } from "react/compiler-runtime"; // @enableEmitInstrumentForget @compilationMode:"annotation" import { identity } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js index 88ffefe220..58729d6bee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/conflict-codegen-instrument-forget.js @@ -1,4 +1,4 @@ -// @enableEmitInstrumentForget @compilationMode(annotation) +// @enableEmitInstrumentForget @compilationMode:"annotation" import {identity} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.expect.md new file mode 100644 index 0000000000..0f01fa0a76 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.expect.md @@ -0,0 +1,136 @@ + +## Input + +```javascript +import {Stringify, identity} from 'shared-runtime'; + +function foo() { + try { + identity(`${Symbol('0')}`); // Uncaught TypeError: Cannot convert a Symbol value to a string (leave as is) + } catch {} + + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: foo, + params: [], + isComponent: false, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify, identity } from "shared-runtime"; + +function foo() { + const $ = _c(1); + try { + identity(`${Symbol("0")}`); + } catch {} + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ( + + ); + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: foo, + params: [], + isComponent: false, +}; + +``` + +### Eval output +(kind: ok)
{"value":[true,true,"a\nb","\n","a1b"," abc A\n\nŧ","abc1def","abc1def2","abc1def2ghi","a4bcde6f","120","NaN","Infinity","-Infinity","9007199254740991","-9007199254740991","1.7976931348623157e+308","5e-324","0","\n ","[object Object]","1,2,3","true","false","null","undefined","1234567890","0123456789","01234567890","01234567890","0123401234567890123456789067890","012340123456789067890","0","",""]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.js new file mode 100644 index 0000000000..656a54db5c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-template-literal.js @@ -0,0 +1,56 @@ +import {Stringify, identity} from 'shared-runtime'; + +function foo() { + try { + identity(`${Symbol('0')}`); // Uncaught TypeError: Cannot convert a Symbol value to a string (leave as is) + } catch {} + + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: foo, + params: [], + isComponent: false, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-unary-number.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-unary-number.expect.md new file mode 100644 index 0000000000..074c321481 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-unary-number.expect.md @@ -0,0 +1,74 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +function foo() { + const a = -1; + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: foo, + params: [], + isComponent: false, +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +function foo() { + const $ = _c(1); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = ( + + ); + $[0] = t0; + } else { + t0 = $[0]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: foo, + params: [], + isComponent: false, +}; + +``` + +### Eval output +(kind: ok)
{"value":[-2,0,true,null,null,null,null,null]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-unary-number.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-unary-number.js new file mode 100644 index 0000000000..8ef8ec0e6c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-unary-number.js @@ -0,0 +1,25 @@ +import {Stringify} from 'shared-runtime'; + +function foo() { + const a = -1; + return ( + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: foo, + params: [], + isComponent: false, +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-unary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-unary.expect.md index 3ba5ea6bb9..aaea767643 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-unary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/constant-propagation-unary.expect.md @@ -58,7 +58,7 @@ function foo() { n0: true, n1: false, n2: false, - n3: !-1, + n3: false, s0: true, s1: false, s2: false, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md index 75da05a26e..d74ebd119c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @eslintSuppressionRules(my-app/react-rule) +// @eslintSuppressionRules:["my-app","react-rule"] /* eslint-disable my-app/react-rule */ function lowercasecomponent() { @@ -19,7 +19,7 @@ function lowercasecomponent() { ## Error ``` - 1 | // @eslintSuppressionRules(my-app/react-rule) + 1 | // @eslintSuppressionRules:["my-app","react-rule"] 2 | > 3 | /* eslint-disable my-app/react-rule */ | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. eslint-disable my-app/react-rule (3:3) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js index 96d08827d5..331e551596 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.js @@ -1,4 +1,4 @@ -// @eslintSuppressionRules(my-app/react-rule) +// @eslintSuppressionRules:["my-app","react-rule"] /* eslint-disable my-app/react-rule */ function lowercasecomponent() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md index 100dafaf38..6b63344a76 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component({ref}) { const value = ref.current; return
{value}
; @@ -14,7 +14,7 @@ function Component({ref}) { ## Error ``` - 1 | // @validateRefAccessDuringRender @compilationMode(infer) + 1 | // @validateRefAccessDuringRender @compilationMode:"infer" 2 | function Component({ref}) { > 3 | const value = ref.current; | ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js index c5276b79b2..81c58cf252 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-destructure.js @@ -1,4 +1,4 @@ -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component({ref}) { const value = ref.current; return
{value}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md index 4d8c4c735c..c49aea8740 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const value = props.ref.current; return
{value}
; @@ -14,7 +14,7 @@ function Component(props) { ## Error ``` - 1 | // @validateRefAccessDuringRender @compilationMode(infer) + 1 | // @validateRefAccessDuringRender @compilationMode:"infer" 2 | function Component(props) { > 3 | const value = props.ref.current; | ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js index c31a081366..abb1ff86fb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-read-ref-prop-in-render-property-load.js @@ -1,4 +1,4 @@ -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const value = props.ref.current; return
{value}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md index c00aaca4fe..5038379283 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const ref = props.ref; ref.current = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js index b939ea540b..32997b8978 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-write-ref-prop-in-render.js @@ -1,4 +1,4 @@ -// @validateRefAccessDuringRender @compilationMode(infer) +// @validateRefAccessDuringRender @compilationMode:"infer" function Component(props) { const ref = props.ref; ref.current = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-functiondecl-hoisting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-functiondecl-hoisting.expect.md new file mode 100644 index 0000000000..6e2310bd10 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-functiondecl-hoisting.expect.md @@ -0,0 +1,43 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +/** + * Fixture currently fails with + * Found differences in evaluator results + * Non-forget (expected): + * (kind: ok)
{"result":{"value":2},"fn":{"kind":"Function","result":{"value":2}},"shouldInvokeFns":true}
+ * Forget: + * (kind: exception) bar is not a function + */ +function Foo({value}) { + const result = bar(); + function bar() { + return {value}; + } + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{value: 2}], +}; + +``` + + +## Error + +``` + 10 | */ + 11 | function Foo({value}) { +> 12 | const result = bar(); + | ^^^ Todo: [PruneHoistedContexts] Rewrite hoisted function references (12:12) + 13 | function bar() { + 14 | return {value}; + 15 | } +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-functiondecl-hoisting.tsx similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/bug-functiondecl-hoisting.tsx rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-functiondecl-hoisting.tsx diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md index 1d88b8d50c..2b9092213b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js index e23016cc49..d67232371d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-hoisted-function-in-unreachable-code.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-valid-functiondecl-hoisting.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-valid-functiondecl-hoisting.expect.md new file mode 100644 index 0000000000..fb8988c8f8 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-valid-functiondecl-hoisting.expect.md @@ -0,0 +1,46 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; +/** + * Also see error.todo-functiondecl-hoisting.tsx which shows *invalid* + * compilation cases. + * + * This bailout specifically is a false positive for since this function's only + * reference-before-definition are within other functions which are not invoked. + */ +function Foo() { + 'use memo'; + + function foo() { + return bar(); + } + function bar() { + return 42; + } + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], +}; + +``` + + +## Error + +``` + 13 | return bar(); + 14 | } +> 15 | function bar() { + | ^^^ Todo: [PruneHoistedContexts] Rewrite hoisted function references (15:15) + 16 | return 42; + 17 | } + 18 | +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-valid-functiondecl-hoisting.tsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-valid-functiondecl-hoisting.tsx new file mode 100644 index 0000000000..063492b89c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-valid-functiondecl-hoisting.tsx @@ -0,0 +1,25 @@ +import {Stringify} from 'shared-runtime'; +/** + * Also see error.todo-functiondecl-hoisting.tsx which shows *invalid* + * compilation cases. + * + * This bailout specifically is a false positive for since this function's only + * reference-before-definition are within other functions which are not invoked. + */ +function Foo() { + 'use memo'; + + function foo() { + return bar(); + } + function bar() { + return 42; + } + + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md index a4a885d94b..68b275e34c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateBlocklistedImports(DangerousImport) +// @validateBlocklistedImports:["DangerousImport"] import {foo} from 'DangerousImport'; import {useIdentity} from 'shared-runtime'; @@ -17,7 +17,7 @@ function useHook() { ## Error ``` - 1 | // @validateBlocklistedImports(DangerousImport) + 1 | // @validateBlocklistedImports:["DangerousImport"] > 2 | import {foo} from 'DangerousImport'; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Bailing out due to blocklisted import. Import from module DangerousImport (2:2) 3 | import {useIdentity} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts index fcde4a1922..d33c175bcf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.validate-blocklisted-imports.ts @@ -1,4 +1,4 @@ -// @validateBlocklistedImports(DangerousImport) +// @validateBlocklistedImports:["DangerousImport"] import {foo} from 'DangerousImport'; import {useIdentity} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md index 14bc703149..df2f4b01a9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; @@ -43,7 +43,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { useEffect, useMemo, useState } from "react"; import { ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js index 0bc38808b9..f1d96b5635 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-dont-refresh-const-changes-prod.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md index 1af628811b..56a7289ae8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) @enableResetCacheOnSourceFileChanges +// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; @@ -46,7 +46,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableResetCacheOnSourceFileChanges +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enableResetCacheOnSourceFileChanges import { useEffect, useMemo, useState } from "react"; import { ValidateMemoization } from "shared-runtime"; @@ -63,12 +63,12 @@ function unsafeUpdateConst() { function Component() { const $ = _c(3); if ( - $[0] !== "8d7015668f857996c3d895a7a90e3e16b8a791d5b9cd13f2c76e1c254aeedebb" + $[0] !== "a585d27423c1181e7b6305ff909458183d284658c3c3d2e3764e1128be302fd7" ) { for (let $i = 0; $i < 3; $i += 1) { $[$i] = Symbol.for("react.memo_cache_sentinel"); } - $[0] = "8d7015668f857996c3d895a7a90e3e16b8a791d5b9cd13f2c76e1c254aeedebb"; + $[0] = "a585d27423c1181e7b6305ff909458183d284658c3c3d2e3764e1128be302fd7"; } useState(_temp); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js index cc5c1a4abf..9130f13c15 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fast-refresh-refresh-on-const-changes-dev.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) @enableResetCacheOnSourceFileChanges +// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges import {useEffect, useMemo, useState} from 'react'; import {ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-param-nested-fbt.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-param-nested-fbt.expect.md new file mode 100644 index 0000000000..eb8073282e --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-param-nested-fbt.expect.md @@ -0,0 +1,56 @@ + +## Input + +```javascript +import fbt from 'fbt'; +import {Stringify} from 'shared-runtime'; + +/** + * MemoizeFbtAndMacroOperands needs to account for nested fbt calls. + * Expected fixture `fbt-param-call-arguments` to succeed but it failed with error: + * /fbt-param-call-arguments.ts: Line 19 Column 11: fbt: unsupported babel node: Identifier + * --- + * t3 + * --- + */ +function Component({firstname, lastname}) { + 'use memo'; + return ( + + {fbt( + [ + 'Name: ', + fbt.param('firstname', ), + ', ', + fbt.param( + 'lastname', + + {fbt('(inner fbt)', 'Inner fbt value')} + + ), + ], + 'Name' + )} + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{firstname: 'first', lastname: 'last'}], + sequentialRenders: [{firstname: 'first', lastname: 'last'}], +}; + +``` + + +## Error + +``` +Line 19 Column 11: fbt: unsupported babel node: Identifier +--- +t3 +--- +``` + + \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-param-nested-fbt.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-param-nested-fbt.js new file mode 100644 index 0000000000..14e3278e39 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/fbt/error.todo-fbt-param-nested-fbt.js @@ -0,0 +1,38 @@ +import fbt from 'fbt'; +import {Stringify} from 'shared-runtime'; + +/** + * MemoizeFbtAndMacroOperands needs to account for nested fbt calls. + * Expected fixture `fbt-param-call-arguments` to succeed but it failed with error: + * /fbt-param-call-arguments.ts: Line 19 Column 11: fbt: unsupported babel node: Identifier + * --- + * t3 + * --- + */ +function Component({firstname, lastname}) { + 'use memo'; + return ( + + {fbt( + [ + 'Name: ', + fbt.param('firstname', ), + ', ', + fbt.param( + 'lastname', + + {fbt('(inner fbt)', 'Inner fbt value')} + + ), + ], + 'Name' + )} + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{firstname: 'first', lastname: 'last'}], + sequentialRenders: [{firstname: 'first', lastname: 'last'}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md index 6256e5ec90..5eaa593fa1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableEmitInstrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode:"annotation" @gating function Bar(props) { 'use forget'; @@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { shouldInstrument, useRenderCounter } from "react-compiler-runtime"; import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @enableEmitInstrumentForget @compilationMode(annotation) @gating +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @enableEmitInstrumentForget @compilationMode:"annotation" @gating const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js index 6730630ac3..425b99da8b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/codegen-instrument-forget-gating-test.js @@ -1,4 +1,4 @@ -// @enableEmitInstrumentForget @compilationMode(annotation) @gating +// @enableEmitInstrumentForget @compilationMode:"annotation" @gating function Bar(props) { 'use forget'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md index ae7896e7a5..fbe3c554c1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js index 1ec9a902b2..369dc3bbd0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-default-function.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md index 4c368a8254..8665addb8a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; @@ -35,7 +35,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js index 5b7f7c81b7..af2f098130 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function-and-default.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export default function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md index c3e2e4a042..9661fe3d2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export function Bar(props) { 'use forget'; return
{props.bar}
; @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" export const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js index 5bcf9ac67d..e07144b160 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test-export-function.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" export function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md index 8ccce56713..0cd67fd1d2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" function Bar(props) { 'use forget'; return
{props.bar}
; @@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(annotation) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"annotation" const Bar = isForgetEnabled_Fixtures() ? function Bar(props) { "use forget"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js index 3bb92654d9..bc45933af3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/gating-test.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(annotation) +// @gating @compilationMode:"annotation" function Bar(props) { 'use forget'; return
{props.bar}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md index aff21ee0bd..1106722259 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @compilationMode(infer) +// @gating @compilationMode:"infer" import React from 'react'; export default React.forwardRef(function notNamedLikeAComponent(props) { return
; @@ -14,7 +14,7 @@ export default React.forwardRef(function notNamedLikeAComponent(props) { ```javascript import { c as _c } from "react/compiler-runtime"; -import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode(infer) +import { isForgetEnabled_Fixtures } from "ReactForgetFeatureFlag"; // @gating @compilationMode:"infer" import React from "react"; export default React.forwardRef( isForgetEnabled_Fixtures() diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js index 79acdd821a..518c8eeee3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/gating/infer-function-expression-React-memo-gating.js @@ -1,4 +1,4 @@ -// @gating @compilationMode(infer) +// @gating @compilationMode:"infer" import React from 'react'; export default React.forwardRef(function notNamedLikeAComponent(props) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md index f75514e8a8..d23e808983 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default hook useFoo(bar: number) { return [bar]; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js index 217fbe1636..13db3325ce 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/hook-declaration-basic.flow.js @@ -1,4 +1,4 @@ -// @flow @compilationMode(infer) +// @flow @compilationMode:"infer" export default hook useFoo(bar: number) { return [bar]; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md index dbc90978d5..59875c8d2a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @customMacros(idx.*.b) +// @customMacros:"idx.*.b" function Component(props) { // outlined @@ -31,7 +31,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @customMacros(idx.*.b) +import { c as _c } from "react/compiler-runtime"; // @customMacros:"idx.*.b" function Component(props) { const $ = _c(16); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js index 4b944ddcca..5a362bbbe7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining-wildcard.js @@ -1,4 +1,4 @@ -// @customMacros(idx.*.b) +// @customMacros:"idx.*.b" function Component(props) { // outlined diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md index f1bced727b..9c748d84be 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @customMacros(idx.a) +// @customMacros:"idx.a" function Component(props) { // outlined @@ -25,7 +25,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @customMacros(idx.a) +import { c as _c } from "react/compiler-runtime"; // @customMacros:"idx.a" function Component(props) { const $ = _c(10); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js index f5b034b91d..1b2cadb5d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-method-no-outlining.js @@ -1,4 +1,4 @@ -// @customMacros(idx.a) +// @customMacros:"idx.a" function Component(props) { // outlined diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md index a0fafc56c8..880cdcdb21 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @customMacros(idx) +// @customMacros:"idx" import idx from 'idx'; function Component(props) { @@ -21,7 +21,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @customMacros(idx) +import { c as _c } from "react/compiler-runtime"; // @customMacros:"idx" function Component(props) { var _ref2; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js index 7d16c8d2b7..8cdf205ddd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/idx-no-outlining.js @@ -1,4 +1,4 @@ -// @customMacros(idx) +// @customMacros:"idx" import idx from 'idx'; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md index 4263d8db8d..129d3a72e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useNoAlias} from 'shared-runtime'; // This should be compiled by Forget @@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { useNoAlias } from "shared-runtime"; // This should be compiled by Forget diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js index e354c67bb2..a03aa10aa8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-compile-hooks-with-multiple-params.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useNoAlias} from 'shared-runtime'; // This should be compiled by Forget diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md index d0999fca0c..9b35a83801 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Takes multiple parameters - not a component! function Component(foo, bar) { return
; @@ -18,7 +18,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Takes multiple parameters - not a component! function Component(foo, bar) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js index 9b2cf36a19..442c367fde 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-dont-compile-components-with-multiple-params.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Takes multiple parameters - not a component! function Component(foo, bar) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md index cd977ae834..8cbe8da62f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import useMyEffect from 'useEffectWrapper'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js index 8061b44a3d..992cd828ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn-default-import.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import useMyEffect from 'useEffectWrapper'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md index e36e9b3c0e..8feec25376 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import {useEffect} from 'react'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js index 813c3b6c3d..106a3c7352 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.callsite-in-non-react-fn.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none) +// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none" import {useEffect} from 'react'; function nonReactFn(arg) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md index ee17fb048e..eeec00995b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js index a011e3bf14..dac029e0ca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.non-inlined-effect-fn.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md index 71ecefeeea..416ede4556 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import React from 'react'; function NonReactiveDepInEffect() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js index 237535fe5b..b1b0e8d2fe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-import-default-property-useEffect.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import React from 'react'; function NonReactiveDepInEffect() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md index 575fd2101f..a396b9c3ca 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useSpecialEffect} from 'shared-runtime'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js index d3c83c2562..2c53d1a21b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.todo-syntax.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useSpecialEffect} from 'shared-runtime'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md index 52b99a393e..c3413f9fd0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({propVal}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js index 53eeda5050..d00ce2ac98 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/error.use-no-memo.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({propVal}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md index 7099388378..701f54b663 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; import {print} from 'shared-runtime'; @@ -20,7 +20,7 @@ function Component({foo}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useEffect } from "react"; import { print } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js index fe00af3922..f1b3de4f7f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-granular-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md index d0532a495a..05ef40c150 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; @@ -19,7 +19,7 @@ function Component({arrRef}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useEffect, useRef } from "react"; import { print } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js index ff2cda6b89..f497d7e595 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect-ref-access.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect, useRef} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md index 5da3ceca22..fa1df3ef88 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({foo}) { @@ -17,7 +17,7 @@ function Component({foo}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useEffect } from "react"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js index 806ca5322f..2e2eb7bc08 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/bailout-retry/mutate-after-useeffect.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useEffect} from 'react'; function Component({foo}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.expect.md rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.expect.md diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js similarity index 100% rename from compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit-lint-repro.js rename to compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/no-emit-lint-repro.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md new file mode 100644 index 0000000000..bd70c0138d --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.expect.md @@ -0,0 +1,64 @@ + +## Input + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo({ propVal }) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +## Logs + +``` +{"kind":"CompileError","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"detail":{"reason":"This mutates a variable that React considers immutable","description":null,"loc":{"start":{"line":11,"column":2,"index":320},"end":{"line":11,"column":6,"index":324},"filename":"retry-no-emit.ts","identifierName":"arr2"},"suggestions":null,"severity":"InvalidReact"}} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":7,"column":2,"index":216},"end":{"line":7,"column":36,"index":250},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":7,"column":31,"index":245},"end":{"line":7,"column":34,"index":248},"filename":"retry-no-emit.ts","identifierName":"arr"}]} +{"kind":"AutoDepsDecorations","fnLoc":{"start":{"line":10,"column":2,"index":274},"end":{"line":10,"column":44,"index":316},"filename":"retry-no-emit.ts"},"decorations":[{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":25,"index":297},"end":{"line":10,"column":29,"index":301},"filename":"retry-no-emit.ts","identifierName":"arr2"},{"start":{"line":10,"column":35,"index":307},"end":{"line":10,"column":42,"index":314},"filename":"retry-no-emit.ts","identifierName":"propVal"}]} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":5,"column":0,"index":163},"end":{"line":13,"column":1,"index":357},"filename":"retry-no-emit.ts"},"fnName":"Foo","memoSlots":0,"memoBlocks":0,"memoValues":0,"prunedMemoBlocks":0,"prunedMemoValues":0} +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js new file mode 100644 index 0000000000..d1dda06a04 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-no-emit.js @@ -0,0 +1,19 @@ +// @inferEffectDependencies @noEmit @panicThreshold:"none" @loggerTestOnly +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md new file mode 100644 index 0000000000..6e4ddeeb2b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.expect.md @@ -0,0 +1,59 @@ + +## Input + +```javascript +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + 'use memo'; + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; + +``` + +## Code + +```javascript +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import { print } from "shared-runtime"; +import useEffectWrapper from "useEffectWrapper"; + +function Foo({ propVal }) { + "use memo"; + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + + return { arr, arr2 }; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{ propVal: 1 }], + sequentialRenders: [{ propVal: 1 }, { propVal: 2 }], +}; + +``` + +### Eval output +(kind: ok) {"arr":[1],"arr2":[2]} +{"arr":[2],"arr2":[2]} +logs: [[ 1 ],[ 2 ]] \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js new file mode 100644 index 0000000000..2650cbe5d7 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/no-emit/retry-opt-in--no-emit.js @@ -0,0 +1,21 @@ +// @compilationMode:"all" @inferEffectDependencies @panicThreshold:"none" @noEmit +import {print} from 'shared-runtime'; +import useEffectWrapper from 'useEffectWrapper'; + +function Foo({propVal}) { + 'use memo'; + const arr = [propVal]; + useEffectWrapper(() => print(arr)); + + const arr2 = []; + useEffectWrapper(() => arr2.push(propVal)); + arr2.push(2); + + return {arr, arr2}; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [{propVal: 1}], + sequentialRenders: [{propVal: 1}, {propVal: 2}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md index 8038a8d071..89a89c41e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" React.memo(props => { return
; }); @@ -12,7 +12,7 @@ React.memo(props => { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" React.memo((props) => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js index a8da0532df..d9e1eaf31a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-React-memo.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" React.memo(props => { return
; }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md index 92bbaf7c30..c183bf949c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" const Component = props => { return
; }; @@ -12,7 +12,7 @@ const Component = props => { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" const Component = (props) => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js index 6dff8028b9..c12f61d18f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-assignment.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" const Component = props => { return
; }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md index 24a7050c68..49886e0ac3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" const Component = function ComponentName(props) { return ; @@ -13,7 +13,7 @@ const Component = function ComponentName(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" const Component = function ComponentName(props) { const $ = _c(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js index 7fe4c9c0cc..deac307a3b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-expression-component.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" const Component = function ComponentName(props) { return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md index 011006b2aa..ca1d6e4336 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" React.forwardRef(props => { return
; }); @@ -12,7 +12,7 @@ React.forwardRef(props => { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" React.forwardRef((props) => { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js index c0abf753c2..f2b458df8d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-function-forwardRef.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" React.forwardRef(props => { return
; }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md index 51c9a14abf..d92f1a6c45 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const [state, _] = useState(null); return [state]; @@ -13,7 +13,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function Component(props) { const $ = _c(2); const [state] = useState(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js index 791542cb34..57c03cd222 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-hook-call.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const [state, _] = useState(null); return [state]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md index 96cae6ee6a..ed175c61ab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { return
; } @@ -12,7 +12,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function Component(props) { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js index bb552b5f2a..73607ce519 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { return
; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md index c19248a0c0..5f3da3fd8e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Foo({}, ref) { return
; @@ -18,7 +18,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function Foo(t0, ref) { const $ = _c(2); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js index aaf9492528..cefd53b123 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-component-with-ref-arg.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Foo({}, ref) { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md index d1ed626a97..a94055469a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function useStateValue(props) { const [state, _] = useState(null); return [state]; @@ -13,7 +13,7 @@ function useStateValue(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function useStateValue(props) { const $ = _c(2); const [state] = useState(null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js index ed90449f36..7191fe6cb0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-hook-call.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function useStateValue(props) { const [state, _] = useState(null); return [state]; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md index dfc6fdbc2e..3f4c0a0173 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function useDiv(props) { return
; } @@ -12,7 +12,7 @@ function useDiv(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" function useDiv(props) { const $ = _c(1); let t0; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js index 3453fc8286..2fef05df39 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-functions-hook-with-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function useDiv(props) { return
; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md index 4c89f59919..963bbc7d3e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {Stringify} from 'shared-runtime'; @@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx index c8e396b057..cb9d22a9f1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-nested-object-method.jsx @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md index b5710f5840..1334ab1f0a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useIdentity, identity} from 'shared-runtime'; function Component(fakeProps: number) { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import { useIdentity, identity } from "shared-runtime"; function Component(fakeProps: number) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts index 81ec3e34e3..e628f9c685 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-annot.ts @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useIdentity, identity} from 'shared-runtime'; function Component(fakeProps: number) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md index adb9a1da40..28672a1da1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const result = f(props); function helper() { @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const result = f(props); function helper() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js index 9d9e070ca9..11a21ab56e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-nested-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const result = f(props); function helper() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md index 70782b6aab..27d7ee1c1e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const ignore = ; return {foo: f(props)}; @@ -22,7 +22,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const ignore = ; return { foo: f(props) }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js index f48e2b1e15..950988ef63 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-no-component-obj-return.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component(props) { const ignore = ; return {foo: f(props)}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md index 0637082e50..d4d4c1bc0e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This component is skipped bc it doesn't call any hooks or // use JSX: function Component(props) { @@ -14,7 +14,7 @@ function Component(props) { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This component is skipped bc it doesn't call any hooks or // use JSX: function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js index 5f6aa674b2..c1396aca94 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-skip-components-without-hooks-or-jsx.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // This component is skipped bc it doesn't call any hooks or // use JSX: function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md index 2e052ecf33..9f4d85de70 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements import {identity} from 'shared-runtime'; function Component(props) { @@ -25,7 +25,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoJSXInTryStatements +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoJSXInTryStatements import { identity } from "shared-runtime"; function Component(props) { @@ -65,8 +65,8 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":11,"column":11,"index":214},"end":{"line":11,"column":32,"index":235},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":83},"end":{"line":17,"column":1,"index":290},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":17,"column":1,"index":298},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js index 2e5a3618b4..3cf5cc9b8a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js @@ -1,4 +1,4 @@ -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements import {identity} from 'shared-runtime'; function Component(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md index 702d317a4b..9fe89f25ee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements function Component(props) { let el; try { @@ -18,7 +18,7 @@ function Component(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoJSXInTryStatements +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoJSXInTryStatements function Component(props) { const $ = _c(1); let el; @@ -42,8 +42,8 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":5,"column":9,"index":96},"end":{"line":5,"column":16,"index":103},"filename":"invalid-jsx-in-try-with-catch.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":41},"end":{"line":10,"column":1,"index":152},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":49},"end":{"line":10,"column":1,"index":160},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js index 815fb4ae9e..d01a93bf5a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js @@ -1,4 +1,4 @@ -// @logger @validateNoJSXInTryStatements +// @loggerTestOnly @validateNoJSXInTryStatements function Component(props) { let el; try { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index d220617dc1..e116fb4fd9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { @@ -24,7 +24,7 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoSetStateInPassiveEffects +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInPassiveEffects import { useEffect, useState } from "react"; function Component() { @@ -65,8 +65,8 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":264},"end":{"line":13,"column":5,"index":265},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":16,"column":1,"index":292},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":272},"end":{"line":13,"column":5,"index":273},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":99},"end":{"line":16,"column":1,"index":300},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js index b03c08609a..bbf976f0bd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js @@ -1,4 +1,4 @@ -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index 667f57b7cc..202071455b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { @@ -18,7 +18,7 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateNoSetStateInPassiveEffects +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInPassiveEffects import { useEffect, useState } from "react"; function Component() { @@ -45,8 +45,8 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":179},"end":{"line":7,"column":12,"index":187},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":10,"column":1,"index":224},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":187},"end":{"line":7,"column":12,"index":195},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":99},"end":{"line":10,"column":1,"index":232},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js index e806b359c6..e526ad82e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js @@ -1,4 +1,4 @@ -// @logger @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInPassiveEffects import {useEffect, useState} from 'react'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/labeled-break-within-label-switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/labeled-break-within-label-switch.expect.md index 49c2506045..441b6ae314 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/labeled-break-within-label-switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/labeled-break-within-label-switch.expect.md @@ -40,16 +40,16 @@ function useHook(cond) { log = []; switch (CONST_STRING0) { case CONST_STRING0: { - log.push(`@A`); + log.push("@A"); bb0: { if (cond) { break bb0; } - log.push(`@B`); + log.push("@B"); } - log.push(`@C`); + log.push("@C"); } } $[0] = cond; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md index 99796806bc..b6583dbc9e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger +// @loggerTestOnly import {createContext, use, useState} from 'react'; import { Stringify, @@ -56,7 +56,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly import { createContext, use, useState } from "react"; import { Stringify, @@ -129,8 +129,8 @@ export const FIXTURE_ENTRYPOINT = { ## Logs ``` -{"kind":"CompileSuccess","fnLoc":{"start":{"line":10,"column":0,"index":159},"end":{"line":33,"column":1,"index":903},"filename":"log-pruned-memoization.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":2,"prunedMemoValues":3} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":37,"column":0,"index":939},"end":{"line":43,"column":1,"index":1037},"filename":"log-pruned-memoization.ts"},"fnName":"Wrapper","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":10,"column":0,"index":167},"end":{"line":33,"column":1,"index":911},"filename":"log-pruned-memoization.ts"},"fnName":"Component","memoSlots":6,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":2,"prunedMemoValues":3} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":37,"column":0,"index":947},"end":{"line":43,"column":1,"index":1045},"filename":"log-pruned-memoization.ts"},"fnName":"Wrapper","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js index d0097b4eb3..42c39d1e71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/log-pruned-memoization.js @@ -1,4 +1,4 @@ -// @logger +// @loggerTestOnly import {createContext, use, useState} from 'react'; import { Stringify, diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md index 55ffec877d..08a3ccb87f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros:"cx" import {identity} from 'shared-runtime'; const DARK = 'dark'; @@ -47,7 +47,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros:"cx" import { identity } from "shared-runtime"; const DARK = "dark"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js index ac6a475e0c..c72f693754 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-assigned-to-temporary.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros:"cx" import {identity} from 'shared-runtime'; const DARK = 'dark'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md index aeae54128b..c99b05a113 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) import {identity} from 'shared-runtime'; const DARK = 'dark'; @@ -49,7 +49,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) import { identity } from "shared-runtime"; const DARK = "dark"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js index fe7abb7cdf..4254c5a442 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/meta-isms/repro-cx-namespace-assigned-to-temporary.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) +// @compilationMode:"infer" @enableAssumeHooksFollowRulesOfReact:false @customMacros(cx) import {identity} from 'shared-runtime'; const DARK = 'dark'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md index 3224997b40..13e67f0e49 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" import {useHook} from 'shared-runtime'; function InvalidComponent(props) { @@ -21,7 +21,7 @@ function ValidComponent(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @panicThreshold(none) +import { c as _c } from "react/compiler-runtime"; // @panicThreshold:"none" import { useHook } from "shared-runtime"; function InvalidComponent(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js index 6a3d52c864..80f9bdd109 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/multiple-components-first-is-invalid.js @@ -1,4 +1,4 @@ -// @panicThreshold(none) +// @panicThreshold:"none" import {useHook} from 'shared-runtime'; function InvalidComponent(props) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md index 3297892ea2..4c5da20170 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" import {useMemo} from 'react'; import {ValidateMemoization} from 'shared-runtime'; @@ -24,7 +24,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @compilationMode(infer) +import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" import { useMemo } from "react"; import { ValidateMemoization } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js index 4c2d322ad3..5882e3cc6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/props-method-dependency.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" import {useMemo} from 'react'; import {ValidateMemoization} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md new file mode 100644 index 0000000000..cfbaa34568 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.expect.md @@ -0,0 +1,27 @@ + +## Input + +```javascript +// @panicThreshold(none) +'use no memo'; + +function Foo() { + return ; +} + +``` + +## Code + +```javascript +// @panicThreshold(none) +"use no memo"; + +function Foo() { + return ; +} + +``` + +### Eval output +(kind: exception) Fixture not implemented \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.js new file mode 100644 index 0000000000..405295ee4c --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-bailout-nopanic-shouldnt-outline.js @@ -0,0 +1,6 @@ +// @panicThreshold(none) +'use no memo'; + +function Foo() { + return ; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md index 6477654d3d..b19a06519f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire component Foo(useDynamicHook) { useDynamicHook(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js index d0313aa42f..54b1818213 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-dont-add-hook-guards-on-retry.js @@ -1,4 +1,4 @@ -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire component Foo(useDynamicHook) { useDynamicHook(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md index ae9edc478e..a153a5cf74 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" import {useNoAlias} from 'shared-runtime'; const cond = true; @@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" import { useNoAlias } from "shared-runtime"; const cond = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js index ffed061d99..fc96e1435f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-retain-source-when-bailout.js @@ -1,4 +1,4 @@ -// @panicThreshold(none) +// @panicThreshold:"none" import {useNoAlias} from 'shared-runtime'; const cond = true; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md index 7394c17b9c..2d1860bc3b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const f = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js index 240926d979..bfe82523a3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-function-expression-object-expression.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const f = () => { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md index 4d1e7e530c..7403afcaf7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const x = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js index 54d4a89d46..779cad41f9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-in-nested-object-method.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" function Component() { 'use memo'; const x = { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md index 4356c33c9a..82b6998634 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Regression test for some internal code. // This shows how the "callback rule" is more relaxed, // and doesn't kick in unless we're confident we're in @@ -20,7 +20,7 @@ function makeListener(instance) { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Regression test for some internal code. // This shows how the "callback rule" is more relaxed, // and doesn't kick in unless we're confident we're in diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js index f83f535928..360593a570 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-0592bd574811.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Regression test for some internal code. // This shows how the "callback rule" is more relaxed, // and doesn't kick in unless we're confident we're in diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md index 9a6ca0d249..f5bd1a042a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can call hooks. function createHook() { return function useHook() { @@ -16,7 +16,7 @@ function createHook() { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can call hooks. function createHook() { return function useHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js index f27e6bcf5f..fcdf80d224 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-2bec02ac982b.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can call hooks. function createHook() { return function useHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md index ea8869bacd..11330a9c4d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can use hooks. function createHook() { return function useHookWithHook() { @@ -15,7 +15,7 @@ function createHook() { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can use hooks. function createHook() { return function useHookWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js index ce6244346f..3f3a4c0576 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-33a6e23edac1.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because hooks can use hooks. function createHook() { return function useHookWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md index dfb4fa6df7..c0f5ad939d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because components can use hooks. function createComponentWithHook() { return function ComponentWithHook() { @@ -15,7 +15,7 @@ function createComponentWithHook() { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because components can use hooks. function createComponentWithHook() { return function ComponentWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js index 2aced0739c..31a5d85356 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-8f1c2c3f71c9.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // Valid because components can use hooks. function createComponentWithHook() { return function ComponentWithHook() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md index 1c676d9825..ce42d44a0c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This is valid because "use"-prefixed functions called in // unnamed function arguments are not assumed to be hooks. unknownFunction(function (foo, bar) { @@ -16,7 +16,7 @@ unknownFunction(function (foo, bar) { ## Code ```javascript -// @compilationMode(infer) +// @compilationMode:"infer" // This is valid because "use"-prefixed functions called in // unnamed function arguments are not assumed to be hooks. unknownFunction(function (foo, bar) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js index a480e74a83..b7744c5ff7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/rules-of-hooks-fe6042f7628b.js @@ -1,4 +1,4 @@ -// @compilationMode(infer) +// @compilationMode:"infer" // This is valid because "use"-prefixed functions called in // unnamed function arguments are not assumed to be hooks. unknownFunction(function (foo, bar) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md index 36a68d07c4..4bdc7c7d92 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-assignment-to-scope-declarations.expect.md @@ -97,7 +97,7 @@ function foo(name) { const t0 = `${name}!`; let t1; if ($[0] !== t0) { - t1 = { status: ``, text: t0 }; + t1 = { status: "", text: t0 }; $[0] = t0; $[1] = t1; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md index d8e991dc46..2bbfc31a11 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/sequential-destructuring-both-mixed-local-and-scope-declaration.expect.md @@ -102,7 +102,7 @@ function foo(name) { const t0 = `${name}!`; let t1; if ($[0] !== t0) { - t1 = { status: ``, text: t0 }; + t1 = { status: "", text: t0 }; $[0] = t0; $[1] = t1; } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md index 6103aa5094..bed1b9e601 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @panicThreshold(none) @compilationMode(annotation) +// @gating @panicThreshold:"none" @compilationMode:"annotation" let someGlobal = 'joe'; function Component() { @@ -21,7 +21,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @gating @panicThreshold(none) @compilationMode(annotation) +// @gating @panicThreshold:"none" @compilationMode:"annotation" let someGlobal = "joe"; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js index 1a350a1df7..8d0264cd17 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-annotation-mode.js @@ -1,4 +1,4 @@ -// @gating @panicThreshold(none) @compilationMode(annotation) +// @gating @panicThreshold:"none" @compilationMode:"annotation" let someGlobal = 'joe'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md index abbf9aff13..36b91bcf2b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @gating @panicThreshold(none) @compilationMode(infer) +// @gating @panicThreshold:"none" @compilationMode:"infer" let someGlobal = 'joe'; function Component() { @@ -20,7 +20,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -// @gating @panicThreshold(none) @compilationMode(infer) +// @gating @panicThreshold:"none" @compilationMode:"infer" let someGlobal = "joe"; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js index dedabda9d6..1de4cb4490 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/should-bailout-without-compilation-infer-mode.js @@ -1,4 +1,4 @@ -// @gating @panicThreshold(none) @compilationMode(infer) +// @gating @panicThreshold:"none" @compilationMode:"infer" let someGlobal = 'joe'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md index 836d0fe7d9..af8103b7ae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { let Component; if (props.cond) { @@ -18,7 +18,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(3); let Component; @@ -50,9 +50,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":10,"index":194},"end":{"line":9,"column":19,"index":203},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":5,"column":16,"index":116},"end":{"line":5,"column":33,"index":133},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":10,"column":1,"index":209},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":10,"index":202},"end":{"line":9,"column":19,"index":211},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":5,"column":16,"index":124},"end":{"line":5,"column":33,"index":141},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":10,"column":1,"index":217},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js index 580d330728..6ff7ff321c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { let Component; if (props.cond) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md index a222bd44ca..7720863da3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = createComponent(); return ; @@ -13,7 +13,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(1); let t0; @@ -32,9 +32,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":112},"end":{"line":4,"column":19,"index":121},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":37,"index":100},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":127},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":120},"end":{"line":4,"column":19,"index":129},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":37,"index":108},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":135},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js index ebbb2b239d..209cc83d65 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = createComponent(); return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md index 41339bd8c4..8d218bf24b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { function Component() { return
; @@ -15,7 +15,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(1); let t0; @@ -37,9 +37,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":6,"column":10,"index":122},"end":{"line":6,"column":19,"index":131},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":2,"index":65},"end":{"line":5,"column":3,"index":111},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":7,"column":1,"index":137},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":6,"column":10,"index":130},"end":{"line":6,"column":19,"index":139},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":2,"index":73},"end":{"line":5,"column":3,"index":119},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":7,"column":1,"index":145},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js index 1ce24193d2..e48712dbf7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { function Component() { return
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md index c53cf894cd..e3bc7a5eb5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = props.foo.bar(); return ; @@ -13,7 +13,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(4); let t0; @@ -41,9 +41,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":110},"end":{"line":4,"column":19,"index":119},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":35,"index":98},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":125},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":118},"end":{"line":4,"column":19,"index":127},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":35,"index":106},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":133},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js index 01c3fb85f9..7f43ffacbd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = props.foo.bar(); return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md index d45f263adb..02e9f4f4a4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = new ComponentFactory(); return ; @@ -13,7 +13,7 @@ function Example(props) { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @logger @validateStaticComponents +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateStaticComponents function Example(props) { const $ = _c(1); let t0; @@ -32,9 +32,9 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":117},"end":{"line":4,"column":19,"index":126},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":83},"end":{"line":3,"column":42,"index":105},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":37},"end":{"line":5,"column":1,"index":132},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":125},"end":{"line":4,"column":19,"index":134},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":42,"index":113},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":140},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js index 4d5ba62d1e..a735e076d9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.js @@ -1,4 +1,4 @@ -// @logger @validateStaticComponents +// @loggerTestOnly @validateStaticComponents function Example(props) { const Component = new ComponentFactory(); return ; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md index acf34a474c..6c81defa1b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag-meta-internal.expect.md @@ -19,7 +19,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react"; // @target="donotuse_meta_internal" +import { c as _c } from "react/compiler-runtime"; // @target="donotuse_meta_internal" function Component() { const $ = _c(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md index b4cf41e369..b27b786d57 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/target-flag.expect.md @@ -19,7 +19,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react-compiler-runtime"; // @target="18" +import { c as _c } from "react/compiler-runtime"; // @target="18" function Component() { const $ = _c(1); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/template-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/template-literal.expect.md index ffdc91c8c3..cdc9738d65 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/template-literal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/template-literal.expect.md @@ -20,7 +20,7 @@ function componentB(props) { ```javascript function componentA(props) { let t = `hello ${props.a}, ${props.b}!`; - t = t + ``; + t = t + ""; return t; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md index 77bdd31275..b11be5668d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateNoCapitalizedCalls @enableFire @panicThreshold(none) +// @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" import {fire} from 'react'; const CapitalizedCall = require('shared-runtime').sum; @@ -24,7 +24,7 @@ function Component({prop1, bar}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" import { fire } from "react"; const CapitalizedCall = require("shared-runtime").sum; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js index 679945eaab..bd9715e91a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-capitalized-fn-call.js @@ -1,4 +1,4 @@ -// @validateNoCapitalizedCalls @enableFire @panicThreshold(none) +// @validateNoCapitalizedCalls @enableFire @panicThreshold:"none" import {fire} from 'react'; const CapitalizedCall = require('shared-runtime').sum; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md index 30bd6d42e5..99774bdd3e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {useRef} from 'react'; function Component({props, bar}) { @@ -26,7 +26,7 @@ function Component({props, bar}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { useRef } from "react"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js index 5312e5707c..be89a6d3b9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-eslint-suppressions.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {useRef} from 'react'; function Component({props, bar}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md index 6477a01126..1a22df2941 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none) +// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold:"none" import {fire} from 'react'; import {sum} from 'shared-runtime'; @@ -24,7 +24,7 @@ function Component({prop1, bar}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold:"none" import { fire } from "react"; import { sum } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js index c3bb8b4216..4ba2de0eb1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-preserve-memo.js @@ -1,4 +1,4 @@ -// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold(none) +// @validatePreserveExistingMemoizationGuarantees @enableFire @panicThreshold:"none" import {fire} from 'react'; import {sum} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md index e6ce051f10..0f1d745537 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; function Component({prop1}) { @@ -20,7 +20,7 @@ function Component({prop1}) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { fire } from "react"; function Component(t0) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js index b0cfd4fe39..6b250a1692 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-prop-write.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; function Component({prop1}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md index 79f5a2986d..b55526e921 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @enableFire @panicThreshold(none) +// @flow @enableFire @panicThreshold:"none" import {fire} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js index f649fe5902..1dcb5b5c47 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/bailout-validate-ref-current-access.js @@ -1,4 +1,4 @@ -// @flow @enableFire @panicThreshold(none) +// @flow @enableFire @panicThreshold:"none" import {fire} from 'react'; import {print} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md index c5d7456d65..aa3d989296 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js index b1ee459177..52c224e321 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md index ddcc86ff00..0141ffb8ad 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; console.log(fire == null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js index 25a60a9716..afeead0812 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; console.log(fire == null); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md index 84a27b43b8..275012351c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js index 92e1ff211a..685309a250 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md index 42c800f8e5..36ed7a3d36 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useRef} from 'react'; import {useSpecialEffect} from 'shared-runtime'; @@ -25,7 +25,7 @@ function useFoo({cond}) { ## Code ```javascript -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import { useRef } from "react"; import { useSpecialEffect } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js index f3a57bb912..4f6b908b53 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/infer-deps-on-retry.js @@ -1,4 +1,4 @@ -// @inferEffectDependencies @panicThreshold(none) +// @inferEffectDependencies @panicThreshold:"none" import {useRef} from 'react'; import {useSpecialEffect} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md index 7ba4ee2811..06268ea854 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** @@ -43,7 +43,7 @@ function FireComponent(props) { ## Code ```javascript -import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { c as _c, useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { fire } from "react"; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js index 899fa33376..e11ab6b6de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/no-fire-todo-syntax-shouldnt-throw.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire} from 'react'; /** diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md index dde2b692f4..2c81d693b1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire, useEffect} from 'react'; import {Stringify} from 'shared-runtime'; @@ -29,7 +29,7 @@ function Component(props) { ## Code ```javascript -import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold(none) +import { useFire } from "react/compiler-runtime"; // @enableFire @panicThreshold:"none" import { fire, useEffect } from "react"; import { Stringify } from "shared-runtime"; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js index fa8c034bfb..602f121ad7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-validate-conditional-hook.js @@ -1,4 +1,4 @@ -// @enableFire @panicThreshold(none) +// @enableFire @panicThreshold:"none" import {fire, useEffect} from 'react'; import {Stringify} from 'shared-runtime'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md index c7ed50ceba..75c46e196e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire import {useEffect, fire} from 'react'; function Component(props, useDynamicHook) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js index 077982e8d4..5fc11c9a82 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/repro-dont-add-hook-guards-on-retry.js @@ -1,4 +1,4 @@ -// @flow @enableEmitHookGuards @panicThreshold(none) @enableFire +// @flow @enableEmitHookGuards @panicThreshold:"none" @enableFire import {useEffect, fire} from 'react'; function Component(props, useDynamicHook) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md index f0c6bce342..2a419a6415 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" // unclosed disable rule should affect all components /* eslint-disable react-hooks/rules-of-hooks */ @@ -20,7 +20,7 @@ function ValidComponent2(props) { ## Code ```javascript -// @panicThreshold(none) +// @panicThreshold:"none" // unclosed disable rule should affect all components /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js index 121f100418..a3e2bab77e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unclosed-eslint-suppression-skips-all-components.js @@ -1,4 +1,4 @@ -// @panicThreshold(none) +// @panicThreshold:"none" // unclosed disable rule should affect all components /* eslint-disable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unlabeled-break-within-label-switch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unlabeled-break-within-label-switch.expect.md index 579905a649..a990ee04e5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unlabeled-break-within-label-switch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/unlabeled-break-within-label-switch.expect.md @@ -40,14 +40,14 @@ function useHook(cond) { log = []; bb0: switch (CONST_STRING0) { case CONST_STRING0: { - log.push(`@A`); + log.push("@A"); if (cond) { break bb0; } - log.push(`@B`); + log.push("@B"); - log.push(`@C`); + log.push("@C"); } } $[0] = cond; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md new file mode 100644 index 0000000000..c47501945b --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.expect.md @@ -0,0 +1,37 @@ + +## Input + +```javascript +// @noEmit + +function Foo() { + 'use memo'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], +}; + +``` + +## Code + +```javascript +// @noEmit + +function Foo() { + "use memo"; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], +}; + +``` + +### Eval output +(kind: ok) \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.js new file mode 100644 index 0000000000..04ec880761 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-memo-noemit.js @@ -0,0 +1,11 @@ +// @noEmit + +function Foo() { + 'use memo'; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Foo, + params: [], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md index 69c1b9bbbb..375a38664e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @compilationMode(all) +// @compilationMode:"all" 'use no memo'; function TestComponent({x}) { @@ -15,7 +15,7 @@ function TestComponent({x}) { ## Code ```javascript -// @compilationMode(all) +// @compilationMode:"all" "use no memo"; function TestComponent({ x }) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js index 9b314e1f99..21eec7ab2c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-memo-module-scope-usememo-function-scope.js @@ -1,4 +1,4 @@ -// @compilationMode(all) +// @compilationMode:"all" 'use no memo'; function TestComponent({x}) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index 60865b8aa7..086e010fea 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -30,7 +30,6 @@ export { export { Effect, ValueKind, - parseConfigPragmaForTests, printHIR, printFunctionWithOutlined, validateEnvironmentConfig, @@ -43,6 +42,7 @@ export { printReactiveFunction, printReactiveFunctionWithOutlined, } from './ReactiveScopes'; +export {parseConfigPragmaForTests} from './Utils/TestUtils'; declare global { let __DEV__: boolean | null | undefined; } diff --git a/compiler/packages/react-mcp-server/src/index.ts b/compiler/packages/react-mcp-server/src/index.ts index 138dc57dc1..2ec747eac4 100644 --- a/compiler/packages/react-mcp-server/src/index.ts +++ b/compiler/packages/react-mcp-server/src/index.ts @@ -22,6 +22,12 @@ import assertExhaustive from './utils/assertExhaustive'; import {convert} from 'html-to-text'; import {measurePerformance} from './tools/runtimePerf'; +function calculateMean(values: number[]): string { + return values.length > 0 + ? values.reduce((acc, curr) => acc + curr, 0) / values.length + 'ms' + : 'could not collect'; +} + const server = new McpServer({ name: 'React', version: '0.0.0', @@ -326,17 +332,16 @@ server.tool( # React Component Performance Results ## Mean Render Time -${results.renderTime / iterations}ms +${calculateMean(results.renderTime)} ## Mean Web Vitals -- Cumulative Layout Shift (CLS): ${results.webVitals.cls / iterations}ms -- Largest Contentful Paint (LCP): ${results.webVitals.lcp / iterations}ms -- Interaction to Next Paint (INP): ${results.webVitals.inp / iterations}ms -- First Input Delay (FID): ${results.webVitals.fid / iterations}ms +- Cumulative Layout Shift (CLS): ${calculateMean(results.webVitals.cls)} +- Largest Contentful Paint (LCP): ${calculateMean(results.webVitals.lcp)} +- Interaction to Next Paint (INP): ${calculateMean(results.webVitals.inp)} ## Mean React Profiler -- Actual Duration: ${results.reactProfiler.actualDuration / iterations}ms -- Base Duration: ${results.reactProfiler.baseDuration / iterations}ms +- Actual Duration: ${calculateMean(results.reactProfiler.actualDuration)} +- Base Duration: ${calculateMean(results.reactProfiler.baseDuration)} `; return { diff --git a/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts b/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts index 7f4d0a1efe..30badc833d 100644 --- a/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts +++ b/compiler/packages/react-mcp-server/src/tools/runtimePerf.ts @@ -8,25 +8,51 @@ import * as babelPresetEnv from '@babel/preset-env'; import * as babelPresetReact from '@babel/preset-react'; type PerformanceResults = { - renderTime: number; + renderTime: number[]; webVitals: { - cls: number; - lcp: number; - inp: number; - fid: number; - ttfb: number; + cls: number[]; + lcp: number[]; + inp: number[]; + fid: number[]; + ttfb: number[]; }; reactProfiler: { - id: number; - phase: number; - actualDuration: number; - baseDuration: number; - startTime: number; - commitTime: number; + id: number[]; + phase: number[]; + actualDuration: number[]; + baseDuration: number[]; + startTime: number[]; + commitTime: number[]; }; error: Error | null; }; +type EvaluationResults = { + renderTime: number | null; + webVitals: { + cls: number | null; + lcp: number | null; + inp: number | null; + fid: number | null; + ttfb: number | null; + }; + reactProfiler: { + id: number | null; + phase: number | null; + actualDuration: number | null; + baseDuration: number | null; + startTime: number | null; + commitTime: number | null; + }; + error: Error | null; +}; + +function delay(time: number) { + return new Promise(function (resolve) { + setTimeout(resolve, time); + }); +} + export async function measurePerformance( code: string, iterations: number, @@ -72,21 +98,21 @@ export async function measurePerformance( const html = buildHtml(transpiled); let performanceResults: PerformanceResults = { - renderTime: 0, + renderTime: [], webVitals: { - cls: 0, - lcp: 0, - inp: 0, - fid: 0, - ttfb: 0, + cls: [], + lcp: [], + inp: [], + fid: [], + ttfb: [], }, reactProfiler: { - id: 0, - phase: 0, - actualDuration: 0, - baseDuration: 0, - startTime: 0, - commitTime: 0, + id: [], + phase: [], + actualDuration: [], + baseDuration: [], + startTime: [], + commitTime: [], }, error: null, }; @@ -96,38 +122,73 @@ export async function measurePerformance( await page.waitForFunction( 'window.__RESULT__ !== undefined && (window.__RESULT__.renderTime !== null || window.__RESULT__.error !== null)', ); + // ui chaos monkey - await page.waitForFunction(`window.__RESULT__ !== undefined && (function() { - for (const el of [...document.querySelectorAll('a'), ...document.querySelectorAll('button')]) { - console.log(el); - el.click(); + const selectors = await page.evaluate(() => { + window.__INTERACTABLE_SELECTORS__ = []; + const elements = Array.from(document.querySelectorAll('a')).concat( + Array.from(document.querySelectorAll('button')), + ); + for (const el of elements) { + window.__INTERACTABLE_SELECTORS__.push(el.tagName.toLowerCase()); } - return true; - })() `); - const evaluationResult: PerformanceResults = await page.evaluate(() => { + return window.__INTERACTABLE_SELECTORS__; + }); + + await Promise.all( + selectors.map(async (selector: string) => { + try { + await page.click(selector); + } catch (e) { + console.log(`warning: Could not click ${selector}: ${e.message}`); + } + }), + ); + await delay(500); + + // Visit a new page for 1s to background the current page so that WebVitals can finish being calculated + const tempPage = await browser.newPage(); + await tempPage.evaluate(() => { + return new Promise(resolve => { + setTimeout(() => { + resolve(true); + }, 1000); + }); + }); + await tempPage.close(); + + const evaluationResult: EvaluationResults = await page.evaluate(() => { return (window as any).__RESULT__; }); - // TODO: investigate why webvital metrics are not populating correctly - performanceResults.renderTime += evaluationResult.renderTime; - performanceResults.webVitals.cls += evaluationResult.webVitals.cls || 0; - performanceResults.webVitals.lcp += evaluationResult.webVitals.lcp || 0; - performanceResults.webVitals.inp += evaluationResult.webVitals.inp || 0; - performanceResults.webVitals.fid += evaluationResult.webVitals.fid || 0; - performanceResults.webVitals.ttfb += evaluationResult.webVitals.ttfb || 0; + if (evaluationResult.renderTime !== null) { + performanceResults.renderTime.push(evaluationResult.renderTime); + } - performanceResults.reactProfiler.id += - evaluationResult.reactProfiler.actualDuration || 0; - performanceResults.reactProfiler.phase += - evaluationResult.reactProfiler.phase || 0; - performanceResults.reactProfiler.actualDuration += - evaluationResult.reactProfiler.actualDuration || 0; - performanceResults.reactProfiler.baseDuration += - evaluationResult.reactProfiler.baseDuration || 0; - performanceResults.reactProfiler.startTime += - evaluationResult.reactProfiler.startTime || 0; - performanceResults.reactProfiler.commitTime += - evaluationResult.reactProfiler.commitTime || 0; + const webVitalMetrics = ['cls', 'lcp', 'inp', 'fid', 'ttfb'] as const; + for (const metric of webVitalMetrics) { + if (evaluationResult.webVitals[metric] !== null) { + performanceResults.webVitals[metric].push( + evaluationResult.webVitals[metric], + ); + } + } + + const profilerMetrics = [ + 'id', + 'phase', + 'actualDuration', + 'baseDuration', + 'startTime', + 'commitTime', + ] as const; + for (const metric of profilerMetrics) { + if (evaluationResult.reactProfiler[metric] !== null) { + performanceResults.reactProfiler[metric].push( + evaluationResult.reactProfiler[metric], + ); + } + } performanceResults.error = evaluationResult.error; } @@ -159,14 +220,14 @@ function buildHtml(transpiled: string) { renderTime: null, webVitals: {}, reactProfiler: {}, - error: null + error: null, }; - webVitals.onCLS((metric) => { window.__RESULT__.webVitals.cls = metric; }); - webVitals.onLCP((metric) => { window.__RESULT__.webVitals.lcp = metric; }); - webVitals.onINP((metric) => { window.__RESULT__.webVitals.inp = metric; }); - webVitals.onFID((metric) => { window.__RESULT__.webVitals.fid = metric; }); - webVitals.onTTFB((metric) => { window.__RESULT__.webVitals.ttfb = metric; }); + webVitals.onCLS(({value}) => { window.__RESULT__.webVitals.cls = value; }); + webVitals.onLCP(({value}) => { window.__RESULT__.webVitals.lcp = value; }); + webVitals.onINP(({value}) => { window.__RESULT__.webVitals.inp = value; }); + webVitals.onFID(({value}) => { window.__RESULT__.webVitals.fid = value; }); + webVitals.onTTFB(({value}) => { window.__RESULT__.webVitals.ttfb = value; }); try { ${transpiled} diff --git a/compiler/packages/snap/src/compiler.ts b/compiler/packages/snap/src/compiler.ts index 6fce644542..0cadf30bf0 100644 --- a/compiler/packages/snap/src/compiler.ts +++ b/compiler/packages/snap/src/compiler.ts @@ -19,11 +19,7 @@ import type { CompilerPipelineValue, } from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Effect, ValueKind} from 'babel-plugin-react-compiler/src/HIR'; -import type { - Macro, - MacroMethod, - parseConfigPragmaForTests as ParseConfigPragma, -} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils'; import * as HermesParser from 'hermes-parser'; import invariant from 'invariant'; import path from 'path'; @@ -47,52 +43,10 @@ function makePluginOptions( EffectEnum: typeof Effect, ValueKindEnum: typeof ValueKind, ): [PluginOptions, Array<{filename: string | null; event: LoggerEvent}>] { - let gating = null; - let hookPattern: string | null = null; // TODO(@mofeiZ) rewrite snap fixtures to @validatePreserveExistingMemo:false let validatePreserveExistingMemoizationGuarantees = false; - let customMacros: null | Array = null; - let validateBlocklistedImports = null; let target: CompilerReactTarget = '19'; - if (firstLine.includes('@gating')) { - gating = { - source: 'ReactForgetFeatureFlag', - importSpecifierName: 'isForgetEnabled_Fixtures', - }; - } - - const targetMatch = /@target="([^"]+)"/.exec(firstLine); - if (targetMatch) { - if (targetMatch[1] === 'donotuse_meta_internal') { - target = { - kind: targetMatch[1], - runtimeModule: 'react', - }; - } else { - // @ts-ignore - target = targetMatch[1]; - } - } - - let eslintSuppressionRules: Array | null = null; - const eslintSuppressionMatch = /@eslintSuppressionRules\(([^)]+)\)/.exec( - firstLine, - ); - if (eslintSuppressionMatch != null) { - eslintSuppressionRules = eslintSuppressionMatch[1].split('|'); - } - - let flowSuppressions: boolean = false; - if (firstLine.includes('@enableFlowSuppressions')) { - flowSuppressions = true; - } - - let ignoreUseNoForget: boolean = false; - if (firstLine.includes('@ignoreUseNoForget')) { - ignoreUseNoForget = true; - } - /** * Snap currently runs all fixtures without `validatePreserveExistingMemo` as * most fixtures are interested in compilation output, not whether the @@ -105,64 +59,9 @@ function makePluginOptions( validatePreserveExistingMemoizationGuarantees = true; } - const hookPatternMatch = /@hookPattern:"([^"]+)"/.exec(firstLine); - if ( - hookPatternMatch && - hookPatternMatch.length > 1 && - hookPatternMatch[1].trim().length > 0 - ) { - hookPattern = hookPatternMatch[1].trim(); - } else if (firstLine.includes('@hookPattern')) { - throw new Error( - 'Invalid @hookPattern:"..." pragma, must contain the prefix between balanced double quotes eg @hookPattern:"pattern"', - ); - } - - const customMacrosMatch = /@customMacros\(([^)]+)\)/.exec(firstLine); - if ( - customMacrosMatch && - customMacrosMatch.length > 1 && - customMacrosMatch[1].trim().length > 0 - ) { - const customMacrosStrs = customMacrosMatch[1] - .split(' ') - .map(s => s.trim()) - .filter(s => s.length > 0); - if (customMacrosStrs.length > 0) { - customMacros = []; - for (const customMacroStr of customMacrosStrs) { - const props: Array = []; - const customMacroSplit = customMacroStr.split('.'); - if (customMacroSplit.length > 0) { - for (const elt of customMacroSplit.slice(1)) { - if (elt === '*') { - props.push({type: 'wildcard'}); - } else if (elt.length > 0) { - props.push({type: 'name', name: elt}); - } - } - customMacros.push([customMacroSplit[0], props]); - } - } - } - } - - const validateBlocklistedImportsMatch = - /@validateBlocklistedImports\(([^)]+)\)/.exec(firstLine); - if ( - validateBlocklistedImportsMatch && - validateBlocklistedImportsMatch.length > 1 && - validateBlocklistedImportsMatch[1].trim().length > 0 - ) { - validateBlocklistedImports = validateBlocklistedImportsMatch[1] - .split(' ') - .map(s => s.trim()) - .filter(s => s.length > 0); - } - const logs: Array<{filename: string | null; event: LoggerEvent}> = []; const logger: Logger = { - logEvent: firstLine.includes('@logger') + logEvent: firstLine.includes('@loggerTestOnly') ? (filename, event) => { logs.push({filename, event}); } @@ -179,17 +78,10 @@ function makePluginOptions( EffectEnum, ValueKindEnum, }), - customMacros, assertValidMutableRanges: true, - hookPattern, validatePreserveExistingMemoizationGuarantees, - validateBlocklistedImports, }, logger, - gating, - eslintSuppressionRules, - flowSuppressions, - ignoreUseNoForget, enableReanimatedCheck: false, target, }; diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index a72acf34db..2478e6a545 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -7,7 +7,7 @@ import {codeFrameColumns} from '@babel/code-frame'; import type {PluginObj} from '@babel/core'; -import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/HIR/Environment'; +import type {parseConfigPragmaForTests as ParseConfigPragma} from 'babel-plugin-react-compiler/src/Utils/TestUtils'; import type {printFunctionWithOutlined as PrintFunctionWithOutlined} from 'babel-plugin-react-compiler/src/HIR/PrintHIR'; import type {printReactiveFunctionWithOutlined as PrintReactiveFunctionWithOutlined} from 'babel-plugin-react-compiler/src/ReactiveScopes/PrintReactiveFunction'; import {TransformResult, transformFixtureInput} from './compiler'; diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/EventDispatchCase.js b/fixtures/dom/src/components/fixtures/fragment-refs/EventDispatchCase.js new file mode 100644 index 0000000000..ef5a959021 --- /dev/null +++ b/fixtures/dom/src/components/fixtures/fragment-refs/EventDispatchCase.js @@ -0,0 +1,157 @@ +import TestCase from '../../TestCase'; +import Fixture from '../../Fixture'; + +const React = window.React; +const {Fragment, useRef, useState} = React; + +function WrapperComponent(props) { + return props.children; +} + +const initialState = { + child: false, + parent: false, + grandparent: false, +}; + +export default function EventListenerCase() { + const fragmentRef = useRef(null); + const [clickedState, setClickedState] = useState({...initialState}); + const [fragmentEventFired, setFragmentEventFired] = useState(false); + const [bubblesState, setBubblesState] = useState(true); + + function setClick(id) { + setClickedState(prev => ({...prev, [id]: true})); + } + + function fragmentClickHandler(e) { + setFragmentEventFired(true); + } + + return ( + + +
  • + Each box has regular click handlers, you can click each one to observe + the status changing through standard bubbling. +
  • +
  • Clear the clicked state
  • +
  • + Click the "Dispatch click event" button to dispatch a click event on + the Fragment. The event will be dispatched on the Fragment's parent, + so the child will not change state. +
  • +
  • + Click the "Add event listener" button to add a click event listener on + the Fragment. This registers a handler that will turn the child blue + on click. +
  • +
  • + Now click the "Dispatch click event" button again. You can see that it + will fire the Fragment's event handler in addition to bubbling the + click from the parent. +
  • +
  • + If you turn off bubbling, only the Fragment's event handler will be + called. +
  • +
    + + +

    + Dispatching an event on a Fragment will forward the dispatch to its + parent for the standard case. You can observe when dispatching that + the parent handler is called in additional to bubbling from there. A + delay is added to make the bubbling more clear.{' '} +

    +

    + When there have been event handlers added to the Fragment, the + Fragment's event handler will be called in addition to bubbling from + the parent. Without bubbling, only the Fragment's event handler will + be called. +

    +
    + + + + + + + + + +
    { + setTimeout(() => { + setClick('grandparent'); + }, 200); + }} + className="card"> + Fragment grandparent - clicked:{' '} + {clickedState.grandparent ? 'true' : 'false'} +
    { + setTimeout(() => { + setClick('parent'); + }, 100); + }} + className="card"> + Fragment parent - clicked: {clickedState.parent ? 'true' : 'false'} + +
    { + setClick('child'); + }}> + Fragment child - clicked:{' '} + {clickedState.child ? 'true' : 'false'} +
    +
    +
    +
    +
    +
    + ); +} diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/FocusCase.js b/fixtures/dom/src/components/fixtures/fragment-refs/FocusCase.js index 888107c9e0..baff30895c 100644 --- a/fixtures/dom/src/components/fixtures/fragment-refs/FocusCase.js +++ b/fixtures/dom/src/components/fixtures/fragment-refs/FocusCase.js @@ -43,11 +43,18 @@ export default function FocusCase() {
    -
    Unfocusable div
    - +
    +

    Unfocusable div

    +
    +
    +

    Unfocusable div with nested focusable button

    + +
    -
    Unfocusable div
    +
    +

    Unfocusable div

    +
    diff --git a/fixtures/dom/src/components/fixtures/fragment-refs/index.js b/fixtures/dom/src/components/fixtures/fragment-refs/index.js index b84f273177..23b440938c 100644 --- a/fixtures/dom/src/components/fixtures/fragment-refs/index.js +++ b/fixtures/dom/src/components/fixtures/fragment-refs/index.js @@ -1,5 +1,6 @@ import FixtureSet from '../../FixtureSet'; import EventListenerCase from './EventListenerCase'; +import EventDispatchCase from './EventDispatchCase'; import IntersectionObserverCase from './IntersectionObserverCase'; import ResizeObserverCase from './ResizeObserverCase'; import FocusCase from './FocusCase'; @@ -11,6 +12,7 @@ export default function FragmentRefsPage() { return ( + diff --git a/fixtures/dom/src/style.css b/fixtures/dom/src/style.css index 66fda7afe0..e507014d68 100644 --- a/fixtures/dom/src/style.css +++ b/fixtures/dom/src/style.css @@ -365,6 +365,10 @@ tbody tr:nth-child(even) { background-color: green; } +.highlight-focused-children * { + margin-left: 10px; +} + .highlight-focused-children *:focus { outline: 2px solid green; } diff --git a/fixtures/ssr/src/components/Page.js b/fixtures/ssr/src/components/Page.js index d7b4d5c813..1a4c6f79a3 100644 --- a/fixtures/ssr/src/components/Page.js +++ b/fixtures/ssr/src/components/Page.js @@ -11,10 +11,17 @@ const autofocusedInputs = [ ]; export default class Page extends Component { - state = {active: false}; + state = {active: false, value: ''}; handleClick = e => { this.setState({active: true}); }; + handleChange = e => { + this.setState({value: e.target.value}); + }; + componentDidMount() { + // Rerender on mount + this.setState({mounted: true}); + } render() { const link = ( @@ -30,6 +37,10 @@ export default class Page extends Component {

    Autofocus on page load: {autofocusedInputs}

    {!this.state.active ? link : 'Thanks!'}

    {this.state.active &&

    Autofocus on update: {autofocusedInputs}

    } +

    + Controlled input:{' '} + +

    ); diff --git a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts index 6be40df532..ac7d0f3a06 100644 --- a/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts +++ b/packages/eslint-plugin-react-hooks/src/rules/RulesOfHooks.ts @@ -128,7 +128,7 @@ const rule = { docs: { description: 'enforces the Rules of Hooks', recommended: true, - url: 'https://reactjs.org/docs/hooks-rules.html', + url: 'https://react.dev/reference/rules/rules-of-hooks', }, }, create(context: Rule.RuleContext) { diff --git a/packages/react-art/src/ReactART.js b/packages/react-art/src/ReactART.js index d0d747396b..f6756ba633 100644 --- a/packages/react-art/src/ReactART.js +++ b/packages/react-art/src/ReactART.js @@ -13,7 +13,11 @@ import { updateContainerSync, injectIntoDevTools, flushSyncWork, + defaultOnUncaughtError, + defaultOnCaughtError, + defaultOnRecoverableError, } from 'react-reconciler/src/ReactFiberReconciler'; + import Transform from 'art/core/transform'; import Mode from 'art/modes/current'; import FastNoSideEffects from 'art/modes/fast-noSideEffects'; @@ -21,6 +25,10 @@ import {disableLegacyMode} from 'shared/ReactFeatureFlags'; import {TYPES, childrenAsString} from './ReactARTInternals'; +function defaultOnDefaultTransitionIndicator() { + // Noop +} + Mode.setCurrent( // Change to 'art/modes/dom' for easier debugging via SVG FastNoSideEffects, @@ -75,6 +83,11 @@ class Surface extends React.Component { false, false, '', + defaultOnUncaughtError, + defaultOnCaughtError, + defaultOnRecoverableError, + defaultOnDefaultTransitionIndicator, + null, ); // We synchronously flush updates coming from above so that they commit together // and so that refs resolve before the parent life cycles. diff --git a/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js b/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js index 0c75abf06b..c9247f6a88 100644 --- a/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js +++ b/packages/react-cache/src/__tests__/ReactCacheOld-test.internal.js @@ -126,8 +126,8 @@ describe('ReactCache', () => { await waitForAll([ 'Suspend! [Hi]', 'Loading...', - - ...(gate('enableSiblingPrerendering') ? ['Suspend! [Hi]'] : []), + // pre-warming + 'Suspend! [Hi]', ]); jest.advanceTimersByTime(100); @@ -150,8 +150,8 @@ describe('ReactCache', () => { await waitForAll([ 'Suspend! [Hi]', 'Loading...', - - ...(gate('enableSiblingPrerendering') ? ['Suspend! [Hi]'] : []), + // pre-warming + 'Suspend! [Hi]', ]); textResourceShouldFail = true; @@ -195,8 +195,8 @@ describe('ReactCache', () => { await waitForAll([ 'App', 'Loading...', - - ...(gate('enableSiblingPrerendering') ? ['App'] : []), + // pre-warming + 'App', ]); assertConsoleErrorDev([ 'Invalid key type. Expected a string, number, symbol, or ' + @@ -204,27 +204,24 @@ describe('ReactCache', () => { 'To use non-primitive values as keys, you must pass a hash ' + 'function as the second argument to createResource().\n' + ' in App (at **)', - ...(gate('enableSiblingPrerendering') - ? [ - 'Invalid key type. Expected a string, number, symbol, or ' + - "boolean, but instead received: [ 'Hi', 100 ]\n\n" + - 'To use non-primitive values as keys, you must pass a hash ' + - 'function as the second argument to createResource().\n' + - ' in App (at **)', - ] - : []), + + // pre-warming + 'Invalid key type. Expected a string, number, symbol, or ' + + "boolean, but instead received: [ 'Hi', 100 ]\n\n" + + 'To use non-primitive values as keys, you must pass a hash ' + + 'function as the second argument to createResource().\n' + + ' in App (at **)', ]); } else { await waitForAll([ 'App', 'Loading...', - - ...(gate('enableSiblingPrerendering') ? ['App'] : []), + // pre-warming + 'App', ]); } }); - // @gate enableSiblingPrerendering it('evicts least recently used values', async () => { ReactCache.unstable_setGlobalCacheLimit(3); @@ -240,15 +237,28 @@ describe('ReactCache', () => { await waitForPaint(['Suspend! [1]', 'Loading...']); jest.advanceTimersByTime(100); assertLog(['Promise resolved [1]']); - await waitForAll([1, 'Suspend! [2]']); + await waitForAll([ + 1, + 'Suspend! [2]', + ...(gate('alwaysThrottleRetries') + ? [] + : [1, 'Suspend! [2]', 'Suspend! [3]']), + ]); jest.advanceTimersByTime(100); - assertLog(['Promise resolved [2]']); - await waitForAll([1, 2, 'Suspend! [3]']); + assertLog([ + 'Promise resolved [2]', + ...(gate('alwaysThrottleRetries') ? [] : ['Promise resolved [3]']), + ]); + await waitForAll([ + 1, + 2, + ...(gate('alwaysThrottleRetries') ? ['Suspend! [3]'] : [3]), + ]); jest.advanceTimersByTime(100); - assertLog(['Promise resolved [3]']); - await waitForAll([1, 2, 3]); + assertLog(gate('alwaysThrottleRetries') ? ['Promise resolved [3]'] : []); + await waitForAll(gate('alwaysThrottleRetries') ? [1, 2, 3] : []); await act(() => jest.advanceTimersByTime(100)); expect(root).toMatchRenderedOutput('123'); @@ -378,8 +388,8 @@ describe('ReactCache', () => { await waitForAll([ 'Suspend! [Hi]', 'Loading...', - - ...(gate('enableSiblingPrerendering') ? ['Suspend! [Hi]'] : []), + // pre-warming + 'Suspend! [Hi]', ]); resolveThenable('Hi'); diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 3234814952..7d6bbd5c1f 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -15,7 +15,7 @@ import type { ReactAsyncInfo, ReactTimeInfo, ReactStackTrace, - ReactCallSite, + ReactFunctionLocation, ReactErrorInfoDev, } from 'shared/ReactTypes'; import type {LazyComponent} from 'react/src/ReactLazy'; @@ -1074,7 +1074,7 @@ function loadServerReference, T>( bound: null | Thenable>, name?: string, // DEV-only env?: string, // DEV-only - location?: ReactCallSite, // DEV-only + location?: ReactFunctionLocation, // DEV-only }, parentObject: Object, key: string, @@ -2226,6 +2226,8 @@ function createFakeFunction( sourceMap: null | string, line: number, col: number, + enclosingLine: number, + enclosingCol: number, environmentName: string, ): FakeFunction { // This creates a fake copy of a Server Module. It represents a module that has already @@ -2243,24 +2245,102 @@ function createFakeFunction( // This allows us to use the original source map as the source map of this fake file to // point to the original source. let code; - if (line <= 1) { - const minSize = encodedName.length + 7; - code = - '({' + - encodedName + - ':_=>' + - ' '.repeat(col < minSize ? 0 : col - minSize) + - '_()})\n' + - comment; + // Normalize line/col to zero based. + if (enclosingLine < 1) { + enclosingLine = 0; } else { + enclosingLine--; + } + if (enclosingCol < 1) { + enclosingCol = 0; + } else { + enclosingCol--; + } + if (line < 1) { + line = 0; + } else { + line--; + } + if (col < 1) { + col = 0; + } else { + col--; + } + if (line < enclosingLine || (line === enclosingLine && col < enclosingCol)) { + // Protection against invalid enclosing information. Should not happen. + enclosingLine = 0; + enclosingCol = 0; + } + if (line < 1) { + // Fit everything on the first line. + const minCol = encodedName.length + 3; + let enclosingColDistance = enclosingCol - minCol; + if (enclosingColDistance < 0) { + enclosingColDistance = 0; + } + let colDistance = col - enclosingColDistance - minCol - 3; + if (colDistance < 0) { + colDistance = 0; + } code = - comment + - '\n'.repeat(line - 2) + '({' + encodedName + - ':_=>\n' + - ' '.repeat(col < 1 ? 0 : col - 1) + + ':' + + ' '.repeat(enclosingColDistance) + + '_=>' + + ' '.repeat(colDistance) + '_()})'; + } else if (enclosingLine < 1) { + // Fit just the enclosing function on the first line. + const minCol = encodedName.length + 3; + let enclosingColDistance = enclosingCol - minCol; + if (enclosingColDistance < 0) { + enclosingColDistance = 0; + } + code = + '({' + + encodedName + + ':' + + ' '.repeat(enclosingColDistance) + + '_=>' + + '\n'.repeat(line - enclosingLine) + + ' '.repeat(col) + + '_()})'; + } else if (enclosingLine === line) { + // Fit the enclosing function and callsite on same line. + let colDistance = col - enclosingCol - 3; + if (colDistance < 0) { + colDistance = 0; + } + code = + '\n'.repeat(enclosingLine - 1) + + '({' + + encodedName + + ':\n' + + ' '.repeat(enclosingCol) + + '_=>' + + ' '.repeat(colDistance) + + '_()})'; + } else { + // This is the ideal because we can always encode any position. + code = + '\n'.repeat(enclosingLine - 1) + + '({' + + encodedName + + ':\n' + + ' '.repeat(enclosingCol) + + '_=>' + + '\n'.repeat(line - enclosingLine) + + ' '.repeat(col) + + '_()})'; + } + + if (enclosingLine < 1) { + // If the function starts at the first line, we append the comment after. + code = code + '\n' + comment; + } else { + // Otherwise we prepend the comment on the first line. + code = comment + code; } if (filename.startsWith('/')) { @@ -2320,7 +2400,7 @@ function buildFakeCallStack( const frameKey = frame.join('-') + '-' + environmentName; let fn = fakeFunctionCache.get(frameKey); if (fn === undefined) { - const [name, filename, line, col] = frame; + const [name, filename, line, col, enclosingLine, enclosingCol] = frame; const findSourceMapURL = response._debugFindSourceMapURL; const sourceMap = findSourceMapURL ? findSourceMapURL(filename, environmentName) @@ -2331,6 +2411,8 @@ function buildFakeCallStack( sourceMap, line, col, + enclosingLine, + enclosingCol, environmentName, ); // TODO: This cache should technically live on the response since the _debugFindSourceMapURL diff --git a/packages/react-client/src/ReactFlightReplyClient.js b/packages/react-client/src/ReactFlightReplyClient.js index e3c4ec20ba..6a0a37b787 100644 --- a/packages/react-client/src/ReactFlightReplyClient.js +++ b/packages/react-client/src/ReactFlightReplyClient.js @@ -13,7 +13,7 @@ import type { FulfilledThenable, RejectedThenable, ReactCustomFormAction, - ReactCallSite, + ReactFunctionLocation, } from 'shared/ReactTypes'; import type {LazyComponent} from 'react/src/ReactLazy'; import type {TemporaryReferenceSet} from './ReactFlightTemporaryReferences'; @@ -1248,7 +1248,7 @@ export function createBoundServerReference, T>( bound: null | Thenable>, name?: string, // DEV-only env?: string, // DEV-only - location?: ReactCallSite, // DEV-only + location?: ReactFunctionLocation, // DEV-only }, callServer: CallServerCallback, encodeFormAction?: EncodeFormActionCallback, @@ -1309,7 +1309,7 @@ const v8FrameRegExp = // filename:0:0 const jscSpiderMonkeyFrameRegExp = /(?:(.*)@)?(.*):(\d+):(\d+)/; -function parseStackLocation(error: Error): null | ReactCallSite { +function parseStackLocation(error: Error): null | ReactFunctionLocation { // This parsing is special in that we know that the calling function will always // be a module that initializes the server action. We also need this part to work // cross-browser so not worth a Config. It's DEV only so not super code size @@ -1350,6 +1350,7 @@ function parseStackLocation(error: Error): null | ReactCallSite { if (filename === '') { filename = ''; } + // This is really the enclosingLine/Column. const line = +(parsed[3] || parsed[6]); const col = +(parsed[4] || parsed[7]); diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index 4449207e88..b954f32ecd 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -1957,8 +1957,8 @@ describe('ReactFlight', () => { }); expect(ReactNoop).toMatchRenderedOutput( <> -
    -
    +
    +
    , ); }); @@ -1981,8 +1981,8 @@ describe('ReactFlight', () => { }); expect(ReactNoop).toMatchRenderedOutput( <> -
    -
    +
    +
    , ); }); @@ -2021,8 +2021,8 @@ describe('ReactFlight', () => { assertLog(['ClientDoubler']); expect(ReactNoop).toMatchRenderedOutput( <> -
    :S1:
    -
    :S1:
    +
    «S1»
    +
    «S1»
    , ); }); diff --git a/packages/react-devtools-core/package.json b/packages/react-devtools-core/package.json index 6794f63177..581a24edba 100644 --- a/packages/react-devtools-core/package.json +++ b/packages/react-devtools-core/package.json @@ -1,6 +1,6 @@ { "name": "react-devtools-core", - "version": "6.1.1", + "version": "6.1.2", "description": "Use react-devtools outside of the browser", "license": "MIT", "main": "./dist/backend.js", diff --git a/packages/react-devtools-extensions/chrome/manifest.json b/packages/react-devtools-extensions/chrome/manifest.json index 5cf6397bc7..fa4607d1c6 100644 --- a/packages/react-devtools-extensions/chrome/manifest.json +++ b/packages/react-devtools-extensions/chrome/manifest.json @@ -2,9 +2,9 @@ "manifest_version": 3, "name": "React Developer Tools", "description": "Adds React debugging tools to the Chrome Developer Tools.", - "version": "6.1.1", - "version_name": "6.1.1", - "minimum_chrome_version": "102", + "version": "6.1.2", + "version_name": "6.1.2", + "minimum_chrome_version": "114", "icons": { "16": "icons/16-production.png", "32": "icons/32-production.png", diff --git a/packages/react-devtools-extensions/edge/manifest.json b/packages/react-devtools-extensions/edge/manifest.json index 512dd888f7..59d4a8b1ff 100644 --- a/packages/react-devtools-extensions/edge/manifest.json +++ b/packages/react-devtools-extensions/edge/manifest.json @@ -2,9 +2,9 @@ "manifest_version": 3, "name": "React Developer Tools", "description": "Adds React debugging tools to the Microsoft Edge Developer Tools.", - "version": "6.1.1", - "version_name": "6.1.1", - "minimum_chrome_version": "102", + "version": "6.1.2", + "version_name": "6.1.2", + "minimum_chrome_version": "114", "icons": { "16": "icons/16-production.png", "32": "icons/32-production.png", diff --git a/packages/react-devtools-extensions/firefox/manifest.json b/packages/react-devtools-extensions/firefox/manifest.json index 22dee6a10a..3080eadbb0 100644 --- a/packages/react-devtools-extensions/firefox/manifest.json +++ b/packages/react-devtools-extensions/firefox/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "React Developer Tools", "description": "Adds React debugging tools to the Firefox Developer Tools.", - "version": "6.1.1", + "version": "6.1.2", "browser_specific_settings": { "gecko": { "id": "@react-devtools", diff --git a/packages/react-devtools-inline/package.json b/packages/react-devtools-inline/package.json index 2a5227d977..bfa564b73b 100644 --- a/packages/react-devtools-inline/package.json +++ b/packages/react-devtools-inline/package.json @@ -1,6 +1,6 @@ { "name": "react-devtools-inline", - "version": "6.1.1", + "version": "6.1.2", "description": "Embed react-devtools within a website", "license": "MIT", "main": "./dist/backend.js", diff --git a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js index e632227ab3..d46cac42b7 100644 --- a/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js +++ b/packages/react-devtools-shared/src/__tests__/TimelineProfiler-test.js @@ -15,22 +15,11 @@ import { normalizeCodeLocInfo, } from './utils'; -import {ReactVersion} from '../../../../ReactVersions'; -import semver from 'semver'; - let React = require('react'); let Scheduler; let store; let utils; -// TODO: This is how other DevTools tests access the version but we should find -// a better solution for this -const ReactVersionTestingAgainst = process.env.REACT_VERSION || ReactVersion; -// Disabling this while the flag is off in experimental. Leaving the logic so we can -// restore the behavior when we turn the flag back on. -const enableSiblingPrerendering = - false && semver.gte(ReactVersionTestingAgainst, '19.0.0'); - // This flag is on experimental which disables timeline profiler. const enableComponentPerformanceTrack = React.version.startsWith('19') && React.version.includes('experimental'); @@ -1678,8 +1667,8 @@ describe('Timeline profiler', () => { await waitForAll([ 'suspended', - - ...(enableSiblingPrerendering ? ['suspended'] : []), + // pre-warming + 'suspended', ]); Scheduler.unstable_advanceTime(10); @@ -1691,8 +1680,7 @@ describe('Timeline profiler', () => { const timelineData = stopProfilingAndGetTimelineData(); // Verify the Suspense event and duration was recorded. - if (enableSiblingPrerendering) { - expect(timelineData.suspenseEvents).toMatchInlineSnapshot(` + expect(timelineData.suspenseEvents).toMatchInlineSnapshot(` [ { "componentName": "Example", @@ -1720,29 +1708,11 @@ describe('Timeline profiler', () => { }, ] `); - } else { - const suspenseEvent = timelineData.suspenseEvents[0]; - expect(suspenseEvent).toMatchInlineSnapshot(` - { - "componentName": "Example", - "depth": 0, - "duration": 10, - "id": "0", - "phase": "mount", - "promiseName": "", - "resolution": "resolved", - "timestamp": 10, - "type": "suspense", - "warning": null, - } - `); - } // There should be two batches of renders: Suspeneded and resolved. expect(timelineData.batchUIDToMeasuresMap.size).toBe(2); - expect(timelineData.componentMeasures).toHaveLength( - enableSiblingPrerendering ? 3 : 2, - ); + // An additional measure with pre-warming + expect(timelineData.componentMeasures).toHaveLength(3); }); it('should mark concurrent render with suspense that rejects', async () => { @@ -1769,11 +1739,7 @@ describe('Timeline profiler', () => { , ); - await waitForAll([ - 'suspended', - - ...(enableSiblingPrerendering ? ['suspended'] : []), - ]); + await waitForAll(['suspended', 'suspended']); Scheduler.unstable_advanceTime(10); rejectFn(); @@ -1784,8 +1750,7 @@ describe('Timeline profiler', () => { const timelineData = stopProfilingAndGetTimelineData(); // Verify the Suspense event and duration was recorded. - if (enableSiblingPrerendering) { - expect(timelineData.suspenseEvents).toMatchInlineSnapshot(` + expect(timelineData.suspenseEvents).toMatchInlineSnapshot(` [ { "componentName": "Example", @@ -1813,30 +1778,11 @@ describe('Timeline profiler', () => { }, ] `); - } else { - expect(timelineData.suspenseEvents).toHaveLength(1); - const suspenseEvent = timelineData.suspenseEvents[0]; - expect(suspenseEvent).toMatchInlineSnapshot(` - { - "componentName": "Example", - "depth": 0, - "duration": 10, - "id": "0", - "phase": "mount", - "promiseName": "", - "resolution": "rejected", - "timestamp": 10, - "type": "suspense", - "warning": null, - } - `); - } // There should be two batches of renders: Suspeneded and resolved. expect(timelineData.batchUIDToMeasuresMap.size).toBe(2); - expect(timelineData.componentMeasures).toHaveLength( - enableSiblingPrerendering ? 3 : 2, - ); + // An additional measure with pre-warming + expect(timelineData.componentMeasures).toHaveLength(3); }); it('should mark cascading class component state updates', async () => { diff --git a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js index 3e01397546..b9e2cd9068 100644 --- a/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js +++ b/packages/react-devtools-shared/src/backend/views/TraceUpdates/canvas.js @@ -65,6 +65,24 @@ function drawWeb(nodeToData: Map) { drawGroupBorders(context, group); drawGroupLabel(context, group); }); + + if (canvas !== null) { + if (nodeToData.size === 0 && canvas.matches(':popover-open')) { + // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API + canvas.hidePopover(); + return; + } + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API + if (canvas.matches(':popover-open')) { + // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API + canvas.hidePopover(); + } + // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API + canvas.showPopover(); + } } type GroupItem = { @@ -191,7 +209,15 @@ function destroyNative(agent: Agent) { function destroyWeb() { if (canvas !== null) { + if (canvas.matches(':popover-open')) { + // $FlowFixMe[prop-missing]: Flow doesn't recognize Popover API + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API + canvas.hidePopover(); + } + + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API and loses canvas nullability tracking if (canvas.parentNode != null) { + // $FlowFixMe[incompatible-call]: Flow doesn't track that canvas is non-null here canvas.parentNode.removeChild(canvas); } canvas = null; @@ -204,6 +230,9 @@ export function destroy(agent: Agent): void { function initialize(): void { canvas = window.document.createElement('canvas'); + canvas.setAttribute('popover', 'manual'); + + // $FlowFixMe[incompatible-use]: Flow doesn't recognize Popover API canvas.style.cssText = ` xx-background-color: red; xx-opacity: 0.5; @@ -213,7 +242,10 @@ function initialize(): void { position: fixed; right: 0; top: 0; - z-index: 1000000000; + background-color: transparent; + outline: none; + box-shadow: none; + border: none; `; const root = window.document.documentElement; diff --git a/packages/react-devtools-shell/src/app/TraceUpdatesTest/index.js b/packages/react-devtools-shell/src/app/TraceUpdatesTest/index.js new file mode 100644 index 0000000000..dd847ad7ae --- /dev/null +++ b/packages/react-devtools-shell/src/app/TraceUpdatesTest/index.js @@ -0,0 +1,87 @@ +/** + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + * + * @flow + */ + +import * as React from 'react'; +import {useRef, useState} from 'react'; + +const Counter = () => { + const [count, setCount] = useState(0); + + return ( +
    +

    Count: {count}

    + +
    + ); +}; + +function DialogComponent() { + const dialogRef = useRef(null); + + const openDialog = () => { + if (dialogRef.current) { + dialogRef.current.showModal(); + } + }; + + const closeDialog = () => { + if (dialogRef.current) { + dialogRef.current.close(); + } + }; + + return ( +
    + + +

    Dialog Content

    + + +
    +
    + ); +} + +function RegularComponent() { + return ( +
    +

    Regular Component

    + +
    + ); +} + +export default function TraceUpdatesTest(): React.Node { + return ( +
    +

    TraceUpdates Test

    + +
    +

    Standard Component

    + +
    + +
    +

    Dialog Component (top-layer element)

    + +
    + +
    +

    How to Test:

    +
      +
    1. Open DevTools Components panel
    2. +
    3. Enable "Highlight updates when components render" in settings
    4. +
    5. Click increment buttons and observe highlights
    6. +
    7. Open the dialog and test increments there as well
    8. +
    +
    +
    + ); +} diff --git a/packages/react-devtools-shell/src/app/index.js b/packages/react-devtools-shell/src/app/index.js index 11f13bec47..8de2f949bb 100644 --- a/packages/react-devtools-shell/src/app/index.js +++ b/packages/react-devtools-shell/src/app/index.js @@ -19,6 +19,7 @@ import Toggle from './Toggle'; import ErrorBoundaries from './ErrorBoundaries'; import PartiallyStrictApp from './PartiallyStrictApp'; import SuspenseTree from './SuspenseTree'; +import TraceUpdatesTest from './TraceUpdatesTest'; import {ignoreErrors, ignoreLogs, ignoreWarnings} from './console'; import './styles.css'; @@ -112,6 +113,7 @@ function mountTestApp() { mountApp(SuspenseTree); mountApp(DeeplyNestedComponents); mountApp(Iframe); + mountApp(TraceUpdatesTest); if (shouldRenderLegacy) { mountLegacyApp(PartiallyStrictApp); diff --git a/packages/react-devtools-timeline/package.json b/packages/react-devtools-timeline/package.json index 9dbf65b931..0c178f4b7f 100644 --- a/packages/react-devtools-timeline/package.json +++ b/packages/react-devtools-timeline/package.json @@ -1,7 +1,7 @@ { "private": true, "name": "react-devtools-timeline", - "version": "6.1.1", + "version": "6.1.2", "license": "MIT", "dependencies": { "@elg/speedscope": "1.9.0-a6f84db", diff --git a/packages/react-devtools/CHANGELOG.md b/packages/react-devtools/CHANGELOG.md index b1a4fd9a80..079f949f38 100644 --- a/packages/react-devtools/CHANGELOG.md +++ b/packages/react-devtools/CHANGELOG.md @@ -4,6 +4,13 @@ --- +### 6.1.2 +May 7, 2025 + +* Restore "double-click to view owners tree" functionality ([eps1lon](https://github.com/eps1lon) in [#33039](https://github.com/facebook/react/pull/33039)) + +--- + ### 6.1.1 February 7, 2025 diff --git a/packages/react-devtools/package.json b/packages/react-devtools/package.json index a384b16816..dfe88b0937 100644 --- a/packages/react-devtools/package.json +++ b/packages/react-devtools/package.json @@ -1,6 +1,6 @@ { "name": "react-devtools", - "version": "6.1.1", + "version": "6.1.2", "description": "Use react-devtools outside of the browser", "license": "MIT", "repository": { @@ -26,7 +26,7 @@ "electron": "^23.1.2", "internal-ip": "^6.2.0", "minimist": "^1.2.3", - "react-devtools-core": "6.1.1", + "react-devtools-core": "6.1.2", "update-notifier": "^2.1.0" } } diff --git a/packages/react-dom-bindings/src/client/ReactDOMComponent.js b/packages/react-dom-bindings/src/client/ReactDOMComponent.js index 2343445ae0..16e3bceb4a 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMComponent.js +++ b/packages/react-dom-bindings/src/client/ReactDOMComponent.js @@ -49,7 +49,6 @@ import { } from './ReactDOMTextarea'; import {setSrcObject} from './ReactDOMSrcObject'; import {validateTextNesting} from './validateDOMNesting'; -import {track} from './inputValueTracking'; import setTextContent from './setTextContent'; import { createDangerousStringForStyles, @@ -64,9 +63,12 @@ import {validateProperties as validateInputProperties} from '../shared/ReactDOMN import {validateProperties as validateUnknownProperties} from '../shared/ReactDOMUnknownPropertyHook'; import sanitizeURL from '../shared/sanitizeURL'; +import noop from 'shared/noop'; + import {trackHostMutation} from 'react-reconciler/src/ReactFiberMutationTracking'; import { + enableHydrationChangeEvent, enableScrollEndPolyfill, enableSrcObject, enableTrustedTypesIntegration, @@ -319,8 +321,6 @@ function checkForUnmatchedText( return false; } -function noop() {} - export function trapClickOnNonInteractiveElement(node: HTMLElement) { // Mobile Safari does not fire properly bubble click events on // non-interactive elements, which means delegated click listeners do not @@ -1187,7 +1187,6 @@ export function setInitialProperties( name, false, ); - track((domElement: any)); return; } case 'select': { @@ -1285,7 +1284,6 @@ export function setInitialProperties( // up necessary since we never stop tracking anymore. validateTextareaProps(domElement, props); initTextarea(domElement, value, defaultValue, children); - track((domElement: any)); return; } case 'option': { @@ -3100,17 +3098,18 @@ export function hydrateProperties( // option and select we don't quite do the same thing and select // is not resilient to the DOM state changing so we don't do that here. // TODO: Consider not doing this for input and textarea. - initInput( - domElement, - props.value, - props.defaultValue, - props.checked, - props.defaultChecked, - props.type, - props.name, - true, - ); - track((domElement: any)); + if (!enableHydrationChangeEvent) { + initInput( + domElement, + props.value, + props.defaultValue, + props.checked, + props.defaultChecked, + props.type, + props.name, + true, + ); + } break; case 'option': validateOptionProps(domElement, props); @@ -3134,8 +3133,14 @@ export function hydrateProperties( // TODO: Make sure we check if this is still unmounted or do any clean // up necessary since we never stop tracking anymore. validateTextareaProps(domElement, props); - initTextarea(domElement, props.value, props.defaultValue, props.children); - track((domElement: any)); + if (!enableHydrationChangeEvent) { + initTextarea( + domElement, + props.value, + props.defaultValue, + props.children, + ); + } break; } diff --git a/packages/react-dom-bindings/src/client/ReactDOMInput.js b/packages/react-dom-bindings/src/client/ReactDOMInput.js index 33c04e48d0..b6e665e128 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMInput.js +++ b/packages/react-dom-bindings/src/client/ReactDOMInput.js @@ -12,13 +12,17 @@ import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCur import {getFiberCurrentPropsFromNode} from './ReactDOMComponentTree'; import {getToStringValue, toString} from './ToStringValue'; -import {updateValueIfChanged} from './inputValueTracking'; +import {track, trackHydrated, updateValueIfChanged} from './inputValueTracking'; import getActiveElement from './getActiveElement'; -import {disableInputAttributeSyncing} from 'shared/ReactFeatureFlags'; +import { + disableInputAttributeSyncing, + enableHydrationChangeEvent, +} from 'shared/ReactFeatureFlags'; import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion'; import type {ToStringValue} from './ToStringValue'; import escapeSelectorAttributeValueInsideDoubleQuotes from './escapeSelectorAttributeValueInsideDoubleQuotes'; +import {queueChangeEvent} from '../events/ReactDOMEventReplaying'; let didWarnValueDefaultValue = false; let didWarnCheckedDefaultChecked = false; @@ -229,6 +233,8 @@ export function initInput( // Avoid setting value attribute on submit/reset inputs as it overrides the // default value provided by the browser. See: #12872 if (isButton && (value === undefined || value === null)) { + // We track the value just in case it changes type later on. + track((element: any)); return; } @@ -239,7 +245,7 @@ export function initInput( // Do not assign value if it is already set. This prevents user text input // from being lost during SSR hydration. - if (!isHydrating) { + if (!isHydrating || enableHydrationChangeEvent) { if (disableInputAttributeSyncing) { // When not syncing the value attribute, the value property points // directly to the React prop. Only assign it if it exists. @@ -297,7 +303,7 @@ export function initInput( typeof checkedOrDefault !== 'symbol' && !!checkedOrDefault; - if (isHydrating) { + if (isHydrating && !enableHydrationChangeEvent) { // Detach .checked from .defaultChecked but leave user input alone node.checked = node.checked; } else { @@ -335,6 +341,43 @@ export function initInput( } node.name = name; } + track((element: any)); +} + +export function hydrateInput( + element: Element, + value: ?string, + defaultValue: ?string, + checked: ?boolean, + defaultChecked: ?boolean, +): void { + const node: HTMLInputElement = (element: any); + + const defaultValueStr = + defaultValue != null ? toString(getToStringValue(defaultValue)) : ''; + const initialValue = + value != null ? toString(getToStringValue(value)) : defaultValueStr; + + const checkedOrDefault = checked != null ? checked : defaultChecked; + // TODO: This 'function' or 'symbol' check isn't replicated in other places + // so this semantic is inconsistent. + const initialChecked = + typeof checkedOrDefault !== 'function' && + typeof checkedOrDefault !== 'symbol' && + !!checkedOrDefault; + + // Detach .checked from .defaultChecked but leave user input alone + node.checked = node.checked; + + const changed = trackHydrated((node: any), initialValue, initialChecked); + if (changed) { + // If the current value is different, that suggests that the user + // changed it before hydration. Queue a replay of the change event. + // For radio buttons the change event only fires on the selected one. + if (node.type !== 'radio' || node.checked) { + queueChangeEvent(node); + } + } } export function restoreControlledInputState(element: Element, props: Object) { diff --git a/packages/react-dom-bindings/src/client/ReactDOMSelect.js b/packages/react-dom-bindings/src/client/ReactDOMSelect.js index 984abbc07c..00136aa817 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMSelect.js +++ b/packages/react-dom-bindings/src/client/ReactDOMSelect.js @@ -12,6 +12,7 @@ import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCur import {getToStringValue, toString} from './ToStringValue'; import isArray from 'shared/isArray'; +import {queueChangeEvent} from '../events/ReactDOMEventReplaying'; let didWarnValueDefaultValue; @@ -86,7 +87,7 @@ function updateOptions( } else { // Do not set `select.value` as exact behavior isn't consistent across all // browsers for all cases. - const selectedValue = toString(getToStringValue((propValue: any))); + const selectedValue = toString(getToStringValue(propValue)); let defaultSelected = null; for (let i = 0; i < options.length; i++) { if (options[i].value === selectedValue) { @@ -157,6 +158,59 @@ export function initSelect( } } +export function hydrateSelect( + element: Element, + value: ?string, + defaultValue: ?string, + multiple: ?boolean, +): void { + const node: HTMLSelectElement = (element: any); + const options: HTMLOptionsCollection = node.options; + + const propValue: any = value != null ? value : defaultValue; + + let changed = false; + + if (multiple) { + const selectedValues = (propValue: ?Array); + const selectedValue: {[string]: boolean} = {}; + if (selectedValues != null) { + for (let i = 0; i < selectedValues.length; i++) { + // Prefix to avoid chaos with special keys. + selectedValue['$' + selectedValues[i]] = true; + } + } + for (let i = 0; i < options.length; i++) { + const expectedSelected = selectedValue.hasOwnProperty( + '$' + options[i].value, + ); + if (options[i].selected !== expectedSelected) { + changed = true; + break; + } + } + } else { + let selectedValue = + propValue == null ? null : toString(getToStringValue(propValue)); + for (let i = 0; i < options.length; i++) { + if (selectedValue == null && !options[i].disabled) { + // We expect the first non-disabled option to be selected if the selected is null. + selectedValue = options[i].value; + } + const expectedSelected = options[i].value === selectedValue; + if (options[i].selected !== expectedSelected) { + changed = true; + break; + } + } + } + if (changed) { + // If the current selection is different than our initial that suggests that the user + // changed it before hydration. Queue a replay of the change event. + queueChangeEvent(node); + } +} + export function updateSelect( element: Element, value: ?string, diff --git a/packages/react-dom-bindings/src/client/ReactDOMTextarea.js b/packages/react-dom-bindings/src/client/ReactDOMTextarea.js index b0a1f520fd..bc346b4bce 100644 --- a/packages/react-dom-bindings/src/client/ReactDOMTextarea.js +++ b/packages/react-dom-bindings/src/client/ReactDOMTextarea.js @@ -13,6 +13,9 @@ import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCur import {getToStringValue, toString} from './ToStringValue'; import {disableTextareaChildren} from 'shared/ReactFeatureFlags'; +import {track, trackHydrated} from './inputValueTracking'; +import {queueChangeEvent} from '../events/ReactDOMEventReplaying'; + let didWarnValDefaultVal = false; /** @@ -140,6 +143,33 @@ export function initTextarea( node.value = textContent; } } + + track((element: any)); +} + +export function hydrateTextarea( + element: Element, + value: ?string, + defaultValue: ?string, +): void { + const node: HTMLTextAreaElement = (element: any); + let initialValue = value; + if (initialValue == null) { + if (defaultValue == null) { + defaultValue = ''; + } + initialValue = defaultValue; + } + // Track the value that we last observed which is the hydrated value so + // that any change event that fires will trigger onChange on the actual + // current value. + const stringValue = toString(getToStringValue(initialValue)); + const changed = trackHydrated((node: any), stringValue, false); + if (changed) { + // If the current value is different, that suggests that the user + // changed it before hydration. Queue a replay of the change event. + queueChangeEvent(node); + } } export function restoreControlledTextareaState( diff --git a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js index e1cd27b69b..5aa26b2634 100644 --- a/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js +++ b/packages/react-dom-bindings/src/client/ReactFiberConfigDOM.js @@ -37,6 +37,11 @@ import {runWithFiberInDEV} from 'react-reconciler/src/ReactCurrentFiber'; import hasOwnProperty from 'shared/hasOwnProperty'; import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion'; import {REACT_CONTEXT_TYPE} from 'shared/ReactSymbols'; +import { + isFiberContainedBy, + isFiberFollowing, + isFiberPreceding, +} from 'react-reconciler/src/ReactFiberTreeReflection'; export { setCurrentUpdatePriority, @@ -60,7 +65,10 @@ import { } from './ReactDOMComponentTree'; import { traverseFragmentInstance, - getFragmentParentHostInstance, + getFragmentParentHostFiber, + getNextSiblingHostFiber, + getInstanceFromHostFiber, + traverseFragmentInstanceDeeply, } from 'react-reconciler/src/ReactFiberTreeReflection'; export {detachDeletedInstance}; @@ -75,6 +83,9 @@ import { diffHydratedText, trapClickOnNonInteractiveElement, } from './ReactDOMComponent'; +import {hydrateInput} from './ReactDOMInput'; +import {hydrateTextarea} from './ReactDOMTextarea'; +import {hydrateSelect} from './ReactDOMSelect'; import {getSelectionInformation, restoreSelection} from './ReactInputSelection'; import setTextContent from './setTextContent'; import { @@ -96,7 +107,10 @@ import { DOCUMENT_FRAGMENT_NODE, } from './HTMLNodeType'; -import {retryIfBlockedOn} from '../events/ReactDOMEventReplaying'; +import { + flushEventReplaying, + retryIfBlockedOn, +} from '../events/ReactDOMEventReplaying'; import { enableCreateEventHandleAPI, @@ -108,6 +122,7 @@ import { enableSuspenseyImages, enableSrcObject, enableViewTransition, + enableHydrationChangeEvent, } from 'shared/ReactFeatureFlags'; import { HostComponent, @@ -124,6 +139,9 @@ import {requestFormReset as requestFormResetOnFiber} from 'react-reconciler/src/ import ReactDOMSharedInternals from 'shared/ReactDOMSharedInternals'; export {default as rendererVersion} from 'shared/ReactVersion'; + +import noop from 'shared/noop'; + export const rendererPackageName = 'react-dom'; export const extraDevToolsConfig = null; @@ -154,6 +172,10 @@ export type Props = { top?: null | number, is?: string, size?: number, + value?: string, + defaultValue?: string, + checked?: boolean, + defaultChecked?: boolean, multiple?: boolean, src?: string | Blob | MediaSource | MediaStream, // TODO: Response srcSet?: string, @@ -611,6 +633,27 @@ export function finalizeInitialChildren( } } +export function finalizeHydratedChildren( + domElement: Instance, + type: string, + props: Props, + hostContext: HostContext, +): boolean { + // TOOD: Consider unifying this with hydrateInstance. + if (!enableHydrationChangeEvent) { + return false; + } + switch (type) { + case 'input': + case 'select': + case 'textarea': + case 'img': + return true; + default: + return false; + } +} + export function shouldSetTextContent(type: string, props: Props): boolean { return ( type === 'textarea' || @@ -819,6 +862,49 @@ export function commitMount( } } +export function commitHydratedInstance( + domElement: Instance, + type: string, + props: Props, + internalInstanceHandle: Object, +): void { + if (!enableHydrationChangeEvent) { + return; + } + // This fires in the commit phase if a hydrated instance needs to do further + // work in the commit phase. Similar to commitMount. However, this should not + // do things that would've already happened such as set auto focus since that + // would steal focus. It's only scheduled if finalizeHydratedChildren returns + // true. + switch (type) { + case 'input': { + hydrateInput( + domElement, + props.value, + props.defaultValue, + props.checked, + props.defaultChecked, + ); + break; + } + case 'select': { + hydrateSelect( + domElement, + props.value, + props.defaultValue, + props.multiple, + ); + break; + } + case 'textarea': + hydrateTextarea(domElement, props.value, props.defaultValue); + break; + case 'img': + // TODO: Should we replay onLoad events? + break; + } +} + export function commitUpdate( domElement: Instance, type: string, @@ -2515,6 +2601,7 @@ export type FragmentInstanceType = { listener: EventListener, optionsOrUseCapture?: EventListenerOptionsOrUseCapture, ): void, + dispatchEvent(event: Event): boolean, focus(focusOptions?: FocusOptions): void, focusLast(focusOptions?: FocusOptions): void, blur(): void, @@ -2524,6 +2611,7 @@ export type FragmentInstanceType = { getRootNode(getRootNodeOptions?: { composed: boolean, }): Document | ShadowRoot | FragmentInstanceType, + compareDocumentPosition(otherNode: Instance): number, }; function FragmentInstance(this: FragmentInstanceType, fragmentFiber: Fiber) { @@ -2561,12 +2649,13 @@ FragmentInstance.prototype.addEventListener = function ( this._eventListeners = listeners; }; function addEventListenerToChild( - child: Instance, + child: Fiber, type: string, listener: EventListener, optionsOrUseCapture?: EventListenerOptionsOrUseCapture, ): boolean { - child.addEventListener(type, listener, optionsOrUseCapture); + const instance = getInstanceFromHostFiber(child); + instance.addEventListener(type, listener, optionsOrUseCapture); return false; } // $FlowFixMe[prop-missing] @@ -2600,43 +2689,89 @@ FragmentInstance.prototype.removeEventListener = function ( } }; function removeEventListenerFromChild( - child: Instance, + child: Fiber, type: string, listener: EventListener, optionsOrUseCapture?: EventListenerOptionsOrUseCapture, ): boolean { - child.removeEventListener(type, listener, optionsOrUseCapture); + const instance = getInstanceFromHostFiber(child); + instance.removeEventListener(type, listener, optionsOrUseCapture); return false; } // $FlowFixMe[prop-missing] +FragmentInstance.prototype.dispatchEvent = function ( + this: FragmentInstanceType, + event: Event, +): boolean { + const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber); + if (parentHostFiber === null) { + return true; + } + const parentHostInstance = + getInstanceFromHostFiber(parentHostFiber); + const eventListeners = this._eventListeners; + if ( + (eventListeners !== null && eventListeners.length > 0) || + !event.bubbles + ) { + const temp = document.createTextNode(''); + if (eventListeners) { + for (let i = 0; i < eventListeners.length; i++) { + const {type, listener, optionsOrUseCapture} = eventListeners[i]; + temp.addEventListener(type, listener, optionsOrUseCapture); + } + } + parentHostInstance.appendChild(temp); + const cancelable = temp.dispatchEvent(event); + if (eventListeners) { + for (let i = 0; i < eventListeners.length; i++) { + const {type, listener, optionsOrUseCapture} = eventListeners[i]; + temp.removeEventListener(type, listener, optionsOrUseCapture); + } + } + parentHostInstance.removeChild(temp); + return cancelable; + } else { + return parentHostInstance.dispatchEvent(event); + } +}; +// $FlowFixMe[prop-missing] FragmentInstance.prototype.focus = function ( this: FragmentInstanceType, focusOptions?: FocusOptions, ): void { - traverseFragmentInstance( + traverseFragmentInstanceDeeply( this._fragmentFiber, - setFocusIfFocusable, + setFocusOnFiberIfFocusable, focusOptions, ); }; +function setFocusOnFiberIfFocusable( + fiber: Fiber, + focusOptions?: FocusOptions, +): boolean { + const instance = getInstanceFromHostFiber(fiber); + return setFocusIfFocusable(instance, focusOptions); +} // $FlowFixMe[prop-missing] FragmentInstance.prototype.focusLast = function ( this: FragmentInstanceType, focusOptions?: FocusOptions, ): void { - const children: Array = []; - traverseFragmentInstance(this._fragmentFiber, collectChildren, children); + const children: Array = []; + traverseFragmentInstanceDeeply( + this._fragmentFiber, + collectChildren, + children, + ); for (let i = children.length - 1; i >= 0; i--) { const child = children[i]; - if (setFocusIfFocusable(child, focusOptions)) { + if (setFocusOnFiberIfFocusable(child, focusOptions)) { break; } } }; -function collectChildren( - child: Instance, - collection: Array, -): boolean { +function collectChildren(child: Fiber, collection: Array): boolean { collection.push(child); return false; } @@ -2649,12 +2784,13 @@ FragmentInstance.prototype.blur = function (this: FragmentInstanceType): void { blurActiveElementWithinFragment, ); }; -function blurActiveElementWithinFragment(child: Instance): boolean { +function blurActiveElementWithinFragment(child: Fiber): boolean { // TODO: We can get the activeElement from the parent outside of the loop when we have a reference. - const ownerDocument = child.ownerDocument; - if (child === ownerDocument.activeElement) { + const instance = getInstanceFromHostFiber(child); + const ownerDocument = instance.ownerDocument; + if (instance === ownerDocument.activeElement) { // $FlowFixMe[prop-missing] - child.blur(); + instance.blur(); return true; } return false; @@ -2671,10 +2807,11 @@ FragmentInstance.prototype.observeUsing = function ( traverseFragmentInstance(this._fragmentFiber, observeChild, observer); }; function observeChild( - child: Instance, + child: Fiber, observer: IntersectionObserver | ResizeObserver, ) { - observer.observe(child); + const instance = getInstanceFromHostFiber(child); + observer.observe(instance); return false; } // $FlowFixMe[prop-missing] @@ -2695,10 +2832,11 @@ FragmentInstance.prototype.unobserveUsing = function ( } }; function unobserveChild( - child: Instance, + child: Fiber, observer: IntersectionObserver | ResizeObserver, ) { - observer.unobserve(child); + const instance = getInstanceFromHostFiber(child); + observer.unobserve(instance); return false; } // $FlowFixMe[prop-missing] @@ -2709,9 +2847,10 @@ FragmentInstance.prototype.getClientRects = function ( traverseFragmentInstance(this._fragmentFiber, collectClientRects, rects); return rects; }; -function collectClientRects(child: Instance, rects: Array): boolean { +function collectClientRects(child: Fiber, rects: Array): boolean { + const instance = getInstanceFromHostFiber(child); // $FlowFixMe[method-unbinding] - rects.push.apply(rects, child.getClientRects()); + rects.push.apply(rects, instance.getClientRects()); return false; } // $FlowFixMe[prop-missing] @@ -2719,15 +2858,144 @@ FragmentInstance.prototype.getRootNode = function ( this: FragmentInstanceType, getRootNodeOptions?: {composed: boolean}, ): Document | ShadowRoot | FragmentInstanceType { - const parentHostInstance = getFragmentParentHostInstance(this._fragmentFiber); - if (parentHostInstance === null) { + const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber); + if (parentHostFiber === null) { return this; } + const parentHostInstance = + getInstanceFromHostFiber(parentHostFiber); const rootNode = // $FlowFixMe[incompatible-cast] Flow expects Node (parentHostInstance.getRootNode(getRootNodeOptions): Document | ShadowRoot); return rootNode; }; +// $FlowFixMe[prop-missing] +FragmentInstance.prototype.compareDocumentPosition = function ( + this: FragmentInstanceType, + otherNode: Instance, +): number { + const parentHostFiber = getFragmentParentHostFiber(this._fragmentFiber); + if (parentHostFiber === null) { + return Node.DOCUMENT_POSITION_DISCONNECTED; + } + const children: Array = []; + traverseFragmentInstance(this._fragmentFiber, collectChildren, children); + + let result = Node.DOCUMENT_POSITION_DISCONNECTED; + if (children.length === 0) { + // If the fragment has no children, we can use the parent and + // siblings to determine a position. + const parentHostInstance = + getInstanceFromHostFiber(parentHostFiber); + const parentResult = parentHostInstance.compareDocumentPosition(otherNode); + result = parentResult; + if (parentHostInstance === otherNode) { + result = Node.DOCUMENT_POSITION_CONTAINS; + } else { + if (parentResult & Node.DOCUMENT_POSITION_CONTAINED_BY) { + // otherNode is one of the fragment's siblings. Use the next + // sibling to determine if its preceding or following. + const nextSiblingFiber = getNextSiblingHostFiber(this._fragmentFiber); + if (nextSiblingFiber === null) { + result = Node.DOCUMENT_POSITION_PRECEDING; + } else { + const nextSiblingInstance = + getInstanceFromHostFiber(nextSiblingFiber); + const nextSiblingResult = + nextSiblingInstance.compareDocumentPosition(otherNode); + if ( + nextSiblingResult === 0 || + nextSiblingResult & Node.DOCUMENT_POSITION_FOLLOWING + ) { + result = Node.DOCUMENT_POSITION_FOLLOWING; + } else { + result = Node.DOCUMENT_POSITION_PRECEDING; + } + } + } + } + + result |= Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; + return result; + } + + const firstElement = getInstanceFromHostFiber(children[0]); + const lastElement = getInstanceFromHostFiber( + children[children.length - 1], + ); + const firstResult = firstElement.compareDocumentPosition(otherNode); + const lastResult = lastElement.compareDocumentPosition(otherNode); + if ( + (firstResult & Node.DOCUMENT_POSITION_FOLLOWING && + lastResult & Node.DOCUMENT_POSITION_PRECEDING) || + otherNode === firstElement || + otherNode === lastElement + ) { + result = Node.DOCUMENT_POSITION_CONTAINED_BY; + } else { + result = firstResult; + } + + if ( + result & Node.DOCUMENT_POSITION_DISCONNECTED || + result & Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + ) { + return result; + } + + // Now that we have the result from the DOM API, we double check it matches + // the state of the React tree. If it doesn't, we have a case of portaled or + // otherwise injected elements and we return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC. + const documentPositionMatchesFiberPosition = + validateDocumentPositionWithFiberTree( + result, + this._fragmentFiber, + children[0], + children[children.length - 1], + otherNode, + ); + if (documentPositionMatchesFiberPosition) { + return result; + } + return Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; +}; + +function validateDocumentPositionWithFiberTree( + documentPosition: number, + fragmentFiber: Fiber, + precedingBoundaryFiber: Fiber, + followingBoundaryFiber: Fiber, + otherNode: Instance, +): boolean { + const otherFiber = getClosestInstanceFromNode(otherNode); + if (documentPosition & Node.DOCUMENT_POSITION_CONTAINED_BY) { + return !!otherFiber && isFiberContainedBy(fragmentFiber, otherFiber); + } + if (documentPosition & Node.DOCUMENT_POSITION_CONTAINS) { + if (otherFiber === null) { + // otherFiber could be null if its the document or body element + const ownerDocument = otherNode.ownerDocument; + return otherNode === ownerDocument || otherNode === ownerDocument.body; + } + return isFiberContainedBy(otherFiber, fragmentFiber); + } + if (documentPosition & Node.DOCUMENT_POSITION_PRECEDING) { + return ( + !!otherFiber && + (otherFiber === precedingBoundaryFiber || + isFiberPreceding(precedingBoundaryFiber, otherFiber)) + ); + } + if (documentPosition & Node.DOCUMENT_POSITION_FOLLOWING) { + return ( + !!otherFiber && + (otherFiber === followingBoundaryFiber || + isFiberFollowing(followingBoundaryFiber, otherFiber)) + ); + } + + return false; +} function normalizeListenerOptions( opts: ?EventListenerOptionsOrUseCapture, @@ -3583,6 +3851,12 @@ export function commitHydratedSuspenseInstance( retryIfBlockedOn(suspenseInstance); } +export function flushHydrationEvents(): void { + if (enableHydrationChangeEvent) { + flushEventReplaying(); + } +} + export function shouldDeleteUnhydratedTailInstances( parentType: string, ): boolean { @@ -5357,16 +5631,14 @@ type SuspendedState = { }; let suspendedState: null | SuspendedState = null; -// We use a noop function when we begin suspending because if possible we want the -// waitfor step to finish synchronously. If it doesn't we'll return a function to -// provide the actual unsuspend function and that will get completed when the count -// hits zero or it will get cancelled if the root starts new work. -function noop() {} - export function startSuspendingCommit(): void { suspendedState = { stylesheets: null, count: 0, + // We use a noop function when we begin suspending because if possible we want the + // waitfor step to finish synchronously. If it doesn't we'll return a function to + // provide the actual unsuspend function and that will get completed when the count + // hits zero or it will get cancelled if the root starts new work. unsuspend: noop, }; } diff --git a/packages/react-dom-bindings/src/client/inputValueTracking.js b/packages/react-dom-bindings/src/client/inputValueTracking.js index 5e1ca58687..f89617fe6d 100644 --- a/packages/react-dom-bindings/src/client/inputValueTracking.js +++ b/packages/react-dom-bindings/src/client/inputValueTracking.js @@ -51,18 +51,16 @@ function getValueFromNode(node: HTMLInputElement): string { return value; } -function trackValueOnNode(node: any): ?ValueTracker { - const valueField = isCheckable(node) ? 'checked' : 'value'; +function trackValueOnNode( + node: any, + valueField: 'checked' | 'value', + currentValue: string, +): ?ValueTracker { const descriptor = Object.getOwnPropertyDescriptor( node.constructor.prototype, valueField, ); - if (__DEV__) { - checkFormFieldValueStringCoercion(node[valueField]); - } - let currentValue = '' + node[valueField]; - // if someone has already defined a value or Safari, then bail // and don't track value will cause over reporting of changes, // but it's better then a hard failure @@ -123,7 +121,39 @@ export function track(node: ElementWithValueTracker) { return; } - node._valueTracker = trackValueOnNode(node); + const valueField = isCheckable(node) ? 'checked' : 'value'; + // This is read from the DOM so always safe to coerce. We really shouldn't + // be coercing to a string at all. It's just historical. + // eslint-disable-next-line react-internal/safe-string-coercion + const initialValue = '' + (node[valueField]: any); + node._valueTracker = trackValueOnNode(node, valueField, initialValue); +} + +export function trackHydrated( + node: ElementWithValueTracker, + initialValue: string, + initialChecked: boolean, +): boolean { + // For hydration, the initial value is not the current value but the value + // that we last observed which is what the initial server render was. + if (getTracker(node)) { + return false; + } + + let valueField; + let expectedValue; + if (isCheckable(node)) { + valueField = 'checked'; + // eslint-disable-next-line react-internal/safe-string-coercion + expectedValue = '' + (initialChecked: any); + } else { + valueField = 'value'; + expectedValue = initialValue; + } + // eslint-disable-next-line react-internal/safe-string-coercion + const currentValue = '' + (node[valueField]: any); + node._valueTracker = trackValueOnNode(node, valueField, expectedValue); + return currentValue !== expectedValue; } export function updateValueIfChanged(node: ElementWithValueTracker): boolean { diff --git a/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js b/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js index b4733c7781..916786128d 100644 --- a/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js +++ b/packages/react-dom-bindings/src/events/DOMPluginEventSystem.js @@ -36,6 +36,7 @@ import { HostText, ScopeComponent, } from 'react-reconciler/src/ReactWorkTags'; +import {getLowestCommonAncestor} from 'react-reconciler/src/ReactFiberTreeReflection'; import getEventTarget from './getEventTarget'; import { @@ -891,46 +892,6 @@ function getParent(inst: Fiber | null): Fiber | null { return null; } -/** - * Return the lowest common ancestor of A and B, or null if they are in - * different trees. - */ -function getLowestCommonAncestor(instA: Fiber, instB: Fiber): Fiber | null { - let nodeA: null | Fiber = instA; - let nodeB: null | Fiber = instB; - let depthA = 0; - for (let tempA: null | Fiber = nodeA; tempA; tempA = getParent(tempA)) { - depthA++; - } - let depthB = 0; - for (let tempB: null | Fiber = nodeB; tempB; tempB = getParent(tempB)) { - depthB++; - } - - // If A is deeper, crawl up. - while (depthA - depthB > 0) { - nodeA = getParent(nodeA); - depthA--; - } - - // If B is deeper, crawl up. - while (depthB - depthA > 0) { - nodeB = getParent(nodeB); - depthB--; - } - - // Walk in lockstep until we find a match. - let depth = depthA; - while (depth--) { - if (nodeA === nodeB || (nodeB !== null && nodeA === nodeB.alternate)) { - return nodeA; - } - nodeA = getParent(nodeA); - nodeB = getParent(nodeB); - } - return null; -} - function accumulateEnterLeaveListenersForEvent( dispatchQueue: DispatchQueue, event: KnownReactSyntheticEvent, @@ -992,7 +953,8 @@ export function accumulateEnterLeaveTwoPhaseListeners( from: Fiber | null, to: Fiber | null, ): void { - const common = from && to ? getLowestCommonAncestor(from, to) : null; + const common = + from && to ? getLowestCommonAncestor(from, to, getParent) : null; if (from !== null) { accumulateEnterLeaveListenersForEvent( diff --git a/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js b/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js index a6f6f3055a..2763b2fc12 100644 --- a/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js +++ b/packages/react-dom-bindings/src/events/ReactDOMEventReplaying.js @@ -56,9 +56,11 @@ import { attemptHydrationAtCurrentPriority, } from 'react-reconciler/src/ReactFiberReconciler'; +import {enableHydrationChangeEvent} from 'shared/ReactFeatureFlags'; + // TODO: Upgrade this definition once we're on a newer version of Flow that // has this definition built-in. -type PointerEvent = Event & { +type PointerEventType = Event & { pointerId: number, relatedTarget: EventTarget | null, ... @@ -84,6 +86,8 @@ const queuedPointers: Map = new Map(); const queuedPointerCaptures: Map = new Map(); // We could consider replaying selectionchange and touchmoves too. +const queuedChangeEventTargets: Array = []; + type QueuedHydrationTarget = { blockedOn: null | Container | ActivityInstance | SuspenseInstance, target: Node, @@ -164,13 +168,13 @@ export function clearIfContinuousEvent( break; case 'pointerover': case 'pointerout': { - const pointerId = ((nativeEvent: any): PointerEvent).pointerId; + const pointerId = ((nativeEvent: any): PointerEventType).pointerId; queuedPointers.delete(pointerId); break; } case 'gotpointercapture': case 'lostpointercapture': { - const pointerId = ((nativeEvent: any): PointerEvent).pointerId; + const pointerId = ((nativeEvent: any): PointerEventType).pointerId; queuedPointerCaptures.delete(pointerId); break; } @@ -268,7 +272,7 @@ export function queueIfContinuousEvent( return true; } case 'pointerover': { - const pointerEvent = ((nativeEvent: any): PointerEvent); + const pointerEvent = ((nativeEvent: any): PointerEventType); const pointerId = pointerEvent.pointerId; queuedPointers.set( pointerId, @@ -284,7 +288,7 @@ export function queueIfContinuousEvent( return true; } case 'gotpointercapture': { - const pointerEvent = ((nativeEvent: any): PointerEvent); + const pointerEvent = ((nativeEvent: any): PointerEventType); const pointerId = pointerEvent.pointerId; queuedPointerCaptures.set( pointerId, @@ -421,6 +425,31 @@ function attemptReplayContinuousQueuedEventInMap( } } +function replayChangeEvent(target: EventTarget): void { + // Dispatch a fake "change" event for the input. + const element: HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement = + (target: any); + if (element.nodeName === 'INPUT') { + if (element.type === 'checkbox' || element.type === 'radio') { + // Checkboxes always fire a click event regardless of how the change was made. + const EventCtr = + typeof PointerEvent === 'function' ? PointerEvent : Event; + target.dispatchEvent(new EventCtr('click', {bubbles: true})); + // For checkboxes the input event uses the Event constructor instead of InputEvent. + target.dispatchEvent(new Event('input', {bubbles: true})); + } else { + if (typeof InputEvent === 'function') { + target.dispatchEvent(new InputEvent('input', {bubbles: true})); + } + } + } else if (element.nodeName === 'TEXTAREA') { + if (typeof InputEvent === 'function') { + target.dispatchEvent(new InputEvent('input', {bubbles: true})); + } + } + target.dispatchEvent(new Event('change', {bubbles: true})); +} + function replayUnblockedEvents() { hasScheduledReplayAttempt = false; // Replay any continuous events. @@ -435,6 +464,29 @@ function replayUnblockedEvents() { } queuedPointers.forEach(attemptReplayContinuousQueuedEventInMap); queuedPointerCaptures.forEach(attemptReplayContinuousQueuedEventInMap); + if (enableHydrationChangeEvent) { + for (let i = 0; i < queuedChangeEventTargets.length; i++) { + replayChangeEvent(queuedChangeEventTargets[i]); + } + queuedChangeEventTargets.length = 0; + } +} + +export function flushEventReplaying(): void { + // Synchronously flush any event replaying so that it gets observed before + // any new updates are applied. + if (hasScheduledReplayAttempt) { + replayUnblockedEvents(); + } +} + +export function queueChangeEvent(target: EventTarget): void { + if (enableHydrationChangeEvent) { + queuedChangeEventTargets.push(target); + if (!hasScheduledReplayAttempt) { + hasScheduledReplayAttempt = true; + } + } } function scheduleCallbackIfUnblocked( @@ -445,10 +497,12 @@ function scheduleCallbackIfUnblocked( queuedEvent.blockedOn = null; if (!hasScheduledReplayAttempt) { hasScheduledReplayAttempt = true; - // Schedule a callback to attempt replaying as many events as are - // now unblocked. This first might not actually be unblocked yet. - // We could check it early to avoid scheduling an unnecessary callback. - scheduleCallback(NormalPriority, replayUnblockedEvents); + if (!enableHydrationChangeEvent) { + // Schedule a callback to attempt replaying as many events as are + // now unblocked. This first might not actually be unblocked yet. + // We could check it early to avoid scheduling an unnecessary callback. + scheduleCallback(NormalPriority, replayUnblockedEvents); + } } } } diff --git a/packages/react-dom/src/ReactDOMSharedInternals.js b/packages/react-dom/src/ReactDOMSharedInternals.js index ad603e83cb..38a7d45dc5 100644 --- a/packages/react-dom/src/ReactDOMSharedInternals.js +++ b/packages/react-dom/src/ReactDOMSharedInternals.js @@ -10,6 +10,8 @@ import type {EventPriority} from 'react-reconciler/src/ReactEventPriorities'; import type {HostDispatcher} from './shared/ReactDOMTypes'; +import noop from 'shared/noop'; + // This should line up with NoEventPriority from react-reconciler/src/ReactEventPriorities // but we can't depend on the react-reconciler from this isomorphic code. export const NoEventPriority: EventPriority = (0: any); @@ -24,8 +26,6 @@ type ReactDOMInternals = { ) => null | Element | Text), }; -function noop() {} - function requestFormReset(element: HTMLFormElement) { throw new Error( 'Invalid form element. requestFormReset must be passed a form that was ' + diff --git a/packages/react-dom/src/ReactDOMSharedInternalsFB.js b/packages/react-dom/src/ReactDOMSharedInternalsFB.js index 4e425d5ddc..03c1b4f11b 100644 --- a/packages/react-dom/src/ReactDOMSharedInternalsFB.js +++ b/packages/react-dom/src/ReactDOMSharedInternalsFB.js @@ -12,6 +12,8 @@ import type {HostDispatcher} from './shared/ReactDOMTypes'; import {NoEventPriority} from 'react-reconciler/src/ReactEventPriorities'; +import noop from 'shared/noop'; + type ReactDOMInternals = { Events: [any, any, any, any, any, any], d /* ReactDOMCurrentDispatcher */: HostDispatcher, @@ -23,8 +25,6 @@ type ReactDOMInternals = { ) => null | Element | Text), }; -function noop() {} - const DefaultDispatcher: HostDispatcher = { f /* flushSyncWork */: noop, r /* requestFormReset */: noop, diff --git a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js index 24a9c4308a..c1dc8a33cc 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFiberAsync-test.js @@ -751,7 +751,8 @@ describe('ReactDOMFiberAsync', () => { // Because it suspended, it remains on the current path expect(div.textContent).toBe('/path/a'); }); - assertLog(gate('enableSiblingPrerendering') ? ['Suspend! [/path/b]'] : []); + // pre-warming + assertLog(['Suspend! [/path/b]']); await act(async () => { resolvePromise(); diff --git a/packages/react-dom/src/__tests__/ReactDOMForm-test.js b/packages/react-dom/src/__tests__/ReactDOMForm-test.js index 93edccf9bc..03b9076b33 100644 --- a/packages/react-dom/src/__tests__/ReactDOMForm-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMForm-test.js @@ -1437,8 +1437,8 @@ describe('ReactDOMForm', () => { assertLog([ 'Suspend! [Count: 0]', 'Loading...', - - ...(gate('enableSiblingPrerendering') ? ['Suspend! [Count: 0]'] : []), + // pre-warming + 'Suspend! [Count: 0]', ]); await act(() => resolveText('Count: 0')); assertLog(['Count: 0']); @@ -1448,8 +1448,8 @@ describe('ReactDOMForm', () => { assertLog([ 'Suspend! [Count: 1]', 'Loading...', - - ...(gate('enableSiblingPrerendering') ? ['Suspend! [Count: 1]'] : []), + // pre-warming + 'Suspend! [Count: 1]', ]); expect(container.textContent).toBe('Loading...'); @@ -1482,8 +1482,8 @@ describe('ReactDOMForm', () => { await act(() => root.render()); assertLog([ 'Suspend! [Count: 0]', - - ...(gate('enableSiblingPrerendering') ? ['Suspend! [Count: 0]'] : []), + // pre-warming + 'Suspend! [Count: 0]', ]); await act(() => resolveText('Count: 0')); assertLog(['Count: 0']); @@ -1501,8 +1501,8 @@ describe('ReactDOMForm', () => { ]); assertLog([ 'Suspend! [Count: 1]', - - ...(gate('enableSiblingPrerendering') ? ['Suspend! [Count: 1]'] : []), + // pre-warming + 'Suspend! [Count: 1]', ]); expect(container.textContent).toBe('Count: 0'); }); @@ -1670,6 +1670,37 @@ describe('ReactDOMForm', () => { expect(divRef.current.textContent).toEqual('Current username: acdlite'); }); + it('parallel form submissions do not throw', async () => { + const formRef = React.createRef(); + let resolve = null; + function App() { + async function submitForm() { + Scheduler.log('Action'); + if (!resolve) { + await new Promise(res => { + resolve = res; + }); + } + } + return
    ; + } + const root = ReactDOMClient.createRoot(container); + await act(() => root.render()); + + // Start first form submission + await act(async () => { + formRef.current.requestSubmit(); + }); + assertLog(['Action']); + + // Submit form again while first form action is still pending + await act(async () => { + formRef.current.requestSubmit(); + resolve(); // Resolve the promise to allow the first form action to complete + }); + assertLog(['Action']); + }); + it( 'requestFormReset works with inputs that are not descendants ' + 'of the form element', diff --git a/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js b/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js index c3d3a9ca7e..e7e1d053a7 100644 --- a/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMFragmentRefs-test.js @@ -11,6 +11,8 @@ let React; let ReactDOMClient; +let ReactDOM; +let createPortal; let act; let container; let Fragment; @@ -31,6 +33,8 @@ describe('FragmentRefs', () => { Fragment = React.Fragment; Activity = React.unstable_Activity; ReactDOMClient = require('react-dom/client'); + ReactDOM = require('react-dom'); + createPortal = ReactDOM.createPortal; act = require('internal-test-utils').act; const IntersectionMocks = require('./utils/IntersectionMocks'); mockIntersectionObserver = IntersectionMocks.mockIntersectionObserver; @@ -40,6 +44,7 @@ describe('FragmentRefs', () => { require('internal-test-utils').assertConsoleErrorDev; container = document.createElement('div'); + document.body.innerHTML = ''; document.body.appendChild(container); }); @@ -140,6 +145,32 @@ describe('FragmentRefs', () => { document.activeElement.blur(); }); + // @gate enableFragmentRefs + it('focuses deeply nested focusable children, depth first', async () => { + const fragmentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + function Test() { + return ( + + + + + ); + } + await act(() => { + root.render(); + }); + await act(() => { + fragmentRef.current.focus(); + }); + expect(document.activeElement.id).toEqual('grandchild-a'); + }); + // @gate enableFragmentRefs it('preserves document order when adding and removing children', async () => { const fragmentRef = React.createRef(); @@ -223,6 +254,34 @@ describe('FragmentRefs', () => { expect(document.activeElement.id).toEqual('child-c'); document.activeElement.blur(); }); + + // @gate enableFragmentRefs + it('focuses deeply nested focusable children, depth first', async () => { + const fragmentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + function Test() { + return ( + + + + + ); + } + await act(() => { + root.render(); + }); + await act(() => { + fragmentRef.current.focusLast(); + }); + expect(document.activeElement.id).toEqual('grandchild-b'); + }); }); describe('blur()', () => { @@ -294,339 +353,44 @@ describe('FragmentRefs', () => { }); }); - describe('event listeners', () => { - // @gate enableFragmentRefs - it('adds and removes event listeners from children', async () => { - const parentRef = React.createRef(); - const fragmentRef = React.createRef(); - const childARef = React.createRef(); - const childBRef = React.createRef(); - const root = ReactDOMClient.createRoot(container); + describe('events', () => { + describe('add/remove event listeners', () => { + // @gate enableFragmentRefs + it('adds and removes event listeners from children', async () => { + const parentRef = React.createRef(); + const fragmentRef = React.createRef(); + const childARef = React.createRef(); + const childBRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); - let logs = []; + let logs = []; - function handleFragmentRefClicks() { - logs.push('fragmentRef'); - } + function handleFragmentRefClicks() { + logs.push('fragmentRef'); + } - function Test() { - React.useEffect(() => { - fragmentRef.current.addEventListener( - 'click', - handleFragmentRefClicks, - ); - - return () => { - fragmentRef.current.removeEventListener( + function Test() { + React.useEffect(() => { + fragmentRef.current.addEventListener( 'click', handleFragmentRefClicks, ); - }; - }, []); - return ( -
    - - <>Text -
    A
    - <> -
    B
    - -
    -
    - ); - } - await act(() => { - root.render(); - }); - - childARef.current.addEventListener('click', () => { - logs.push('A'); - }); - - childBRef.current.addEventListener('click', () => { - logs.push('B'); - }); - - // Clicking on the parent should not trigger any listeners - parentRef.current.click(); - expect(logs).toEqual([]); - - // Clicking a child triggers its own listeners and the Fragment's - childARef.current.click(); - expect(logs).toEqual(['fragmentRef', 'A']); - - logs = []; - - childBRef.current.click(); - expect(logs).toEqual(['fragmentRef', 'B']); - - logs = []; - - fragmentRef.current.removeEventListener('click', handleFragmentRefClicks); - - childARef.current.click(); - expect(logs).toEqual(['A']); - - logs = []; - - childBRef.current.click(); - expect(logs).toEqual(['B']); - }); - - // @gate enableFragmentRefs - it('adds and removes event listeners from children with multiple fragments', async () => { - const fragmentRef = React.createRef(); - const nestedFragmentRef = React.createRef(); - const nestedFragmentRef2 = React.createRef(); - const childARef = React.createRef(); - const childBRef = React.createRef(); - const childCRef = React.createRef(); - const root = ReactDOMClient.createRoot(container); - - await act(() => { - root.render( -
    - -
    A
    -
    - + return () => { + fragmentRef.current.removeEventListener( + 'click', + handleFragmentRefClicks, + ); + }; + }, []); + return ( +
    + + <>Text +
    A
    + <>
    B
    -
    -
    - -
    C
    -
    -
    -
    , - ); - }); - - let logs = []; - - function handleFragmentRefClicks() { - logs.push('fragmentRef'); - } - - function handleNestedFragmentRefClicks() { - logs.push('nestedFragmentRef'); - } - - function handleNestedFragmentRef2Clicks() { - logs.push('nestedFragmentRef2'); - } - - fragmentRef.current.addEventListener('click', handleFragmentRefClicks); - nestedFragmentRef.current.addEventListener( - 'click', - handleNestedFragmentRefClicks, - ); - nestedFragmentRef2.current.addEventListener( - 'click', - handleNestedFragmentRef2Clicks, - ); - - childBRef.current.click(); - // Event bubbles to the parent fragment - expect(logs).toEqual(['nestedFragmentRef', 'fragmentRef']); - - logs = []; - - childARef.current.click(); - expect(logs).toEqual(['fragmentRef']); - - logs = []; - childCRef.current.click(); - expect(logs).toEqual(['fragmentRef', 'nestedFragmentRef2']); - - logs = []; - - fragmentRef.current.removeEventListener('click', handleFragmentRefClicks); - nestedFragmentRef.current.removeEventListener( - 'click', - handleNestedFragmentRefClicks, - ); - childCRef.current.click(); - expect(logs).toEqual(['nestedFragmentRef2']); - }); - - // @gate enableFragmentRefs - it('adds an event listener to a newly added child', async () => { - const fragmentRef = React.createRef(); - const childRef = React.createRef(); - const root = ReactDOMClient.createRoot(container); - let showChild; - - function Component() { - const [shouldShowChild, setShouldShowChild] = React.useState(false); - showChild = () => { - setShouldShowChild(true); - }; - - return ( -
    - -
    A
    - {shouldShowChild && ( -
    - B -
    - )} -
    -
    - ); - } - - await act(() => { - root.render(); - }); - - expect(fragmentRef.current).not.toBe(null); - expect(childRef.current).toBe(null); - - let hasClicked = false; - fragmentRef.current.addEventListener('click', () => { - hasClicked = true; - }); - - await act(() => { - showChild(); - }); - expect(childRef.current).not.toBe(null); - - childRef.current.click(); - expect(hasClicked).toBe(true); - }); - - // @gate enableFragmentRefs - it('applies event listeners to host children nested within non-host children', async () => { - const fragmentRef = React.createRef(); - const childRef = React.createRef(); - const nestedChildRef = React.createRef(); - const root = ReactDOMClient.createRoot(container); - - await act(() => { - root.render( -
    - -
    Host A
    - - - -
    Host B
    -
    -
    -
    -
    -
    , - ); - }); - const logs = []; - fragmentRef.current.addEventListener('click', e => { - logs.push(e.target.textContent); - }); - - expect(logs).toEqual([]); - childRef.current.click(); - expect(logs).toEqual(['Host A']); - nestedChildRef.current.click(); - expect(logs).toEqual(['Host A', 'Host B']); - }); - - // @gate enableFragmentRefs - it('allows adding and cleaning up listeners in effects', async () => { - const root = ReactDOMClient.createRoot(container); - - let logs = []; - function logClick(e) { - logs.push(e.currentTarget.id); - } - - let rerender; - let removeEventListeners; - - function Test() { - const fragmentRef = React.useRef(null); - // eslint-disable-next-line no-unused-vars - const [_, setState] = React.useState(0); - rerender = () => { - setState(p => p + 1); - }; - removeEventListeners = () => { - fragmentRef.current.removeEventListener('click', logClick); - }; - React.useEffect(() => { - fragmentRef.current.addEventListener('click', logClick); - - return removeEventListeners; - }); - - return ( - -
    - - ); - } - - // The event listener was applied - await act(() => root.render()); - expect(logs).toEqual([]); - document.querySelector('#child-a').click(); - expect(logs).toEqual(['child-a']); - - // The event listener can be removed and re-added - logs = []; - await act(rerender); - document.querySelector('#child-a').click(); - expect(logs).toEqual(['child-a']); - }); - - // @gate enableFragmentRefs - it('does not apply removed event listeners to new children', async () => { - const root = ReactDOMClient.createRoot(container); - const fragmentRef = React.createRef(null); - function Test() { - return ( - -
    - - ); - } - - let logs = []; - function logClick(e) { - logs.push(e.currentTarget.id); - } - await act(() => { - root.render(); - }); - fragmentRef.current.addEventListener('click', logClick); - const childA = document.querySelector('#child-a'); - childA.click(); - expect(logs).toEqual(['child-a']); - - logs = []; - fragmentRef.current.removeEventListener('click', logClick); - childA.click(); - expect(logs).toEqual([]); - }); - - describe('with activity', () => { - // @gate enableFragmentRefs && enableActivity - it('does not apply event listeners to hidden trees', async () => { - const parentRef = React.createRef(); - const fragmentRef = React.createRef(); - const root = ReactDOMClient.createRoot(container); - - function Test() { - return ( -
    - -
    Child 1
    - -
    Child 2
    -
    -
    Child 3
    +
    ); @@ -636,105 +400,544 @@ describe('FragmentRefs', () => { root.render(); }); - const logs = []; - fragmentRef.current.addEventListener('click', e => { - logs.push(e.target.textContent); + childARef.current.addEventListener('click', () => { + logs.push('A'); }); - const [child1, child2, child3] = parentRef.current.children; - child1.click(); - child2.click(); - child3.click(); - expect(logs).toEqual(['Child 1', 'Child 3']); + childBRef.current.addEventListener('click', () => { + logs.push('B'); + }); + + // Clicking on the parent should not trigger any listeners + parentRef.current.click(); + expect(logs).toEqual([]); + + // Clicking a child triggers its own listeners and the Fragment's + childARef.current.click(); + expect(logs).toEqual(['fragmentRef', 'A']); + + logs = []; + + childBRef.current.click(); + expect(logs).toEqual(['fragmentRef', 'B']); + + logs = []; + + fragmentRef.current.removeEventListener( + 'click', + handleFragmentRefClicks, + ); + + childARef.current.click(); + expect(logs).toEqual(['A']); + + logs = []; + + childBRef.current.click(); + expect(logs).toEqual(['B']); }); - // @gate enableFragmentRefs && enableActivity - it('applies event listeners to visible trees', async () => { - const parentRef = React.createRef(); + // @gate enableFragmentRefs + it('adds and removes event listeners from children with multiple fragments', async () => { const fragmentRef = React.createRef(); + const nestedFragmentRef = React.createRef(); + const nestedFragmentRef2 = React.createRef(); + const childARef = React.createRef(); + const childBRef = React.createRef(); + const childCRef = React.createRef(); const root = ReactDOMClient.createRoot(container); - function Test() { - return ( -
    - -
    Child 1
    - -
    Child 2
    -
    -
    Child 3
    -
    -
    - ); - } - await act(() => { - root.render(); - }); - - const logs = []; - fragmentRef.current.addEventListener('click', e => { - logs.push(e.target.textContent); - }); - - const [child1, child2, child3] = parentRef.current.children; - child1.click(); - child2.click(); - child3.click(); - expect(logs).toEqual(['Child 1', 'Child 2', 'Child 3']); - }); - - // @gate enableFragmentRefs && enableActivity - it('handles Activity modes switching', async () => { - const fragmentRef = React.createRef(); - const fragmentRef2 = React.createRef(); - const parentRef = React.createRef(); - const root = ReactDOMClient.createRoot(container); - - function Test({mode}) { - return ( -
    + root.render( +
    - -
    Child
    - -
    Child 2
    +
    A
    +
    + +
    B
    - +
    + +
    C
    +
    -
    +
    , ); - } - - await act(() => { - root.render(); }); let logs = []; + + function handleFragmentRefClicks() { + logs.push('fragmentRef'); + } + + function handleNestedFragmentRefClicks() { + logs.push('nestedFragmentRef'); + } + + function handleNestedFragmentRef2Clicks() { + logs.push('nestedFragmentRef2'); + } + + fragmentRef.current.addEventListener('click', handleFragmentRefClicks); + nestedFragmentRef.current.addEventListener( + 'click', + handleNestedFragmentRefClicks, + ); + nestedFragmentRef2.current.addEventListener( + 'click', + handleNestedFragmentRef2Clicks, + ); + + childBRef.current.click(); + // Event bubbles to the parent fragment + expect(logs).toEqual(['nestedFragmentRef', 'fragmentRef']); + + logs = []; + + childARef.current.click(); + expect(logs).toEqual(['fragmentRef']); + + logs = []; + childCRef.current.click(); + expect(logs).toEqual(['fragmentRef', 'nestedFragmentRef2']); + + logs = []; + + fragmentRef.current.removeEventListener( + 'click', + handleFragmentRefClicks, + ); + nestedFragmentRef.current.removeEventListener( + 'click', + handleNestedFragmentRefClicks, + ); + childCRef.current.click(); + expect(logs).toEqual(['nestedFragmentRef2']); + }); + + // @gate enableFragmentRefs + it('adds an event listener to a newly added child', async () => { + const fragmentRef = React.createRef(); + const childRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + let showChild; + + function Component() { + const [shouldShowChild, setShouldShowChild] = React.useState(false); + showChild = () => { + setShouldShowChild(true); + }; + + return ( +
    + +
    A
    + {shouldShowChild && ( +
    + B +
    + )} +
    +
    + ); + } + + await act(() => { + root.render(); + }); + + expect(fragmentRef.current).not.toBe(null); + expect(childRef.current).toBe(null); + + let hasClicked = false; fragmentRef.current.addEventListener('click', () => { - logs.push('clicked 1'); + hasClicked = true; }); - fragmentRef2.current.addEventListener('click', () => { - logs.push('clicked 2'); - }); - parentRef.current.lastChild.click(); - expect(logs).toEqual(['clicked 1', 'clicked 2']); - logs = []; await act(() => { - root.render(); + showChild(); }); - parentRef.current.firstChild.click(); - parentRef.current.lastChild.click(); + expect(childRef.current).not.toBe(null); + + childRef.current.click(); + expect(hasClicked).toBe(true); + }); + + // @gate enableFragmentRefs + it('applies event listeners to host children nested within non-host children', async () => { + const fragmentRef = React.createRef(); + const childRef = React.createRef(); + const nestedChildRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + await act(() => { + root.render( +
    + +
    Host A
    + + + +
    Host B
    +
    +
    +
    +
    +
    , + ); + }); + const logs = []; + fragmentRef.current.addEventListener('click', e => { + logs.push(e.target.textContent); + }); + expect(logs).toEqual([]); + childRef.current.click(); + expect(logs).toEqual(['Host A']); + nestedChildRef.current.click(); + expect(logs).toEqual(['Host A', 'Host B']); + }); + + // @gate enableFragmentRefs + it('allows adding and cleaning up listeners in effects', async () => { + const root = ReactDOMClient.createRoot(container); + + let logs = []; + function logClick(e) { + logs.push(e.currentTarget.id); + } + + let rerender; + let removeEventListeners; + + function Test() { + const fragmentRef = React.useRef(null); + // eslint-disable-next-line no-unused-vars + const [_, setState] = React.useState(0); + rerender = () => { + setState(p => p + 1); + }; + removeEventListeners = () => { + fragmentRef.current.removeEventListener('click', logClick); + }; + React.useEffect(() => { + fragmentRef.current.addEventListener('click', logClick); + + return removeEventListeners; + }); + + return ( + +
    + + ); + } + + // The event listener was applied + await act(() => root.render()); + expect(logs).toEqual([]); + document.querySelector('#child-a').click(); + expect(logs).toEqual(['child-a']); + + // The event listener can be removed and re-added + logs = []; + await act(rerender); + document.querySelector('#child-a').click(); + expect(logs).toEqual(['child-a']); + }); + + // @gate enableFragmentRefs + it('does not apply removed event listeners to new children', async () => { + const root = ReactDOMClient.createRoot(container); + const fragmentRef = React.createRef(null); + function Test() { + return ( + +
    + + ); + } + + let logs = []; + function logClick(e) { + logs.push(e.currentTarget.id); + } + await act(() => { + root.render(); + }); + fragmentRef.current.addEventListener('click', logClick); + const childA = document.querySelector('#child-a'); + childA.click(); + expect(logs).toEqual(['child-a']); logs = []; + fragmentRef.current.removeEventListener('click', logClick); + childA.click(); + expect(logs).toEqual([]); + }); + + // @gate enableFragmentRefs + it('applies event listeners to portaled children', async () => { + const fragmentRef = React.createRef(); + const childARef = React.createRef(); + const childBRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + function Test() { + return ( + +
    + {createPortal( +
    , + document.body, + )} + + ); + } + await act(() => { - root.render(); + root.render(); }); - parentRef.current.lastChild.click(); - // Event order is flipped here because the nested child re-registers first - expect(logs).toEqual(['clicked 2', 'clicked 1']); + + const logs = []; + fragmentRef.current.addEventListener('click', e => { + logs.push(e.target.id); + }); + + childARef.current.click(); + expect(logs).toEqual(['child-a']); + + logs.length = 0; + childBRef.current.click(); + expect(logs).toEqual(['child-b']); + }); + + describe('with activity', () => { + // @gate enableFragmentRefs && enableActivity + it('does not apply event listeners to hidden trees', async () => { + const parentRef = React.createRef(); + const fragmentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + function Test() { + return ( +
    + +
    Child 1
    + +
    Child 2
    +
    +
    Child 3
    +
    +
    + ); + } + + await act(() => { + root.render(); + }); + + const logs = []; + fragmentRef.current.addEventListener('click', e => { + logs.push(e.target.textContent); + }); + + const [child1, child2, child3] = parentRef.current.children; + child1.click(); + child2.click(); + child3.click(); + expect(logs).toEqual(['Child 1', 'Child 3']); + }); + + // @gate enableFragmentRefs && enableActivity + it('applies event listeners to visible trees', async () => { + const parentRef = React.createRef(); + const fragmentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + function Test() { + return ( +
    + +
    Child 1
    + +
    Child 2
    +
    +
    Child 3
    +
    +
    + ); + } + + await act(() => { + root.render(); + }); + + const logs = []; + fragmentRef.current.addEventListener('click', e => { + logs.push(e.target.textContent); + }); + + const [child1, child2, child3] = parentRef.current.children; + child1.click(); + child2.click(); + child3.click(); + expect(logs).toEqual(['Child 1', 'Child 2', 'Child 3']); + }); + + // @gate enableFragmentRefs && enableActivity + it('handles Activity modes switching', async () => { + const fragmentRef = React.createRef(); + const fragmentRef2 = React.createRef(); + const parentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + function Test({mode}) { + return ( +
    + + +
    Child
    + +
    Child 2
    +
    +
    +
    +
    + ); + } + + await act(() => { + root.render(); + }); + + let logs = []; + fragmentRef.current.addEventListener('click', () => { + logs.push('clicked 1'); + }); + fragmentRef2.current.addEventListener('click', () => { + logs.push('clicked 2'); + }); + parentRef.current.lastChild.click(); + expect(logs).toEqual(['clicked 1', 'clicked 2']); + + logs = []; + await act(() => { + root.render(); + }); + parentRef.current.firstChild.click(); + parentRef.current.lastChild.click(); + expect(logs).toEqual([]); + + logs = []; + await act(() => { + root.render(); + }); + parentRef.current.lastChild.click(); + // Event order is flipped here because the nested child re-registers first + expect(logs).toEqual(['clicked 2', 'clicked 1']); + }); + }); + }); + + describe('dispatchEvent()', () => { + // @gate enableFragmentRefs + it('fires events on the host parent if bubbles=true', async () => { + const fragmentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + let logs = []; + + function handleClick(e) { + logs.push([e.type, e.target.id, e.currentTarget.id]); + } + + function Test({isMounted}) { + return ( +
    +
    + {isMounted && ( + +
    + Hi +
    +
    + )} +
    +
    + ); + } + + await act(() => { + root.render(); + }); + + let isCancelable = !fragmentRef.current.dispatchEvent( + new MouseEvent('click', {bubbles: true}), + ); + expect(logs).toEqual([ + ['click', 'parent', 'parent'], + ['click', 'parent', 'grandparent'], + ]); + expect(isCancelable).toBe(false); + + const fragmentInstanceHandle = fragmentRef.current; + await act(() => { + root.render(); + }); + logs = []; + isCancelable = !fragmentInstanceHandle.dispatchEvent( + new MouseEvent('click', {bubbles: true}), + ); + expect(logs).toEqual([]); + expect(isCancelable).toBe(false); + + logs = []; + isCancelable = !fragmentInstanceHandle.dispatchEvent( + new MouseEvent('click', {bubbles: false}), + ); + expect(logs).toEqual([]); + expect(isCancelable).toBe(false); + }); + + // @gate enableFragmentRefs + it('fires events on self, and only self if bubbles=false', async () => { + const fragmentRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + let logs = []; + + function handleClick(e) { + logs.push([e.type, e.target.id, e.currentTarget.id]); + } + + function Test() { + return ( +
    + +
    + ); + } + + await act(() => { + root.render(); + }); + + fragmentRef.current.addEventListener('click', handleClick); + + fragmentRef.current.dispatchEvent( + new MouseEvent('click', {bubbles: true}), + ); + expect(logs).toEqual([ + ['click', undefined, undefined], + ['click', 'parent', 'parent'], + ]); + + logs = []; + + fragmentRef.current.dispatchEvent( + new MouseEvent('click', {bubbles: false}), + ); + expect(logs).toEqual([['click', undefined, undefined]]); }); }); }); @@ -966,4 +1169,568 @@ describe('FragmentRefs', () => { expect(fragmentHandle.getRootNode()).toBe(fragmentHandle); }); }); + + describe('compareDocumentPosition', () => { + function expectPosition(position, spec) { + const positionResult = { + following: (position & Node.DOCUMENT_POSITION_FOLLOWING) !== 0, + preceding: (position & Node.DOCUMENT_POSITION_PRECEDING) !== 0, + contains: (position & Node.DOCUMENT_POSITION_CONTAINS) !== 0, + containedBy: (position & Node.DOCUMENT_POSITION_CONTAINED_BY) !== 0, + disconnected: (position & Node.DOCUMENT_POSITION_DISCONNECTED) !== 0, + implementationSpecific: + (position & Node.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC) !== 0, + }; + expect(positionResult).toEqual(spec); + } + // @gate enableFragmentRefs + it('returns the relationship between the fragment instance and a given node', async () => { + const fragmentRef = React.createRef(); + const beforeRef = React.createRef(); + const afterRef = React.createRef(); + const middleChildRef = React.createRef(); + const firstChildRef = React.createRef(); + const lastChildRef = React.createRef(); + const containerRef = React.createRef(); + const disconnectedElement = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + + function Test() { + return ( +
    +
    + +
    +
    +
    + +
    +
    + ); + } + + await act(() => root.render()); + + // document.body is preceding and contains the fragment + expectPosition( + fragmentRef.current.compareDocumentPosition(document.body), + { + preceding: true, + following: false, + contains: true, + containedBy: false, + disconnected: false, + implementationSpecific: false, + }, + ); + + // beforeRef is preceding the fragment + expectPosition( + fragmentRef.current.compareDocumentPosition(beforeRef.current), + { + preceding: true, + following: false, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: false, + }, + ); + + // afterRef is following the fragment + expectPosition( + fragmentRef.current.compareDocumentPosition(afterRef.current), + { + preceding: false, + following: true, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: false, + }, + ); + + // firstChildRef is contained by the fragment + expectPosition( + fragmentRef.current.compareDocumentPosition(firstChildRef.current), + { + preceding: false, + following: false, + contains: false, + containedBy: true, + disconnected: false, + implementationSpecific: false, + }, + ); + + // middleChildRef is contained by the fragment + expectPosition( + fragmentRef.current.compareDocumentPosition(middleChildRef.current), + { + preceding: false, + following: false, + contains: false, + containedBy: true, + disconnected: false, + implementationSpecific: false, + }, + ); + + // lastChildRef is contained by the fragment + expectPosition( + fragmentRef.current.compareDocumentPosition(lastChildRef.current), + { + preceding: false, + following: false, + contains: false, + containedBy: true, + disconnected: false, + implementationSpecific: false, + }, + ); + + // containerRef preceds and contains the fragment + expectPosition( + fragmentRef.current.compareDocumentPosition(containerRef.current), + { + preceding: true, + following: false, + contains: true, + containedBy: false, + disconnected: false, + implementationSpecific: false, + }, + ); + + expectPosition( + fragmentRef.current.compareDocumentPosition(disconnectedElement), + { + preceding: false, + following: true, + contains: false, + containedBy: false, + disconnected: true, + implementationSpecific: true, + }, + ); + }); + + // @gate enableFragmentRefs + it('handles fragment instances with one child', async () => { + const fragmentRef = React.createRef(); + const beforeRef = React.createRef(); + const afterRef = React.createRef(); + const containerRef = React.createRef(); + const onlyChildRef = React.createRef(); + const disconnectedElement = document.createElement('div'); + const root = ReactDOMClient.createRoot(container); + + function Test() { + return ( +
    +
    +
    + +
    + +
    +
    +
    + ); + } + + await act(() => root.render()); + expectPosition( + fragmentRef.current.compareDocumentPosition(beforeRef.current), + { + preceding: true, + following: false, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: false, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(afterRef.current), + { + preceding: false, + following: true, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: false, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(onlyChildRef.current), + { + preceding: false, + following: false, + contains: false, + containedBy: true, + disconnected: false, + implementationSpecific: false, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(containerRef.current), + { + preceding: true, + following: false, + contains: true, + containedBy: false, + disconnected: false, + implementationSpecific: false, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(disconnectedElement), + { + preceding: false, + following: true, + contains: false, + containedBy: false, + disconnected: true, + implementationSpecific: true, + }, + ); + }); + + // @gate enableFragmentRefs + it('handles empty fragment instances', async () => { + const fragmentRef = React.createRef(); + const beforeParentRef = React.createRef(); + const beforeRef = React.createRef(); + const afterRef = React.createRef(); + const afterParentRef = React.createRef(); + const containerRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + function Test() { + return ( + <> +
    +
    +
    + +
    +
    +
    + + ); + } + + await act(() => root.render()); + + expectPosition( + fragmentRef.current.compareDocumentPosition(document.body), + { + preceding: true, + following: false, + contains: true, + containedBy: false, + disconnected: false, + implementationSpecific: true, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(beforeRef.current), + { + preceding: true, + following: false, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: true, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(beforeParentRef.current), + { + preceding: true, + following: false, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: true, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(afterRef.current), + { + preceding: false, + following: true, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: true, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(afterParentRef.current), + { + preceding: false, + following: true, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: true, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(containerRef.current), + { + preceding: false, + following: false, + contains: true, + containedBy: false, + disconnected: false, + implementationSpecific: true, + }, + ); + }); + + // @gate enableFragmentRefs + it('returns disconnected for comparison with an unmounted fragment instance', async () => { + const fragmentRef = React.createRef(); + const containerRef = React.createRef(); + const root = ReactDOMClient.createRoot(container); + + function Test({mount}) { + return ( +
    + {mount && ( + +
    + + )} +
    + ); + } + + await act(() => root.render()); + + const fragmentHandle = fragmentRef.current; + + expectPosition( + fragmentHandle.compareDocumentPosition(containerRef.current), + { + preceding: true, + following: false, + contains: true, + containedBy: false, + disconnected: false, + implementationSpecific: false, + }, + ); + + await act(() => { + root.render(); + }); + + expectPosition( + fragmentHandle.compareDocumentPosition(containerRef.current), + { + preceding: false, + following: false, + contains: false, + containedBy: false, + disconnected: true, + implementationSpecific: false, + }, + ); + }); + + describe('with portals', () => { + // @gate enableFragmentRefs + it('handles portaled elements', async () => { + const fragmentRef = React.createRef(); + const portaledSiblingRef = React.createRef(); + const portaledChildRef = React.createRef(); + + function Test() { + return ( +
    + {createPortal(
    , document.body)} + + {createPortal(
    , document.body)} +
    + +
    + ); + } + + const root = ReactDOMClient.createRoot(container); + await act(() => root.render()); + + // The sibling is preceding in both the DOM and the React tree + expectPosition( + fragmentRef.current.compareDocumentPosition( + portaledSiblingRef.current, + ), + { + preceding: true, + following: false, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: false, + }, + ); + + // The child is contained by in the React tree but not in the DOM + expectPosition( + fragmentRef.current.compareDocumentPosition(portaledChildRef.current), + { + preceding: false, + following: false, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: true, + }, + ); + }); + + // @gate enableFragmentRefs + it('handles multiple portals to the same element', async () => { + const root = ReactDOMClient.createRoot(container); + const fragmentRef = React.createRef(); + const childARef = React.createRef(); + const childBRef = React.createRef(); + const childCRef = React.createRef(); + + function Test() { + const [c, setC] = React.useState(false); + React.useEffect(() => { + setC(true); + }); + + return ( + <> + {createPortal( + +
    + {c ?
    : null} + , + document.body, + )} + {createPortal(

    , document.body)} + + ); + } + + await act(() => root.render()); + + // Due to effect, order is A->B->C + expect(document.body.innerHTML).toBe( + '

    ' + + '
    ' + + '

    ' + + '
    ', + ); + + expectPosition( + fragmentRef.current.compareDocumentPosition(document.body), + { + preceding: true, + following: false, + contains: true, + containedBy: false, + disconnected: false, + implementationSpecific: false, + }, + ); + + expectPosition( + fragmentRef.current.compareDocumentPosition(childARef.current), + { + preceding: false, + following: false, + contains: false, + containedBy: true, + disconnected: false, + implementationSpecific: false, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(childBRef.current), + { + preceding: false, + following: false, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: true, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(childCRef.current), + { + preceding: false, + following: false, + contains: false, + containedBy: true, + disconnected: false, + implementationSpecific: false, + }, + ); + }); + + // @gate enableFragmentRefs + it('handles empty fragments', async () => { + const fragmentRef = React.createRef(); + const childARef = React.createRef(); + const childBRef = React.createRef(); + + function Test() { + return ( + <> +
    + {createPortal(, document.body)} +
    + + ); + } + + const root = ReactDOMClient.createRoot(container); + await act(() => root.render()); + + expectPosition( + fragmentRef.current.compareDocumentPosition(document.body), + { + preceding: true, + following: false, + contains: true, + containedBy: false, + disconnected: false, + implementationSpecific: true, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(childARef.current), + { + preceding: true, + following: false, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: true, + }, + ); + expectPosition( + fragmentRef.current.compareDocumentPosition(childBRef.current), + { + preceding: true, + following: false, + contains: false, + containedBy: false, + disconnected: false, + implementationSpecific: true, + }, + ); + }); + }); + }); }); diff --git a/packages/react-dom/src/__tests__/ReactDOMInput-test.js b/packages/react-dom/src/__tests__/ReactDOMInput-test.js index 5b47095d7c..04bd96fe2e 100644 --- a/packages/react-dom/src/__tests__/ReactDOMInput-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMInput-test.js @@ -1536,10 +1536,14 @@ describe('ReactDOMInput', () => { ReactDOMClient.hydrateRoot(container, ); }); - // Currently, we don't fire onChange when hydrating - assertLog([]); - // Strangely, we leave `b` checked even though we rendered A with - // checked={true} and B with checked={false}. Arguably this is a bug. + if (gate(flags => flags.enableHydrationChangeEvent)) { + // We replayed the click since the value changed before hydration. + assertLog(['click b']); + } else { + assertLog([]); + // Strangely, we leave `b` checked even though we rendered A with + // checked={true} and B with checked={false}. Arguably this is a bug. + } expect(a.checked).toBe(false); expect(b.checked).toBe(true); expect(c.checked).toBe(false); @@ -1554,22 +1558,35 @@ describe('ReactDOMInput', () => { dispatchEventOnNode(c, 'click'); }); - // then since C's onClick doesn't set state, A becomes rechecked. assertLog(['click c']); - expect(a.checked).toBe(true); - expect(b.checked).toBe(false); - expect(c.checked).toBe(false); + if (gate(flags => flags.enableHydrationChangeEvent)) { + // then since C's onClick doesn't set state, B becomes rechecked. + expect(a.checked).toBe(false); + expect(b.checked).toBe(true); + expect(c.checked).toBe(false); + } else { + // then since C's onClick doesn't set state, A becomes rechecked + // since in this branch we didn't replay to select B. + expect(a.checked).toBe(true); + expect(b.checked).toBe(false); + expect(c.checked).toBe(false); + } expect(isCheckedDirty(a)).toBe(true); expect(isCheckedDirty(b)).toBe(true); expect(isCheckedDirty(c)).toBe(true); assertInputTrackingIsCurrent(container); - // And we can also change to B properly after hydration. await act(async () => { setUntrackedChecked.call(b, true); dispatchEventOnNode(b, 'click'); }); - assertLog(['click b']); + if (gate(flags => flags.enableHydrationChangeEvent)) { + // Since we already had this selected, this doesn't trigger a change again. + assertLog([]); + } else { + // And we can also change to B properly after hydration. + assertLog(['click b']); + } expect(a.checked).toBe(false); expect(b.checked).toBe(true); expect(c.checked).toBe(false); @@ -1628,8 +1645,12 @@ describe('ReactDOMInput', () => { ReactDOMClient.hydrateRoot(container, ); }); - // Currently, we don't fire onChange when hydrating - assertLog([]); + if (gate(flags => flags.enableHydrationChangeEvent)) { + // We replayed the click since the value changed before hydration. + assertLog(['click b']); + } else { + assertLog([]); + } expect(a.checked).toBe(false); expect(b.checked).toBe(true); expect(c.checked).toBe(false); diff --git a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js index 1cae7f15b0..70109949ba 100644 --- a/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js +++ b/packages/react-dom/src/__tests__/ReactDOMServerIntegrationUserInteraction-test.js @@ -52,6 +52,12 @@ describe('ReactDOMServerIntegrationUserInteraction', () => { } this.setState({value: event.target.value}); } + componentDidMount() { + if (this.props.cascade) { + // Trigger a cascading render immediately upon hydration which rerenders the input. + this.setState({cascade: true}); + } + } render() { return ( { } this.setState({value: event.target.value}); } + componentDidMount() { + if (this.props.cascade) { + // Trigger a cascading render immediately upon hydration which rerenders the textarea. + this.setState({cascade: true}); + } + } render() { return (