Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e64b889dd3 |
@@ -579,7 +579,6 @@ module.exports = {
|
||||
JSONValue: 'readonly',
|
||||
JSResourceReference: 'readonly',
|
||||
MouseEventHandler: 'readonly',
|
||||
NavigateEvent: 'readonly',
|
||||
PropagationPhases: 'readonly',
|
||||
PropertyDescriptor: 'readonly',
|
||||
React$AbstractComponent: 'readonly',
|
||||
@@ -635,6 +634,5 @@ module.exports = {
|
||||
AsyncLocalStorage: 'readonly',
|
||||
async_hooks: 'readonly',
|
||||
globalThis: 'readonly',
|
||||
navigation: 'readonly',
|
||||
},
|
||||
};
|
||||
|
||||
@@ -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' }}
|
||||
|
||||
@@ -332,10 +332,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
|
||||
|
||||
1
.github/workflows/runtime_discord_notify.yml
vendored
1
.github/workflows/runtime_discord_notify.yml
vendored
@@ -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' }}
|
||||
|
||||
@@ -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' }}
|
||||
|
||||
14
compiler/.gitignore
vendored
14
compiler/.gitignore
vendored
@@ -1,14 +1,28 @@
|
||||
.DS_Store
|
||||
.spr.yml
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
debug/
|
||||
target/
|
||||
|
||||
# These are backup files generated by rustfmt
|
||||
**/*.rs.bk
|
||||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
node_modules
|
||||
.watchmanconfig
|
||||
.watchman-cookie-*
|
||||
dist
|
||||
.vscode
|
||||
!packages/playground/.vscode
|
||||
.spr.yml
|
||||
testfilter.txt
|
||||
|
||||
bundle-oss.sh
|
||||
|
||||
# forgive
|
||||
*.vsix
|
||||
.vscode-test
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { c as _c } from "react/compiler-runtime"; //
|
||||
@compilationMode:"all"
|
||||
@compilationMode(all)
|
||||
function nonReactFn() {
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @compilationMode:"infer"
|
||||
// @compilationMode(infer)
|
||||
function nonReactFn() {
|
||||
return {};
|
||||
}
|
||||
@@ -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 {};
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -37,10 +37,6 @@ const PanicThresholdOptionsSchema = z.enum([
|
||||
]);
|
||||
|
||||
export type PanicThresholdOptions = z.infer<typeof PanicThresholdOptionsSchema>;
|
||||
const DynamicGatingOptionsSchema = z.object({
|
||||
source: z.string(),
|
||||
});
|
||||
export type DynamicGatingOptions = z.infer<typeof DynamicGatingOptionsSchema>;
|
||||
|
||||
export type PluginOptions = {
|
||||
environment: EnvironmentConfig;
|
||||
@@ -69,28 +65,6 @@ export type PluginOptions = {
|
||||
*/
|
||||
gating: ExternalFunction | null;
|
||||
|
||||
/**
|
||||
* If specified, this enables dynamic gating which matches `use memo if(...)`
|
||||
* directives.
|
||||
*
|
||||
* Example usage:
|
||||
* ```js
|
||||
* // @dynamicGating:{"source":"myModule"}
|
||||
* export function MyComponent() {
|
||||
* 'use memo if(isEnabled)';
|
||||
* return <div>...</div>;
|
||||
* }
|
||||
* ```
|
||||
* This will emit:
|
||||
* ```js
|
||||
* import {isEnabled} from 'myModule';
|
||||
* export const MyComponent = isEnabled()
|
||||
* ? <optimized version>
|
||||
* : <original version>;
|
||||
* ```
|
||||
*/
|
||||
dynamicGating: DynamicGatingOptions | null;
|
||||
|
||||
panicThreshold: PanicThresholdOptions;
|
||||
|
||||
/*
|
||||
@@ -124,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;
|
||||
/*
|
||||
@@ -132,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.
|
||||
@@ -270,7 +244,6 @@ export const defaultOptions: PluginOptions = {
|
||||
logger: null,
|
||||
gating: null,
|
||||
noEmit: false,
|
||||
dynamicGating: null,
|
||||
eslintSuppressionRules: null,
|
||||
flowSuppressions: true,
|
||||
ignoreUseNoForget: false,
|
||||
@@ -319,25 +292,6 @@ export function parsePluginOptions(obj: unknown): PluginOptions {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case 'dynamicGating': {
|
||||
if (value == null) {
|
||||
parsedOptions[key] = null;
|
||||
} else {
|
||||
const result = DynamicGatingOptionsSchema.safeParse(value);
|
||||
if (result.success) {
|
||||
parsedOptions[key] = result.data;
|
||||
} else {
|
||||
CompilerError.throwInvalidConfig({
|
||||
reason:
|
||||
'Could not parse dynamic gating. Update React Compiler config to fix the error',
|
||||
description: `${fromZodError(result.error)}`,
|
||||
loc: null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
parsedOptions[key] = value;
|
||||
}
|
||||
|
||||
@@ -130,7 +130,6 @@ function run(
|
||||
mode,
|
||||
config,
|
||||
contextIdentifiers,
|
||||
func,
|
||||
logger,
|
||||
filename,
|
||||
code,
|
||||
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
CompilerErrorDetail,
|
||||
ErrorSeverity,
|
||||
} from '../CompilerError';
|
||||
import {ExternalFunction, ReactFunctionType} from '../HIR/Environment';
|
||||
import {EnvironmentConfig, ReactFunctionType} from '../HIR/Environment';
|
||||
import {CodegenFunction} from '../ReactiveScopes';
|
||||
import {isComponentDeclaration} from '../Utils/ComponentDeclaration';
|
||||
import {isHookDeclaration} from '../Utils/HookDeclaration';
|
||||
@@ -31,7 +31,6 @@ import {
|
||||
suppressionsToCompilerError,
|
||||
} from './Suppression';
|
||||
import {GeneratedSource} from '../HIR';
|
||||
import {Err, Ok, Result} from '../Utils/Result';
|
||||
|
||||
export type CompilerPass = {
|
||||
opts: PluginOptions;
|
||||
@@ -41,93 +40,22 @@ export type CompilerPass = {
|
||||
};
|
||||
export const OPT_IN_DIRECTIVES = new Set(['use forget', 'use memo']);
|
||||
export const OPT_OUT_DIRECTIVES = new Set(['use no forget', 'use no memo']);
|
||||
const DYNAMIC_GATING_DIRECTIVE = new RegExp('^use memo if\\(([^\\)]*)\\)$');
|
||||
|
||||
export function tryFindDirectiveEnablingMemoization(
|
||||
export function findDirectiveEnablingMemoization(
|
||||
directives: Array<t.Directive>,
|
||||
opts: PluginOptions,
|
||||
): Result<t.Directive | null, CompilerError> {
|
||||
const optIn = directives.find(directive =>
|
||||
): Array<t.Directive> {
|
||||
return directives.filter(directive =>
|
||||
OPT_IN_DIRECTIVES.has(directive.value.value),
|
||||
);
|
||||
if (optIn != null) {
|
||||
return Ok(optIn);
|
||||
}
|
||||
const dynamicGating = findDirectivesDynamicGating(directives, opts);
|
||||
if (dynamicGating.isOk()) {
|
||||
return Ok(dynamicGating.unwrap()?.directive ?? null);
|
||||
} else {
|
||||
return Err(dynamicGating.unwrapErr());
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
);
|
||||
}
|
||||
function findDirectivesDynamicGating(
|
||||
directives: Array<t.Directive>,
|
||||
opts: PluginOptions,
|
||||
): Result<
|
||||
{
|
||||
gating: ExternalFunction;
|
||||
directive: t.Directive;
|
||||
} | null,
|
||||
CompilerError
|
||||
> {
|
||||
if (opts.dynamicGating === null) {
|
||||
return Ok(null);
|
||||
}
|
||||
const errors = new CompilerError();
|
||||
const result: Array<{directive: t.Directive; match: string}> = [];
|
||||
|
||||
for (const directive of directives) {
|
||||
const maybeMatch = DYNAMIC_GATING_DIRECTIVE.exec(directive.value.value);
|
||||
if (maybeMatch != null && maybeMatch[1] != null) {
|
||||
if (t.isValidIdentifier(maybeMatch[1])) {
|
||||
result.push({directive, match: maybeMatch[1]});
|
||||
} else {
|
||||
errors.push({
|
||||
reason: `Dynamic gating directive is not a valid JavaScript identifier`,
|
||||
description: `Found '${directive.value.value}'`,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
loc: directive.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
if (errors.hasErrors()) {
|
||||
return Err(errors);
|
||||
} else if (result.length > 1) {
|
||||
const error = new CompilerError();
|
||||
error.push({
|
||||
reason: `Multiple dynamic gating directives found`,
|
||||
description: `Expected a single directive but found [${result
|
||||
.map(r => r.directive.value.value)
|
||||
.join(', ')}]`,
|
||||
severity: ErrorSeverity.InvalidReact,
|
||||
loc: result[0].directive.loc ?? null,
|
||||
suggestions: null,
|
||||
});
|
||||
return Err(error);
|
||||
} else if (result.length === 1) {
|
||||
return Ok({
|
||||
gating: {
|
||||
source: opts.dynamicGating.source,
|
||||
importSpecifierName: result[0].match,
|
||||
},
|
||||
directive: result[0].directive,
|
||||
});
|
||||
} else {
|
||||
return Ok(null);
|
||||
}
|
||||
}
|
||||
|
||||
function isCriticalError(err: unknown): boolean {
|
||||
return !(err instanceof CompilerError) || err.isCritical();
|
||||
@@ -160,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,
|
||||
@@ -183,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,
|
||||
@@ -193,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;
|
||||
@@ -266,6 +187,7 @@ export function createNewFunctionNode(
|
||||
}
|
||||
}
|
||||
// Avoid visiting the new transformed version
|
||||
ALREADY_COMPILED.add(transformedFn);
|
||||
return transformedFn;
|
||||
}
|
||||
|
||||
@@ -317,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',
|
||||
@@ -339,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
|
||||
@@ -386,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;
|
||||
}
|
||||
|
||||
@@ -490,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});
|
||||
@@ -505,6 +346,7 @@ function findFunctionsToCompile(
|
||||
* can reference `this` which is unsafe for compilation
|
||||
*/
|
||||
node.skip();
|
||||
return;
|
||||
},
|
||||
|
||||
ClassExpression(node: NodePath<t.ClassExpression>) {
|
||||
@@ -513,6 +355,7 @@ function findFunctionsToCompile(
|
||||
* can reference `this` which is unsafe for compilation
|
||||
*/
|
||||
node.skip();
|
||||
return;
|
||||
},
|
||||
|
||||
FunctionDeclaration: traverseFunction,
|
||||
@@ -527,251 +370,223 @@ function findFunctionsToCompile(
|
||||
filename: pass.filename ?? null,
|
||||
},
|
||||
);
|
||||
return queue;
|
||||
}
|
||||
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,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: {
|
||||
optIn: t.Directive | null;
|
||||
optOut: t.Directive | null;
|
||||
};
|
||||
if (fn.node.body.type !== 'BlockStatement') {
|
||||
directives = {
|
||||
optIn: null,
|
||||
optOut: null,
|
||||
};
|
||||
} else {
|
||||
const optIn = tryFindDirectiveEnablingMemoization(
|
||||
fn.node.body.directives,
|
||||
programContext.opts,
|
||||
/**
|
||||
* 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,
|
||||
);
|
||||
if (optIn.isErr()) {
|
||||
/**
|
||||
* If parsing opt-in directive fails, it's most likely that React Compiler
|
||||
* was not tested or rolled out on this function. In that case, we handle
|
||||
* the error and fall back to the safest option which is to not optimize
|
||||
* the function.
|
||||
*/
|
||||
handleError(optIn.unwrapErr(), programContext, fn.node.loc ?? null);
|
||||
return null;
|
||||
}
|
||||
directives = {
|
||||
optIn: optIn.unwrapOr(null),
|
||||
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);
|
||||
let compileResult:
|
||||
| {kind: 'compile'; compiledFn: CodegenFunction}
|
||||
| {kind: 'error'; error: unknown};
|
||||
if (suppressionsInFunction.length > 0) {
|
||||
compileResult = {
|
||||
kind: 'error',
|
||||
error: suppressionsToCompilerError(suppressionsInFunction),
|
||||
};
|
||||
} else {
|
||||
handleError(compileResult.error, programContext, fn.node.loc ?? null);
|
||||
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};
|
||||
}
|
||||
}
|
||||
const retryResult = retryCompileFunction(fn, fnType, programContext);
|
||||
if (retryResult == null) {
|
||||
|
||||
if (compileResult.kind === 'error') {
|
||||
/**
|
||||
* If an opt out directive is present, log only instead of throwing and don't mark as
|
||||
* containing a critical error.
|
||||
*/
|
||||
if (optOutDirectives.length > 0) {
|
||||
logError(compileResult.error, pass, fn.node.loc ?? null);
|
||||
} else {
|
||||
handleError(compileResult.error, pass, fn.node.loc ?? null);
|
||||
}
|
||||
// If non-memoization features are enabled, retry regardless of error kind
|
||||
if (
|
||||
!(environment.enableFire || environment.inferEffectDependencies != null)
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
compileResult = {
|
||||
kind: 'compile',
|
||||
compiledFn: compileFn(
|
||||
fn,
|
||||
environment,
|
||||
fnType,
|
||||
'no_inferred_memo',
|
||||
programContext,
|
||||
pass.opts.logger,
|
||||
pass.filename,
|
||||
pass.code,
|
||||
),
|
||||
};
|
||||
if (
|
||||
!compileResult.compiledFn.hasFireRewrite &&
|
||||
!compileResult.compiledFn.hasInferredEffect
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
} catch (err) {
|
||||
// TODO: we might want to log error here, but this will also result in duplicate logging
|
||||
if (err instanceof CompilerError) {
|
||||
retryErrors.push({fn, error: err});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Otherwise if 'use no forget/memo' is present, we still run the code through the compiler
|
||||
* for validation but we don't mutate the babel AST. This allows us to flag if there is an
|
||||
* unused 'use no forget/memo' directive.
|
||||
*/
|
||||
if (pass.opts.ignoreUseNoForget === false && optOutDirectives.length > 0) {
|
||||
for (const directive of optOutDirectives) {
|
||||
pass.opts.logger?.logEvent(pass.filename, {
|
||||
kind: 'CompileSkip',
|
||||
fnLoc: fn.node.body.loc ?? null,
|
||||
reason: `Skipped due to '${directive.value.value}' directive.`,
|
||||
loc: directive.loc ?? null,
|
||||
});
|
||||
}
|
||||
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 {
|
||||
let referencedBeforeDeclared = null;
|
||||
const referencedBeforeDeclared =
|
||||
pass.opts.gating != null
|
||||
? getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns)
|
||||
: null;
|
||||
for (const result of compiledFns) {
|
||||
const {kind, originalFn, compiledFn} = result;
|
||||
const transformedFn = createNewFunctionNode(originalFn, compiledFn);
|
||||
programContext.alreadyCompiled.add(transformedFn);
|
||||
|
||||
let dynamicGating: ExternalFunction | null = null;
|
||||
if (originalFn.node.body.type === 'BlockStatement') {
|
||||
const result = findDirectivesDynamicGating(
|
||||
originalFn.node.body.directives,
|
||||
pass.opts,
|
||||
);
|
||||
if (result.isOk()) {
|
||||
dynamicGating = result.unwrap()?.gating ?? null;
|
||||
}
|
||||
}
|
||||
const functionGating = dynamicGating ?? pass.opts.gating;
|
||||
if (kind === 'original' && functionGating != null) {
|
||||
referencedBeforeDeclared ??=
|
||||
getFunctionReferencedBeforeDeclarationAtTopLevel(program, compiledFns);
|
||||
if (referencedBeforeDeclared != null && kind === 'original') {
|
||||
CompilerError.invariant(pass.opts.gating != null, {
|
||||
reason: "Expected 'gating' import to be present",
|
||||
loc: null,
|
||||
});
|
||||
insertGatedFunctionDeclaration(
|
||||
originalFn,
|
||||
transformedFn,
|
||||
programContext,
|
||||
functionGating,
|
||||
pass.opts.gating,
|
||||
referencedBeforeDeclared.has(result),
|
||||
);
|
||||
} else {
|
||||
@@ -783,6 +598,7 @@ function applyCompiledFunctions(
|
||||
if (compiledFns.length > 0) {
|
||||
addImportsToProgram(program, programContext);
|
||||
}
|
||||
return {retryErrors, inferredEffectLocations};
|
||||
}
|
||||
|
||||
function shouldSkipCompilation(
|
||||
@@ -824,16 +640,15 @@ 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') {
|
||||
const optInDirectives = tryFindDirectiveEnablingMemoization(
|
||||
fn.node.body.directives,
|
||||
pass.opts,
|
||||
);
|
||||
if (optInDirectives.unwrapOr(null) != null) {
|
||||
if (findDirectiveEnablingMemoization(fn.node.body.directives).length > 0)
|
||||
return getComponentOrHookLike(fn, hookPattern) ?? 'Other';
|
||||
}
|
||||
}
|
||||
|
||||
// Component and hook declarations are known components/hooks
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -70,14 +70,12 @@ import {BuiltInArrayId} from './ObjectShape';
|
||||
export function lower(
|
||||
func: NodePath<t.Function>,
|
||||
env: Environment,
|
||||
// Bindings captured from the outer function, in case lower() is called recursively (for lambdas)
|
||||
bindings: Bindings | null = null,
|
||||
capturedRefs: Array<t.Identifier> = [],
|
||||
// the outermost function being compiled, in case lower() is called recursively (for lambdas)
|
||||
parent: NodePath<t.Function> | null = null,
|
||||
): Result<HIRFunction, CompilerError> {
|
||||
const builder = new HIRBuilder(env, {
|
||||
bindings,
|
||||
context: capturedRefs,
|
||||
});
|
||||
const builder = new HIRBuilder(env, parent ?? func, bindings, capturedRefs);
|
||||
const context: HIRFunction['context'] = [];
|
||||
|
||||
for (const ref of capturedRefs ?? []) {
|
||||
@@ -217,7 +215,7 @@ export function lower(
|
||||
return Ok({
|
||||
id,
|
||||
params,
|
||||
fnType: bindings == null ? env.fnType : 'Other',
|
||||
fnType: parent == null ? env.fnType : 'Other',
|
||||
returnTypeAnnotation: null, // TODO: extract the actual return type node if present
|
||||
returnType: makeType(),
|
||||
body: builder.build(),
|
||||
@@ -3419,7 +3417,7 @@ function lowerFunction(
|
||||
| t.ObjectMethod
|
||||
>,
|
||||
): LoweredFunction | null {
|
||||
const componentScope: Scope = builder.environment.parentFunction.scope;
|
||||
const componentScope: Scope = builder.parentFunction.scope;
|
||||
const capturedContext = gatherCapturedContext(expr, componentScope);
|
||||
|
||||
/*
|
||||
@@ -3430,10 +3428,13 @@ function lowerFunction(
|
||||
* This isn't a problem in practice because use Babel's scope analysis to
|
||||
* identify the correct references.
|
||||
*/
|
||||
const lowering = lower(expr, builder.environment, builder.bindings, [
|
||||
...builder.context,
|
||||
...capturedContext,
|
||||
]);
|
||||
const lowering = lower(
|
||||
expr,
|
||||
builder.environment,
|
||||
builder.bindings,
|
||||
[...builder.context, ...capturedContext],
|
||||
builder.parentFunction,
|
||||
);
|
||||
let loweredFunc: HIRFunction;
|
||||
if (lowering.isErr()) {
|
||||
lowering
|
||||
@@ -3455,7 +3456,7 @@ function lowerExpressionToTemporary(
|
||||
return lowerValueToTemporary(builder, value);
|
||||
}
|
||||
|
||||
export function lowerValueToTemporary(
|
||||
function lowerValueToTemporary(
|
||||
builder: HIRBuilder,
|
||||
value: InstructionValue,
|
||||
): Place {
|
||||
|
||||
@@ -47,7 +47,7 @@ import {
|
||||
ShapeRegistry,
|
||||
addHook,
|
||||
} from './ObjectShape';
|
||||
import {Scope as BabelScope, NodePath} from '@babel/traverse';
|
||||
import {Scope as BabelScope} from '@babel/traverse';
|
||||
import {TypeSchema} from './TypeSchema';
|
||||
|
||||
export const ReactElementSymbolSchema = z.object({
|
||||
@@ -675,7 +675,6 @@ export class Environment {
|
||||
|
||||
#contextIdentifiers: Set<t.Identifier>;
|
||||
#hoistedIdentifiers: Set<t.Identifier>;
|
||||
parentFunction: NodePath<t.Function>;
|
||||
|
||||
constructor(
|
||||
scope: BabelScope,
|
||||
@@ -683,7 +682,6 @@ export class Environment {
|
||||
compilerMode: CompilerMode,
|
||||
config: EnvironmentConfig,
|
||||
contextIdentifiers: Set<t.Identifier>,
|
||||
parentFunction: NodePath<t.Function>, // the outermost function being compiled
|
||||
logger: Logger | null,
|
||||
filename: string | null,
|
||||
code: string | null,
|
||||
@@ -742,7 +740,6 @@ export class Environment {
|
||||
this.#moduleTypes.set(REANIMATED_MODULE_NAME, reanimatedModuleType);
|
||||
}
|
||||
|
||||
this.parentFunction = parentFunction;
|
||||
this.#contextIdentifiers = contextIdentifiers;
|
||||
this.#hoistedIdentifiers = new Set();
|
||||
}
|
||||
|
||||
@@ -110,6 +110,7 @@ export default class HIRBuilder {
|
||||
#bindings: Bindings;
|
||||
#env: Environment;
|
||||
#exceptionHandlerStack: Array<BlockId> = [];
|
||||
parentFunction: NodePath<t.Function>;
|
||||
errors: CompilerError = new CompilerError();
|
||||
/**
|
||||
* Traversal context: counts the number of `fbt` tag parents
|
||||
@@ -135,17 +136,16 @@ export default class HIRBuilder {
|
||||
|
||||
constructor(
|
||||
env: Environment,
|
||||
options?: {
|
||||
bindings?: Bindings | null;
|
||||
context?: Array<t.Identifier>;
|
||||
entryBlockKind?: BlockKind;
|
||||
},
|
||||
parentFunction: NodePath<t.Function>, // the outermost function being compiled
|
||||
bindings: Bindings | null = null,
|
||||
context: Array<t.Identifier> | null = null,
|
||||
) {
|
||||
this.#env = env;
|
||||
this.#bindings = options?.bindings ?? new Map();
|
||||
this.#context = options?.context ?? [];
|
||||
this.#bindings = bindings ?? new Map();
|
||||
this.parentFunction = parentFunction;
|
||||
this.#context = context ?? [];
|
||||
this.#entry = makeBlockId(env.nextBlockId);
|
||||
this.#current = newBlock(this.#entry, options?.entryBlockKind ?? 'block');
|
||||
this.#current = newBlock(this.#entry, 'block');
|
||||
}
|
||||
|
||||
currentBlockKind(): BlockKind {
|
||||
@@ -239,7 +239,7 @@ export default class HIRBuilder {
|
||||
|
||||
// Check if the binding is from module scope
|
||||
const outerBinding =
|
||||
this.#env.parentFunction.scope.parent.getBinding(originalName);
|
||||
this.parentFunction.scope.parent.getBinding(originalName);
|
||||
if (babelBinding === outerBinding) {
|
||||
const path = babelBinding.path;
|
||||
if (path.isImportDefaultSpecifier()) {
|
||||
@@ -293,7 +293,7 @@ export default class HIRBuilder {
|
||||
const binding = this.#resolveBabelBinding(path);
|
||||
if (binding) {
|
||||
// Check if the binding is from module scope, if so return null
|
||||
const outerBinding = this.#env.parentFunction.scope.parent.getBinding(
|
||||
const outerBinding = this.parentFunction.scope.parent.getBinding(
|
||||
path.node.name,
|
||||
);
|
||||
if (binding === outerBinding) {
|
||||
@@ -376,7 +376,7 @@ export default class HIRBuilder {
|
||||
}
|
||||
|
||||
// Terminate the current block w the given terminal, and start a new block
|
||||
terminate(terminal: Terminal, nextBlockKind: BlockKind | null): BlockId {
|
||||
terminate(terminal: Terminal, nextBlockKind: BlockKind | null): void {
|
||||
const {id: blockId, kind, instructions} = this.#current;
|
||||
this.#completed.set(blockId, {
|
||||
kind,
|
||||
@@ -390,7 +390,6 @@ export default class HIRBuilder {
|
||||
const nextId = this.#env.nextBlockId;
|
||||
this.#current = newBlock(nextId, nextBlockKind);
|
||||
}
|
||||
return blockId;
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -747,11 +746,6 @@ function getReversePostorderedBlocks(func: HIR): HIR['blocks'] {
|
||||
* (eg bb2 then bb1), we ensure that they get reversed back to the correct order.
|
||||
*/
|
||||
const block = func.blocks.get(blockId)!;
|
||||
CompilerError.invariant(block != null, {
|
||||
reason: '[HIRBuilder] Unexpected null block',
|
||||
description: `expected block ${blockId} to exist`,
|
||||
loc: GeneratedSource,
|
||||
});
|
||||
const successors = [...eachTerminalSuccessor(block.terminal)].reverse();
|
||||
const fallthrough = terminalFallthrough(block.terminal);
|
||||
|
||||
|
||||
@@ -509,73 +509,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) {
|
||||
|
||||
@@ -10,6 +10,7 @@ import {CompilerError} from '../CompilerError';
|
||||
import {
|
||||
CompilationMode,
|
||||
defaultOptions,
|
||||
PanicThresholdOptions,
|
||||
parsePluginOptions,
|
||||
PluginOptions,
|
||||
} from '../Entrypoint';
|
||||
@@ -18,24 +19,14 @@ 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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: {
|
||||
@@ -93,37 +84,34 @@ const testComplexConfigDefaults: PartialEnvironmentConfig = {
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* For snap test fixtures and playground only.
|
||||
*/
|
||||
function parseConfigPragmaEnvironmentForTest(
|
||||
pragma: string,
|
||||
): EnvironmentConfig {
|
||||
const maybeConfig: Partial<Record<keyof EnvironmentConfig, unknown>> = {};
|
||||
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);
|
||||
const valIdx = keyVal.indexOf(':');
|
||||
const key = valIdx === -1 ? keyVal : keyVal.slice(0, valIdx);
|
||||
const val = valIdx === -1 ? undefined : keyVal.slice(valIdx + 1);
|
||||
let [key, val = undefined] = keyVal.split(':');
|
||||
const isSet = val === undefined || val === 'true';
|
||||
if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) {
|
||||
|
||||
if (isSet && key in testComplexConfigDefaults) {
|
||||
maybeConfig[key] =
|
||||
testComplexConfigDefaults[key as keyof PartialEnvironmentConfig];
|
||||
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('.');
|
||||
if (key === 'customMacros' && val) {
|
||||
const valSplit = val.split('.');
|
||||
if (valSplit.length > 0) {
|
||||
const props = [];
|
||||
for (const elt of valSplit.slice(1)) {
|
||||
if (elt === '*') {
|
||||
@@ -133,9 +121,21 @@ function parseConfigPragmaEnvironmentForTest(
|
||||
}
|
||||
}
|
||||
maybeConfig[key] = [[valSplit[0], props]];
|
||||
continue;
|
||||
}
|
||||
maybeConfig[key] = parsedVal;
|
||||
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);
|
||||
@@ -156,13 +156,6 @@ function parseConfigPragmaEnvironmentForTest(
|
||||
suggestions: null,
|
||||
});
|
||||
}
|
||||
|
||||
const testComplexPluginOptionDefaults: Partial<PluginOptions> = {
|
||||
gating: {
|
||||
source: 'ReactForgetFeatureFlag',
|
||||
importSpecifierName: 'isForgetEnabled_Fixtures',
|
||||
},
|
||||
};
|
||||
export function parseConfigPragmaForTests(
|
||||
pragma: string,
|
||||
defaults: {
|
||||
@@ -170,41 +163,44 @@ export function parseConfigPragmaForTests(
|
||||
},
|
||||
): PluginOptions {
|
||||
const environment = parseConfigPragmaEnvironmentForTest(pragma);
|
||||
const options: Record<keyof PluginOptions, unknown> = {
|
||||
...defaultOptions,
|
||||
panicThreshold: 'all_errors',
|
||||
compilationMode: defaults.compilationMode,
|
||||
environment,
|
||||
};
|
||||
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;
|
||||
}
|
||||
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;
|
||||
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(options);
|
||||
return parsePluginOptions({
|
||||
environment,
|
||||
compilationMode,
|
||||
panicThreshold,
|
||||
noEmit,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -452,7 +452,7 @@ function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
|
||||
reason:
|
||||
'Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)',
|
||||
loc: callee.loc,
|
||||
description: `Cannot call ${hookKind === 'Custom' ? 'hook' : hookKind} within a function expression`,
|
||||
description: `Cannot call ${hookKind} within a function component`,
|
||||
suggestions: null,
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @compilationMode:"infer"
|
||||
// @compilationMode(infer)
|
||||
const Test = () => <div />;
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @compilationMode:"infer"
|
||||
// @compilationMode(infer)
|
||||
class Component {
|
||||
_renderMessage = () => {
|
||||
const Message = () => {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @enableEmitInstrumentForget @compilationMode:"annotation"
|
||||
// @enableEmitInstrumentForget @compilationMode(annotation)
|
||||
|
||||
function Bar(props) {
|
||||
'use forget';
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @flow @compilationMode:"infer"
|
||||
// @flow @compilationMode(infer)
|
||||
export default component Foo(bar: number) {
|
||||
return <Bar bar={bar} />;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @flow @compilationMode:"infer"
|
||||
// @flow @compilationMode(infer)
|
||||
export default component Foo(bar: number) {
|
||||
return <Bar bar={bar} />;
|
||||
}
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @enableEmitInstrumentForget @compilationMode:"annotation"
|
||||
// @enableEmitInstrumentForget @compilationMode(annotation)
|
||||
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -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>
|
||||
@@ -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,
|
||||
};
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @eslintSuppressionRules:["my-app","react-rule"]
|
||||
// @eslintSuppressionRules(my-app/react-rule)
|
||||
|
||||
/* eslint-disable my-app/react-rule */
|
||||
function lowercasecomponent() {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateRefAccessDuringRender @compilationMode:"infer"
|
||||
// @validateRefAccessDuringRender @compilationMode(infer)
|
||||
function Component({ref}) {
|
||||
const value = ref.current;
|
||||
return <div>{value}</div>;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateRefAccessDuringRender @compilationMode:"infer"
|
||||
// @validateRefAccessDuringRender @compilationMode(infer)
|
||||
function Component(props) {
|
||||
const value = props.ref.current;
|
||||
return <div>{value}</div>;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @validateRefAccessDuringRender @compilationMode:"infer"
|
||||
// @validateRefAccessDuringRender @compilationMode(infer)
|
||||
function Component(props) {
|
||||
const ref = props.ref;
|
||||
ref.current = true;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateRefAccessDuringRender @compilationMode:"infer"
|
||||
// @validateRefAccessDuringRender @compilationMode(infer)
|
||||
function Component(props) {
|
||||
const ref = props.ref;
|
||||
ref.current = true;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @compilationMode:"infer"
|
||||
// @compilationMode(infer)
|
||||
function Component() {
|
||||
return <Foo />;
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @compilationMode:"infer"
|
||||
// @compilationMode(infer)
|
||||
function Component() {
|
||||
return <Foo />;
|
||||
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @validateBlocklistedImports:["DangerousImport"]
|
||||
// @validateBlocklistedImports(DangerousImport)
|
||||
import {foo} from 'DangerousImport';
|
||||
import {useIdentity} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @compilationMode:"infer"
|
||||
// @compilationMode(infer)
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @compilationMode:"infer" @enableResetCacheOnSourceFileChanges
|
||||
// @compilationMode(infer) @enableResetCacheOnSourceFileChanges
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import {ValidateMemoization} from 'shared-runtime';
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @enableEmitInstrumentForget @compilationMode:"annotation" @gating
|
||||
// @enableEmitInstrumentForget @compilationMode(annotation) @gating
|
||||
|
||||
function Bar(props) {
|
||||
'use forget';
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation"
|
||||
|
||||
function Foo() {
|
||||
'use memo if(getTrue)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation"
|
||||
const Foo = getTrue()
|
||||
? function Foo() {
|
||||
"use memo if(getTrue)";
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div>hello world</div>;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
: function Foo() {
|
||||
"use memo if(getTrue)";
|
||||
return <div>hello world</div>;
|
||||
};
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>hello world</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"} @compilationMode:"annotation"
|
||||
|
||||
function Foo() {
|
||||
'use memo if(getTrue)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -1,66 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
function Foo({value}) {
|
||||
'use memo if(getTrue)';
|
||||
|
||||
const initialValue = useMemo(() => identity(value), []);
|
||||
return (
|
||||
<>
|
||||
<div>initial value {initialValue}</div>
|
||||
<div>current value {value}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{value: 1}],
|
||||
sequentialRenders: [{value: 1}, {value: 2}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly
|
||||
|
||||
import { useMemo } from "react";
|
||||
import { identity } from "shared-runtime";
|
||||
|
||||
function Foo({ value }) {
|
||||
"use memo if(getTrue)";
|
||||
|
||||
const initialValue = useMemo(() => identity(value), []);
|
||||
return (
|
||||
<>
|
||||
<div>initial value {initialValue}</div>
|
||||
<div>current value {value}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{ value: 1 }],
|
||||
sequentialRenders: [{ value: 1 }, { value: 2 }],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"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":"The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source","severity":"CannotPreserveMemoization","suggestions":null,"loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"}}}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>initial value 1</div><div>current value 1</div>
|
||||
<div>initial value 1</div><div>current value 2</div>
|
||||
@@ -1,22 +0,0 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"} @validatePreserveExistingMemoizationGuarantees @panicThreshold:"none" @loggerTestOnly
|
||||
|
||||
import {useMemo} from 'react';
|
||||
import {identity} from 'shared-runtime';
|
||||
|
||||
function Foo({value}) {
|
||||
'use memo if(getTrue)';
|
||||
|
||||
const initialValue = useMemo(() => identity(value), []);
|
||||
return (
|
||||
<>
|
||||
<div>initial value {initialValue}</div>
|
||||
<div>current value {value}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{value: 1}],
|
||||
sequentialRenders: [{value: 1}, {value: 2}],
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"}
|
||||
|
||||
function Foo() {
|
||||
'use memo if(getFalse)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { getFalse } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"}
|
||||
const Foo = getFalse()
|
||||
? function Foo() {
|
||||
"use memo if(getFalse)";
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div>hello world</div>;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
: function Foo() {
|
||||
"use memo if(getFalse)";
|
||||
return <div>hello world</div>;
|
||||
};
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>hello world</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"}
|
||||
|
||||
function Foo() {
|
||||
'use memo if(getFalse)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -1,50 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"}
|
||||
|
||||
function Foo() {
|
||||
'use memo if(getTrue)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { c as _c } from "react/compiler-runtime";
|
||||
import { getTrue } from "shared-runtime"; // @dynamicGating:{"source":"shared-runtime"}
|
||||
const Foo = getTrue()
|
||||
? function Foo() {
|
||||
"use memo if(getTrue)";
|
||||
const $ = _c(1);
|
||||
let t0;
|
||||
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
|
||||
t0 = <div>hello world</div>;
|
||||
$[0] = t0;
|
||||
} else {
|
||||
t0 = $[0];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
: function Foo() {
|
||||
"use memo if(getTrue)";
|
||||
return <div>hello world</div>;
|
||||
};
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>hello world</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"}
|
||||
|
||||
function Foo() {
|
||||
'use memo if(getTrue)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none"
|
||||
|
||||
function Foo() {
|
||||
'use memo if(true)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none"
|
||||
|
||||
function Foo() {
|
||||
"use memo if(true)";
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>hello world</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none"
|
||||
|
||||
function Foo() {
|
||||
'use memo if(true)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly
|
||||
|
||||
function Foo() {
|
||||
'use memo if(getTrue)';
|
||||
'use memo if(getFalse)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly
|
||||
|
||||
function Foo() {
|
||||
"use memo if(getTrue)";
|
||||
"use memo if(getFalse)";
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Logs
|
||||
|
||||
```
|
||||
{"kind":"CompileError","fnLoc":{"start":{"line":3,"column":0,"index":86},"end":{"line":7,"column":1,"index":190},"filename":"dynamic-gating-invalid-multiple.ts"},"detail":{"reason":"Multiple dynamic gating directives found","description":"Expected a single directive but found [use memo if(getTrue), use memo if(getFalse)]","severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":2,"index":105},"end":{"line":4,"column":25,"index":128},"filename":"dynamic-gating-invalid-multiple.ts"}}}
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>hello world</div>
|
||||
@@ -1,12 +0,0 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @loggerTestOnly
|
||||
|
||||
function Foo() {
|
||||
'use memo if(getTrue)';
|
||||
'use memo if(getFalse)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @noEmit
|
||||
|
||||
function Foo() {
|
||||
'use memo if(getTrue)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @noEmit
|
||||
|
||||
function Foo() {
|
||||
"use memo if(getTrue)";
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
### Eval output
|
||||
(kind: ok) <div>hello world</div>
|
||||
@@ -1,11 +0,0 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"} @noEmit
|
||||
|
||||
function Foo() {
|
||||
'use memo if(getTrue)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies
|
||||
import {useEffect} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function ReactiveVariable({propVal}) {
|
||||
'use memo if(invalid identifier)';
|
||||
const arr = [propVal];
|
||||
useEffect(() => print(arr));
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: ReactiveVariable,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
6 | 'use memo if(invalid identifier)';
|
||||
7 | const arr = [propVal];
|
||||
> 8 | useEffect(() => print(arr));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (8:8)
|
||||
9 | }
|
||||
10 |
|
||||
11 | export const FIXTURE_ENTRYPOINT = {
|
||||
```
|
||||
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"} @panicThreshold:"none" @inferEffectDependencies
|
||||
import {useEffect} from 'react';
|
||||
import {print} from 'shared-runtime';
|
||||
|
||||
function ReactiveVariable({propVal}) {
|
||||
'use memo if(invalid identifier)';
|
||||
const arr = [propVal];
|
||||
useEffect(() => print(arr));
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: ReactiveVariable,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -1,32 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"}
|
||||
|
||||
function Foo() {
|
||||
'use memo if(true)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
2 |
|
||||
3 | function Foo() {
|
||||
> 4 | 'use memo if(true)';
|
||||
| ^^^^^^^^^^^^^^^^^^^^ InvalidReact: Dynamic gating directive is not a valid JavaScript identifier. Found 'use memo if(true)' (4:4)
|
||||
5 | return <div>hello world</div>;
|
||||
6 | }
|
||||
7 |
|
||||
```
|
||||
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"}
|
||||
|
||||
function Foo() {
|
||||
'use memo if(true)';
|
||||
return <div>hello world</div>;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Foo,
|
||||
params: [{}],
|
||||
};
|
||||
@@ -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";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @gating @compilationMode:"annotation"
|
||||
// @gating @compilationMode(annotation)
|
||||
export default function Bar(props) {
|
||||
'use forget';
|
||||
return <div>{props.bar}</div>;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @gating @compilationMode:"annotation"
|
||||
// @gating @compilationMode(annotation)
|
||||
export default function Bar(props) {
|
||||
'use forget';
|
||||
return <div>{props.bar}</div>;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @gating @compilationMode:"annotation"
|
||||
// @gating @compilationMode(annotation)
|
||||
export function Bar(props) {
|
||||
'use forget';
|
||||
return <div>{props.bar}</div>;
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @gating @compilationMode:"annotation"
|
||||
// @gating @compilationMode(annotation)
|
||||
function Bar(props) {
|
||||
'use forget';
|
||||
return <div>{props.bar}</div>;
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @gating @compilationMode:"infer"
|
||||
// @gating @compilationMode(infer)
|
||||
import React from 'react';
|
||||
export default React.forwardRef(function notNamedLikeAComponent(props) {
|
||||
return <div />;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @flow @compilationMode:"infer"
|
||||
// @flow @compilationMode(infer)
|
||||
export default hook useFoo(bar: number) {
|
||||
return [bar];
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @flow @compilationMode:"infer"
|
||||
// @flow @compilationMode(infer)
|
||||
export default hook useFoo(bar: number) {
|
||||
return [bar];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @customMacros:"idx.*.b"
|
||||
// @customMacros(idx.*.b)
|
||||
|
||||
function Component(props) {
|
||||
// outlined
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @customMacros:"idx.a"
|
||||
// @customMacros(idx.a)
|
||||
|
||||
function Component(props) {
|
||||
// outlined
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @customMacros:"idx"
|
||||
// @customMacros(idx)
|
||||
import idx from 'idx';
|
||||
|
||||
function Component(props) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @compilationMode:"infer"
|
||||
// @compilationMode(infer)
|
||||
import {useNoAlias} from 'shared-runtime';
|
||||
|
||||
// This should be compiled by Forget
|
||||
|
||||
@@ -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 />;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @compilationMode:"infer"
|
||||
// @compilationMode(infer)
|
||||
// Takes multiple parameters - not a component!
|
||||
function Component(foo, bar) {
|
||||
return <div />;
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
|
||||
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
|
||||
import useMyEffect from 'useEffectWrapper';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
|
||||
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
|
||||
import useMyEffect from 'useEffectWrapper';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
|
||||
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @inferEffectDependencies @compilationMode:"infer" @panicThreshold:"none"
|
||||
// @inferEffectDependencies @compilationMode(infer) @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
function nonReactFn(arg) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @inferEffectDependencies @panicThreshold:"none"
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// @inferEffectDependencies @panicThreshold:"none"
|
||||
// @inferEffectDependencies @panicThreshold(none)
|
||||
import {useEffect} from 'react';
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none"
|
||||
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
|
||||
/**
|
||||
* TODO: run the non-forget enabled version through the effect inference
|
||||
* pipeline.
|
||||
*/
|
||||
function Component({foo}) {
|
||||
'use memo if(getTrue)';
|
||||
const arr = [];
|
||||
useEffectWrapper(() => arr.push(foo));
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{foo: 1}],
|
||||
sequentialRenders: [{foo: 1}, {foo: 2}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
10 | 'use memo if(getTrue)';
|
||||
11 | const arr = [];
|
||||
> 12 | useEffectWrapper(() => arr.push(foo));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (12:12)
|
||||
13 | arr.push(2);
|
||||
14 | return arr;
|
||||
15 | }
|
||||
```
|
||||
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
// @dynamicGating:{"source":"shared-runtime"} @inferEffectDependencies @panicThreshold:"none"
|
||||
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
|
||||
/**
|
||||
* TODO: run the non-forget enabled version through the effect inference
|
||||
* pipeline.
|
||||
*/
|
||||
function Component({foo}) {
|
||||
'use memo if(getTrue)';
|
||||
const arr = [];
|
||||
useEffectWrapper(() => arr.push(foo));
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{foo: 1}],
|
||||
sequentialRenders: [{foo: 1}, {foo: 2}],
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @gating @inferEffectDependencies @panicThreshold:"none"
|
||||
import useEffectWrapper from 'useEffectWrapper';
|
||||
|
||||
/**
|
||||
* TODO: run the non-forget enabled version through the effect inference
|
||||
* pipeline.
|
||||
*/
|
||||
function Component({foo}) {
|
||||
const arr = [];
|
||||
useEffectWrapper(() => arr.push(foo));
|
||||
arr.push(2);
|
||||
return arr;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [{foo: 1}],
|
||||
sequentialRenders: [{foo: 1}, {foo: 2}],
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
|
||||
## Error
|
||||
|
||||
```
|
||||
8 | function Component({foo}) {
|
||||
9 | const arr = [];
|
||||
> 10 | useEffectWrapper(() => arr.push(foo));
|
||||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: [InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics. (10:10)
|
||||
11 | arr.push(2);
|
||||
12 | return arr;
|
||||
13 | }
|
||||
```
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user