Compare commits

..

1 Commits

Author SHA1 Message Date
Joe Savona
6616654253 [compiler] Show a ref name hint when assigning to non-ref in a callback
In #34125 I added a hint where if you assign to the .current property of a frozen object, we suggest naming the variable as `ref` or `-Ref`. However, the tracking for mutations that assign to .current specifically wasn't propagated past function expression boundaries, which meant that the hint only showed up if you mutated the ref in the main body of the component/hook. That's less likely to happen since most folks know not to access refs in render. What's more likely is that you'll (correctly) assign a ref in an effect or callback, but the compiler will throw an error. By showing a hint in this case we can help people understand the naming pattern.
2025-08-27 12:16:05 -07:00
213 changed files with 815 additions and 4731 deletions

View File

@@ -1,7 +1,7 @@
{
"packages": ["packages/react", "packages/react-dom", "packages/react-server-dom-webpack", "packages/scheduler"],
"buildCommand": "download-build-in-codesandbox-ci",
"node": "20",
"node": "18",
"publishDirectory": {
"react": "build/oss-experimental/react",
"react-dom": "build/oss-experimental/react-dom",

View File

@@ -547,10 +547,12 @@ module.exports = {
},
globals: {
$ElementType: 'readonly',
$Flow$ModuleRef: 'readonly',
$FlowFixMe: 'readonly',
$Keys: 'readonly',
$NonMaybeType: 'readonly',
$PropertyType: 'readonly',
$ReadOnly: 'readonly',
$ReadOnlyArray: 'readonly',
$ArrayBufferView: 'readonly',
@@ -565,7 +567,6 @@ module.exports = {
BigInt: 'readonly',
BigInt64Array: 'readonly',
BigUint64Array: 'readonly',
CacheType: 'readonly',
Class: 'readonly',
ClientRect: 'readonly',
CopyInspectedElementPath: 'readonly',
@@ -583,12 +584,11 @@ module.exports = {
mixin$Animatable: 'readonly',
MouseEventHandler: 'readonly',
NavigateEvent: 'readonly',
PerformanceMeasureOptions: 'readonly',
PropagationPhases: 'readonly',
PropertyDescriptor: 'readonly',
PropertyDescriptorMap: 'readonly',
Proxy$traps: 'readonly',
React$AbstractComponent: 'readonly',
React$Component: 'readonly',
React$ComponentType: 'readonly',
React$Config: 'readonly',
React$Context: 'readonly',
React$Element: 'readonly',

View File

@@ -57,6 +57,8 @@ jobs:
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- run: npx playwright install --with-deps chromium
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
- run: npx playwright install-deps
if: steps.cache_playwright_browsers.outputs.cache-hit == 'true'
- run: CI=true yarn test
- run: ls -R test-results
if: '!cancelled()'

View File

@@ -316,7 +316,7 @@ jobs:
if: steps.node_modules.outputs.cache-hit != 'true'
- run: ./scripts/react-compiler/build-compiler.sh && ./scripts/react-compiler/link-compiler.sh
- run: yarn workspace eslint-plugin-react-hooks test
# ----- BUILD -----
build_and_lint:
name: yarn build and lint
@@ -811,18 +811,9 @@ jobs:
pattern: _build_*
path: build
merge-multiple: true
- name: Check Playwright version
id: playwright_version
run: echo "playwright_version=$(npm ls @playwright/test | grep @playwright | sed 's/.*@//' | head -1)" >> "$GITHUB_OUTPUT"
- name: Cache Playwright Browsers for version ${{ steps.playwright_version.outputs.playwright_version }}
id: cache_playwright_browsers
uses: actions/cache@v4
with:
path: ~/.cache/ms-playwright
key: playwright-browsers-v6-${{ runner.arch }}-${{ runner.os }}-${{ steps.playwright_version.outputs.playwright_version }}
- name: Playwright install deps
if: steps.cache_playwright_browsers.outputs.cache-hit != 'true'
run: npx playwright install --with-deps chromium
- run: |
npx playwright install
sudo npx playwright install-deps
- run: ./scripts/ci/run_devtools_e2e_tests.js
env:
RELEASE_CHANNEL: experimental

View File

@@ -1,103 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import MonacoEditor, {loader, type Monaco} from '@monaco-editor/react';
import {parseConfigPragmaAsString} from 'babel-plugin-react-compiler';
import type {editor} from 'monaco-editor';
import * as monaco from 'monaco-editor';
import parserBabel from 'prettier/plugins/babel';
import * as prettierPluginEstree from 'prettier/plugins/estree';
import * as prettier from 'prettier/standalone';
import {useState, useEffect} from 'react';
import {Resizable} from 're-resizable';
import {useStore} from '../StoreContext';
import {monacoOptions} from './monacoOptions';
loader.config({monaco});
export default function ConfigEditor(): JSX.Element {
const [, setMonaco] = useState<Monaco | null>(null);
const store = useStore();
// Parse string-based override config from pragma comment and format it
const [configJavaScript, setConfigJavaScript] = useState('');
useEffect(() => {
const pragma = store.source.substring(0, store.source.indexOf('\n'));
const configString = `(${parseConfigPragmaAsString(pragma)})`;
prettier
.format(configString, {
semi: true,
parser: 'babel-ts',
plugins: [parserBabel, prettierPluginEstree],
})
.then(formatted => {
setConfigJavaScript(formatted);
})
.catch(error => {
console.error('Error formatting config:', error);
setConfigJavaScript('({})'); // Return empty object if not valid for now
//TODO: Add validation and error handling for config
});
console.log('Config:', configString);
}, [store.source]);
const handleChange: (value: string | undefined) => void = value => {
if (!value) return;
// TODO: Implement sync logic to update pragma comments in the source
console.log('Config changed:', value);
};
const handleMount: (
_: editor.IStandaloneCodeEditor,
monaco: Monaco,
) => void = (_, monaco) => {
setMonaco(monaco);
const uri = monaco.Uri.parse(`file:///config.js`);
const model = monaco.editor.getModel(uri);
if (model) {
model.updateOptions({tabSize: 2});
}
};
return (
<div className="relative flex flex-col flex-none border-r border-gray-200">
<h2 className="p-4 duration-150 ease-in border-b cursor-default border-grey-200 font-light text-secondary">
Config Overrides
</h2>
<Resizable
minWidth={300}
maxWidth={600}
defaultSize={{width: 350, height: 'auto'}}
enable={{right: true}}
className="!h-[calc(100vh_-_3.5rem_-_4rem)]">
<MonacoEditor
path={'config.js'}
language={'javascript'}
value={configJavaScript}
onMount={handleMount}
onChange={handleChange}
options={{
...monacoOptions,
readOnly: true,
lineNumbers: 'off',
folding: false,
renderLineHighlight: 'none',
scrollBeyondLastLine: false,
hideCursorInOverviewRuler: true,
overviewRulerBorder: false,
overviewRulerLanes: 0,
fontSize: 12,
}}
/>
</Resizable>
</div>
);
}

View File

@@ -37,7 +37,6 @@ import {
type Store,
} from '../../lib/stores';
import {useStore, useStoreDispatch} from '../StoreContext';
import ConfigEditor from './ConfigEditor';
import Input from './Input';
import {
CompilerOutput,
@@ -47,7 +46,6 @@ import {
} from './Output';
import {transformFromAstSync} from '@babel/core';
import {LoggerEvent} from 'babel-plugin-react-compiler/dist/Entrypoint';
import {useSearchParams} from 'next/navigation';
function parseInput(
input: string,
@@ -293,13 +291,7 @@ export default function Editor(): JSX.Element {
[deferredStore.source],
);
// TODO: Remove this once the config editor is more stable
const searchParams = useSearchParams();
const search = searchParams.get('showConfig');
const shouldShowConfig = search === 'true';
useMountEffect(() => {
// Initialize store
let mountStore: Store;
try {
mountStore = initStoreFromUrlOrLocalStorage();
@@ -336,7 +328,6 @@ export default function Editor(): JSX.Element {
return (
<>
<div className="relative flex basis top-14">
{shouldShowConfig && <ConfigEditor />}
<div className={clsx('relative sm:basis-1/4')}>
<Input language={language} errors={errors} />
</div>

View File

@@ -36,14 +36,6 @@ export enum ErrorSeverity {
* memoization.
*/
CannotPreserveMemoization = 'CannotPreserveMemoization',
/**
* An API that is known to be incompatible with the compiler. Generally as a result of
* the library using "interior mutability", ie having a value whose referential identity
* stays the same but which provides access to values that can change. For example a
* function that doesn't change but returns different results, or an object that doesn't
* change identity but whose properties change.
*/
IncompatibleLibrary = 'IncompatibleLibrary',
/**
* Unhandled syntax that we don't support yet.
*/
@@ -466,8 +458,7 @@ export class CompilerError extends Error {
case ErrorSeverity.InvalidJS:
case ErrorSeverity.InvalidReact:
case ErrorSeverity.InvalidConfig:
case ErrorSeverity.UnsupportedJS:
case ErrorSeverity.IncompatibleLibrary: {
case ErrorSeverity.UnsupportedJS: {
return true;
}
case ErrorSeverity.CannotPreserveMemoization:
@@ -515,9 +506,8 @@ function printErrorSummary(severity: ErrorSeverity, message: string): string {
severityCategory = 'Error';
break;
}
case ErrorSeverity.IncompatibleLibrary:
case ErrorSeverity.CannotPreserveMemoization: {
severityCategory = 'Compilation Skipped';
severityCategory = 'Memoization';
break;
}
case ErrorSeverity.Invariant: {
@@ -551,15 +541,9 @@ export enum ErrorCategory {
// Checking for valid usage of manual memoization
UseMemo = 'UseMemo',
// Checking for higher order functions acting as factories for components/hooks
Factories = 'Factories',
// Checks that manual memoization is preserved
PreserveManualMemo = 'PreserveManualMemo',
// Checks for known incompatible libraries
IncompatibleLibrary = 'IncompatibleLibrary',
// Checking for no mutations of props, hook arguments, hook return values
Immutability = 'Immutability',
@@ -719,16 +703,6 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
recommended: true,
};
}
case ErrorCategory.Factories: {
return {
category,
name: 'component-hook-factories',
description:
'Validates against higher order functions defining nested components or hooks. ' +
'Components and hooks should be defined at the module level',
recommended: true,
};
}
case ErrorCategory.FBT: {
return {
category,
@@ -883,15 +857,6 @@ function getRuleForCategoryImpl(category: ErrorCategory): LintRule {
recommended: true,
};
}
case ErrorCategory.IncompatibleLibrary: {
return {
category,
name: 'incompatible-library',
description:
'Validates against usage of libraries which are incompatible with memoization (manual or automatic)',
recommended: true,
};
}
default: {
assertExhaustive(category, `Unsupported category ${category}`);
}

View File

@@ -494,20 +494,7 @@ function findFunctionsToCompile(
): Array<CompileSource> {
const queue: Array<CompileSource> = [];
const traverseFunction = (fn: BabelFn, pass: CompilerPass): void => {
// In 'all' mode, compile only top level functions
if (
pass.opts.compilationMode === 'all' &&
fn.scope.getProgramParent() !== fn.scope.parent
) {
return;
}
const fnType = getReactFunctionType(fn, pass);
if (pass.opts.environment.validateNoDynamicallyCreatedComponentsOrHooks) {
validateNoDynamicallyCreatedComponentsOrHooks(fn, pass, programContext);
}
if (fnType === null || programContext.alreadyCompiled.has(fn.node)) {
return;
}
@@ -852,73 +839,6 @@ function shouldSkipCompilation(
return false;
}
/**
* Validates that Components/Hooks are always defined at module level. This prevents scope reference
* errors that occur when the compiler attempts to optimize the nested component/hook while its
* parent function remains uncompiled.
*/
function validateNoDynamicallyCreatedComponentsOrHooks(
fn: BabelFn,
pass: CompilerPass,
programContext: ProgramContext,
): void {
const parentNameExpr = getFunctionName(fn);
const parentName =
parentNameExpr !== null && parentNameExpr.isIdentifier()
? parentNameExpr.node.name
: '<anonymous>';
const validateNestedFunction = (
nestedFn: NodePath<
t.FunctionDeclaration | t.FunctionExpression | t.ArrowFunctionExpression
>,
): void => {
if (
nestedFn.node === fn.node ||
programContext.alreadyCompiled.has(nestedFn.node)
) {
return;
}
if (nestedFn.scope.getProgramParent() !== nestedFn.scope.parent) {
const nestedFnType = getReactFunctionType(nestedFn as BabelFn, pass);
const nestedFnNameExpr = getFunctionName(nestedFn as BabelFn);
const nestedName =
nestedFnNameExpr !== null && nestedFnNameExpr.isIdentifier()
? nestedFnNameExpr.node.name
: '<anonymous>';
if (nestedFnType === 'Component' || nestedFnType === 'Hook') {
CompilerError.throwDiagnostic({
category: ErrorCategory.Factories,
severity: ErrorSeverity.InvalidReact,
reason: `Components and hooks cannot be created dynamically`,
description: `The function \`${nestedName}\` appears to be a React ${nestedFnType.toLowerCase()}, but it's defined inside \`${parentName}\`. Components and Hooks should always be declared at module scope`,
details: [
{
kind: 'error',
message: 'this function dynamically created a component/hook',
loc: parentNameExpr?.node.loc ?? fn.node.loc ?? null,
},
{
kind: 'error',
message: 'the component is created here',
loc: nestedFnNameExpr?.node.loc ?? nestedFn.node.loc ?? null,
},
],
});
}
}
nestedFn.skip();
};
fn.traverse({
FunctionDeclaration: validateNestedFunction,
FunctionExpression: validateNestedFunction,
ArrowFunctionExpression: validateNestedFunction,
});
}
function getReactFunctionType(
fn: BabelFn,
pass: CompilerPass,
@@ -957,6 +877,11 @@ function getReactFunctionType(
return componentSyntaxType;
}
case 'all': {
// Compile only top level functions
if (fn.scope.getProgramParent() !== fn.scope.parent) {
return null;
}
return getComponentOrHookLike(fn, hookPattern) ?? 'Other';
}
default: {

View File

@@ -1,91 +0,0 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
import {Effect, ValueKind} from '..';
import {TypeConfig} from './TypeSchema';
/**
* Libraries developed before we officially documented the [Rules of React](https://react.dev/reference/rules)
* implement APIs which cannot be memoized safely, either via manual or automatic memoization.
*
* Any non-hook API that is designed to be called during render (not events/effects) should be safe to memoize:
*
* ```js
* function Component() {
* const {someFunction} = useLibrary();
* // it should always be safe to memoize functions like this
* const result = useMemo(() => someFunction(), [someFunction]);
* }
* ```
*
* However, some APIs implement "interior mutability" — mutating values rather than copying into a new value
* and setting state with the new value. Such functions (`someFunction()` in the example) could return different
* values even though the function itself is the same object. This breaks memoization, since React relies on
* the outer object (or function) changing if part of its value has changed.
*
* Given that we didn't have the Rules of React precisely documented prior to the introduction of React compiler,
* it's understandable that some libraries accidentally shipped APIs that break this rule. However, developers
* can easily run into pitfalls with these APIs. They may manually memoize them, which can break their app. Or
* they may try using React Compiler, and think that the compiler has broken their code.
*
* To help ensure that developers can successfully use the compiler with existing code, this file teaches the
* compiler about specific APIs that are known to be incompatible with memoization. We've tried to be as precise
* as possible.
*
* The React team is open to collaborating with library authors to help develop compatible versions of these APIs,
* and we have already reached out to the teams who own any API listed here to ensure they are aware of the issue.
*/
export function defaultModuleTypeProvider(
moduleName: string,
): TypeConfig | null {
switch (moduleName) {
case 'react-hook-form': {
return {
kind: 'object',
properties: {
useForm: {
kind: 'hook',
returnType: {
kind: 'object',
properties: {
// Only the `watch()` function returned by react-hook-form's `useForm()` API is incompatible
watch: {
kind: 'function',
positionalParams: [],
restParam: Effect.Read,
calleeEffect: Effect.Read,
returnType: {kind: 'type', name: 'Any'},
returnValueKind: ValueKind.Mutable,
knownIncompatible: `React Hook Form's \`useForm()\` API returns a \`watch()\` function which cannot be memoized safely.`,
},
},
},
},
},
};
}
case '@tanstack/react-table': {
return {
kind: 'object',
properties: {
/*
* Many of the properties of `useReactTable()`'s return value are incompatible, so we mark the entire hook
* as incompatible
*/
useReactTable: {
kind: 'hook',
positionalParams: [],
restParam: Effect.Read,
returnType: {kind: 'type', name: 'Any'},
knownIncompatible: `TanStack Table's \`useReactTable()\` API returns functions that cannot be memoized safely`,
},
},
};
}
}
return null;
}

View File

@@ -50,7 +50,6 @@ import {
import {Scope as BabelScope, NodePath} from '@babel/traverse';
import {TypeSchema} from './TypeSchema';
import {FlowTypeEnv} from '../Flood/Types';
import {defaultModuleTypeProvider} from './DefaultModuleTypeProvider';
export const ReactElementSymbolSchema = z.object({
elementSymbol: z.union([
@@ -651,13 +650,6 @@ export const EnvironmentConfigSchema = z.object({
* useMemo(() => { ... }, [...]);
*/
validateNoVoidUseMemo: z.boolean().default(false),
/**
* Validates that Components/Hooks are always defined at module level. This prevents scope
* reference errors that occur when the compiler attempts to optimize the nested component/hook
* while its parent function remains uncompiled.
*/
validateNoDynamicallyCreatedComponentsOrHooks: z.boolean().default(false),
});
export type EnvironmentConfig = z.infer<typeof EnvironmentConfigSchema>;
@@ -861,16 +853,10 @@ export class Environment {
#resolveModuleType(moduleName: string, loc: SourceLocation): Global | null {
let moduleType = this.#moduleTypes.get(moduleName);
if (moduleType === undefined) {
/*
* NOTE: Zod doesn't work when specifying a function as a default, so we have to
* fallback to the default value here
*/
const moduleTypeProvider =
this.config.moduleTypeProvider ?? defaultModuleTypeProvider;
if (moduleTypeProvider == null) {
if (this.config.moduleTypeProvider == null) {
return null;
}
const unparsedModuleConfig = moduleTypeProvider(moduleName);
const unparsedModuleConfig = this.config.moduleTypeProvider(moduleName);
if (unparsedModuleConfig != null) {
const parsedModuleConfig = TypeSchema.safeParse(unparsedModuleConfig);
if (!parsedModuleConfig.success) {

View File

@@ -1001,7 +1001,6 @@ export function installTypeConfig(
mutableOnlyIfOperandsAreMutable:
typeConfig.mutableOnlyIfOperandsAreMutable === true,
aliasing: typeConfig.aliasing,
knownIncompatible: typeConfig.knownIncompatible ?? null,
});
}
case 'hook': {
@@ -1020,7 +1019,6 @@ export function installTypeConfig(
returnValueKind: typeConfig.returnValueKind ?? ValueKind.Frozen,
noAlias: typeConfig.noAlias === true,
aliasing: typeConfig.aliasing,
knownIncompatible: typeConfig.knownIncompatible ?? null,
});
}
case 'object': {

View File

@@ -323,22 +323,6 @@ export default class HIRBuilder {
],
});
}
if (node.name === 'this') {
CompilerError.throwDiagnostic({
severity: ErrorSeverity.UnsupportedJS,
category: ErrorCategory.UnsupportedSyntax,
reason: '`this` is not supported syntax',
description:
'React Compiler does not support compiling functions that use `this`',
details: [
{
kind: 'error',
message: '`this` was used here',
loc: node.loc ?? GeneratedSource,
},
],
});
}
const originalName = node.name;
let name = originalName;
let index = 0;

View File

@@ -332,7 +332,6 @@ export type FunctionSignature = {
mutableOnlyIfOperandsAreMutable?: boolean;
impure?: boolean;
knownIncompatible?: string | null | undefined;
canonicalName?: string;

View File

@@ -880,8 +880,7 @@ export function printType(type: Type): string {
if (type.kind === 'Object' && type.shapeId != null) {
return `:T${type.kind}<${type.shapeId}>`;
} else if (type.kind === 'Function' && type.shapeId != null) {
const returnType = printType(type.return);
return `:T${type.kind}<${type.shapeId}>()${returnType !== '' ? `: ${returnType}` : ''}`;
return `:T${type.kind}<${type.shapeId}>`;
} else {
return `:T${type.kind}`;
}

View File

@@ -251,7 +251,6 @@ export type FunctionTypeConfig = {
impure?: boolean | null | undefined;
canonicalName?: string | null | undefined;
aliasing?: AliasingSignatureConfig | null | undefined;
knownIncompatible?: string | null | undefined;
};
export const FunctionTypeSchema: z.ZodType<FunctionTypeConfig> = z.object({
kind: z.literal('function'),
@@ -265,7 +264,6 @@ export const FunctionTypeSchema: z.ZodType<FunctionTypeConfig> = z.object({
impure: z.boolean().nullable().optional(),
canonicalName: z.string().nullable().optional(),
aliasing: AliasingSignatureSchema.nullable().optional(),
knownIncompatible: z.string().nullable().optional(),
});
export type HookTypeConfig = {
@@ -276,7 +274,6 @@ export type HookTypeConfig = {
returnValueKind?: ValueKind | null | undefined;
noAlias?: boolean | null | undefined;
aliasing?: AliasingSignatureConfig | null | undefined;
knownIncompatible?: string | null | undefined;
};
export const HookTypeSchema: z.ZodType<HookTypeConfig> = z.object({
kind: z.literal('hook'),
@@ -286,7 +283,6 @@ export const HookTypeSchema: z.ZodType<HookTypeConfig> = z.object({
returnValueKind: ValueKindSchema.nullable().optional(),
noAlias: z.boolean().nullable().optional(),
aliasing: AliasingSignatureSchema.nullable().optional(),
knownIncompatible: z.string().nullable().optional(),
});
export type BuiltInTypeConfig =

View File

@@ -27,7 +27,6 @@ import {
InstructionKind,
InstructionValue,
isArrayType,
isJsxType,
isMapType,
isPrimitiveType,
isRefOrRefValue,
@@ -1872,23 +1871,6 @@ function computeSignatureForInstruction(
});
}
}
for (const prop of value.props) {
if (
prop.kind === 'JsxAttribute' &&
prop.place.identifier.type.kind === 'Function' &&
(isJsxType(prop.place.identifier.type.return) ||
(prop.place.identifier.type.return.kind === 'Phi' &&
prop.place.identifier.type.return.operands.some(operand =>
isJsxType(operand),
)))
) {
// Any props which return jsx are assumed to be called during render
effects.push({
kind: 'Render',
place: prop.place,
});
}
}
}
break;
}
@@ -2170,27 +2152,6 @@ function computeEffectsForLegacySignature(
}),
});
}
if (signature.knownIncompatible != null && state.env.isInferredMemoEnabled) {
const errors = new CompilerError();
errors.pushDiagnostic(
CompilerDiagnostic.create({
category: ErrorCategory.IncompatibleLibrary,
severity: ErrorSeverity.IncompatibleLibrary,
reason: 'Use of incompatible library',
description: [
'This API returns functions which cannot be memoized without leading to stale UI. ' +
'To prevent this, by default React Compiler will skip memoizing this component/hook. ' +
'However, you may see issues if values from this API are passed to other components/hooks that are ' +
'memoized.',
].join(''),
}).withDetail({
kind: 'error',
loc: receiver.loc,
message: signature.knownIncompatible,
}),
);
throw errors;
}
const stores: Array<Place> = [];
const captures: Array<Place> = [];
function visit(place: Place, effect: Effect): void {

View File

@@ -175,41 +175,6 @@ export function alignReactiveScopesToBlockScopesHIR(fn: HIRFunction): void {
if (node != null) {
valueBlockNodes.set(fallthrough, node);
}
} else if (terminal.kind === 'goto') {
/**
* If we encounter a goto that is not to the natural fallthrough of the current
* block (not the topmost fallthrough on the stack), then this is a goto to a
* label. Any scopes that extend beyond the goto must be extended to include
* the labeled range, so that the break statement doesn't accidentally jump
* out of the scope. We do this by extending the start and end of the scope's
* range to the label and its fallthrough respectively.
*/
const start = activeBlockFallthroughRanges.find(
range => range.fallthrough === terminal.block,
);
if (start != null && start !== activeBlockFallthroughRanges.at(-1)) {
const fallthroughBlock = fn.body.blocks.get(start.fallthrough)!;
const firstId =
fallthroughBlock.instructions[0]?.id ?? fallthroughBlock.terminal.id;
for (const scope of activeScopes) {
/**
* activeScopes is only filtered at block start points, so some of the
* scopes may not actually be active anymore, ie we've past their end
* instruction. Only extend ranges for scopes that are actually active.
*
* TODO: consider pruning activeScopes per instruction
*/
if (scope.range.end <= terminal.id) {
continue;
}
scope.range.start = makeInstructionId(
Math.min(start.range.start, scope.range.start),
);
scope.range.end = makeInstructionId(
Math.max(firstId, scope.range.end),
);
}
}
}
/*

View File

@@ -411,9 +411,7 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
this.state = state;
this.options = {
memoizeJsxElements: !this.env.config.enableForest,
forceMemoizePrimitives:
this.env.config.enableForest ||
this.env.config.enablePreserveExistingMemoizationGuarantees,
forceMemoizePrimitives: this.env.config.enableForest,
};
}
@@ -536,23 +534,9 @@ class CollectDependenciesVisitor extends ReactiveFunctionVisitor<
case 'JSXText':
case 'BinaryExpression':
case 'UnaryExpression': {
if (options.forceMemoizePrimitives) {
/**
* Because these instructions produce primitives we usually don't consider
* them as escape points: they are known to copy, not return references.
* However if we're forcing memoization of primitives then we mark these
* instructions as needing memoization and walk their rvalues to ensure
* any scopes transitively reachable from the rvalues are considered for
* memoization. Note: we may still prune primitive-producing scopes if
* they don't ultimately escape at all.
*/
const level = MemoizationLevel.Memoized;
return {
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],
rvalues: [...eachReactiveValueOperand(value)],
};
}
const level = MemoizationLevel.Never;
const level = options.forceMemoizePrimitives
? MemoizationLevel.Memoized
: MemoizationLevel.Never;
return {
// All of these instructions return a primitive value and never need to be memoized
lvalues: lvalue !== null ? [{place: lvalue, level}] : [],

View File

@@ -777,15 +777,6 @@ class Unifier {
return {kind: 'Phi', operands: type.operands.map(o => this.get(o))};
}
if (type.kind === 'Function') {
return {
kind: 'Function',
isConstructor: type.isConstructor,
shapeId: type.shapeId,
return: this.get(type.return),
};
}
return type;
}
}

View File

@@ -182,11 +182,6 @@ export function parseConfigPragmaForTests(
environment?: PartialEnvironmentConfig;
},
): PluginOptions {
const overridePragma = parseConfigPragmaAsString(pragma);
if (overridePragma !== '') {
return parseConfigStringAsJS(overridePragma, defaults);
}
const environment = parseConfigPragmaEnvironmentForTest(
pragma,
defaults.environment ?? {},
@@ -222,104 +217,3 @@ export function parseConfigPragmaForTests(
}
return parsePluginOptions(options);
}
export function parseConfigPragmaAsString(pragma: string): string {
// Check if it's in JS override format
for (const {key, value: val} of splitPragma(pragma)) {
if (key === 'OVERRIDE' && val != null) {
return val;
}
}
return '';
}
function parseConfigStringAsJS(
configString: string,
defaults: {
compilationMode: CompilationMode;
environment?: PartialEnvironmentConfig;
},
): PluginOptions {
let parsedConfig: any;
try {
// Parse the JavaScript object literal
parsedConfig = new Function(`return ${configString}`)();
} catch (error) {
CompilerError.invariant(false, {
reason: 'Failed to parse config pragma as JavaScript object',
description: `Could not parse: ${configString}. Error: ${error}`,
loc: null,
suggestions: null,
});
}
console.log('OVERRIDE:', parsedConfig);
const options: Record<keyof PluginOptions, unknown> = {
...defaultOptions,
panicThreshold: 'all_errors',
compilationMode: defaults.compilationMode,
environment: defaults.environment ?? defaultOptions.environment,
};
// Apply parsed config, merging environment if it exists
if (parsedConfig.environment) {
const mergedEnvironment = {
...(options.environment as Record<string, unknown>),
...parsedConfig.environment,
};
// Apply complex defaults for environment flags that are set to true
const environmentConfig: Partial<Record<keyof EnvironmentConfig, unknown>> =
{};
for (const [key, value] of Object.entries(mergedEnvironment)) {
if (hasOwnProperty(EnvironmentConfigSchema.shape, key)) {
if (value === true && key in testComplexConfigDefaults) {
environmentConfig[key] = testComplexConfigDefaults[key];
} else {
environmentConfig[key] = value;
}
}
}
// Validate environment config
const validatedEnvironment =
EnvironmentConfigSchema.safeParse(environmentConfig);
if (!validatedEnvironment.success) {
CompilerError.invariant(false, {
reason: 'Invalid environment configuration in config pragma',
description: `${fromZodError(validatedEnvironment.error)}`,
loc: null,
suggestions: null,
});
}
if (validatedEnvironment.data.enableResetCacheOnSourceFileChanges == null) {
validatedEnvironment.data.enableResetCacheOnSourceFileChanges = false;
}
options.environment = validatedEnvironment.data;
}
// Apply other config options
for (const [key, value] of Object.entries(parsedConfig)) {
if (key === 'environment') {
continue;
}
if (hasOwnProperty(defaultOptions, key)) {
if (value === true && key in testComplexPluginOptionDefaults) {
options[key] = testComplexPluginOptionDefaults[key];
} else if (key === 'target' && value === 'donotuse_meta_internal') {
options[key] = {
kind: value,
runtimeModule: 'react',
};
} else {
options[key] = value;
}
}
}
return parsePluginOptions(options);
}

View File

@@ -284,7 +284,8 @@ function validateInferredDep(
CompilerDiagnostic.create({
category: ErrorCategory.PreserveManualMemo,
severity: ErrorSeverity.CannotPreserveMemoization,
reason: 'Existing memoization could not be preserved',
reason:
'Compilation skipped because existing memoization could not be preserved',
description: [
'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. ',
@@ -538,7 +539,8 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
CompilerDiagnostic.create({
category: ErrorCategory.PreserveManualMemo,
severity: ErrorSeverity.CannotPreserveMemoization,
reason: 'Existing memoization could not be preserved',
reason:
'Compilation skipped because existing memoization could not be preserved',
description: [
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. ',
'This dependency may be mutated later, which could cause the value to change unexpectedly.',
@@ -586,7 +588,8 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
CompilerDiagnostic.create({
category: ErrorCategory.PreserveManualMemo,
severity: ErrorSeverity.CannotPreserveMemoization,
reason: 'Existing memoization could not be preserved',
reason:
'Compilation skipped because existing memoization could not be preserved',
description: [
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output. ',
DEBUG

View File

@@ -46,16 +46,14 @@ function useFoo(t0) {
t1 = $[0];
}
let items = t1;
if ($[1] !== cond) {
bb0: {
if (cond) {
items = [];
} else {
break bb0;
}
items.push(2);
bb0: if ($[1] !== cond) {
if (cond) {
items = [];
} else {
break bb0;
}
items.push(2);
$[1] = cond;
$[2] = items;
} else {

View File

@@ -1,77 +0,0 @@
## Input
```javascript
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
function Component(props) {
const result = useMemo(
() => makeObject(props.value).value + 1,
[props.value]
);
console.log(result);
return 'ok';
}
function makeObject(value) {
console.log(value);
return {value};
}
export const TODO_FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
sequentialRenders: [
{value: 42},
{value: 42},
{value: 3.14},
{value: 3.14},
{value: 42},
{value: 3.14},
{value: 42},
{value: 3.14},
],
};
```
## Code
```javascript
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import { useMemo } from "react";
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
function Component(props) {
const result = makeObject(props.value).value + 1;
console.log(result);
return "ok";
}
function makeObject(value) {
console.log(value);
return { value };
}
export const TODO_FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
sequentialRenders: [
{ value: 42 },
{ value: 42 },
{ value: 3.14 },
{ value: 3.14 },
{ value: 42 },
{ value: 3.14 },
{ value: 42 },
{ value: 3.14 },
],
};
```
### Eval output
(kind: exception) Fixture not implemented

View File

@@ -1,32 +0,0 @@
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
function Component(props) {
const result = useMemo(
() => makeObject(props.value).value + 1,
[props.value]
);
console.log(result);
return 'ok';
}
function makeObject(value) {
console.log(value);
return {value};
}
export const TODO_FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
sequentialRenders: [
{value: 42},
{value: 42},
{value: 3.14},
{value: 3.14},
{value: 42},
{value: 3.14},
{value: 42},
{value: 3.14},
],
};

View File

@@ -1,81 +0,0 @@
## Input
```javascript
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
function Component(props) {
const result = makeObject(props.value).value + 1;
console.log(result);
return 'ok';
}
function makeObject(value) {
console.log(value);
return {value};
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
sequentialRenders: [
{value: 42},
{value: 42},
{value: 3.14},
{value: 3.14},
{value: 42},
{value: 3.14},
{value: 42},
{value: 3.14},
],
};
```
## Code
```javascript
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import { useMemo } from "react";
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
function Component(props) {
const result = makeObject(props.value).value + 1;
console.log(result);
return "ok";
}
function makeObject(value) {
console.log(value);
return { value };
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
sequentialRenders: [
{ value: 42 },
{ value: 42 },
{ value: 3.14 },
{ value: 3.14 },
{ value: 42 },
{ value: 3.14 },
{ value: 42 },
{ value: 3.14 },
],
};
```
### Eval output
(kind: ok) "ok"
"ok"
"ok"
"ok"
"ok"
"ok"
"ok"
"ok"
logs: [42,43,42,43,3.14,4.140000000000001,3.14,4.140000000000001,42,43,3.14,4.140000000000001,42,43,3.14,4.140000000000001]

View File

@@ -1,29 +0,0 @@
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
function Component(props) {
const result = makeObject(props.value).value + 1;
console.log(result);
return 'ok';
}
function makeObject(value) {
console.log(value);
return {value};
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
sequentialRenders: [
{value: 42},
{value: 42},
{value: 3.14},
{value: 3.14},
{value: 42},
{value: 3.14},
{value: 42},
{value: 3.14},
],
};

View File

@@ -24,18 +24,9 @@ function useThing(fn) {
```
Found 1 error:
Error: `this` is not supported syntax
Error: Expected a non-reserved identifier name
React Compiler does not support compiling functions that use `this`
error.reserved-words.ts:8:28
6 |
7 | if (ref.current === null) {
> 8 | ref.current = function (this: unknown, ...args) {
| ^^^^^^^^^^^^^ `this` was used here
9 | return fnRef.current.call(this, ...args);
10 | };
11 | }
`this` is a reserved word in JavaScript and cannot be used as an identifier name.
```

View File

@@ -26,7 +26,7 @@ function Component(props) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source.

View File

@@ -26,7 +26,7 @@ function Component(props) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source.

View File

@@ -1,34 +0,0 @@
## Input
```javascript
import {knownIncompatible} from 'ReactCompilerKnownIncompatibleTest';
function Component() {
const data = knownIncompatible();
return <div>Error</div>;
}
```
## Error
```
Found 1 error:
Compilation Skipped: Use of incompatible library
This API returns functions which cannot be memoized without leading to stale UI. To prevent this, by default React Compiler will skip memoizing this component/hook. However, you may see issues if values from this API are passed to other components/hooks that are memoized.
error.invalid-known-incompatible-function.ts:4:15
2 |
3 | function Component() {
> 4 | const data = knownIncompatible();
| ^^^^^^^^^^^^^^^^^ useKnownIncompatible is known to be incompatible
5 | return <div>Error</div>;
6 | }
7 |
```

View File

@@ -1,6 +0,0 @@
import {knownIncompatible} from 'ReactCompilerKnownIncompatibleTest';
function Component() {
const data = knownIncompatible();
return <div>Error</div>;
}

View File

@@ -1,33 +0,0 @@
## Input
```javascript
import {useKnownIncompatibleIndirect} from 'ReactCompilerKnownIncompatibleTest';
function Component() {
const {incompatible} = useKnownIncompatibleIndirect();
return <div>{incompatible()}</div>;
}
```
## Error
```
Found 1 error:
Compilation Skipped: Use of incompatible library
This API returns functions which cannot be memoized without leading to stale UI. To prevent this, by default React Compiler will skip memoizing this component/hook. However, you may see issues if values from this API are passed to other components/hooks that are memoized.
error.invalid-known-incompatible-hook-return-property.ts:5:15
3 | function Component() {
4 | const {incompatible} = useKnownIncompatibleIndirect();
> 5 | return <div>{incompatible()}</div>;
| ^^^^^^^^^^^^ useKnownIncompatibleIndirect returns an incompatible() function that is known incompatible
6 | }
7 |
```

View File

@@ -1,6 +0,0 @@
import {useKnownIncompatibleIndirect} from 'ReactCompilerKnownIncompatibleTest';
function Component() {
const {incompatible} = useKnownIncompatibleIndirect();
return <div>{incompatible()}</div>;
}

View File

@@ -1,34 +0,0 @@
## Input
```javascript
import {useKnownIncompatible} from 'ReactCompilerKnownIncompatibleTest';
function Component() {
const data = useKnownIncompatible();
return <div>Error</div>;
}
```
## Error
```
Found 1 error:
Compilation Skipped: Use of incompatible library
This API returns functions which cannot be memoized without leading to stale UI. To prevent this, by default React Compiler will skip memoizing this component/hook. However, you may see issues if values from this API are passed to other components/hooks that are memoized.
error.invalid-known-incompatible-hook.ts:4:15
2 |
3 | function Component() {
> 4 | const data = useKnownIncompatible();
| ^^^^^^^^^^^^^^^^^^^^ useKnownIncompatible is known to be incompatible
5 | return <div>Error</div>;
6 | }
7 |
```

View File

@@ -1,6 +0,0 @@
import {useKnownIncompatible} from 'ReactCompilerKnownIncompatibleTest';
function Component() {
const data = useKnownIncompatible();
return <div>Error</div>;
}

View File

@@ -1,44 +0,0 @@
## Input
```javascript
function Component() {
const renderItem = item => {
// Multiple returns so that the return type is a Phi (union)
if (item == null) {
return null;
}
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}
```
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
error.invalid-mutate-global-in-render-helper-phi-return-prop.ts:12:4
10 | // called during render, and thus it's unsafe to mutate globals or call
11 | // other impure code.
> 12 | global.property = true;
| ^^^^^^ value cannot be modified
13 | return <Item item={item} value={rand} />;
14 | };
15 | return <ItemList renderItem={renderItem} />;
```

View File

@@ -1,16 +0,0 @@
function Component() {
const renderItem = item => {
// Multiple returns so that the return type is a Phi (union)
if (item == null) {
return null;
}
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}

View File

@@ -1,40 +0,0 @@
## Input
```javascript
function Component() {
const renderItem = item => {
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}
```
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a variable defined outside a component or hook is not allowed. Consider using an effect.
error.invalid-mutate-global-in-render-helper-prop.ts:8:4
6 | // called during render, and thus it's unsafe to mutate globals or call
7 | // other impure code.
> 8 | global.property = true;
| ^^^^^^ value cannot be modified
9 | return <Item item={item} value={rand} />;
10 | };
11 | return <ItemList renderItem={renderItem} />;
```

View File

@@ -1,12 +0,0 @@
function Component() {
const renderItem = item => {
// Normally we assume that it's safe to mutate globals in a function passed
// as a prop, because the prop could be used as an event handler or effect.
// But if the function returns JSX we can assume it's a render helper, ie
// called during render, and thus it's unsafe to mutate globals or call
// other impure code.
global.property = true;
return <Item item={item} value={rand} />;
};
return <ItemList renderItem={renderItem} />;
}

View File

@@ -20,7 +20,7 @@ function Component(props) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items.edges.nodes`, but the source dependencies were [props.items?.edges?.nodes]. Inferred different dependency than source.

View File

@@ -25,7 +25,7 @@ function Component(props) {
```
Found 1 error:
Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
Memoization: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
error.invalid-useEffect-dep-not-memoized-bc-range-overlaps-hook.ts:9:2
7 |

View File

@@ -22,7 +22,7 @@ function Component(props) {
```
Found 1 error:
Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
Memoization: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
error.invalid-useEffect-dep-not-memoized.ts:6:2
4 | function Component(props) {

View File

@@ -22,7 +22,7 @@ function Component(props) {
```
Found 1 error:
Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
Memoization: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
error.invalid-useInsertionEffect-dep-not-memoized.ts:6:2
4 | function Component(props) {

View File

@@ -22,7 +22,7 @@ function Component(props) {
```
Found 1 error:
Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
Memoization: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
error.invalid-useLayoutEffect-dep-not-memoized.ts:6:2
4 | function Component(props) {

View File

@@ -1,54 +0,0 @@
## Input
```javascript
// @validateNoDynamicallyCreatedComponentsOrHooks
export function getInput(a) {
const Wrapper = () => {
const handleChange = () => {
a.onChange();
};
return <input onChange={handleChange} />;
};
return Wrapper;
}
export const FIXTURE_ENTRYPOINT = {
fn: getInput,
isComponent: false,
params: [{onChange() {}}],
};
```
## Error
```
Found 1 error:
Error: Components and hooks cannot be created dynamically
The function `Wrapper` appears to be a React component, but it's defined inside `getInput`. Components and Hooks should always be declared at module scope
error.nested-component-in-normal-function.ts:2:16
1 | // @validateNoDynamicallyCreatedComponentsOrHooks
> 2 | export function getInput(a) {
| ^^^^^^^^ this function dynamically created a component/hook
3 | const Wrapper = () => {
4 | const handleChange = () => {
5 | a.onChange();
error.nested-component-in-normal-function.ts:3:8
1 | // @validateNoDynamicallyCreatedComponentsOrHooks
2 | export function getInput(a) {
> 3 | const Wrapper = () => {
| ^^^^^^^ the component is created here
4 | const handleChange = () => {
5 | a.onChange();
6 | };
```

View File

@@ -1,18 +0,0 @@
// @validateNoDynamicallyCreatedComponentsOrHooks
export function getInput(a) {
const Wrapper = () => {
const handleChange = () => {
a.onChange();
};
return <input onChange={handleChange} />;
};
return Wrapper;
}
export const FIXTURE_ENTRYPOINT = {
fn: getInput,
isComponent: false,
params: [{onChange() {}}],
};

View File

@@ -1,59 +0,0 @@
## Input
```javascript
// @validateNoDynamicallyCreatedComponentsOrHooks
import {useState} from 'react';
function createCustomHook(config) {
function useConfiguredState() {
const [state, setState] = useState(0);
const increment = () => {
setState(state + config.step);
};
return [state, increment];
}
return useConfiguredState;
}
export const FIXTURE_ENTRYPOINT = {
fn: createCustomHook,
isComponent: false,
params: [{step: 1}],
};
```
## Error
```
Found 1 error:
Error: Components and hooks cannot be created dynamically
The function `useConfiguredState` appears to be a React hook, but it's defined inside `createCustomHook`. Components and Hooks should always be declared at module scope
error.nested-hook-in-normal-function.ts:4:9
2 | import {useState} from 'react';
3 |
> 4 | function createCustomHook(config) {
| ^^^^^^^^^^^^^^^^ this function dynamically created a component/hook
5 | function useConfiguredState() {
6 | const [state, setState] = useState(0);
7 |
error.nested-hook-in-normal-function.ts:5:11
3 |
4 | function createCustomHook(config) {
> 5 | function useConfiguredState() {
| ^^^^^^^^^^^^^^^^^^ the component is created here
6 | const [state, setState] = useState(0);
7 |
8 | const increment = () => {
```

View File

@@ -1,22 +0,0 @@
// @validateNoDynamicallyCreatedComponentsOrHooks
import {useState} from 'react';
function createCustomHook(config) {
function useConfiguredState() {
const [state, setState] = useState(0);
const increment = () => {
setState(state + config.step);
};
return [state, increment];
}
return useConfiguredState;
}
export const FIXTURE_ENTRYPOINT = {
fn: createCustomHook,
isComponent: false,
params: [{step: 1}],
};

View File

@@ -33,7 +33,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `Ref.current`, but the source dependencies were []. Inferred dependency not present in source.

View File

@@ -33,7 +33,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `notaref.current`, but the source dependencies were []. Inferred dependency not present in source.

View File

@@ -44,7 +44,7 @@ component Component() {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.

View File

@@ -54,7 +54,7 @@ component Component(id) {
```
Found 3 errors:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.
@@ -76,7 +76,7 @@ React Compiler has skipped optimizing this component because the existing manual
18 | const setCurrentIndex = useCallback(
19 | (index: number) => {
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
@@ -88,7 +88,7 @@ React Compiler has skipped optimizing this component because the existing manual
30 |
31 | if (prevId !== id) {
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.

View File

@@ -52,7 +52,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.

View File

@@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Compilation Skipped: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
Memoization: React Compiler has skipped optimizing this component because the effect dependencies could not be memoized. Unmemoized effect dependencies can trigger an infinite loop or other unexpected behavior
error.validate-memoized-effect-deps-invalidated-dep-value.ts:11:2
9 | const y = [x];

View File

@@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 2 errors:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
@@ -40,7 +40,7 @@ error.validate-object-entries-mutation.ts:6:57
8 | value.updated = true;
9 | });
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.

View File

@@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 2 errors:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
@@ -40,7 +40,7 @@ error.validate-object-values-mutation.ts:6:55
8 | value.updated = true;
9 | });
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.

View File

@@ -58,7 +58,7 @@ export const FIXTURE_ENTRYPOINT = {
## 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":{"options":{"category":"PreserveManualMemo","severity":"CannotPreserveMemoization","reason":"Existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source.","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}}
{"kind":"CompileError","fnLoc":{"start":{"line":6,"column":0,"index":206},"end":{"line":16,"column":1,"index":433},"filename":"dynamic-gating-bailout-nopanic.ts"},"detail":{"options":{"category":"PreserveManualMemo","severity":"CannotPreserveMemoization","reason":"Compilation skipped because existing memoization could not be preserved","description":"React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `value`, but the source dependencies were []. Inferred dependency not present in source.","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":9,"column":31,"index":288},"end":{"line":9,"column":52,"index":309},"filename":"dynamic-gating-bailout-nopanic.ts"},"message":"Could not preserve existing manual memoization"}]}}}
```
### Eval output

View File

@@ -1,107 +0,0 @@
## Input
```javascript
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
function Component(props) {
const result = useMemo(() => {
return makeObject(props.value).value + 1;
}, [props.value]);
return <ValidateMemoization inputs={[props.value]} output={result} />;
}
function makeObject(value) {
console.log(value);
return {value};
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
sequentialRenders: [
{value: 42},
{value: 42},
{value: 3.14},
{value: 3.14},
{value: 42},
{value: 3.14},
{value: 42},
{value: 3.14},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import { useMemo } from "react";
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
function Component(props) {
const $ = _c(7);
let t0;
if ($[0] !== props.value) {
t0 = makeObject(props.value);
$[0] = props.value;
$[1] = t0;
} else {
t0 = $[1];
}
const result = t0.value + 1;
let t1;
if ($[2] !== props.value) {
t1 = [props.value];
$[2] = props.value;
$[3] = t1;
} else {
t1 = $[3];
}
let t2;
if ($[4] !== result || $[5] !== t1) {
t2 = <ValidateMemoization inputs={t1} output={result} />;
$[4] = result;
$[5] = t1;
$[6] = t2;
} else {
t2 = $[6];
}
return t2;
}
function makeObject(value) {
console.log(value);
return { value };
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ value: 42 }],
sequentialRenders: [
{ value: 42 },
{ value: 42 },
{ value: 3.14 },
{ value: 3.14 },
{ value: 42 },
{ value: 3.14 },
{ value: 42 },
{ value: 3.14 },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[42],"output":43}</div>
<div>{"inputs":[42],"output":43}</div>
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
<div>{"inputs":[42],"output":43}</div>
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
<div>{"inputs":[42],"output":43}</div>
<div>{"inputs":[3.14],"output":4.140000000000001}</div>
logs: [42,3.14,42,3.14,42,3.14]

View File

@@ -1,30 +0,0 @@
// @compilationMode:"infer" @enablePreserveExistingMemoizationGuarantees @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
function Component(props) {
const result = useMemo(() => {
return makeObject(props.value).value + 1;
}, [props.value]);
return <ValidateMemoization inputs={[props.value]} output={result} />;
}
function makeObject(value) {
console.log(value);
return {value};
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{value: 42}],
sequentialRenders: [
{value: 42},
{value: 42},
{value: 3.14},
{value: 3.14},
{value: 42},
{value: 3.14},
{value: 42},
{value: 3.14},
],
};

View File

@@ -49,12 +49,12 @@ import {
} from "shared-runtime";
function useFoo(t0) {
const $ = _c(4);
const $ = _c(3);
const { data } = t0;
let obj;
let myDiv = null;
if ($[0] !== data.cond || $[1] !== data.cond1) {
bb0: if (data.cond) {
bb0: if (data.cond) {
if ($[0] !== data.cond1) {
obj = makeObject_Primitives();
if (data.cond1) {
myDiv = <Stringify value={mutateAndReturn(obj)} />;
@@ -62,14 +62,13 @@ function useFoo(t0) {
}
mutate(obj);
$[0] = data.cond1;
$[1] = obj;
$[2] = myDiv;
} else {
obj = $[1];
myDiv = $[2];
}
$[0] = data.cond;
$[1] = data.cond1;
$[2] = obj;
$[3] = myDiv;
} else {
obj = $[2];
myDiv = $[3];
}
return myDiv;
}

View File

@@ -31,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 2 errors:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
@@ -44,7 +44,7 @@ error.invalid-useCallback-captures-reassigned-context.ts:11:37
13 | x = makeArray();
14 |
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.

View File

@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.

View File

@@ -31,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.

View File

@@ -42,7 +42,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.

View File

@@ -28,7 +28,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB`, but the source dependencies were [propA, propB.x.y]. Inferred less specific property than source.

View File

@@ -31,7 +31,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 2 errors:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propA`, but the source dependencies were [propA.a, propB.x.y]. Inferred less specific property than source.
@@ -60,7 +60,7 @@ error.hoist-useCallback-infer-conditional-value-block.ts:6:21
16 |
17 | export const FIXTURE_ENTRYPOINT = {
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB`, but the source dependencies were [propA.a, propB.x.y]. Inferred less specific property than source.

View File

@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 2 errors:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This dependency may be mutated later, which could cause the value to change unexpectedly.
@@ -45,7 +45,7 @@ error.invalid-useCallback-captures-reassigned-context.ts:12:37
14 | x = makeArray();
15 |
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.

View File

@@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `ref`, but the source dependencies were []. Inferred dependency not present in source.

View File

@@ -30,7 +30,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. This value was memoized in source but not in compilation output.

View File

@@ -21,7 +21,7 @@ function useHook(x) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `aliasedX`, but the source dependencies were [x, aliasedProp]. Inferred different dependency than source.

View File

@@ -27,7 +27,7 @@ export const FIXTURE_ENTRYPOINT = {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB?.x.y`, but the source dependencies were [propA, propB.x.y]. Inferred different dependency than source.

View File

@@ -26,7 +26,7 @@ function Component({propA, propB}) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB`, but the source dependencies were [propA?.a, propB.x.y]. Inferred less specific property than source.

View File

@@ -19,7 +19,7 @@ function Component({propA}) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propA`, but the source dependencies were [propA.x]. Inferred less specific property than source.

View File

@@ -21,7 +21,7 @@ function useHook(x) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `x`, but the source dependencies were [aliasedX, aliasedProp]. Inferred different dependency than source.

View File

@@ -26,7 +26,7 @@ function Component({propA, propB}) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB`, but the source dependencies were [propA?.a, propB.x.y]. Inferred less specific property than source.

View File

@@ -26,7 +26,7 @@ function Component({propA, propB}) {
```
Found 2 errors:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propA`, but the source dependencies were [propA.a, propB.x.y]. Inferred less specific property than source.
@@ -54,7 +54,7 @@ error.useMemo-infer-less-specific-conditional-value-block.ts:6:17
15 | }
16 |
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propB`, but the source dependencies were [propA.a, propB.x.y]. Inferred less specific property than source.

View File

@@ -21,7 +21,7 @@ function Component({propA}) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propA`, but the source dependencies were [propA.x]. Inferred less specific property than source.

View File

@@ -19,7 +19,7 @@ function Component({propA}) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `propA`, but the source dependencies were [propA.x]. Inferred less specific property than source.

View File

@@ -32,7 +32,7 @@ function useFoo(input1) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `input1`, but the source dependencies were [y]. Inferred different dependency than source.

View File

@@ -26,7 +26,7 @@ function Component(props) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source.

View File

@@ -26,7 +26,7 @@ function Component(props) {
```
Found 1 error:
Compilation Skipped: Existing memoization could not be preserved
Memoization: Compilation skipped because existing memoization could not be preserved
React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source.

View File

@@ -34,16 +34,17 @@ import { c as _c } from "react/compiler-runtime"; // @enablePropagateDepsInHIR
import { useMemo } from "react";
function Component(props) {
const $ = _c(5);
const $ = _c(6);
let t0;
if (
$[0] !== props.a ||
$[1] !== props.b ||
$[2] !== props.cond ||
$[3] !== props.cond2
) {
bb0: {
const y = [];
bb0: {
let y;
if (
$[0] !== props.a ||
$[1] !== props.b ||
$[2] !== props.cond ||
$[3] !== props.cond2
) {
y = [];
if (props.cond) {
y.push(props.a);
}
@@ -53,15 +54,17 @@ function Component(props) {
}
y.push(props.b);
t0 = y;
$[0] = props.a;
$[1] = props.b;
$[2] = props.cond;
$[3] = props.cond2;
$[4] = y;
$[5] = t0;
} else {
y = $[4];
t0 = $[5];
}
$[0] = props.a;
$[1] = props.b;
$[2] = props.cond;
$[3] = props.cond2;
$[4] = t0;
} else {
t0 = $[4];
t0 = y;
}
const x = t0;
return x;

View File

@@ -1,118 +0,0 @@
## Input
```javascript
import {useMemo} from 'react';
import {
makeObject_Primitives,
mutate,
Stringify,
ValidateMemoization,
} from 'shared-runtime';
function Component({cond}) {
const memoized = useMemo(() => {
const value = makeObject_Primitives();
if (cond) {
return value;
} else {
mutate(value);
return value;
}
}, [cond]);
return <ValidateMemoization inputs={[cond]} output={memoized} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{cond: false}],
sequentialRenders: [
{cond: false},
{cond: false},
{cond: true},
{cond: true},
{cond: false},
{cond: true},
{cond: false},
{cond: true},
],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
import {
makeObject_Primitives,
mutate,
Stringify,
ValidateMemoization,
} from "shared-runtime";
function Component(t0) {
const $ = _c(7);
const { cond } = t0;
let t1;
if ($[0] !== cond) {
const value = makeObject_Primitives();
if (cond) {
t1 = value;
} else {
mutate(value);
t1 = value;
}
$[0] = cond;
$[1] = t1;
} else {
t1 = $[1];
}
const memoized = t1;
let t2;
if ($[2] !== cond) {
t2 = [cond];
$[2] = cond;
$[3] = t2;
} else {
t2 = $[3];
}
let t3;
if ($[4] !== memoized || $[5] !== t2) {
t3 = <ValidateMemoization inputs={t2} output={memoized} />;
$[4] = memoized;
$[5] = t2;
$[6] = t3;
} else {
t3 = $[6];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ cond: false }],
sequentialRenders: [
{ cond: false },
{ cond: false },
{ cond: true },
{ cond: true },
{ cond: false },
{ cond: true },
{ cond: false },
{ cond: true },
],
};
```
### Eval output
(kind: ok) <div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
<div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>
<div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>
<div>{"inputs":[false],"output":{"a":0,"b":"value1","c":true,"wat0":"joe"}}</div>
<div>{"inputs":[true],"output":{"a":0,"b":"value1","c":true}}</div>

View File

@@ -1,35 +0,0 @@
import {useMemo} from 'react';
import {
makeObject_Primitives,
mutate,
Stringify,
ValidateMemoization,
} from 'shared-runtime';
function Component({cond}) {
const memoized = useMemo(() => {
const value = makeObject_Primitives();
if (cond) {
return value;
} else {
mutate(value);
return value;
}
}, [cond]);
return <ValidateMemoization inputs={[cond]} output={memoized} />;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{cond: false}],
sequentialRenders: [
{cond: false},
{cond: false},
{cond: true},
{cond: true},
{cond: false},
{cond: true},
{cond: false},
{cond: true},
],
};

View File

@@ -33,16 +33,17 @@ import { c as _c } from "react/compiler-runtime";
import { useMemo } from "react";
function Component(props) {
const $ = _c(5);
const $ = _c(6);
let t0;
if (
$[0] !== props.a ||
$[1] !== props.b ||
$[2] !== props.cond ||
$[3] !== props.cond2
) {
bb0: {
const y = [];
bb0: {
let y;
if (
$[0] !== props.a ||
$[1] !== props.b ||
$[2] !== props.cond ||
$[3] !== props.cond2
) {
y = [];
if (props.cond) {
y.push(props.a);
}
@@ -52,15 +53,17 @@ function Component(props) {
}
y.push(props.b);
t0 = y;
$[0] = props.a;
$[1] = props.b;
$[2] = props.cond;
$[3] = props.cond2;
$[4] = y;
$[5] = t0;
} else {
y = $[4];
t0 = $[5];
}
$[0] = props.a;
$[1] = props.b;
$[2] = props.cond;
$[3] = props.cond2;
$[4] = t0;
} else {
t0 = $[4];
t0 = y;
}
const x = t0;
return x;

View File

@@ -48,10 +48,7 @@ export {
printReactiveFunction,
printReactiveFunctionWithOutlined,
} from './ReactiveScopes';
export {
parseConfigPragmaForTests,
parseConfigPragmaAsString,
} from './Utils/TestUtils';
export {parseConfigPragmaForTests} from './Utils/TestUtils';
declare global {
let __DEV__: boolean | null | undefined;
}

View File

@@ -198,51 +198,6 @@ export function makeSharedRuntimeTypeProvider({
},
},
};
} else if (moduleName === 'ReactCompilerKnownIncompatibleTest') {
/**
* Fake module used for testing validation of known incompatible
* API validation
*/
return {
kind: 'object',
properties: {
useKnownIncompatible: {
kind: 'hook',
positionalParams: [],
restParam: EffectEnum.Read,
returnType: {kind: 'type', name: 'Any'},
knownIncompatible: `useKnownIncompatible is known to be incompatible`,
},
useKnownIncompatibleIndirect: {
kind: 'hook',
positionalParams: [],
restParam: EffectEnum.Read,
returnType: {
kind: 'object',
properties: {
incompatible: {
kind: 'function',
positionalParams: [],
restParam: EffectEnum.Read,
calleeEffect: EffectEnum.Read,
returnType: {kind: 'type', name: 'Any'},
returnValueKind: ValueKindEnum.Mutable,
knownIncompatible: `useKnownIncompatibleIndirect returns an incompatible() function that is known incompatible`,
},
},
},
},
knownIncompatible: {
kind: 'function',
positionalParams: [],
restParam: EffectEnum.Read,
calleeEffect: EffectEnum.Read,
returnType: {kind: 'type', name: 'Any'},
returnValueKind: ValueKindEnum.Mutable,
knownIncompatible: `useKnownIncompatible is known to be incompatible`,
},
},
};
} else if (moduleName === 'ReactCompilerTest') {
/**
* Fake module used for testing validation that type providers return hook

View File

@@ -3,7 +3,7 @@ import Fixture from '../../Fixture';
const React = window.React;
const {Fragment, useRef} = React;
const {Fragment, useEffect, useRef, useState} = React;
export default function FocusCase() {
const fragmentRef = useRef(null);

View File

@@ -2,7 +2,7 @@ import TestCase from '../../TestCase';
import Fixture from '../../Fixture';
const React = window.React;
const {Fragment, useRef, useState} = React;
const {Fragment, useEffect, useRef, useState} = React;
export default function GetClientRectsCase() {
const fragmentRef = useRef(null);

View File

@@ -1,184 +0,0 @@
import TestCase from '../../TestCase';
import Fixture from '../../Fixture';
import ScrollIntoViewCaseComplex from './ScrollIntoViewCaseComplex';
import ScrollIntoViewCaseSimple from './ScrollIntoViewCaseSimple';
import ScrollIntoViewTargetElement from './ScrollIntoViewTargetElement';
const React = window.React;
const {Fragment, useRef, useState, useEffect} = React;
const ReactDOM = window.ReactDOM;
function Controls({
alignToTop,
setAlignToTop,
scrollVertical,
exampleType,
setExampleType,
}) {
return (
<div>
<label>
Example Type:
<select
value={exampleType}
onChange={e => setExampleType(e.target.value)}>
<option value="simple">Simple</option>
<option value="multiple">Multiple Scroll Containers</option>
<option value="horizontal">Horizontal</option>
<option value="empty">Empty Fragment</option>
</select>
</label>
<div>
<label>
Align to Top:
<input
type="checkbox"
checked={alignToTop}
onChange={e => setAlignToTop(e.target.checked)}
/>
</label>
</div>
<div>
<button onClick={scrollVertical}>scrollIntoView()</button>
</div>
</div>
);
}
export default function ScrollIntoViewCase() {
const [exampleType, setExampleType] = useState('simple');
const [alignToTop, setAlignToTop] = useState(true);
const [caseInViewport, setCaseInViewport] = useState(false);
const fragmentRef = useRef(null);
const testCaseRef = useRef(null);
const noChildRef = useRef(null);
const scrollContainerRef = useRef(null);
const scrollVertical = () => {
fragmentRef.current.experimental_scrollIntoView(alignToTop);
};
const scrollVerticalNoChildren = () => {
noChildRef.current.experimental_scrollIntoView(alignToTop);
};
useEffect(() => {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
setCaseInViewport(true);
} else {
setCaseInViewport(false);
}
});
});
testCaseRef.current.observeUsing(observer);
const lastRef = testCaseRef.current;
return () => {
lastRef.unobserveUsing(observer);
observer.disconnect();
};
});
return (
<Fragment ref={testCaseRef}>
<TestCase title="ScrollIntoView">
<TestCase.Steps>
<li>Toggle alignToTop and click the buttons to scroll</li>
</TestCase.Steps>
<TestCase.ExpectedResult>
<p>When the Fragment has children:</p>
<p>
In order to handle the case where children are split between
multiple scroll containers, we call scrollIntoView on each child in
reverse order.
</p>
<p>When the Fragment does not have children:</p>
<p>
The Fragment still represents a virtual space. We can scroll to the
nearest edge by selecting the host sibling before if
alignToTop=false, or after if alignToTop=true|undefined. We'll fall
back to the other sibling or parent in the case that the preferred
sibling target doesn't exist.
</p>
</TestCase.ExpectedResult>
<Fixture>
<Fixture.Controls>
<Controls
alignToTop={alignToTop}
setAlignToTop={setAlignToTop}
scrollVertical={scrollVertical}
exampleType={exampleType}
setExampleType={setExampleType}
/>
</Fixture.Controls>
{exampleType === 'simple' && (
<Fragment ref={fragmentRef}>
<ScrollIntoViewCaseSimple />
</Fragment>
)}
{exampleType === 'horizontal' && (
<div
style={{
display: 'flex',
overflowX: 'auto',
flexDirection: 'row',
border: '1px solid #ccc',
padding: '1rem 10rem',
marginBottom: '1rem',
width: '100%',
whiteSpace: 'nowrap',
justifyContent: 'space-between',
}}>
<Fragment ref={fragmentRef}>
<ScrollIntoViewCaseSimple />
</Fragment>
</div>
)}
{exampleType === 'multiple' && (
<Fragment>
<div
style={{
height: '50vh',
overflowY: 'auto',
border: '1px solid black',
marginBottom: '1rem',
}}
ref={scrollContainerRef}
/>
<Fragment ref={fragmentRef}>
<ScrollIntoViewCaseComplex
caseInViewport={caseInViewport}
scrollContainerRef={scrollContainerRef}
/>
</Fragment>
</Fragment>
)}
{exampleType === 'empty' && (
<Fragment>
<ScrollIntoViewTargetElement
color="lightyellow"
id="ABOVE EMPTY FRAGMENT"
/>
<Fragment ref={fragmentRef}></Fragment>
<ScrollIntoViewTargetElement
color="lightblue"
id="BELOW EMPTY FRAGMENT"
/>
</Fragment>
)}
<Fixture.Controls>
<Controls
alignToTop={alignToTop}
setAlignToTop={setAlignToTop}
scrollVertical={scrollVertical}
exampleType={exampleType}
setExampleType={setExampleType}
/>
</Fixture.Controls>
</Fixture>
</TestCase>
</Fragment>
);
}

View File

@@ -1,50 +0,0 @@
import ScrollIntoViewTargetElement from './ScrollIntoViewTargetElement';
const React = window.React;
const {Fragment, useRef, useState, useEffect} = React;
const ReactDOM = window.ReactDOM;
export default function ScrollIntoViewCaseComplex({
caseInViewport,
scrollContainerRef,
}) {
const [didMount, setDidMount] = useState(false);
// Hack to portal child into the scroll container
// after the first render. This is to simulate a case where
// an item is portaled into another scroll container.
useEffect(() => {
if (!didMount) {
setDidMount(true);
}
}, []);
return (
<Fragment>
{caseInViewport && (
<div
style={{position: 'fixed', top: 0, backgroundColor: 'red'}}
id="header">
Fixed header
</div>
)}
{didMount &&
ReactDOM.createPortal(
<ScrollIntoViewTargetElement color="red" id="FROM_PORTAL" />,
scrollContainerRef.current
)}
<ScrollIntoViewTargetElement color="lightgreen" id="A" />
<ScrollIntoViewTargetElement color="lightcoral" id="B" />
<ScrollIntoViewTargetElement color="lightblue" id="C" />
{caseInViewport && (
<div
style={{
position: 'fixed',
bottom: 0,
backgroundColor: 'purple',
}}
id="footer">
Fixed footer
</div>
)}
</Fragment>
);
}

View File

@@ -1,14 +0,0 @@
import ScrollIntoViewTargetElement from './ScrollIntoViewTargetElement';
const React = window.React;
const {Fragment} = React;
export default function ScrollIntoViewCaseSimple() {
return (
<Fragment>
<ScrollIntoViewTargetElement color="lightyellow" id="SCROLLABLE-1" />
<ScrollIntoViewTargetElement color="lightpink" id="SCROLLABLE-2" />
<ScrollIntoViewTargetElement color="lightcyan" id="SCROLLABLE-3" />
</Fragment>
);
}

View File

@@ -1,18 +0,0 @@
const React = window.React;
export default function ScrollIntoViewTargetElement({color, id, top}) {
return (
<div
id={id}
style={{
height: 500,
minWidth: 300,
backgroundColor: color,
marginTop: top ? '50vh' : 0,
marginBottom: 100,
flexShrink: 0,
}}>
{id}
</div>
);
}

View File

@@ -5,7 +5,6 @@ import IntersectionObserverCase from './IntersectionObserverCase';
import ResizeObserverCase from './ResizeObserverCase';
import FocusCase from './FocusCase';
import GetClientRectsCase from './GetClientRectsCase';
import ScrollIntoViewCase from './ScrollIntoViewCase';
const React = window.React;
@@ -18,7 +17,6 @@ export default function FragmentRefsPage() {
<ResizeObserverCase />
<FocusCase />
<GetClientRectsCase />
<ScrollIntoViewCase />
</FixtureSet>
);
}

View File

@@ -2,23 +2,14 @@ import './polyfills';
import loadReact, {isLocal} from './react-loader';
if (isLocal()) {
Promise.all([
import('react'),
import('react-dom'),
import('react-dom/client'),
])
.then(([React, ReactDOM, ReactDOMClient]) => {
if (
React === undefined ||
ReactDOM === undefined ||
ReactDOMClient === undefined
) {
Promise.all([import('react'), import('react-dom/client')])
.then(([React, ReactDOMClient]) => {
if (React === undefined || ReactDOMClient === undefined) {
throw new Error(
'Unable to load React. Build experimental and then run `yarn dev` again'
);
}
window.React = React;
window.ReactDOM = ReactDOM;
window.ReactDOMClient = ReactDOMClient;
})
.then(() => import('./components/App'))

View File

@@ -74,15 +74,15 @@
"eslint-plugin-react-internal": "link:./scripts/eslint-rules",
"fbjs-scripts": "^3.0.1",
"filesize": "^6.0.1",
"flow-bin": "^0.279.0",
"flow-remove-types": "^2.279.0",
"flow-bin": "^0.265",
"flow-remove-types": "^2.265",
"flow-typed": "^4.1.1",
"glob": "^7.1.6",
"glob-stream": "^6.1.0",
"google-closure-compiler": "^20230206.0.0",
"gzip-size": "^5.1.1",
"hermes-eslint": "^0.32.0",
"hermes-parser": "^0.32.0",
"hermes-eslint": "^0.25.1",
"hermes-parser": "^0.25.1",
"jest": "^29.4.2",
"jest-cli": "^29.4.2",
"jest-diff": "^29.4.2",

View File

@@ -22,22 +22,15 @@ yarn add eslint-plugin-react-hooks --dev
#### >= 6.0.0
For users of 6.0 and beyond, add the `recommended` config.
For users of 6.0 and beyond, simply add the `recommended` config.
```js
// eslint.config.js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
import * as reactHooks from 'eslint-plugin-react-hooks';
export default defineConfig([
{
files: ["src/**/*.{js,jsx,ts,tsx}"],
plugins: {
'react-hooks': reactHooks,
},
extends: ['react-hooks/recommended'],
},
]);
export default [
// ...
reactHooks.configs.recommended,
];
```
#### 5.2.0
@@ -45,18 +38,12 @@ export default defineConfig([
For users of 5.2.0 (the first version with flat config support), add the `recommended-latest` config.
```js
import reactHooks from 'eslint-plugin-react-hooks';
import { defineConfig } from 'eslint/config';
import * as reactHooks from 'eslint-plugin-react-hooks';
export default defineConfig([
{
files: ["src/**/*.{js,jsx,ts,tsx}"],
plugins: {
'react-hooks': reactHooks,
},
extends: ['react-hooks/recommended-latest'],
},
]);
export default [
// ...
reactHooks.configs['recommended-latest'],
];
```
### Legacy Config (.eslintrc)

View File

@@ -41,7 +41,7 @@
"dependencies": {
"@babel/core": "^7.24.4",
"@babel/parser": "^7.24.4",
"@babel/plugin-proposal-private-methods": "^7.18.6",
"@babel/plugin-transform-private-methods": "^7.24.4",
"hermes-parser": "^0.25.1",
"zod": "^3.22.4",
"zod-validation-error": "^3.0.3"

View File

@@ -341,11 +341,6 @@ export type FindSourceMapURLCallback = (
export type DebugChannelCallback = (message: string) => void;
export type DebugChannel = {
hasReadable: boolean,
callback: DebugChannelCallback | null,
};
type Response = {
_bundlerConfig: ServerConsumerModuleMap,
_serverReferenceConfig: null | ServerManifest,
@@ -367,7 +362,7 @@ type Response = {
_debugRootStack?: null | Error, // DEV-only
_debugRootTask?: null | ConsoleTask, // DEV-only
_debugFindSourceMapURL?: void | FindSourceMapURLCallback, // DEV-only
_debugChannel?: void | DebugChannel, // DEV-only
_debugChannel?: void | DebugChannelCallback, // DEV-only
_blockedConsole?: null | SomeChunk<ConsoleEntry>, // DEV-only
_replayConsole: boolean, // DEV-only
_rootEnvironmentName: string, // DEV-only, the requested environment name.
@@ -409,16 +404,16 @@ function getWeakResponse(response: Response): WeakResponse {
}
}
function closeDebugChannel(debugChannel: DebugChannel): void {
if (debugChannel.callback) {
debugChannel.callback('');
}
function cleanupDebugChannel(debugChannel: DebugChannelCallback): void {
// When a Response gets GC:ed because nobody is referring to any of the objects that lazily
// loads from the Response anymore, then we can close the debug channel.
debugChannel('');
}
// If FinalizationRegistry doesn't exist, we cannot use the debugChannel.
const debugChannelRegistry =
__DEV__ && typeof FinalizationRegistry === 'function'
? new FinalizationRegistry(closeDebugChannel)
? new FinalizationRegistry(cleanupDebugChannel)
: null;
function readChunk<T>(chunk: SomeChunk<T>): T {
@@ -1012,7 +1007,7 @@ export function reportGlobalError(
if (debugChannel !== undefined) {
// If we don't have any more ways of reading data, we don't have to send any
// more neither. So we close the writable side.
closeDebugChannel(debugChannel);
debugChannel('');
response._debugChannel = undefined;
}
}
@@ -1499,8 +1494,8 @@ function waitForReference<T>(
): T {
if (
__DEV__ &&
(response._debugChannel === undefined ||
!response._debugChannel.hasReadable)
// TODO: This should check for the existence of the "readable" side, not the "writable".
response._debugChannel === undefined
) {
if (
referencedChunk.status === PENDING &&
@@ -2267,16 +2262,15 @@ function parseModelString(
case 'Y': {
if (__DEV__) {
if (value.length > 2) {
const debugChannelCallback =
response._debugChannel && response._debugChannel.callback;
if (debugChannelCallback) {
const debugChannel = response._debugChannel;
if (debugChannel) {
if (value[2] === '@') {
// This is a deferred Promise.
const ref = value.slice(3); // We assume this doesn't have a path just id.
const id = parseInt(ref, 16);
if (!response._chunks.has(id)) {
// We haven't seen this id before. Query the server to start sending it.
debugChannelCallback('P:' + ref);
debugChannel('P:' + ref);
}
// Start waiting. This now creates a pending chunk if it doesn't already exist.
// This is the actual Promise we're waiting for.
@@ -2286,7 +2280,7 @@ function parseModelString(
const id = parseInt(ref, 16);
if (!response._chunks.has(id)) {
// We haven't seen this id before. Query the server to start sending it.
debugChannelCallback('Q:' + ref);
debugChannel('Q:' + ref);
}
// Start waiting. This now creates a pending chunk if it doesn't already exist.
const chunk = getChunk(response, id);
@@ -2364,7 +2358,7 @@ function ResponseInstance(
findSourceMapURL: void | FindSourceMapURLCallback, // DEV-only
replayConsole: boolean, // DEV-only
environmentName: void | string, // DEV-only
debugChannel: void | DebugChannel, // DEV-only
debugChannel: void | DebugChannelCallback, // DEV-only
) {
const chunks: Map<number, SomeChunk<any>> = new Map();
this._bundlerConfig = bundlerConfig;
@@ -2426,14 +2420,10 @@ function ResponseInstance(
this._rootEnvironmentName = rootEnv;
if (debugChannel) {
if (debugChannelRegistry === null) {
// We can't safely clean things up later, so we immediately close the
// debug channel.
closeDebugChannel(debugChannel);
// We can't safely clean things up later, so we immediately close the debug channel.
debugChannel('');
this._debugChannel = undefined;
} else {
// When a Response gets GC:ed because nobody is referring to any of the
// objects that lazily load from the Response anymore, then we can close
// the debug channel.
debugChannelRegistry.register(this, debugChannel);
}
}
@@ -2461,7 +2451,7 @@ export function createResponse(
findSourceMapURL: void | FindSourceMapURLCallback, // DEV-only
replayConsole: boolean, // DEV-only
environmentName: void | string, // DEV-only
debugChannel: void | DebugChannel, // DEV-only
debugChannel: void | DebugChannelCallback, // DEV-only
): WeakResponse {
return getWeakResponse(
// $FlowFixMe[invalid-constructor]: the shapes are exact here but Flow doesn't like constructors
@@ -3555,8 +3545,8 @@ function resolveDebugModel(
if (
__DEV__ &&
((debugChunk: any): SomeChunk<any>).status === BLOCKED &&
(response._debugChannel === undefined ||
!response._debugChannel.hasReadable)
// TODO: This should check for the existence of the "readable" side, not the "writable".
response._debugChannel === undefined
) {
if (json[0] === '"' && json[1] === '$') {
const path = json.slice(2, json.length - 1).split(':');

View File

@@ -153,7 +153,7 @@ export function logComponentAborted(
const entryName =
isPrimaryEnv || env === undefined ? name : name + ' [' + env + ']';
if (__DEV__) {
const properties: Array<[string, string]> = [
const properties = [
[
'Aborted',
'The stream was aborted before this Component finished rendering.',
@@ -215,7 +215,7 @@ export function logComponentErrored(
String(error.message)
: // eslint-disable-next-line react-internal/safe-string-coercion
String(error);
const properties: Array<[string, string]> = [['Error', message]];
const properties = [['Error', message]];
if (componentInfo.key != null) {
addValueToProperties('key', componentInfo.key, properties, 0, '');
}

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