Compare commits

..

1 Commits

Author SHA1 Message Date
Mofei Zhang
ccfd5ecd3e [compiler][bugfix] Bail out when a memo block declares hoisted fns
Note that bailing out adds false positives for hoisted functions whose only references are within other functions. For example, this rewrite would be safe.
```js
// source program
  function foo() {
    return bar();
  }
  function bar() {
    return 42;
  }

// compiler output
let bar;
if (/* deps changed */) {
  function foo() {
    return bar();
  }
  bar = function bar() {
    return 42;
  }
}
```
These false positives are difficult to detect because any maybe-call of foo before the definition of bar would be invalid.

Instead of bailing out, we should rewrite hoisted function declarations to the following form.
```js
let bar$0;
if (/* deps changed */) {
  // All references within the declaring memo block
  // or before the function declaration should use
  // the original identifier `bar`
  function foo() {
    return bar();
  }
  function bar() {
    return 42;
  }
  bar$0 = bar;
}
// All references after the declaring memo block
// or after the function declaration should use
// the rewritten declaration `bar$0`
```
2025-04-30 17:21:12 -04:00
462 changed files with 4365 additions and 14280 deletions

View File

@@ -496,7 +496,6 @@ module.exports = {
'packages/react-devtools-shared/src/devtools/views/**/*.js',
'packages/react-devtools-shared/src/hook.js',
'packages/react-devtools-shared/src/backend/console.js',
'packages/react-devtools-shared/src/backend/fiber/renderer.js',
'packages/react-devtools-shared/src/backend/shared/DevToolsComponentStackFrame.js',
'packages/react-devtools-shared/src/frontend/utils/withPermissionsCheck.js',
],
@@ -505,7 +504,6 @@ module.exports = {
__IS_FIREFOX__: 'readonly',
__IS_EDGE__: 'readonly',
__IS_NATIVE__: 'readonly',
__IS_INTERNAL_MCP_BUILD__: 'readonly',
__IS_INTERNAL_VERSION__: 'readonly',
chrome: 'readonly',
},
@@ -581,7 +579,6 @@ module.exports = {
JSONValue: 'readonly',
JSResourceReference: 'readonly',
MouseEventHandler: 'readonly',
NavigateEvent: 'readonly',
PropagationPhases: 'readonly',
PropertyDescriptor: 'readonly',
React$AbstractComponent: 'readonly',
@@ -637,6 +634,5 @@ module.exports = {
AsyncLocalStorage: 'readonly',
async_hooks: 'readonly',
globalThis: 'readonly',
navigation: 'readonly',
},
};

View File

@@ -15,7 +15,6 @@ jobs:
outputs:
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
steps:
- run: echo ${{ github.event.pull_request.author_association }}
- name: Check is member or collaborator
id: check_is_member_or_collaborator
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}

View File

@@ -132,9 +132,9 @@ jobs:
mkdir ./compiled/facebook-www/__test_utils__
mv build/__test_utils__/ReactAllWarnings.js ./compiled/facebook-www/__test_utils__/ReactAllWarnings.js
# Copy eslint-plugin-react-hooks
# Move eslint-plugin-react-hooks into eslint-plugin-react-hooks
mkdir ./compiled/eslint-plugin-react-hooks
cp build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
mv build/oss-experimental/eslint-plugin-react-hooks/cjs/eslint-plugin-react-hooks.development.js \
./compiled/eslint-plugin-react-hooks/index.js
# Move unstable_server-external-runtime.js into facebook-www
@@ -167,12 +167,6 @@ jobs:
rm $RENDERER_FOLDER/ReactFabric-{dev,prod,profiling}.js
rm $RENDERER_FOLDER/ReactNativeRenderer-{dev,prod,profiling}.js
# Copy eslint-plugin-react-hooks
# NOTE: This is different from www, here we include the full package
# including package.json to include dependencies in fbsource.
mkdir "$BASE_FOLDER/tools"
cp -r build/oss-experimental/eslint-plugin-react-hooks "$BASE_FOLDER/tools"
# Move React Native version file
mv build/facebook-react-native/VERSION_NATIVE_FB ./compiled-rn/VERSION_NATIVE_FB
@@ -332,10 +326,10 @@ jobs:
git --no-pager diff -U0 --cached | grep '^[+-]' | head -n 100
echo "===================="
# Ignore REVISION or lines removing @generated headers.
if git diff --cached ':(exclude)*REVISION' ':(exclude)*/eslint-plugin-react-hooks/package.json' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" > /dev/null; then
if git diff --cached ':(exclude)*REVISION' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" > /dev/null; then
echo "Changes detected"
echo "===== Changes ====="
git --no-pager diff --cached ':(exclude)*REVISION' ':(exclude)*/eslint-plugin-react-hooks/package.json' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" | head -n 50
git --no-pager diff --cached ':(exclude)*REVISION' | grep -vE "^(@@|diff|index|\-\-\-|\+\+\+|\- \* @generated SignedSource)" | grep "^[+-]" | head -n 50
echo "==================="
echo "should_commit=true" >> "$GITHUB_OUTPUT"
else

View File

@@ -15,7 +15,6 @@ jobs:
outputs:
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
steps:
- run: echo ${{ github.event.pull_request.author_association }}
- name: Check is member or collaborator
id: check_is_member_or_collaborator
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}

View File

@@ -17,7 +17,6 @@ jobs:
outputs:
is_member_or_collaborator: ${{ steps.check_is_member_or_collaborator.outputs.is_member_or_collaborator }}
steps:
- run: echo ${{ github.event.pull_request.author_association }}
- name: Check is member or collaborator
id: check_is_member_or_collaborator
if: ${{ github.event.pull_request.author_association == 'MEMBER' || github.event.pull_request.author_association == 'COLLABORATOR' }}

View File

@@ -1,9 +1,3 @@
## 19.1.0-rc.2 (May 14, 2025)
## babel-plugin-react-compiler
* Fix for string attribute values with emoji [#33096](https://github.com/facebook/react/pull/33096) by [@josephsavona](https://github.com/josephsavona)
## 19.1.0-rc.1 (April 21, 2025)
## eslint-plugin-react-hooks

View File

@@ -1,5 +1,5 @@
import { c as _c } from "react/compiler-runtime"; // 
        @compilationMode:"all"
        @compilationMode(all)
function nonReactFn() {
  const $ = _c(1);
  let t0;

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @compilationMode(infer)
function nonReactFn() {
  return {};
}

View File

@@ -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 {};
}

View File

@@ -18,9 +18,8 @@ import {
import {getOrInsertWith} from '../Utils/utils';
import {ExternalFunction, isHookName} from '../HIR/Environment';
import {Err, Ok, Result} from '../Utils/Result';
import {LoggerEvent, PluginOptions} from './Options';
import {BabelFn, getReactCompilerRuntimeModule} from './Program';
import {SuppressionRange} from './Suppression';
import {CompilerReactTarget} from './Options';
import {getReactCompilerRuntimeModule} from './Program';
export function validateRestrictedImports(
path: NodePath<t.Program>,
@@ -53,65 +52,32 @@ export function validateRestrictedImports(
}
}
type ProgramContextOptions = {
program: NodePath<t.Program>;
suppressions: Array<SuppressionRange>;
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;
suppressions: Array<SuppressionRange>;
hasModuleScopeOptOut: boolean;
hookPattern: string | null;
/*
* 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<object> | Set<object> = new (WeakSet ?? Set)();
// known generated or referenced identifiers in the program
knownReferencedNames: Set<string> = new Set();
// generated imports
imports: Map<string, Map<string, NonLocalImportSpecifier>> = new Map();
/**
* Metadata from compilation
*/
retryErrors: Array<{fn: BabelFn; error: CompilerError}> = [];
inferredEffectLocations: Set<t.SourceLocation> = new Set();
constructor({
program,
suppressions,
opts,
filename,
code,
hasModuleScopeOptOut,
}: ProgramContextOptions) {
constructor(
program: NodePath<t.Program>,
reactRuntimeModule: CompilerReactTarget,
hookPattern: string | null,
) {
this.hookPattern = hookPattern;
this.scope = program.scope;
this.opts = opts;
this.filename = filename;
this.code = code;
this.reactRuntimeModule = getReactCompilerRuntimeModule(opts.target);
this.suppressions = suppressions;
this.hasModuleScopeOptOut = hasModuleScopeOptOut;
this.reactRuntimeModule = getReactCompilerRuntimeModule(reactRuntimeModule);
}
isHookName(name: string): boolean {
if (this.opts.environment.hookPattern == null) {
if (this.hookPattern == null) {
return isHookName(name);
} else {
const match = new RegExp(this.opts.environment.hookPattern).exec(name);
const match = new RegExp(this.hookPattern).exec(name);
return (
match != null && typeof match[1] === 'string' && isHookName(match[1])
);
@@ -213,12 +179,6 @@ export class ProgramContext {
});
return Err(error);
}
logEvent(event: LoggerEvent): void {
if (this.opts.logger != null) {
this.opts.logger.logEvent(this.filename, event);
}
}
}
function getExistingImports(

View File

@@ -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<string> | null | undefined;
eslintSuppressionRules?: Array<string> | null | undefined;
flowSuppressions: boolean;
/*
@@ -106,7 +106,7 @@ export type PluginOptions = {
*/
ignoreUseNoForget: boolean;
sources: Array<string> | ((filename: string) => boolean) | null;
sources?: Array<string> | ((filename: string) => boolean) | null;
/**
* The compiler has customized support for react-native-reanimated, intended as a temporary workaround.

View File

@@ -103,7 +103,6 @@ import {transformFire} from '../Transform';
import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender';
import {CompilerError} from '..';
import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions';
export type CompilerPipelineValue =
| {kind: 'ast'; name: string; value: CodegenFunction}
@@ -275,10 +274,6 @@ function runWithEnvironment(
if (env.config.validateNoImpureFunctionsInRender) {
validateNoImpureFunctionsInRender(hir).unwrap();
}
if (env.config.validateNoFreezingKnownMutableFunctions) {
validateNoFreezingKnownMutableFunctions(hir).unwrap();
}
}
inferReactivePlaces(hir);

View File

@@ -12,7 +12,7 @@ import {
CompilerErrorDetail,
ErrorSeverity,
} from '../CompilerError';
import {ReactFunctionType} from '../HIR/Environment';
import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment';
import {CodegenFunction} from '../ReactiveScopes';
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
import {isHookDeclaration} from '../Utils/HookDeclaration';
@@ -43,21 +43,17 @@ export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']);
export function findDirectiveEnablingMemoization(
directives: Array<t.Directive>,
): t.Directive | null {
return (
directives.find(directive =>
OPT_IN_DIRECTIVES.has(directive.value.value),
) ?? null
): Array<t.Directive> {
return directives.filter(directive =>
OPT_IN_DIRECTIVES.has(directive.value.value),
);
}
export function findDirectiveDisablingMemoization(
directives: Array<t.Directive>,
): t.Directive | null {
return (
directives.find(directive =>
OPT_OUT_DIRECTIVES.has(directive.value.value),
) ?? null
): Array<t.Directive> {
return directives.filter(directive =>
OPT_OUT_DIRECTIVES.has(directive.value.value),
);
}
@@ -92,16 +88,13 @@ export type CompileResult = {
function logError(
err: unknown,
context: {
opts: PluginOptions;
filename: string | null;
},
pass: CompilerPass,
fnLoc: t.SourceLocation | null,
): void {
if (context.opts.logger) {
if (pass.opts.logger) {
if (err instanceof CompilerError) {
for (const detail of err.details) {
context.opts.logger.logEvent(context.filename, {
pass.opts.logger.logEvent(pass.filename, {
kind: 'CompileError',
fnLoc,
detail: detail.options,
@@ -115,7 +108,7 @@ function logError(
stringifiedError = err?.toString() ?? '[ null ]';
}
context.opts.logger.logEvent(context.filename, {
pass.opts.logger.logEvent(pass.filename, {
kind: 'PipelineError',
fnLoc,
data: stringifiedError,
@@ -125,17 +118,13 @@ function logError(
}
function handleError(
err: unknown,
context: {
opts: PluginOptions;
filename: string | null;
},
pass: CompilerPass,
fnLoc: t.SourceLocation | null,
): void {
logError(err, context, fnLoc);
logError(err, pass, fnLoc);
if (
context.opts.panicThreshold === 'all_errors' ||
(context.opts.panicThreshold === 'critical_errors' &&
isCriticalError(err)) ||
pass.opts.panicThreshold === 'all_errors' ||
(pass.opts.panicThreshold === 'critical_errors' && isCriticalError(err)) ||
isConfigError(err) // Always throws regardless of panic threshold
) {
throw err;
@@ -198,6 +187,7 @@ export function createNewFunctionNode(
}
}
// Avoid visiting the new transformed version
ALREADY_COMPILED.add(transformedFn);
return transformedFn;
}
@@ -249,6 +239,13 @@ 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<object> | Set<object> = new (WeakSet ?? Set)();
const DEFAULT_ESLINT_SUPPRESSIONS = [
'react-hooks/exhaustive-deps',
'react-hooks/rules-of-hooks',
@@ -271,43 +268,41 @@ function isFilePartOfSources(
return false;
}
export type CompileProgramMetadata = {
export type CompileProgramResult = {
retryErrors: Array<{fn: BabelFn; error: CompilerError}>;
inferredEffectLocations: Set<t.SourceLocation>;
};
/**
* 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
* `compileProgram` is directly invoked by the react-compiler babel plugin, so
* exceptions thrown by this function will fail the babel build.
* - call `handleError` if your error is recoverable.
* Unless the error is a warning / info diagnostic, compilation of a function
* / entire file should also be skipped.
* - throw an exception if the error is fatal / not recoverable.
* Examples of this are invalid compiler configs or failure to codegen outlined
* functions *after* already emitting optimized components / hooks that invoke
* the outlined functions.
*/
export function compileProgram(
program: NodePath<t.Program>,
pass: CompilerPass,
): 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.
*/
): CompileProgramResult | null {
if (shouldSkipCompilation(program, pass)) {
return null;
}
const restrictedImportsErr = validateRestrictedImports(
program,
pass.opts.environment,
);
const environment = pass.opts.environment;
const restrictedImportsErr = validateRestrictedImports(program, 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
@@ -318,102 +313,16 @@ export function compileProgram(
pass.opts.eslintSuppressionRules ?? DEFAULT_ESLINT_SUPPRESSIONS,
pass.opts.flowSuppressions,
);
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<CompileSource> = findFunctionsToCompile(
program,
pass,
programContext,
);
const queue: Array<{
kind: 'original' | 'outlined';
fn: BabelFn;
fnType: ReactFunctionType;
}> = [];
const compiledFns: Array<CompileResult> = [];
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<t.Program>,
pass: CompilerPass,
programContext: ProgramContext,
): Array<CompileSource> {
const queue: Array<CompileSource> = [];
const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => {
const fnType = getReactFunctionType(fn, pass);
if (fnType === null || programContext.alreadyCompiled.has(fn.node)) {
const fnType = getReactFunctionType(fn, pass, environment);
if (fnType === null || ALREADY_COMPILED.has(fn.node)) {
return;
}
@@ -422,7 +331,7 @@ function findFunctionsToCompile(
* traversal will loop infinitely.
* Ensure we avoid visiting the original function again.
*/
programContext.alreadyCompiled.add(fn.node);
ALREADY_COMPILED.add(fn.node);
fn.skip();
queue.push({kind: 'original', fn, fnType});
@@ -437,6 +346,7 @@ function findFunctionsToCompile(
* can reference `this` which is unsafe for compilation
*/
node.skip();
return;
},
ClassExpression(node: NodePath<t.ClassExpression>) {
@@ -445,6 +355,7 @@ function findFunctionsToCompile(
* can reference `this` which is unsafe for compilation
*/
node.skip();
return;
},
FunctionDeclaration: traverseFunction,
@@ -459,206 +370,205 @@ function findFunctionsToCompile(
filename: pass.filename ?? null,
},
);
return queue;
}
/**
* 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 {
handleError(compileResult.error, programContext, fn.node.loc ?? null);
const retryErrors: Array<{fn: BabelFn; error: CompilerError}> = [];
const inferredEffectLocations = new Set<t.SourceLocation>();
const processFn = (
fn: BabelFn,
fnType: ReactFunctionType,
): null | CodegenFunction => {
let optInDirectives: Array<t.Directive> = [];
let optOutDirectives: Array<t.Directive> = [];
if (fn.node.body.type === 'BlockStatement') {
optInDirectives = findDirectiveEnablingMemoization(
fn.node.body.directives,
);
optOutDirectives = findDirectiveDisablingMemoization(
fn.node.body.directives,
);
}
const retryResult = retryCompileFunction(fn, fnType, programContext);
if (retryResult == null) {
/**
* 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),
};
} 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};
}
}
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,
});
}
return null;
}
compiledFn = retryResult;
} else {
compiledFn = compileResult.compiledFn;
}
/**
* 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,
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,
});
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,
});
if (programContext.hasModuleScopeOptOut) {
return null;
} else if (programContext.opts.noEmit) {
/**
* 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;
}
/**
* 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 compiledFn.inferredEffectLocations) {
if (loc !== GeneratedSource) {
programContext.inferredEffectLocations.add(loc);
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,
});
}
}
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;
compiledFns.push({
kind: current.kind,
compiledFn: compiled,
originalFn: current.fn,
});
}
}
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.
* Do not modify source if there is a module scope level opt out directive.
*/
const suppressionsInFunction = filterSuppressionsThatAffectFunction(
programContext.suppressions,
fn,
const moduleScopeOptOutDirectives = findDirectiveDisablingMemoization(
program.node.directives,
);
if (suppressionsInFunction.length > 0) {
return {
kind: 'error',
error: suppressionsToCompilerError(suppressionsInFunction),
};
}
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)
) {
if (moduleScopeOptOutDirectives.length > 0) {
return null;
}
/**
* Note that function suppressions are not checked in the retry pipeline, as
* they only affect auto-memoization features.
/*
* Only insert Forget-ified functions if we have not encountered a critical
* error elsewhere in the file, regardless of bailout mode.
*/
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<t.Program>,
compiledFns: Array<CompileResult>,
pass: CompilerPass,
programContext: ProgramContext,
): void {
const referencedBeforeDeclared =
pass.opts.gating != null
? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns)
@@ -666,7 +576,6 @@ function applyCompiledFunctions(
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, {
@@ -689,6 +598,7 @@ function applyCompiledFunctions(
if (compiledFns.length > 0) {
addImportsToProgram(program, programContext);
}
return {retryErrors, inferredEffectLocations};
}
function shouldSkipCompilation(
@@ -730,10 +640,14 @@ function shouldSkipCompilation(
function getReactFunctionType(
fn: BabelFn,
pass: CompilerPass,
/**
* TODO(mofeiZ): remove once we validate PluginOptions with Zod
*/
environment: EnvironmentConfig,
): ReactFunctionType | null {
const hookPattern = pass.opts.environment.hookPattern;
const hookPattern = environment.hookPattern;
if (fn.node.body.type === 'BlockStatement') {
if (findDirectiveEnablingMemoization(fn.node.body.directives) != null)
if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0)
return getComponentOrHookLike(fn, hookPattern) ?? 'Other';
}

View File

@@ -18,7 +18,7 @@ import {
import {getOrInsertWith} from '../Utils/utils';
import {Environment} from '../HIR';
import {DEFAULT_EXPORT} from '../HIR/Environment';
import {CompileProgramMetadata} from './Program';
import {CompileProgramResult} from './Program';
function throwInvalidReact(
options: Omit<CompilerErrorDetailOptions, 'severity'>,
@@ -109,7 +109,7 @@ export default function validateNoUntransformedReferences(
filename: string | null,
logger: Logger | null,
env: EnvironmentConfig,
compileResult: CompileProgramMetadata | null,
compileResult: CompileProgramResult | null,
): void {
const moduleLoadChecks = new Map<
string,
@@ -236,7 +236,7 @@ function transformProgram(
moduleLoadChecks: Map<string, Map<string, CheckInvalidReferenceFn>>,
filename: string | null,
logger: Logger | null,
compileResult: CompileProgramMetadata | null,
compileResult: CompileProgramResult | null,
): void {
const traversalState: TraversalState = {
shouldInvalidateScopes: true,

View File

@@ -9,7 +9,15 @@ import * as t from '@babel/types';
import {ZodError, z} from 'zod';
import {fromZodError} from 'zod-validation-error';
import {CompilerError} from '../CompilerError';
import {Logger, ProgramContext} from '../Entrypoint';
import {
CompilationMode,
defaultOptions,
Logger,
PanicThresholdOptions,
parsePluginOptions,
PluginOptions,
ProgramContext,
} from '../Entrypoint';
import {Err, Ok, Result} from '../Utils/Result';
import {
DEFAULT_GLOBALS,
@@ -150,7 +158,7 @@ export type Hook = z.infer<typeof HookSchema>;
* missing some recursive Object / Function shapeIds
*/
export const EnvironmentConfigSchema = z.object({
const EnvironmentConfigSchema = z.object({
customHooks: z.map(z.string(), HookSchema).default(new Map()),
/**
@@ -359,11 +367,6 @@ export const EnvironmentConfigSchema = z.object({
*/
validateNoImpureFunctionsInRender: z.boolean().default(false),
/**
* Validate against passing mutable functions to hooks
*/
validateNoFreezingKnownMutableFunctions: z.boolean().default(false),
/*
* When enabled, the compiler assumes that hooks follow the Rules of React:
* - Hooks may memoize computation based on any of their parameters, thus
@@ -632,6 +635,191 @@ export const EnvironmentConfigSchema = z.object({
export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
/**
* 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<EnvironmentConfig>;
export type ReactFunctionType = 'Component' | 'Hook' | 'Other';

View File

@@ -25,9 +25,6 @@ import {
BuiltInUseRefId,
BuiltInUseStateId,
BuiltInUseTransitionId,
BuiltInWeakMapId,
BuiltInWeakSetId,
ReanimatedSharedValueId,
ShapeRegistry,
addFunction,
addHook,
@@ -494,38 +491,6 @@ const TYPED_GLOBALS: Array<[string, BuiltInType]> = [
true,
),
],
[
'WeakMap',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.ConditionallyMutateIterator],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInWeakMapId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
},
null,
true,
),
],
[
'WeakSet',
addFunction(
DEFAULT_SHAPES,
[],
{
positionalParams: [Effect.ConditionallyMutateIterator],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInWeakSetId},
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Mutable,
},
null,
true,
),
],
// TODO: rest of Global objects
];
@@ -943,7 +908,7 @@ export function getReanimatedModuleType(registry: ShapeRegistry): ObjectType {
addHook(registry, {
positionalParams: [],
restParam: Effect.Freeze,
returnType: {kind: 'Object', shapeId: ReanimatedSharedValueId},
returnType: {kind: 'Poly'},
returnValueKind: ValueKind.Mutable,
noAlias: true,
calleeEffect: Effect.Read,

View File

@@ -1725,18 +1725,6 @@ export function isRefOrRefValue(id: Identifier): boolean {
return isUseRefType(id) || isRefValueType(id);
}
/*
* Returns true if the type is a Ref or a custom user type that acts like a ref when it
* shouldn't. For now the only other case of this is Reanimated's shared values.
*/
export function isRefOrRefLikeMutableType(type: Type): boolean {
return (
type.kind === 'Object' &&
(type.shapeId === 'BuiltInUseRefId' ||
type.shapeId == 'ReanimatedSharedValueId')
);
}
export function isSetStateType(id: Identifier): boolean {
return id.type.kind === 'Function' && id.type.shapeId === 'BuiltInSetState';
}

View File

@@ -203,8 +203,6 @@ export const BuiltInPropsId = 'BuiltInProps';
export const BuiltInArrayId = 'BuiltInArray';
export const BuiltInSetId = 'BuiltInSet';
export const BuiltInMapId = 'BuiltInMap';
export const BuiltInWeakSetId = 'BuiltInWeakSet';
export const BuiltInWeakMapId = 'BuiltInWeakMap';
export const BuiltInFunctionId = 'BuiltInFunction';
export const BuiltInJsxId = 'BuiltInJsx';
export const BuiltInObjectId = 'BuiltInObject';
@@ -227,9 +225,6 @@ export const BuiltInStartTransitionId = 'BuiltInStartTransition';
export const BuiltInFireId = 'BuiltInFire';
export const BuiltInFireFunctionId = 'BuiltInFireFunction';
// See getReanimatedModuleType() in Globals.ts — this is part of supporting Reanimated's ref-like types
export const ReanimatedSharedValueId = 'ReanimatedSharedValueId';
// ShapeRegistry with default definitions for built-ins.
export const BUILTIN_SHAPES: ShapeRegistry = new Map();
@@ -769,101 +764,6 @@ addObject(BUILTIN_SHAPES, BuiltInMapId, [
],
]);
addObject(BUILTIN_SHAPES, BuiltInWeakSetId, [
[
/**
* add(value)
* Parameters
* value: the value of the element to add to the Set object.
* Returns the Set object with added value.
*/
'add',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInWeakSetId},
calleeEffect: Effect.Store,
// returnValueKind is technically dependent on the ValueKind of the set itself
returnValueKind: ValueKind.Mutable,
}),
],
[
/**
* setInstance.delete(value)
* Returns true if value was already in Set; otherwise false.
*/
'delete',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'has',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
]);
addObject(BUILTIN_SHAPES, BuiltInWeakMapId, [
[
'delete',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Primitive,
}),
],
[
'get',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: {kind: 'Poly'},
calleeEffect: Effect.Capture,
returnValueKind: ValueKind.Mutable,
}),
],
[
'has',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Read],
restParam: null,
returnType: PRIMITIVE_TYPE,
calleeEffect: Effect.Read,
returnValueKind: ValueKind.Primitive,
}),
],
[
/**
* Params
* key: the key of the element to add to the Map object. The key may be
* any JavaScript type (any primitive value or any type of JavaScript
* object).
* value: the value of the element to add to the Map object.
* Returns the Map object.
*/
'set',
addFunction(BUILTIN_SHAPES, [], {
positionalParams: [Effect.Capture, Effect.Capture],
restParam: null,
returnType: {kind: 'Object', shapeId: BuiltInWeakMapId},
calleeEffect: Effect.Store,
returnValueKind: ValueKind.Mutable,
}),
],
]);
addObject(BUILTIN_SHAPES, BuiltInUseStateId, [
['0', {kind: 'Poly'}],
[

View File

@@ -17,6 +17,7 @@ export {buildReactiveScopeTerminalsHIR} from './BuildReactiveScopeTerminalsHIR';
export {computeDominatorTree, computePostDominatorTree} from './Dominator';
export {
Environment,
parseConfigPragmaForTests,
validateEnvironmentConfig,
type EnvironmentConfig,
type ExternalFunction,

View File

@@ -1,134 +0,0 @@
/**
* 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 {
Effect,
HIRFunction,
Identifier,
isMutableEffect,
isRefOrRefLikeMutableType,
makeInstructionId,
} from '../HIR/HIR';
import {eachInstructionValueOperand} from '../HIR/visitors';
import {isMutable} from '../ReactiveScopes/InferReactiveScopeVariables';
import DisjointSet from '../Utils/DisjointSet';
/**
* If a function captures a mutable value but never gets called, we don't infer a
* mutable range for that function. This means that we also don't alias the function
* with its mutable captures.
*
* This case is tricky, because we don't generally know for sure what is a mutation
* and what may just be a normal function call. For example:
*
* ```
* hook useFoo() {
* const x = makeObject();
* return () => {
* return readObject(x); // could be a mutation!
* }
* }
* ```
*
* If we pessimistically assume that all such cases are mutations, we'd have to group
* lots of memo scopes together unnecessarily. However, if there is definitely a mutation:
*
* ```
* hook useFoo(createEntryForKey) {
* const cache = new WeakMap();
* return (key) => {
* let entry = cache.get(key);
* if (entry == null) {
* entry = createEntryForKey(key);
* cache.set(key, entry); // known mutation!
* }
* return entry;
* }
* }
* ```
*
* Then we have to ensure that the function and its mutable captures alias together and
* end up in the same scope. However, aliasing together isn't enough if the function
* and operands all have empty mutable ranges (end = start + 1).
*
* This pass finds function expressions and object methods that have an empty mutable range
* and known-mutable operands which also don't have a mutable range, and ensures that the
* function and those operands are aliased together *and* that their ranges are updated to
* end after the function expression. This is sufficient to ensure that a reactive scope is
* created for the alias set.
*/
export function inferAliasForUncalledFunctions(
fn: HIRFunction,
aliases: DisjointSet<Identifier>,
): void {
for (const block of fn.body.blocks.values()) {
instrs: for (const instr of block.instructions) {
const {lvalue, value} = instr;
if (
value.kind !== 'ObjectMethod' &&
value.kind !== 'FunctionExpression'
) {
continue;
}
/*
* If the function is known to be mutated, we will have
* already aliased any mutable operands with it
*/
const range = lvalue.identifier.mutableRange;
if (range.end > range.start + 1) {
continue;
}
/*
* If the function already has operands with an active mutable range,
* then we don't need to do anything — the function will have already
* been visited and included in some mutable alias set. This case can
* also occur due to visiting the same function in an earlier iteration
* of the outer fixpoint loop.
*/
for (const operand of eachInstructionValueOperand(value)) {
if (isMutable(instr, operand)) {
continue instrs;
}
}
const operands: Set<Identifier> = new Set();
for (const effect of value.loweredFunc.func.effects ?? []) {
if (effect.kind !== 'ContextMutation') {
continue;
}
/*
* We're looking for known-mutations only, so we look at the effects
* rather than function context
*/
if (effect.effect === Effect.Store || effect.effect === Effect.Mutate) {
for (const operand of effect.places) {
/*
* It's possible that function effect analysis thinks there was a context mutation,
* but then InferReferenceEffects figures out some operands are globals and therefore
* creates a non-mutable effect for those operands.
* We should change InferReferenceEffects to swap the ContextMutation for a global
* mutation in that case, but for now we just filter them out here
*/
if (
isMutableEffect(operand.effect, operand.loc) &&
!isRefOrRefLikeMutableType(operand.identifier.type)
) {
operands.add(operand.identifier);
}
}
}
}
if (operands.size !== 0) {
operands.add(lvalue.identifier);
aliases.union([...operands]);
// Update mutable ranges, if the ranges are empty then a reactive scope isn't created
for (const operand of operands) {
operand.mutableRange.end = makeInstructionId(instr.id + 1);
}
}
}
}
}

View File

@@ -6,7 +6,6 @@
*/
import {HIRFunction, Identifier} from '../HIR/HIR';
import {inferAliasForUncalledFunctions} from './InerAliasForUncalledFunctions';
import {inferAliases} from './InferAlias';
import {inferAliasForPhis} from './InferAliasForPhis';
import {inferAliasForStores} from './InferAliasForStores';
@@ -77,7 +76,6 @@ export function inferMutableRanges(ir: HIRFunction): void {
while (true) {
inferMutableRangesForAlias(ir, aliases);
inferAliasForPhis(ir, aliases);
inferAliasForUncalledFunctions(ir, aliases);
const nextAliases = aliases.canonicalize();
if (areEqualMaps(prevAliases, nextAliases)) {
break;

View File

@@ -327,23 +327,6 @@ 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;
}
@@ -509,73 +492,6 @@ 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) {

View File

@@ -2327,12 +2327,9 @@ function codegenInstructionValue(
* u0080 to u009F: C1 control codes
* u00A0 to uFFFF: All non-basic Latin characters
* https://en.wikipedia.org/wiki/List_of_Unicode_characters#Control_codes
*
* u010000 to u10FFFF: Astral plane characters
* https://mathiasbynens.be/notes/javascript-unicode
*/
const STRING_REQUIRES_EXPR_CONTAINER_PATTERN =
/[\u{0000}-\u{001F}\u{007F}\u{0080}-\u{FFFF}\u{010000}-\u{10FFFF}]|"|\\/u;
/[\u{0000}-\u{001F}\u{007F}\u{0080}-\u{FFFF}]|"|\\/u;
function codegenJsxAttribute(
cx: Context,
attribute: JsxAttribute,

View File

@@ -255,12 +255,6 @@ function writeReactiveValue(writer: Writer, value: ReactiveValue): void {
}
}
export function printReactiveTerminal(terminal: ReactiveTerminal): string {
const writer = new Writer();
writeTerminal(writer, terminal);
return writer.complete();
}
function writeTerminal(writer: Writer, terminal: ReactiveTerminal): void {
switch (terminal.kind) {
case 'break': {

View File

@@ -1,210 +0,0 @@
/**
* 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<unknown, unknown> {
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<Record<keyof EnvironmentConfig, unknown>> = {};
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<PluginOptions> = {
gating: {
source: 'ReactForgetFeatureFlag',
importSpecifierName: 'isForgetEnabled_Fixtures',
},
};
export function parseConfigPragmaForTests(
pragma: string,
defaults: {
compilationMode: CompilationMode;
},
): PluginOptions {
const environment = parseConfigPragmaEnvironmentForTest(pragma);
const options: Record<keyof PluginOptions, unknown> = {
...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);
}

View File

@@ -1,130 +0,0 @@
/**
* 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 {CompilerError, Effect, ErrorSeverity} from '..';
import {
FunctionEffect,
HIRFunction,
IdentifierId,
isMutableEffect,
isRefOrRefLikeMutableType,
Place,
} from '../HIR';
import {
eachInstructionValueOperand,
eachTerminalOperand,
} from '../HIR/visitors';
import {Result} from '../Utils/Result';
import {Iterable_some} from '../Utils/utils';
/**
* Validates that functions with known mutations (ie due to types) cannot be passed
* where a frozen value is expected. Example:
*
* ```
* function Component() {
* const cache = new Map();
* const onClick = () => {
* cache.set(...);
* }
* useHook(onClick); // ERROR: cannot pass a mutable value
* return <Foo onClick={onClick} /> // ERROR: cannot pass a mutable value
* }
* ```
*
* Because `onClick` function mutates `cache` when called, `onClick` is equivalent to a mutable
* variables. But unlike other mutables values like an array, the receiver of the function has
* no way to avoid mutation — for example, a function can receive an array and choose not to mutate
* it, but there's no way to know that a function is mutable and avoid calling it.
*
* This pass detects functions with *known* mutations (Store or Mutate, not ConditionallyMutate)
* that are passed where a frozen value is expected and rejects them.
*/
export function validateNoFreezingKnownMutableFunctions(
fn: HIRFunction,
): Result<void, CompilerError> {
const errors = new CompilerError();
const contextMutationEffects: Map<
IdentifierId,
Extract<FunctionEffect, {kind: 'ContextMutation'}>
> = new Map();
function visitOperand(operand: Place): void {
if (operand.effect === Effect.Freeze) {
const effect = contextMutationEffects.get(operand.identifier.id);
if (effect != null) {
errors.push({
reason: `This argument is a function which modifies local variables when called, which can bypass memoization and cause the UI not to update`,
description: `Functions that are returned from hooks, passed as arguments to hooks, or passed as props to components may not mutate local variables`,
loc: operand.loc,
severity: ErrorSeverity.InvalidReact,
});
errors.push({
reason: `The function modifies a local variable here`,
loc: effect.loc,
severity: ErrorSeverity.InvalidReact,
});
}
}
}
for (const block of fn.body.blocks.values()) {
for (const instr of block.instructions) {
const {lvalue, value} = instr;
switch (value.kind) {
case 'LoadLocal': {
const effect = contextMutationEffects.get(value.place.identifier.id);
if (effect != null) {
contextMutationEffects.set(lvalue.identifier.id, effect);
}
break;
}
case 'StoreLocal': {
const effect = contextMutationEffects.get(value.value.identifier.id);
if (effect != null) {
contextMutationEffects.set(lvalue.identifier.id, effect);
contextMutationEffects.set(
value.lvalue.place.identifier.id,
effect,
);
}
break;
}
case 'FunctionExpression': {
const knownMutation = (value.loweredFunc.func.effects ?? []).find(
effect => {
return (
effect.kind === 'ContextMutation' &&
(effect.effect === Effect.Store ||
effect.effect === Effect.Mutate) &&
Iterable_some(effect.places, place => {
return (
isMutableEffect(place.effect, place.loc) &&
!isRefOrRefLikeMutableType(place.identifier.type)
);
})
);
},
);
if (knownMutation && knownMutation.kind === 'ContextMutation') {
contextMutationEffects.set(lvalue.identifier.id, knownMutation);
}
break;
}
default: {
for (const operand of eachInstructionValueOperand(value)) {
visitOperand(operand);
}
}
}
}
for (const operand of eachTerminalOperand(block.terminal)) {
visitOperand(operand);
}
}
return errors.asResult();
}

View File

@@ -141,14 +141,14 @@ function getCompareDependencyResultDescription(
): string {
switch (result) {
case CompareDependencyResult.Ok:
return 'Dependencies equal';
return 'dependencies equal';
case CompareDependencyResult.RootDifference:
case CompareDependencyResult.PathDifference:
return 'Inferred different dependency than source';
return 'inferred different dependency than source';
case CompareDependencyResult.RefAccessDifference:
return 'Differences in ref.current access';
return 'differences in ref.current access';
case CompareDependencyResult.Subpath:
return 'Inferred less specific property than source';
return 'inferred less specific property than source';
}
}
@@ -279,20 +279,17 @@ function validateInferredDep(
severity: ErrorSeverity.CannotPreserveMemoization,
reason:
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected',
description:
DEBUG ||
// If the dependency is a named variable then we can report it. Otherwise only print in debug mode
(dep.identifier.name != null && dep.identifier.name.kind === 'named')
? `The inferred dependency was \`${prettyPrintScopeDependency(
dep,
)}\`, but the source dependencies were [${validDepsInMemoBlock
.map(dep => printManualMemoDependency(dep, true))
.join(', ')}]. ${
errorDiagnostic
? getCompareDependencyResultDescription(errorDiagnostic)
: 'Inferred dependency not present in source'
}`
: null,
description: DEBUG
? `The inferred dependency was \`${prettyPrintScopeDependency(
dep,
)}\`, but the source dependencies were [${validDepsInMemoBlock
.map(dep => printManualMemoDependency(dep, true))
.join(', ')}]. Detail: ${
errorDiagnostic
? getCompareDependencyResultDescription(errorDiagnostic)
: 'none'
}`
: null,
loc: memoLocation,
suggestions: null,
});

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @compilationMode(infer)
const Test = () => <div />;
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;

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @compilationMode(infer)
const Test = () => <div />;
export const FIXTURE_ENTRYPOINT = {

View File

@@ -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 = () => {

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @compilationMode(infer)
class Component {
_renderMessage = () => {
const Message = () => {

View File

@@ -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";

View File

@@ -1,4 +1,4 @@
// @enableEmitInstrumentForget @compilationMode:"annotation"
// @enableEmitInstrumentForget @compilationMode(annotation)
function Bar(props) {
'use forget';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @flow @compilationMode:"infer"
// @flow @compilationMode(infer)
export default component Foo(bar: number) {
return <Bar bar={bar} />;
}

View File

@@ -1,4 +1,4 @@
// @flow @compilationMode:"infer"
// @flow @compilationMode(infer)
export default component Foo(bar: number) {
return <Bar bar={bar} />;
}

View File

@@ -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";

View File

@@ -1,4 +1,4 @@
// @enableEmitInstrumentForget @compilationMode:"annotation"
// @enableEmitInstrumentForget @compilationMode(annotation)
import {identity} from 'shared-runtime';

View File

@@ -1,136 +0,0 @@
## 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 (
<Stringify
value={[
`` === '',
`\n` === '\n',
`a\nb`,
`\n`,
`a${1}b`,
` abc \u0041\n\u000a`,
`abc${1}def`,
`abc${1}def${2}`,
`abc${1}def${2}ghi`,
`a${1 + 3}b${``}c${'d' + `e${2 + 4}f`}`,
`1${2}${Math.sin(0)}`,
`${NaN}`,
`${Infinity}`,
`${-Infinity}`,
`${Number.MAX_SAFE_INTEGER}`,
`${Number.MIN_SAFE_INTEGER}`,
`${Number.MAX_VALUE}`,
`${Number.MIN_VALUE}`,
`${-0}`,
`
`,
`${{}}`,
`${[1, 2, 3]}`,
`${true}`,
`${false}`,
`${null}`,
`${undefined}`,
`123456789${0}`,
`${0}123456789`,
`${0}123456789${0}`,
`${0}1234${5}6789${0}`,
`${0}1234${`${0}123456789${`${0}123456789${0}`}`}6789${0}`,
`${0}1234${`${0}123456789${`${identity(0)}`}`}6789${0}`,
`${`${`${`${0}`}`}`}`,
`${`${`${`${''}`}`}`}`,
`${`${`${`${identity('')}`}`}`}`,
]}
/>
);
}
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 = (
<Stringify
value={[
true,
true,
"a\nb",
"\n",
"a1b",
" abc A\n\n\u0167",
"abc1def",
"abc1def2",
"abc1def2ghi",
"a4bcde6f",
`1${2}${Math.sin(0)}`,
`${NaN}`,
`${Infinity}`,
`${-Infinity}`,
`${Number.MAX_SAFE_INTEGER}`,
`${Number.MIN_SAFE_INTEGER}`,
`${Number.MAX_VALUE}`,
`${Number.MIN_VALUE}`,
"0",
"\n ",
`${{}}`,
`${[1, 2, 3]}`,
"true",
"false",
"null",
`${undefined}`,
"1234567890",
"0123456789",
"01234567890",
"01234567890",
"0123401234567890123456789067890",
`${0}1234${`${0}123456789${`${identity(0)}`}`}6789${0}`,
"0",
"",
`${`${`${`${identity("")}`}`}`}`,
]}
/>
);
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [],
isComponent: false,
};
```
### Eval output
(kind: ok) <div>{"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","",""]}</div>

View File

@@ -1,56 +0,0 @@
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 (
<Stringify
value={[
`` === '',
`\n` === '\n',
`a\nb`,
`\n`,
`a${1}b`,
` abc \u0041\n\u000a`,
`abc${1}def`,
`abc${1}def${2}`,
`abc${1}def${2}ghi`,
`a${1 + 3}b${``}c${'d' + `e${2 + 4}f`}`,
`1${2}${Math.sin(0)}`,
`${NaN}`,
`${Infinity}`,
`${-Infinity}`,
`${Number.MAX_SAFE_INTEGER}`,
`${Number.MIN_SAFE_INTEGER}`,
`${Number.MAX_VALUE}`,
`${Number.MIN_VALUE}`,
`${-0}`,
`
`,
`${{}}`,
`${[1, 2, 3]}`,
`${true}`,
`${false}`,
`${null}`,
`${undefined}`,
`123456789${0}`,
`${0}123456789`,
`${0}123456789${0}`,
`${0}1234${5}6789${0}`,
`${0}1234${`${0}123456789${`${0}123456789${0}`}`}6789${0}`,
`${0}1234${`${0}123456789${`${identity(0)}`}`}6789${0}`,
`${`${`${`${0}`}`}`}`,
`${`${`${`${''}`}`}`}`,
`${`${`${`${identity('')}`}`}`}`,
]}
/>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [],
isComponent: false,
};

View File

@@ -1,74 +0,0 @@
## Input
```javascript
import {Stringify} from 'shared-runtime';
function foo() {
const a = -1;
return (
<Stringify
value={[
2 * a,
-0,
0 === -0,
-Infinity,
-NaN,
a * NaN,
a * Infinity,
a * -Infinity,
]}
/>
);
}
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 = (
<Stringify
value={[
-2,
0,
true,
-Infinity,
-NaN,
-1 * NaN,
-1 * Infinity,
-1 * -Infinity,
]}
/>
);
$[0] = t0;
} else {
t0 = $[0];
}
return t0;
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [],
isComponent: false,
};
```
### Eval output
(kind: ok) <div>{"value":[-2,0,true,null,null,null,null,null]}</div>

View File

@@ -1,25 +0,0 @@
import {Stringify} from 'shared-runtime';
function foo() {
const a = -1;
return (
<Stringify
value={[
2 * a,
-0,
0 === -0,
-Infinity,
-NaN,
a * NaN,
a * Infinity,
a * -Infinity,
]}
/>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: foo,
params: [],
isComponent: false,
};

View File

@@ -58,7 +58,7 @@ function foo() {
n0: true,
n1: false,
n2: false,
n3: false,
n3: !-1,
s0: true,
s1: false,
s2: false,

View File

@@ -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)

View File

@@ -1,4 +1,4 @@
// @eslintSuppressionRules:["my-app","react-rule"]
// @eslintSuppressionRules(my-app/react-rule)
/* eslint-disable my-app/react-rule */
function lowercasecomponent() {

View File

@@ -41,7 +41,7 @@ function Component(props) {
> 10 | return x;
| ^^^^^^^^^^^^^^^^^
> 11 | }, [props?.items, props.cond]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source (4:11)
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11)
12 | return (
13 | <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
14 | );

View File

@@ -41,7 +41,7 @@ function Component(props) {
> 10 | return x;
| ^^^^^^^^^^^^^^^^^
> 11 | }, [props?.items, props.cond]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source (4:11)
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (4:11)
12 | return (
13 | <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
14 | );

View File

@@ -1,34 +0,0 @@
## Input
```javascript
// @validateNoFreezingKnownMutableFunctions
function useFoo() {
const cache = new Map();
useHook(() => {
cache.set('key', 'value');
});
}
```
## Error
```
3 | function useFoo() {
4 | const cache = new Map();
> 5 | useHook(() => {
| ^^^^^^^
> 6 | cache.set('key', 'value');
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 7 | });
| ^^^^ InvalidReact: This argument is a function which modifies local variables when called, which can bypass memoization and cause the UI not to update. Functions that are returned from hooks, passed as arguments to hooks, or passed as props to components may not mutate local variables (5:7)
InvalidReact: The function modifies a local variable here (6:6)
8 | }
9 |
```

View File

@@ -1,8 +0,0 @@
// @validateNoFreezingKnownMutableFunctions
function useFoo() {
const cache = new Map();
useHook(() => {
cache.set('key', 'value');
});
}

View File

@@ -29,7 +29,7 @@ function Component(props) {
> 6 | // deps are optional
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 7 | }, [props.items?.edges?.nodes]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items.edges.nodes`, but the source dependencies were [props.items?.edges?.nodes]. Inferred different dependency than source (3:7)
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (3:7)
8 | return <Foo data={data} />;
9 | }
10 |

View File

@@ -1,30 +0,0 @@
## Input
```javascript
// @validateNoFreezingKnownMutableFunctions
function Component() {
const cache = new Map();
const fn = () => {
cache.set('key', 'value');
};
return <Foo fn={fn} />;
}
```
## Error
```
5 | cache.set('key', 'value');
6 | };
> 7 | return <Foo fn={fn} />;
| ^^ InvalidReact: This argument is a function which modifies local variables when called, which can bypass memoization and cause the UI not to update. Functions that are returned from hooks, passed as arguments to hooks, or passed as props to components may not mutate local variables (7:7)
InvalidReact: The function modifies a local variable here (5:5)
8 | }
9 |
```

View File

@@ -1,8 +0,0 @@
// @validateNoFreezingKnownMutableFunctions
function Component() {
const cache = new Map();
const fn = () => {
cache.set('key', 'value');
};
return <Foo fn={fn} />;
}

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @validateRefAccessDuringRender @compilationMode:"infer"
// @validateRefAccessDuringRender @compilationMode(infer)
function Component({ref}) {
const value = ref.current;
return <div>{value}</div>;
@@ -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)

View File

@@ -1,4 +1,4 @@
// @validateRefAccessDuringRender @compilationMode:"infer"
// @validateRefAccessDuringRender @compilationMode(infer)
function Component({ref}) {
const value = ref.current;
return <div>{value}</div>;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @validateRefAccessDuringRender @compilationMode:"infer"
// @validateRefAccessDuringRender @compilationMode(infer)
function Component(props) {
const value = props.ref.current;
return <div>{value}</div>;
@@ -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)

View File

@@ -1,4 +1,4 @@
// @validateRefAccessDuringRender @compilationMode:"infer"
// @validateRefAccessDuringRender @compilationMode(infer)
function Component(props) {
const value = props.ref.current;
return <div>{value}</div>;

View File

@@ -1,36 +0,0 @@
## Input
```javascript
// @validateNoFreezingKnownMutableFunctions
import {useHook} from 'shared-runtime';
function useFoo() {
useHook(); // for inference to kick in
const cache = new Map();
return () => {
cache.set('key', 'value');
};
}
```
## Error
```
5 | useHook(); // for inference to kick in
6 | const cache = new Map();
> 7 | return () => {
| ^^^^^^^
> 8 | cache.set('key', 'value');
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 9 | };
| ^^^^ InvalidReact: This argument is a function which modifies local variables when called, which can bypass memoization and cause the UI not to update. Functions that are returned from hooks, passed as arguments to hooks, or passed as props to components may not mutate local variables (7:9)
InvalidReact: The function modifies a local variable here (8:8)
10 | }
11 |
```

View File

@@ -1,10 +0,0 @@
// @validateNoFreezingKnownMutableFunctions
import {useHook} from 'shared-runtime';
function useFoo() {
useHook(); // for inference to kick in
const cache = new Map();
return () => {
cache.set('key', 'value');
};
}

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @validateRefAccessDuringRender @compilationMode:"infer"
// @validateRefAccessDuringRender @compilationMode(infer)
function Component(props) {
const ref = props.ref;
ref.current = true;

View File

@@ -1,4 +1,4 @@
// @validateRefAccessDuringRender @compilationMode:"infer"
// @validateRefAccessDuringRender @compilationMode(infer)
function Component(props) {
const ref = props.ref;
ref.current = true;

View File

@@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = {
> 12 | Ref.current?.click();
| ^^^^^^^^^^^^^^^^^^^^^^^^^
> 13 | }, []);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `Ref.current`, but the source dependencies were []. Inferred dependency not present in source (11:13)
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (11:13)
14 |
15 | return <button onClick={onClick} />;
16 | }

View File

@@ -38,7 +38,7 @@ export const FIXTURE_ENTRYPOINT = {
> 12 | notaref.current?.click();
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 13 | }, []);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `notaref.current`, but the source dependencies were []. Inferred dependency not present in source (11:13)
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected (11:13)
14 |
15 | return <button onClick={onClick} />;
16 | }

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @compilationMode(infer)
function Component() {
return <Foo />;

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @compilationMode(infer)
function Component() {
return <Foo />;

View File

@@ -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';

View File

@@ -1,4 +1,4 @@
// @validateBlocklistedImports:["DangerousImport"]
// @validateBlocklistedImports(DangerousImport)
import {foo} from 'DangerousImport';
import {useIdentity} from 'shared-runtime';

View File

@@ -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";

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @compilationMode(infer)
import {useEffect, useMemo, useState} from 'react';
import {ValidateMemoization} from 'shared-runtime';

View File

@@ -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] !== "a585d27423c1181e7b6305ff909458183d284658c3c3d2e3764e1128be302fd7"
$[0] !== "8d7015668f857996c3d895a7a90e3e16b8a791d5b9cd13f2c76e1c254aeedebb"
) {
for (let $i = 0; $i < 3; $i += 1) {
$[$i] = Symbol.for("react.memo_cache_sentinel");
}
$[0] = "a585d27423c1181e7b6305ff909458183d284658c3c3d2e3764e1128be302fd7";
$[0] = "8d7015668f857996c3d895a7a90e3e16b8a791d5b9cd13f2c76e1c254aeedebb";
}
useState(_temp);

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges
// @compilationMode(infer) @enableResetCacheOnSourceFileChanges
import {useEffect, useMemo, useState} from 'react';
import {ValidateMemoization} from 'shared-runtime';

View File

@@ -1,56 +0,0 @@
## 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 (
<Stringify>
{fbt(
[
'Name: ',
fbt.param('firstname', <Stringify key={0} name={firstname} />),
', ',
fbt.param(
'lastname',
<Stringify key={0} name={lastname}>
{fbt('(inner fbt)', 'Inner fbt value')}
</Stringify>
),
],
'Name'
)}
</Stringify>
);
}
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
---
```

View File

@@ -1,38 +0,0 @@
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 (
<Stringify>
{fbt(
[
'Name: ',
fbt.param('firstname', <Stringify key={0} name={firstname} />),
', ',
fbt.param(
'lastname',
<Stringify key={0} name={lastname}>
{fbt('(inner fbt)', 'Inner fbt value')}
</Stringify>
),
],
'Name'
)}
</Stringify>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{firstname: 'first', lastname: 'last'}],
sequentialRenders: [{firstname: 'first', lastname: 'last'}],
};

View File

@@ -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";

View File

@@ -1,4 +1,4 @@
// @enableEmitInstrumentForget @compilationMode:"annotation" @gating
// @enableEmitInstrumentForget @compilationMode(annotation) @gating
function Bar(props) {
'use forget';

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @gating @compilationMode:"annotation"
// @gating @compilationMode(annotation)
export default function Bar(props) {
'use forget';
return <div>{props.bar}</div>;
@@ -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";

View File

@@ -1,4 +1,4 @@
// @gating @compilationMode:"annotation"
// @gating @compilationMode(annotation)
export default function Bar(props) {
'use forget';
return <div>{props.bar}</div>;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @gating @compilationMode:"annotation"
// @gating @compilationMode(annotation)
export default function Bar(props) {
'use forget';
return <div>{props.bar}</div>;
@@ -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";

View File

@@ -1,4 +1,4 @@
// @gating @compilationMode:"annotation"
// @gating @compilationMode(annotation)
export default function Bar(props) {
'use forget';
return <div>{props.bar}</div>;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @gating @compilationMode:"annotation"
// @gating @compilationMode(annotation)
export function Bar(props) {
'use forget';
return <div>{props.bar}</div>;
@@ -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";

View File

@@ -1,4 +1,4 @@
// @gating @compilationMode:"annotation"
// @gating @compilationMode(annotation)
export function Bar(props) {
'use forget';
return <div>{props.bar}</div>;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @gating @compilationMode:"annotation"
// @gating @compilationMode(annotation)
function Bar(props) {
'use forget';
return <div>{props.bar}</div>;
@@ -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";

View File

@@ -1,4 +1,4 @@
// @gating @compilationMode:"annotation"
// @gating @compilationMode(annotation)
function Bar(props) {
'use forget';
return <div>{props.bar}</div>;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @gating @compilationMode:"infer"
// @gating @compilationMode(infer)
import React from 'react';
export default React.forwardRef(function notNamedLikeAComponent(props) {
return <div />;
@@ -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()

View File

@@ -1,4 +1,4 @@
// @gating @compilationMode:"infer"
// @gating @compilationMode(infer)
import React from 'react';
export default React.forwardRef(function notNamedLikeAComponent(props) {
return <div />;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @flow @compilationMode:"infer"
// @flow @compilationMode(infer)
export default hook useFoo(bar: number) {
return [bar];
}

View File

@@ -1,4 +1,4 @@
// @flow @compilationMode:"infer"
// @flow @compilationMode(infer)
export default hook useFoo(bar: number) {
return [bar];
}

View File

@@ -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);

View File

@@ -1,4 +1,4 @@
// @customMacros:"idx.*.b"
// @customMacros(idx.*.b)
function Component(props) {
// outlined

View File

@@ -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);

View File

@@ -1,4 +1,4 @@
// @customMacros:"idx.a"
// @customMacros(idx.a)
function Component(props) {
// outlined

View File

@@ -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;

View File

@@ -1,4 +1,4 @@
// @customMacros:"idx"
// @customMacros(idx)
import idx from 'idx';
function Component(props) {

View File

@@ -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

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @compilationMode(infer)
import {useNoAlias} from 'shared-runtime';
// This should be compiled by Forget

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @compilationMode:"infer"
// @compilationMode(infer)
// Takes multiple parameters - not a component!
function Component(foo, bar) {
return <div />;
@@ -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 <div />;

View File

@@ -1,4 +1,4 @@
// @compilationMode:"infer"
// @compilationMode(infer)
// Takes multiple parameters - not a component!
function Component(foo, bar) {
return <div />;

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import useMyEffect from 'useEffectWrapper';
function nonReactFn(arg) {

View File

@@ -1,4 +1,4 @@
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import useMyEffect from 'useEffectWrapper';
function nonReactFn(arg) {

View File

@@ -2,7 +2,7 @@
## Input
```javascript
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import {useEffect} from 'react';
function nonReactFn(arg) {

View File

@@ -1,4 +1,4 @@
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
import {useEffect} from 'react';
function nonReactFn(arg) {

Some files were not shown because too many files have changed in this diff Show More