Compare commits

..

9 Commits

Author SHA1 Message Date
Joe Savona
94fe97db2f [compiler] Use new diagnostics for core inference errors
Uses the new diagnostic type for errors created during mutation/aliasing inference, such as errors for mutating immutable values like props or state, reassigning globals, etc.
2025-07-24 15:41:42 -07:00
Joseph Savona
48bc166428 [compiler] Update diagnostics for ValidatePreservedManualMemoization (#33759)
Uses the new diagnostic infrastructure for this validation, which lets
us provide a more targeted message on the text that we highlight (eg
"This dependency may be mutated later") separately from the overall
error message.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33759).
* #33981
* #33777
* #33767
* #33765
* #33760
* __->__ #33759
* #33758
2025-07-24 15:39:53 -07:00
Joseph Savona
72848027a5 [compiler] Improve more error messages (#33758)
This PR uses the new diagnostic type for most of the error messages
produced in our explicit validation passes (`Validation/` directory).
One of the validations produced multiple errors as a hack to showing
multiple related locations, which we can now consolidate into a single
diagnostic.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33758).
* #33981
* #33777
* #33767
* #33765
* #33760
* #33759
* __->__ #33758
2025-07-24 15:39:42 -07:00
Joseph Savona
707e321f8f [compiler][wip] Improve diagnostic infra (#33751)
Work in progress, i'm experimenting with revamping our diagnostic infra.
Starting with a better format for representing errors, with an ability
to point ot multiple locations, along with better printing of errors. Of
course, Babel still controls the printing in the majority case so this
still needs more work.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33751).
* #33981
* #33777
* #33767
* #33765
* #33760
* #33759
* #33758
* __->__ #33751
* #33752
* #33753
2025-07-24 15:37:06 -07:00
Joseph Savona
0d39496eab [compiler] Enable additional lints by default (#33752)
Enable more validations to help catch bad patterns, but only in the
linter. These rules are already enabled by default in the compiler _if_
violations could produce unsafe output.

---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33752).
* #33981
* #33777
* #33767
* #33765
* #33760
* #33759
* #33758
* #33751
* __->__ #33752
* #33753
2025-07-24 15:36:54 -07:00
Joseph Savona
6f4294af9b [compiler] Validate against setState in all effect types (#33753)
---
[//]: # (BEGIN SAPLING FOOTER)
Stack created with [Sapling](https://sapling-scm.com). Best reviewed
with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33753).
* #33981
* #33777
* #33767
* #33765
* #33760
* #33759
* #33758
* #33751
* #33752
* __->__ #33753
2025-07-24 15:36:40 -07:00
Joseph Savona
448f781a52 [compiler] Fix for false positive mutation of destructured spread object (#33786)
When destructuring, spread creates a new mutable object that _captures_
part of the original rvalue. This new value is safe to modify.

When making this change I realized that we weren't inferring array
pattern spread as creating an array (in type inference) so I also added
that here.
2025-07-24 15:16:28 -07:00
Sebastian Markbåge
5020d48d28 [DevTools] Feature detect createSidebarPane (#33988)
Same as #33987 but for the sidebar pane creation.
2025-07-24 17:42:06 -04:00
Sebastian Markbåge
3082604bdc [DevTools] Feature detect sources panel (#33987)
I broke Firefox DevTools extension in #33968.

It turns out the Firefox has a placeholder object for the sources panel
which is empty. We need to detect the actual event handler.
2025-07-24 17:38:12 -04:00
342 changed files with 4106 additions and 897 deletions

View File

@@ -12,6 +12,7 @@ import {
pipelineUsesReanimatedPlugin,
} from '../Entrypoint/Reanimated';
import validateNoUntransformedReferences from '../Entrypoint/ValidateNoUntransformedReferences';
import {CompilerError} from '..';
const ENABLE_REACT_COMPILER_TIMINGS =
process.env['ENABLE_REACT_COMPILER_TIMINGS'] === '1';
@@ -34,51 +35,58 @@ export default function BabelPluginReactCompiler(
*/
Program: {
enter(prog, pass): void {
const filename = pass.filename ?? 'unknown';
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:start`, {
detail: 'BabelPlugin:Program:start',
});
}
let opts = parsePluginOptions(pass.opts);
const isDev =
(typeof __DEV__ !== 'undefined' && __DEV__ === true) ||
process.env['NODE_ENV'] === 'development';
if (
opts.enableReanimatedCheck === true &&
pipelineUsesReanimatedPlugin(pass.file.opts.plugins)
) {
opts = injectReanimatedFlag(opts);
}
if (
opts.environment.enableResetCacheOnSourceFileChanges !== false &&
isDev
) {
opts = {
...opts,
environment: {
...opts.environment,
enableResetCacheOnSourceFileChanges: true,
},
};
}
const result = compileProgram(prog, {
opts,
filename: pass.filename ?? null,
comments: pass.file.ast.comments ?? [],
code: pass.file.code,
});
validateNoUntransformedReferences(
prog,
pass.filename ?? null,
opts.logger,
opts.environment,
result,
);
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:end`, {
detail: 'BabelPlugin:Program:end',
try {
const filename = pass.filename ?? 'unknown';
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:start`, {
detail: 'BabelPlugin:Program:start',
});
}
let opts = parsePluginOptions(pass.opts);
const isDev =
(typeof __DEV__ !== 'undefined' && __DEV__ === true) ||
process.env['NODE_ENV'] === 'development';
if (
opts.enableReanimatedCheck === true &&
pipelineUsesReanimatedPlugin(pass.file.opts.plugins)
) {
opts = injectReanimatedFlag(opts);
}
if (
opts.environment.enableResetCacheOnSourceFileChanges !== false &&
isDev
) {
opts = {
...opts,
environment: {
...opts.environment,
enableResetCacheOnSourceFileChanges: true,
},
};
}
const result = compileProgram(prog, {
opts,
filename: pass.filename ?? null,
comments: pass.file.ast.comments ?? [],
code: pass.file.code,
});
validateNoUntransformedReferences(
prog,
pass.filename ?? null,
opts.logger,
opts.environment,
result,
);
if (ENABLE_REACT_COMPILER_TIMINGS === true) {
performance.mark(`${filename}:end`, {
detail: 'BabelPlugin:Program:end',
});
}
} catch (e) {
if (e instanceof CompilerError) {
throw new Error(e.printErrorMessage(pass.file.code));
}
throw e;
}
},
exit(_, pass): void {

View File

@@ -5,6 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {codeFrameColumns} from '@babel/code-frame';
import type {SourceLocation} from './HIR';
import {Err, Ok, Result} from './Utils/Result';
import {assertExhaustive} from './Utils/utils';
@@ -44,6 +45,24 @@ export enum ErrorSeverity {
Invariant = 'Invariant',
}
export type CompilerDiagnosticOptions = {
severity: ErrorSeverity;
category: string;
description: string;
details: Array<CompilerDiagnosticDetail>;
suggestions?: Array<CompilerSuggestion> | null | undefined;
};
export type CompilerDiagnosticDetail =
/**
* A/the source of the error
*/
{
kind: 'error';
loc: SourceLocation | null;
message: string;
};
export enum CompilerSuggestionOperation {
InsertBefore,
InsertAfter,
@@ -74,6 +93,105 @@ export type CompilerErrorDetailOptions = {
suggestions?: Array<CompilerSuggestion> | null | undefined;
};
export class CompilerDiagnostic {
options: CompilerDiagnosticOptions;
constructor(options: CompilerDiagnosticOptions) {
this.options = options;
}
static create(
options: Omit<CompilerDiagnosticOptions, 'details'>,
): CompilerDiagnostic {
return new CompilerDiagnostic({...options, details: []});
}
get category(): CompilerDiagnosticOptions['category'] {
return this.options.category;
}
get description(): CompilerDiagnosticOptions['description'] {
return this.options.description;
}
get severity(): CompilerDiagnosticOptions['severity'] {
return this.options.severity;
}
get suggestions(): CompilerDiagnosticOptions['suggestions'] {
return this.options.suggestions;
}
withDetail(detail: CompilerDiagnosticDetail): CompilerDiagnostic {
this.options.details.push(detail);
return this;
}
primaryLocation(): SourceLocation | null {
return this.options.details.filter(d => d.kind === 'error')[0]?.loc ?? null;
}
printErrorMessage(source: string): string {
const buffer = [
printErrorSummary(this.severity, this.category),
'\n\n',
this.description,
];
for (const detail of this.options.details) {
switch (detail.kind) {
case 'error': {
const loc = detail.loc;
if (loc == null || typeof loc === 'symbol') {
continue;
}
let codeFrame: string;
try {
codeFrame = codeFrameColumns(
source,
{
start: {
line: loc.start.line,
column: loc.start.column + 1,
},
end: {
line: loc.end.line,
column: loc.end.column + 1,
},
},
{
message: detail.message,
},
);
} catch (e) {
codeFrame = detail.message;
}
buffer.push(
`\n\n${loc.filename}:${loc.start.line}:${loc.start.column}\n`,
);
buffer.push(codeFrame);
break;
}
default: {
assertExhaustive(
detail.kind,
`Unexpected detail kind ${(detail as any).kind}`,
);
}
}
}
return buffer.join('');
}
toString(): string {
const buffer = [printErrorSummary(this.severity, this.category)];
if (this.description != null) {
buffer.push(`. ${this.description}.`);
}
const loc = this.primaryLocation();
if (loc != null && typeof loc !== 'symbol') {
buffer.push(` (${loc.start.line}:${loc.start.column})`);
}
return buffer.join('');
}
}
/*
* Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then
* aggregated into a single {@link CompilerError} later.
@@ -101,24 +219,62 @@ export class CompilerErrorDetail {
return this.options.suggestions;
}
printErrorMessage(): string {
const buffer = [`${this.severity}: ${this.reason}`];
primaryLocation(): SourceLocation | null {
return this.loc;
}
printErrorMessage(source: string): string {
const buffer = [printErrorSummary(this.severity, this.reason)];
if (this.description != null) {
buffer.push(`. ${this.description}`);
buffer.push(`\n\n${this.description}.`);
}
if (this.loc != null && typeof this.loc !== 'symbol') {
buffer.push(` (${this.loc.start.line}:${this.loc.end.line})`);
const loc = this.loc;
if (loc != null && typeof loc !== 'symbol') {
let codeFrame: string;
try {
codeFrame = codeFrameColumns(
source,
{
start: {
line: loc.start.line,
column: loc.start.column + 1,
},
end: {
line: loc.end.line,
column: loc.end.column + 1,
},
},
{
message: this.reason,
},
);
} catch (e) {
codeFrame = '';
}
buffer.push(
`\n\n${loc.filename}:${loc.start.line}:${loc.start.column}\n`,
);
buffer.push(codeFrame);
buffer.push('\n\n');
}
return buffer.join('');
}
toString(): string {
return this.printErrorMessage();
const buffer = [printErrorSummary(this.severity, this.reason)];
if (this.description != null) {
buffer.push(`. ${this.description}.`);
}
const loc = this.loc;
if (loc != null && typeof loc !== 'symbol') {
buffer.push(` (${loc.start.line}:${loc.start.column})`);
}
return buffer.join('');
}
}
export class CompilerError extends Error {
details: Array<CompilerErrorDetail> = [];
details: Array<CompilerErrorDetail | CompilerDiagnostic> = [];
static invariant(
condition: unknown,
@@ -136,6 +292,12 @@ export class CompilerError extends Error {
}
}
static throwDiagnostic(options: CompilerDiagnosticOptions): never {
const errors = new CompilerError();
errors.pushDiagnostic(new CompilerDiagnostic(options));
throw errors;
}
static throwTodo(
options: Omit<CompilerErrorDetailOptions, 'severity'>,
): never {
@@ -210,6 +372,21 @@ export class CompilerError extends Error {
return this.name;
}
printErrorMessage(source: string): string {
return (
`Found ${this.details.length} error${this.details.length === 1 ? '' : 's'}:\n` +
this.details.map(detail => detail.printErrorMessage(source)).join('\n')
);
}
merge(other: CompilerError): void {
this.details.push(...other.details);
}
pushDiagnostic(diagnostic: CompilerDiagnostic): void {
this.details.push(diagnostic);
}
push(options: CompilerErrorDetailOptions): CompilerErrorDetail {
const detail = new CompilerErrorDetail({
reason: options.reason,
@@ -260,3 +437,32 @@ export class CompilerError extends Error {
});
}
}
function printErrorSummary(severity: ErrorSeverity, message: string): string {
let severityCategory: string;
switch (severity) {
case ErrorSeverity.InvalidConfig:
case ErrorSeverity.InvalidJS:
case ErrorSeverity.InvalidReact:
case ErrorSeverity.UnsupportedJS: {
severityCategory = 'Error';
break;
}
case ErrorSeverity.CannotPreserveMemoization: {
severityCategory = 'Memoization';
break;
}
case ErrorSeverity.Invariant: {
severityCategory = 'Invariant';
break;
}
case ErrorSeverity.Todo: {
severityCategory = 'Todo';
break;
}
default: {
assertExhaustive(severity, `Unexpected severity '${severity}'`);
}
}
return `${severityCategory}: ${message}`;
}

View File

@@ -7,7 +7,12 @@
import * as t from '@babel/types';
import {z} from 'zod';
import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError';
import {
CompilerDiagnostic,
CompilerError,
CompilerErrorDetail,
CompilerErrorDetailOptions,
} from '../CompilerError';
import {
EnvironmentConfig,
ExternalFunction,
@@ -224,7 +229,7 @@ export type LoggerEvent =
export type CompileErrorEvent = {
kind: 'CompileError';
fnLoc: t.SourceLocation | null;
detail: CompilerErrorDetailOptions;
detail: CompilerErrorDetail | CompilerDiagnostic;
};
export type CompileDiagnosticEvent = {
kind: 'CompileDiagnostic';

View File

@@ -181,7 +181,7 @@ function logError(
context.opts.logger.logEvent(context.filename, {
kind: 'CompileError',
fnLoc,
detail: detail.options,
detail,
});
}
} else {

View File

@@ -8,32 +8,27 @@
import {NodePath} from '@babel/core';
import * as t from '@babel/types';
import {
CompilerError,
CompilerErrorDetailOptions,
EnvironmentConfig,
ErrorSeverity,
Logger,
} from '..';
import {CompilerError, EnvironmentConfig, ErrorSeverity, Logger} from '..';
import {getOrInsertWith} from '../Utils/utils';
import {Environment} from '../HIR';
import {Environment, GeneratedSource} from '../HIR';
import {DEFAULT_EXPORT} from '../HIR/Environment';
import {CompileProgramMetadata} from './Program';
import {CompilerDiagnostic, CompilerDiagnosticOptions} from '../CompilerError';
function throwInvalidReact(
options: Omit<CompilerErrorDetailOptions, 'severity'>,
options: Omit<CompilerDiagnosticOptions, 'severity'>,
{logger, filename}: TraversalState,
): never {
const detail: CompilerErrorDetailOptions = {
...options,
const detail: CompilerDiagnosticOptions = {
severity: ErrorSeverity.InvalidReact,
...options,
};
logger?.logEvent(filename, {
kind: 'CompileError',
fnLoc: null,
detail,
detail: new CompilerDiagnostic(detail),
});
CompilerError.throw(detail);
CompilerError.throwDiagnostic(detail);
}
function isAutodepsSigil(
@@ -97,14 +92,18 @@ function assertValidEffectImportReference(
*/
throwInvalidReact(
{
reason:
'[InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. ' +
'This will break your build! ' +
'To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.',
description: maybeErrorDiagnostic
? `(Bailout reason: ${maybeErrorDiagnostic})`
: null,
loc: parent.node.loc ?? null,
category:
'Cannot infer dependencies of this effect. This will break your build!',
description:
'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' +
(maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''),
details: [
{
kind: 'error',
message: 'Cannot infer dependencies',
loc: parent.node.loc ?? GeneratedSource,
},
],
},
context,
);
@@ -124,13 +123,20 @@ function assertValidFireImportReference(
);
throwInvalidReact(
{
reason:
'[Fire] Untransformed reference to compiler-required feature. ' +
'Either remove this `fire` call or ensure it is successfully transformed by the compiler',
description: maybeErrorDiagnostic
? `(Bailout reason: ${maybeErrorDiagnostic})`
: null,
loc: paths[0].node.loc ?? null,
category:
'[Fire] Untransformed reference to compiler-required feature.',
description:
'Either remove this `fire` call or ensure it is successfully transformed by the compiler' +
maybeErrorDiagnostic
? ` ${maybeErrorDiagnostic}`
: '',
details: [
{
kind: 'error',
message: 'Untransformed `fire` call',
loc: paths[0].node.loc ?? GeneratedSource,
},
],
},
context,
);

View File

@@ -9,6 +9,7 @@ import {NodePath, Scope} from '@babel/traverse';
import * as t from '@babel/types';
import invariant from 'invariant';
import {
CompilerDiagnostic,
CompilerError,
CompilerSuggestionOperation,
ErrorSeverity,
@@ -104,12 +105,18 @@ export function lower(
if (param.isIdentifier()) {
const binding = builder.resolveIdentifier(param);
if (binding.kind !== 'Identifier') {
builder.errors.push({
reason: `(BuildHIR::lower) Could not find binding for param \`${param.node.name}\``,
severity: ErrorSeverity.Invariant,
loc: param.node.loc ?? null,
suggestions: null,
});
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
category: 'Could not find binding',
description: `[BuildHIR] Could not find binding for param \`${param.node.name}\``,
severity: ErrorSeverity.Invariant,
suggestions: null,
}).withDetail({
kind: 'error',
loc: param.node.loc ?? null,
message: 'Could not find binding',
}),
);
return;
}
const place: Place = {
@@ -163,12 +170,18 @@ export function lower(
'Assignment',
);
} else {
builder.errors.push({
reason: `(BuildHIR::lower) Handle ${param.node.type} params`,
severity: ErrorSeverity.Todo,
loc: param.node.loc ?? null,
suggestions: null,
});
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
category: `Handle ${param.node.type} parameters`,
description: `[BuildHIR] Add support for ${param.node.type} parameters`,
severity: ErrorSeverity.Todo,
suggestions: null,
}).withDetail({
kind: 'error',
loc: param.node.loc ?? null,
message: 'Unsupported parameter type',
}),
);
}
});
@@ -188,13 +201,18 @@ export function lower(
lowerStatement(builder, body);
directives = body.get('directives').map(d => d.node.value.value);
} else {
builder.errors.push({
severity: ErrorSeverity.InvalidJS,
reason: `Unexpected function body kind`,
description: `Expected function body to be an expression or a block statement, got \`${body.type}\``,
loc: body.node.loc ?? null,
suggestions: null,
});
builder.errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidJS,
category: `Unexpected function body kind`,
description: `Expected function body to be an expression or a block statement, got \`${body.type}\``,
suggestions: null,
}).withDetail({
kind: 'error',
loc: body.node.loc ?? null,
message: 'Expected a block statement or expression',
}),
);
}
if (builder.errors.hasErrors()) {
@@ -2271,11 +2289,17 @@ function lowerExpression(
});
for (const [name, locations] of Object.entries(fbtLocations)) {
if (locations.length > 1) {
CompilerError.throwTodo({
reason: `Support <${tagName}> tags with multiple <${tagName}:${name}> values`,
loc: locations.at(-1) ?? GeneratedSource,
description: null,
suggestions: null,
CompilerError.throwDiagnostic({
severity: ErrorSeverity.Todo,
category: 'Support duplicate fbt tags',
description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`,
details: locations.map(loc => {
return {
kind: 'error',
message: `Multiple \`<${tagName}:${name}>\` tags found`,
loc,
};
}),
});
}
}
@@ -3503,9 +3527,8 @@ function lowerFunction(
);
let loweredFunc: HIRFunction;
if (lowering.isErr()) {
lowering
.unwrapErr()
.details.forEach(detail => builder.errors.pushErrorDetail(detail));
const functionErrors = lowering.unwrapErr();
builder.errors.merge(functionErrors);
return null;
}
loweredFunc = lowering.unwrap();

View File

@@ -7,7 +7,7 @@
import {Binding, NodePath} from '@babel/traverse';
import * as t from '@babel/types';
import {CompilerError} from '../CompilerError';
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {Environment} from './Environment';
import {
BasicBlock,
@@ -308,9 +308,18 @@ export default class HIRBuilder {
resolveBinding(node: t.Identifier): Identifier {
if (node.name === 'fbt') {
CompilerError.throwTodo({
reason: 'Support local variables named "fbt"',
loc: node.loc ?? null,
CompilerError.throwDiagnostic({
severity: ErrorSeverity.Todo,
category: 'Support local variables named `fbt`',
description:
'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported',
details: [
{
kind: 'error',
message: 'Rename to avoid conflict with fbt plugin',
loc: node.loc ?? GeneratedSource,
},
],
});
}
const originalName = node.name;

View File

@@ -995,13 +995,13 @@ export function printAliasingEffect(effect: AliasingEffect): string {
return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}`;
}
case 'MutateFrozen': {
return `MutateFrozen ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`;
return `MutateFrozen ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`;
}
case 'MutateGlobal': {
return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`;
return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`;
}
case 'Impure': {
return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`;
return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`;
}
case 'Render': {
return `Render ${printPlaceForAliasEffect(effect.place)}`;

View File

@@ -345,6 +345,51 @@ export function* eachPatternOperand(pattern: Pattern): Iterable<Place> {
}
}
export function* eachPatternItem(
pattern: Pattern,
): Iterable<Place | SpreadPattern> {
switch (pattern.kind) {
case 'ArrayPattern': {
for (const item of pattern.items) {
if (item.kind === 'Identifier') {
yield item;
} else if (item.kind === 'Spread') {
yield item;
} else if (item.kind === 'Hole') {
continue;
} else {
assertExhaustive(
item,
`Unexpected item kind \`${(item as any).kind}\``,
);
}
}
break;
}
case 'ObjectPattern': {
for (const property of pattern.properties) {
if (property.kind === 'ObjectProperty') {
yield property.place;
} else if (property.kind === 'Spread') {
yield property;
} else {
assertExhaustive(
property,
`Unexpected item kind \`${(property as any).kind}\``,
);
}
}
break;
}
default: {
assertExhaustive(
pattern,
`Unexpected pattern kind \`${(pattern as any).kind}\``,
);
}
}
}
export function mapInstructionLValues(
instr: Instruction,
fn: (place: Place) => Place,

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerErrorDetailOptions} from '../CompilerError';
import {CompilerDiagnostic} from '../CompilerError';
import {
FunctionExpression,
GeneratedSource,
@@ -133,19 +133,19 @@ export type AliasingEffect =
/**
* Mutation of a value known to be immutable
*/
| {kind: 'MutateFrozen'; place: Place; error: CompilerErrorDetailOptions}
| {kind: 'MutateFrozen'; place: Place; error: CompilerDiagnostic}
/**
* Mutation of a global
*/
| {
kind: 'MutateGlobal';
place: Place;
error: CompilerErrorDetailOptions;
error: CompilerDiagnostic;
}
/**
* Indicates a side-effect that is not safe during render
*/
| {kind: 'Impure'; place: Place; error: CompilerErrorDetailOptions}
| {kind: 'Impure'; place: Place; error: CompilerDiagnostic}
/**
* Indicates that a given place is accessed during render. Used to distingush
* hook arguments that are known to be called immediately vs those used for
@@ -211,9 +211,9 @@ export function hashEffect(effect: AliasingEffect): string {
effect.kind,
effect.place.identifier.id,
effect.error.severity,
effect.error.reason,
effect.error.category,
effect.error.description,
printSourceLocation(effect.error.loc ?? GeneratedSource),
printSourceLocation(effect.error.primaryLocation() ?? GeneratedSource),
].join(':');
}
case 'Mutate':

View File

@@ -326,26 +326,26 @@ function isEffectSafeOutsideRender(effect: FunctionEffect): boolean {
export function getWriteErrorReason(abstractValue: AbstractValue): string {
if (abstractValue.reason.has(ValueReason.Global)) {
return 'Writing to a variable defined outside a component or hook is not allowed. Consider using an effect';
return 'Modifying a variable defined outside a component or hook is not allowed. Consider using an effect';
} else if (abstractValue.reason.has(ValueReason.JsxCaptured)) {
return 'Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX';
return 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX';
} else if (abstractValue.reason.has(ValueReason.Context)) {
return `Mutating a value returned from 'useContext()', which should not be mutated`;
return `Modifying a value returned from 'useContext()' is not allowed.`;
} else if (abstractValue.reason.has(ValueReason.KnownReturnSignature)) {
return 'Mutating a value returned from a function whose return value should not be mutated';
return 'Modifying a value returned from a function whose return value should not be mutated';
} else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) {
return 'Mutating component props or hook arguments is not allowed. Consider using a local variable instead';
return 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead';
} else if (abstractValue.reason.has(ValueReason.State)) {
return "Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead";
return "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead";
} else if (abstractValue.reason.has(ValueReason.ReducerState)) {
return "Mutating a value returned from 'useReducer()', which should not be mutated. Use the dispatch function to update instead";
return "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead";
} else if (abstractValue.reason.has(ValueReason.Effect)) {
return 'Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()';
return 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()';
} else if (abstractValue.reason.has(ValueReason.HookCaptured)) {
return 'Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook';
return 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook';
} else if (abstractValue.reason.has(ValueReason.HookReturn)) {
return 'Updating a value returned from a hook is not allowed. Consider moving the mutation into the hook where the value is constructed';
return 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed';
} else {
return 'This mutates a variable that React considers immutable';
return 'This modifies a variable that React considers immutable';
}
}

View File

@@ -6,6 +6,7 @@
*/
import {
CompilerDiagnostic,
CompilerError,
Effect,
ErrorSeverity,
@@ -36,8 +37,8 @@ import {
ValueReason,
} from '../HIR';
import {
eachInstructionValueLValue,
eachInstructionValueOperand,
eachPatternItem,
eachTerminalOperand,
eachTerminalSuccessor,
} from '../HIR/visitors';
@@ -446,20 +447,24 @@ function applySignature(
reason: value.reason,
context: new Set(),
});
const message =
effect.value.identifier.name !== null &&
effect.value.identifier.name.kind === 'named'
? `\`${effect.value.identifier.name.value}\` cannot be modified`
: 'This value cannot be modified';
effects.push({
kind: 'MutateFrozen',
place: effect.value,
error: {
error: CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
reason,
description:
effect.value.identifier.name !== null &&
effect.value.identifier.name.kind === 'named'
? `Found mutation of \`${effect.value.identifier.name.value}\``
: null,
loc: effect.value.loc,
category: 'This value cannot be modified',
description: reason,
suggestions: null,
},
}).withDetail({
kind: 'error',
loc: effect.value.loc,
message,
}),
});
}
}
@@ -1016,30 +1021,28 @@ function applyEffect(
const description =
effect.value.identifier.name !== null &&
effect.value.identifier.name.kind === 'named'
? `Variable \`${effect.value.identifier.name.value}\` is accessed before it is declared`
: null;
? `Variable \`${effect.value.identifier.name.value}\``
: 'This variable';
const hoistedAccess = context.hoistedContextDeclarations.get(
effect.value.identifier.declarationId,
);
const diagnostic = CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot access variable before it is declared',
description: `${description} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time`,
});
if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) {
applyEffect(
context,
state,
{
kind: 'MutateFrozen',
place: effect.value,
error: {
severity: ErrorSeverity.InvalidReact,
reason: `This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time`,
description,
loc: hoistedAccess.loc,
suggestions: null,
},
},
initialized,
effects,
);
diagnostic.withDetail({
kind: 'error',
loc: hoistedAccess.loc,
message: 'Variable accessed before it is declared',
});
}
diagnostic.withDetail({
kind: 'error',
loc: effect.value.loc,
message: 'The variable is declared here',
});
applyEffect(
context,
@@ -1047,13 +1050,7 @@ function applyEffect(
{
kind: 'MutateFrozen',
place: effect.value,
error: {
severity: ErrorSeverity.InvalidReact,
reason: `This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time`,
description,
loc: effect.value.loc,
suggestions: null,
},
error: diagnostic,
},
initialized,
effects,
@@ -1064,11 +1061,11 @@ function applyEffect(
reason: value.reason,
context: new Set(),
});
const description =
const message =
effect.value.identifier.name !== null &&
effect.value.identifier.name.kind === 'named'
? `Found mutation of \`${effect.value.identifier.name.value}\``
: null;
? `\`${effect.value.identifier.name.value}\` cannot be modified`
: 'This value cannot be modified';
applyEffect(
context,
state,
@@ -1078,13 +1075,15 @@ function applyEffect(
? 'MutateFrozen'
: 'MutateGlobal',
place: effect.value,
error: {
error: CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
reason,
description,
category: 'This value cannot be modified',
description: reason,
}).withDetail({
kind: 'error',
loc: effect.value.loc,
suggestions: null,
},
message,
}),
},
initialized,
effects,
@@ -1864,19 +1863,34 @@ function computeSignatureForInstruction(
break;
}
case 'Destructure': {
for (const patternLValue of eachInstructionValueLValue(value)) {
if (isPrimitiveType(patternLValue.identifier)) {
for (const patternItem of eachPatternItem(value.lvalue.pattern)) {
const place =
patternItem.kind === 'Identifier' ? patternItem : patternItem.place;
if (isPrimitiveType(place.identifier)) {
effects.push({
kind: 'Create',
into: patternLValue,
into: place,
value: ValueKind.Primitive,
reason: ValueReason.Other,
});
} else {
} else if (patternItem.kind === 'Identifier') {
effects.push({
kind: 'CreateFrom',
from: value.value,
into: patternLValue,
into: place,
});
} else {
// Spread creates a new object/array that captures from the RValue
effects.push({
kind: 'Create',
into: place,
reason: ValueReason.Other,
value: ValueKind.Mutable,
});
effects.push({
kind: 'Capture',
from: value.value,
into: place,
});
}
}
@@ -1991,13 +2005,18 @@ function computeSignatureForInstruction(
effects.push({
kind: 'MutateGlobal',
place: value.value,
error: {
reason:
'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)',
loc: instr.loc,
suggestions: null,
error: CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
},
category:
'Cannot reassign variables declared outside of the component/hook',
description:
'Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)',
suggestions: null,
}).withDetail({
kind: 'error',
loc: instr.loc,
message: 'Cannot reassign variable',
}),
});
effects.push({kind: 'Assign', from: value.value, into: lvalue});
break;
@@ -2087,17 +2106,20 @@ function computeEffectsForLegacySignature(
effects.push({
kind: 'Impure',
place: receiver,
error: {
reason:
'Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
description:
signature.canonicalName != null
? `\`${signature.canonicalName}\` is an impure function whose results may change on every call`
: null,
error: CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
loc,
category: 'Cannot call impure function during render',
description:
(signature.canonicalName != null
? `\`${signature.canonicalName}\` is an impure function. `
: '') +
'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
suggestions: null,
},
}).withDetail({
kind: 'error',
loc,
message: 'Cannot call impure function',
}),
});
}
const stores: Array<Place> = [];

View File

@@ -195,7 +195,7 @@ export function inferMutationAliasingRanges(
effect.kind === 'MutateGlobal' ||
effect.kind === 'Impure'
) {
errors.push(effect.error);
errors.pushDiagnostic(effect.error);
functionEffects.push(effect);
} else if (effect.kind === 'Render') {
renders.push({index: index++, place: effect.place});
@@ -549,7 +549,7 @@ function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void {
case 'Impure':
case 'MutateFrozen':
case 'MutateGlobal': {
errors.push(effect.error);
errors.pushDiagnostic(effect.error);
break;
}
}

View File

@@ -360,6 +360,12 @@ function* generateInstructionTypes(
value: makePropertyLiteral(propertyName),
},
});
} else if (item.kind === 'Spread') {
// Array pattern spread always creates an array
yield equation(item.place.identifier.type, {
kind: 'Object',
shapeId: BuiltInArrayId,
});
} else {
break;
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, Effect} from '..';
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
import {HIRFunction, IdentifierId, Place} from '../HIR';
import {
eachInstructionLValue,
@@ -28,16 +28,19 @@ export function validateLocalsNotReassignedAfterRender(fn: HIRFunction): void {
false,
);
if (reassignment !== null) {
CompilerError.throwInvalidReact({
reason:
'Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead',
description:
reassignment.identifier.name !== null &&
reassignment.identifier.name.kind === 'named'
? `Variable \`${reassignment.identifier.name.value}\` cannot be reassigned after render`
: '',
loc: reassignment.loc,
});
const errors = new CompilerError();
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot reassign a variable after render completes',
description: `Reassigning ${reassignment.identifier.name != null && reassignment.identifier.name.kind === 'named' ? `variable \`${reassignment.identifier.name.value}\`` : 'a variable'} after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead`,
}).withDetail({
kind: 'error',
loc: reassignment.loc,
message: 'Cannot reassign variable after render completes',
}),
);
throw errors;
}
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, Effect, ErrorSeverity} from '..';
import {CompilerDiagnostic, CompilerError, Effect, ErrorSeverity} from '..';
import {
FunctionEffect,
HIRFunction,
@@ -57,16 +57,24 @@ export function validateNoFreezingKnownMutableFunctions(
if (operand.effect === Effect.Freeze) {
const effect = contextMutationEffects.get(operand.identifier.id);
if (effect != null) {
errors.push({
reason: `This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead`,
loc: operand.loc,
severity: ErrorSeverity.InvalidReact,
});
errors.push({
reason: `The function modifies a local variable here`,
loc: effect.loc,
severity: ErrorSeverity.InvalidReact,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Cannot modify local variables after render completes',
description: `This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead`,
})
.withDetail({
kind: 'error',
loc: operand.loc,
message:
'This function may (indirectly) reassign or modify local variables after render',
})
.withDetail({
kind: 'error',
loc: effect.loc,
message: 'This modifies a local variable',
}),
);
}
}
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '..';
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
import {HIRFunction} from '../HIR';
import {getFunctionCallSignature} from '../Inference/InferReferenceEffects';
import {Result} from '../Utils/Result';
@@ -34,17 +34,22 @@ export function validateNoImpureFunctionsInRender(
callee.identifier.type,
);
if (signature != null && signature.impure === true) {
errors.push({
reason:
'Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
description:
signature.canonicalName != null
? `\`${signature.canonicalName}\` is an impure function whose results may change on every call`
: null,
severity: ErrorSeverity.InvalidReact,
loc: callee.loc,
suggestions: null,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
category: 'Cannot call impure function during render',
description:
(signature.canonicalName != null
? `\`${signature.canonicalName}\` is an impure function. `
: '') +
'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}).withDetail({
kind: 'error',
loc: callee.loc,
message: 'Cannot call impure function',
}),
);
}
}
}

View File

@@ -5,7 +5,7 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '..';
import {CompilerDiagnostic, CompilerError, ErrorSeverity} from '..';
import {BlockId, HIRFunction} from '../HIR';
import {Result} from '../Utils/Result';
import {retainWhere} from '../Utils/utils';
@@ -34,11 +34,17 @@ export function validateNoJSXInTryStatement(
switch (value.kind) {
case 'JsxExpression':
case 'JsxFragment': {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason: `Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`,
loc: value.loc,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'Avoid constructing JSX within try/catch',
description: `React does not immediately render components when JSX is rendered, so any errors from this component will not be caught by the try/catch. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)`,
}).withDetail({
kind: 'error',
loc: value.loc,
message: 'Avoid constructing JSX within try/catch',
}),
);
break;
}
}

View File

@@ -5,7 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {
HIRFunction,
IdentifierId,
@@ -90,14 +94,21 @@ export function validateNoSetStateInEffects(
if (arg !== undefined && arg.kind === 'Identifier') {
const setState = setStateFunctions.get(arg.identifier.id);
if (setState !== undefined) {
errors.push({
reason:
'Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)',
description: null,
severity: ErrorSeverity.InvalidReact,
loc: setState.loc,
suggestions: null,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
category:
'Calling setState within an effect can trigger cascading renders',
description:
'Calling setState directly within a useEffect causes cascading renders that can hurt performance, and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}).withDetail({
kind: 'error',
loc: setState.loc,
message:
'Avoid calling setState() in the top-level of an effect',
}),
);
}
}
}

View File

@@ -5,7 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {HIRFunction, IdentifierId, isSetStateType} from '../HIR';
import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
import {eachInstructionValueOperand} from '../HIR/visitors';
@@ -122,23 +126,35 @@ function validateNoSetStateInRenderImpl(
unconditionalSetStateFunctions.has(callee.identifier.id)
) {
if (activeManualMemoId !== null) {
errors.push({
reason:
'Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState)',
description: null,
severity: ErrorSeverity.InvalidReact,
loc: callee.loc,
suggestions: null,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
category:
'Calling setState from useMemo may trigger an infinite loop',
description:
'Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}).withDetail({
kind: 'error',
loc: callee.loc,
message: 'Found setState() within useMemo()',
}),
);
} else if (unconditionalBlocks.has(block.id)) {
errors.push({
reason:
'This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState)',
description: null,
severity: ErrorSeverity.InvalidReact,
loc: callee.loc,
suggestions: null,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
category:
'Calling setState during render may trigger an infinite loop',
description:
'Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState)',
severity: ErrorSeverity.InvalidReact,
suggestions: null,
}).withDetail({
kind: 'error',
loc: callee.loc,
message: 'Found setState() within useMemo()',
}),
);
}
}
break;

View File

@@ -5,7 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '../CompilerError';
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {
DeclarationId,
Effect,
@@ -275,27 +279,37 @@ function validateInferredDep(
errorDiagnostic = merge(errorDiagnostic ?? compareResult, compareResult);
}
}
errorState.push({
severity: ErrorSeverity.CannotPreserveMemoization,
reason:
'React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected',
description:
DEBUG ||
// If the dependency is a named variable then we can report it. Otherwise only print in debug mode
(dep.identifier.name != null && dep.identifier.name.kind === 'named')
? `The inferred dependency was \`${prettyPrintScopeDependency(
dep,
)}\`, but the source dependencies were [${validDepsInMemoBlock
.map(dep => printManualMemoDependency(dep, true))
.join(', ')}]. ${
errorDiagnostic
? getCompareDependencyResultDescription(errorDiagnostic)
: 'Inferred dependency not present in source'
}`
: null,
loc: memoLocation,
suggestions: null,
});
errorState.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.CannotPreserveMemoization,
category:
'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. ',
DEBUG ||
// If the dependency is a named variable then we can report it. Otherwise only print in debug mode
(dep.identifier.name != null && dep.identifier.name.kind === 'named')
? `The inferred dependency was \`${prettyPrintScopeDependency(
dep,
)}\`, but the source dependencies were [${validDepsInMemoBlock
.map(dep => printManualMemoDependency(dep, true))
.join(', ')}]. ${
errorDiagnostic
? getCompareDependencyResultDescription(errorDiagnostic)
: 'Inferred dependency not present in source'
}.`
: '',
]
.join('')
.trim(),
suggestions: null,
}).withDetail({
kind: 'error',
loc: memoLocation,
message: 'Could not preserve existing manual memoization',
}),
);
}
class Visitor extends ReactiveFunctionVisitor<VisitorState> {
@@ -519,14 +533,21 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
!this.scopes.has(identifier.scope.id) &&
!this.prunedScopes.has(identifier.scope.id)
) {
state.errors.push({
reason:
'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',
description: null,
severity: ErrorSeverity.CannotPreserveMemoization,
loc,
suggestions: null,
});
state.errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.CannotPreserveMemoization,
category:
'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.',
].join(''),
}).withDetail({
kind: 'error',
loc,
message: 'This dependency may be modified later',
}),
);
}
}
}
@@ -560,16 +581,25 @@ class Visitor extends ReactiveFunctionVisitor<VisitorState> {
for (const identifier of decls) {
if (isUnmemoized(identifier, this.scopes)) {
state.errors.push({
reason:
'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.',
description: DEBUG
? `${printIdentifier(identifier)} was not memoized`
: null,
severity: ErrorSeverity.CannotPreserveMemoization,
loc,
suggestions: null,
});
state.errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.CannotPreserveMemoization,
category:
'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
? `${printIdentifier(identifier)} was not memoized.`
: '',
]
.join('')
.trim(),
}).withDetail({
kind: 'error',
loc,
message: 'Could not preserve existing memoization',
}),
);
}
}
}

View File

@@ -5,7 +5,11 @@
* LICENSE file in the root directory of this source tree.
*/
import {CompilerError, ErrorSeverity} from '..';
import {
CompilerDiagnostic,
CompilerError,
ErrorSeverity,
} from '../CompilerError';
import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
import {Result} from '../Utils/Result';
@@ -63,24 +67,41 @@ export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
}
if (body.loweredFunc.func.params.length > 0) {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason: 'useMemo callbacks may not accept any arguments',
description: null,
loc: body.loc,
suggestions: null,
});
const firstParam = body.loweredFunc.func.params[0];
const loc =
firstParam.kind === 'Identifier'
? firstParam.loc
: firstParam.place.loc;
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category: 'useMemo() callbacks may not accept parameters',
description:
'useMemo() callbacks are called by React to cache calculations across re-renders. They should not take parameters. Instead, directly reference the props, state, or local variables needed for the computation.',
suggestions: null,
}).withDetail({
kind: 'error',
loc,
message: '',
}),
);
}
if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
errors.push({
severity: ErrorSeverity.InvalidReact,
reason:
'useMemo callbacks may not be async or generator functions',
description: null,
loc: body.loc,
suggestions: null,
});
errors.pushDiagnostic(
CompilerDiagnostic.create({
severity: ErrorSeverity.InvalidReact,
category:
'useMemo callbacks may not be async or generator functions',
description:
'useMemo() callbacks are called once and must synchronously return a value',
suggestions: null,
}).withDetail({
kind: 'error',
loc: body.loc,
message: 'Async and generator functions are not supported',
}),
);
}
break;

View File

@@ -20,7 +20,7 @@ describe('parseConfigPragma()', () => {
validateHooksUsage: 1,
} as any);
}).toThrowErrorMatchingInlineSnapshot(
`"InvalidConfig: Could not validate environment config. Update React Compiler config to fix the error. Validation error: Expected boolean, received number at "validateHooksUsage""`,
`"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: Expected boolean, received number at "validateHooksUsage"."`,
);
});
@@ -38,7 +38,7 @@ describe('parseConfigPragma()', () => {
],
} as any);
}).toThrowErrorMatchingInlineSnapshot(
`"InvalidConfig: Could not validate environment config. Update React Compiler config to fix the error. Validation error: autodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex""`,
`"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: autodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex"."`,
);
});

View File

@@ -0,0 +1,104 @@
## Input
```javascript
// @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
function Component(props) {
// Should memoize independently
const x = useMemo(() => makeObject_Primitives(), []);
const rest = useMemo(() => {
const [_, ...rest] = props.array;
// Should be inferred as Array.proto.push which doesn't mutate input
rest.push(x);
return rest;
});
return (
<>
<ValidateMemoization inputs={[]} output={x} />
<ValidateMemoization inputs={[props.array]} output={rest} />
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{array: [0, 1, 2]}],
};
```
## Code
```javascript
import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees
import { useMemo } from "react";
import { makeObject_Primitives, ValidateMemoization } from "shared-runtime";
function Component(props) {
const $ = _c(9);
let t0;
if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
t0 = makeObject_Primitives();
$[0] = t0;
} else {
t0 = $[0];
}
const x = t0;
let rest;
if ($[1] !== props.array) {
[, ...rest] = props.array;
rest.push(x);
$[1] = props.array;
$[2] = rest;
} else {
rest = $[2];
}
const rest_0 = rest;
let t1;
if ($[3] === Symbol.for("react.memo_cache_sentinel")) {
t1 = <ValidateMemoization inputs={[]} output={x} />;
$[3] = t1;
} else {
t1 = $[3];
}
let t2;
if ($[4] !== props.array) {
t2 = [props.array];
$[4] = props.array;
$[5] = t2;
} else {
t2 = $[5];
}
let t3;
if ($[6] !== rest_0 || $[7] !== t2) {
t3 = (
<>
{t1}
<ValidateMemoization inputs={t2} output={rest_0} />
</>
);
$[6] = rest_0;
$[7] = t2;
$[8] = t3;
} else {
t3 = $[8];
}
return t3;
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{ array: [0, 1, 2] }],
};
```
### Eval output
(kind: ok) <div>{"inputs":[],"output":{"a":0,"b":"value1","c":true}}</div><div>{"inputs":[[0,1,2]],"output":[1,2,{"a":0,"b":"value1","c":true}]}</div>

View File

@@ -0,0 +1,28 @@
// @validatePreserveExistingMemoizationGuarantees
import {useMemo} from 'react';
import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime';
function Component(props) {
// Should memoize independently
const x = useMemo(() => makeObject_Primitives(), []);
const rest = useMemo(() => {
const [_, ...rest] = props.array;
// Should be inferred as Array.proto.push which doesn't mutate input
rest.push(x);
return rest;
});
return (
<>
<ValidateMemoization inputs={[]} output={x} />
<ValidateMemoization inputs={[props.array]} output={rest} />
</>
);
}
export const FIXTURE_ENTRYPOINT = {
fn: Component,
params: [{array: [0, 1, 2]}],
};

View File

@@ -15,13 +15,19 @@ function Component(props) {
## Error
```
Found 1 error:
Todo: (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern
error._todo.computed-lval-in-destructure.ts:3:9
1 | function Component(props) {
2 | const computedKey = props.key;
> 3 | const {[computedKey]: x} = props.val;
| ^^^^^^^^^^^^^^^^ Todo: (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern (3:3)
| ^^^^^^^^^^^^^^^^ (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern
4 |
5 | return x;
6 | }
```

View File

@@ -15,10 +15,16 @@ function Component() {
## Error
```
Found 1 error:
Error: Cannot reassign variables declared outside of the component/hook
Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
error.assign-global-in-component-tag-function.ts:3:4
1 | function Component() {
2 | const Foo = () => {
> 3 | someGlobal = true;
| ^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (3:3)
| ^^^^^^^^^^ Cannot reassign variable
4 | };
5 | return <Foo />;
6 | }

View File

@@ -18,10 +18,16 @@ function Component() {
## Error
```
Found 1 error:
Error: Cannot reassign variables declared outside of the component/hook
Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
error.assign-global-in-jsx-children.ts:3:4
1 | function Component() {
2 | const foo = () => {
> 3 | someGlobal = true;
| ^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (3:3)
| ^^^^^^^^^^ Cannot reassign variable
4 | };
5 | // Children are generally access/called during render, so
6 | // modifying a global in a children function is almost

View File

@@ -16,13 +16,19 @@ function Component() {
## Error
```
Found 1 error:
Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
error.assign-global-in-jsx-spread-attribute.ts:4:4
2 | function Component() {
3 | const foo = () => {
> 4 | someGlobal = true;
| ^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (4:4)
| ^^^^^^^^^^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
5 | };
6 | return <div {...foo} />;
7 | }
```

View File

@@ -16,13 +16,21 @@ function Foo(props) {
## Error
```
Found 1 error:
Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
$FlowFixMe[react-rule-hook].
error.bailout-on-flow-suppression.ts:4:2
2 |
3 | function Foo(props) {
> 4 | // $FlowFixMe[react-rule-hook]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. $FlowFixMe[react-rule-hook] (4:4)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
5 | useX();
6 | return null;
7 | }
```

View File

@@ -19,15 +19,35 @@ function lowercasecomponent() {
## Error
```
Found 2 errors:
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
eslint-disable my-app/react-rule.
error.bailout-on-suppression-of-custom-rule.ts:3:0
1 | // @eslintSuppressionRules:["my-app","react-rule"]
2 |
> 3 | /* eslint-disable my-app/react-rule */
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. eslint-disable my-app/react-rule (3:3)
InvalidReact: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. eslint-disable-next-line my-app/react-rule (7:7)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
4 | function lowercasecomponent() {
5 | 'use forget';
6 | const x = [];
Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
eslint-disable-next-line my-app/react-rule.
error.bailout-on-suppression-of-custom-rule.ts:7:2
5 | 'use forget';
6 | const x = [];
> 7 | // eslint-disable-next-line my-app/react-rule
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior
8 | return <div>{x}</div>;
9 | }
10 | /* eslint-enable my-app/react-rule */
```

View File

@@ -36,6 +36,12 @@ function Component() {
## Error
```
Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
error.bug-old-inference-false-positive-ref-validation-in-use-effect.ts:20:12
18 | );
19 | const ref = useRef(null);
> 20 | useEffect(() => {
@@ -47,12 +53,19 @@ function Component() {
> 23 | }
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 24 | }, [update]);
| ^^^^ InvalidReact: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead (20:24)
InvalidReact: The function modifies a local variable here (14:14)
| ^^^^ This function may (indirectly) reassign or modify local variables after render
25 |
26 | return 'ok';
27 | }
error.bug-old-inference-false-positive-ref-validation-in-use-effect.ts:14:6
12 | ...partialParams,
13 | };
> 14 | nextParams.param = 'value';
| ^^^^^^^^^^ This modifies a local variable
15 | console.log(nextParams);
16 | },
17 | [params]
```

View File

@@ -14,13 +14,19 @@ function Component(props) {
## Error
```
Found 1 error:
Invariant: Const declaration cannot be referenced as an expression
error.call-args-destructuring-asignment-complex.ts:3:9
1 | function Component(props) {
2 | let x = makeObject();
> 3 | x.foo(([[x]] = makeObject()));
| ^^^^^ Invariant: Const declaration cannot be referenced as an expression (3:3)
| ^^^^^ Const declaration cannot be referenced as an expression
4 | return x;
5 | }
6 |
```

View File

@@ -14,12 +14,20 @@ function Foo() {
## Error
```
Found 1 error:
Error: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config
Bar may be a component..
error.capitalized-function-call-aliased.ts:4:2
2 | function Foo() {
3 | let x = Bar;
> 4 | x(); // ERROR
| ^^^ InvalidReact: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config. Bar may be a component. (4:4)
| ^^^ Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config
5 | }
6 |
```

View File

@@ -15,13 +15,21 @@ function Component() {
## Error
```
Found 1 error:
Error: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config
SomeFunc may be a component..
error.capitalized-function-call.ts:3:12
1 | // @validateNoCapitalizedCalls
2 | function Component() {
> 3 | const x = SomeFunc();
| ^^^^^^^^^^ InvalidReact: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config. SomeFunc may be a component. (3:3)
| ^^^^^^^^^^ Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config
4 |
5 | return x;
6 | }
```

View File

@@ -15,13 +15,21 @@ function Component() {
## Error
```
Found 1 error:
Error: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config
SomeFunc may be a component..
error.capitalized-method-call.ts:3:12
1 | // @validateNoCapitalizedCalls
2 | function Component() {
> 3 | const x = someGlobal.SomeFunc();
| ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config. SomeFunc may be a component. (3:3)
| ^^^^^^^^^^^^^^^^^^^^^ Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config
4 |
5 | return x;
6 | }
```

View File

@@ -32,19 +32,55 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 4 errors:
Error: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
error.capture-ref-for-mutation.ts:12:13
10 | };
11 | const moveLeft = {
> 12 | handler: handleKey('left')(),
| ^^^^^^^^^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (12:12)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (12:12)
InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (15:15)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (15:15)
| ^^^^^^^^^^^^^^^^^ This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
13 | };
14 | const moveRight = {
15 | handler: handleKey('right')(),
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
error.capture-ref-for-mutation.ts:12:13
10 | };
11 | const moveLeft = {
> 12 | handler: handleKey('left')(),
| ^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
13 | };
14 | const moveRight = {
15 | handler: handleKey('right')(),
Error: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
error.capture-ref-for-mutation.ts:15:13
13 | };
14 | const moveRight = {
> 15 | handler: handleKey('right')(),
| ^^^^^^^^^^^^^^^^^^ This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
16 | };
17 | return [moveLeft, moveRight];
18 | }
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
error.capture-ref-for-mutation.ts:15:13
13 | };
14 | const moveRight = {
> 15 | handler: handleKey('right')(),
| ^^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
16 | };
17 | return [moveLeft, moveRight];
18 | }
```

View File

@@ -16,13 +16,19 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.conditional-hook-unknown-hook-react-namespace.ts:4:8
2 | let x = null;
3 | if (props.cond) {
> 4 | x = React.useNonexistentHook();
| ^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4)
| ^^^^^^^^^^^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
5 | }
6 | return x;
7 | }
```

View File

@@ -16,13 +16,19 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.conditional-hooks-as-method-call.ts:4:8
2 | let x = null;
3 | if (props.cond) {
> 4 | x = Foo.useFoo();
| ^^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4)
| ^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
5 | }
6 | return x;
7 | }
```

View File

@@ -28,10 +28,16 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Error: Cannot reassign a variable after render completes
Reassigning variable `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.context-variable-only-chained-assign.ts:10:19
8 | };
9 | const fn2 = () => {
> 10 | const copy2 = (x = 4);
| ^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `x` cannot be reassigned after render (10:10)
| ^ Cannot reassign variable after render completes
11 | return [invoke(fn1), copy2, identity(copy2)];
12 | };
13 | return invoke(fn2);

View File

@@ -17,10 +17,16 @@ function Component() {
## Error
```
Found 1 error:
Error: Cannot reassign a variable after render completes
Reassigning variable `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.declare-reassign-variable-in-function-declaration.ts:4:4
2 | let x = null;
3 | function foo() {
> 4 | x = 9;
| ^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `x` cannot be reassigned after render (4:4)
| ^ Cannot reassign variable after render completes
5 | }
6 | const y = bar(foo);
7 | return <Child y={y} />;

View File

@@ -22,6 +22,10 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `ArrowFunctionExpression` cannot be safely reordered
error.default-param-accesses-local.ts:3:6
1 | function Component(
2 | x,
> 3 | y = () => {
@@ -29,10 +33,12 @@ export const FIXTURE_ENTRYPOINT = {
> 4 | return x;
| ^^^^^^^^^^^^^
> 5 | }
| ^^^^ Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `ArrowFunctionExpression` cannot be safely reordered (3:5)
| ^^^^ (BuildHIR::node.lowerReorderableExpression) Expression type `ArrowFunctionExpression` cannot be safely reordered
6 | ) {
7 | return y();
8 | }
```

View File

@@ -19,13 +19,21 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Todo: [hoisting] EnterSSA: Expected identifier to be defined before being used
Identifier x$1 is undefined.
error.dont-hoist-inline-reference.ts:3:2
1 | import {identity} from 'shared-runtime';
2 | function useInvalid() {
> 3 | const x = identity(x);
| ^^^^^^^^^^^^^^^^^^^^^^ Todo: [hoisting] EnterSSA: Expected identifier to be defined before being used. Identifier x$1 is undefined (3:3)
| ^^^^^^^^^^^^^^^^^^^^^^ [hoisting] EnterSSA: Expected identifier to be defined before being used
4 | return x;
5 | }
6 |
```

View File

@@ -15,13 +15,21 @@ function useFoo(props) {
## Error
```
Found 1 error:
Todo: Encountered conflicting global in generated program
Conflict from local binding __DEV__.
error.emit-freeze-conflicting-global.ts:3:8
1 | // @enableEmitFreeze @instrumentForget
2 | function useFoo(props) {
> 3 | const __DEV__ = 'conflicting global';
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Encountered conflicting global in generated program. Conflict from local binding __DEV__ (3:3)
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Encountered conflicting global in generated program
4 | console.log(__DEV__);
5 | return foo(props.x);
6 | }
```

View File

@@ -15,10 +15,16 @@ function Component() {
## Error
```
Found 1 error:
Error: Cannot reassign a variable after render completes
Reassigning variable `callback` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.function-expression-references-variable-its-assigned-to.ts:3:4
1 | function Component() {
2 | let callback = () => {
> 3 | callback = null;
| ^^^^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `callback` cannot be reassigned after render (3:3)
| ^^^^^^^^ Cannot reassign variable after render completes
4 | };
5 | return <div onClick={callback} />;
6 | }

View File

@@ -24,6 +24,12 @@ function Component(props) {
## Error
```
Found 1 error:
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.
error.hoist-optional-member-expression-with-conditional-optional.ts:4:23
2 | import {ValidateMemoization} from 'shared-runtime';
3 | function Component(props) {
> 4 | const data = useMemo(() => {
@@ -41,7 +47,7 @@ function Component(props) {
> 10 | return x;
| ^^^^^^^^^^^^^^^^^
> 11 | }, [props?.items, props.cond]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source (4:11)
| ^^^^ Could not preserve existing manual memoization
12 | return (
13 | <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
14 | );

View File

@@ -24,6 +24,12 @@ function Component(props) {
## Error
```
Found 1 error:
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.
error.hoist-optional-member-expression-with-conditional.ts:4:23
2 | import {ValidateMemoization} from 'shared-runtime';
3 | function Component(props) {
> 4 | const data = useMemo(() => {
@@ -41,7 +47,7 @@ function Component(props) {
> 10 | return x;
| ^^^^^^^^^^^^^^^^^
> 11 | }, [props?.items, props.cond]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source (4:11)
| ^^^^ Could not preserve existing manual memoization
12 | return (
13 | <ValidateMemoization inputs={[props?.items, props.cond]} output={data} />
14 | );

View File

@@ -24,6 +24,10 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Todo: Support functions with unreachable code that may contain hoisted declarations
error.hoisting-simple-function-declaration.ts:6:2
4 | }
5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting
> 6 | function baz() {
@@ -31,10 +35,12 @@ export const FIXTURE_ENTRYPOINT = {
> 7 | return bar();
| ^^^^^^^^^^^^^^^^^
> 8 | }
| ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8)
| ^^^^ Support functions with unreachable code that may contain hoisted declarations
9 | }
10 |
11 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -29,10 +29,16 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook
error.hook-call-freezes-captured-identifier.ts:13:2
11 | });
12 |
> 13 | x.value += count;
| ^ InvalidReact: Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook (13:13)
| ^ This value cannot be modified
14 | return <Stringify x={x} cb={cb} />;
15 | }
16 |

View File

@@ -29,10 +29,16 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook
error.hook-call-freezes-captured-memberexpr.ts:13:2
11 | });
12 |
> 13 | x.value += count;
| ^ InvalidReact: Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook (13:13)
| ^ This value cannot be modified
14 | return <Stringify x={x} cb={cb} />;
15 | }
16 |

View File

@@ -23,15 +23,31 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 2 errors:
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.hook-property-load-local-hook.ts:7:12
5 |
6 | function Foo() {
> 7 | let bar = useFoo.useBar;
| ^^^^^^^^^^^^^ InvalidReact: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values (7:7)
InvalidReact: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values (8:8)
| ^^^^^^^^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
8 | return bar();
9 | }
10 |
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.hook-property-load-local-hook.ts:8:9
6 | function Foo() {
7 | let bar = useFoo.useBar;
> 8 | return bar();
| ^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
9 | }
10 |
11 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -20,15 +20,31 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 2 errors:
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
error.hook-ref-value.ts:5:23
3 | function Component(props) {
4 | const ref = useRef();
> 5 | useEffect(() => {}, [ref.current]);
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5)
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
error.hook-ref-value.ts:5:23
3 | function Component(props) {
4 | const ref = useRef();
> 5 | useEffect(() => {}, [ref.current]);
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
6 | }
7 |
8 | export const FIXTURE_ENTRYPOINT = {
```

View File

@@ -15,13 +15,19 @@ function component(a, b) {
## Error
```
Found 1 error:
Error: useMemo callbacks may not be async or generator functions
useMemo() callbacks are called once and must synchronously return a value
error.invalid-ReactUseMemo-async-callback.ts:2:24
1 | function component(a, b) {
> 2 | let x = React.useMemo(async () => {
| ^^^^^^^^^^^^^
> 3 | await a;
| ^^^^^^^^^^^^
> 4 | }, []);
| ^^^^ InvalidReact: useMemo callbacks may not be async or generator functions (2:4)
| ^^^^ Async and generator functions are not supported
5 | return x;
6 | }
7 |

View File

@@ -15,13 +15,19 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
error.invalid-access-ref-during-render.ts:4:16
2 | function Component(props) {
3 | const ref = useRef(null);
> 4 | const value = ref.current;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
5 | return value;
6 | }
7 |
```

View File

@@ -19,12 +19,18 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
error.invalid-aliased-ref-in-callback-invoked-during-render-.ts:9:33
7 | return <Foo item={item} current={current} />;
8 | };
> 9 | return <Items>{props.items.map(item => renderItem(item))}</Items>;
| ^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
| ^^^^^^^^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
10 | }
11 |
```

View File

@@ -15,10 +15,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX
error.invalid-array-push-frozen.ts:4:2
2 | const x = [];
3 | <div>{x}</div>;
> 4 | x.push(props.value);
| ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX (4:4)
| ^ This value cannot be modified
5 | return x;
6 | }
7 |

View File

@@ -14,12 +14,18 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-assign-hook-to-local.ts:2:12
1 | function Component(props) {
> 2 | const x = useState;
| ^^^^^^^^ InvalidReact: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values (2:2)
| ^^^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
3 | const state = x(null);
4 | return state[0];
5 | }
```

View File

@@ -16,10 +16,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX
error.invalid-computed-store-to-frozen-value.ts:5:2
3 | // freeze
4 | <div>{x}</div>;
> 5 | x[0] = true;
| ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX (5:5)
| ^ This value cannot be modified
6 | return x;
7 | }
8 |

View File

@@ -18,13 +18,19 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-conditional-call-aliased-hook-import.ts:6:11
4 | let data;
5 | if (props.cond) {
> 6 | data = readFragment();
| ^^^^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (6:6)
| ^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 | return data;
9 | }
```

View File

@@ -18,13 +18,19 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-conditional-call-aliased-react-hook.ts:6:10
4 | let s;
5 | if (props.cond) {
> 6 | [s] = state();
| ^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (6:6)
| ^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 | return s;
9 | }
```

View File

@@ -18,13 +18,19 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
error.invalid-conditional-call-non-hook-imported-as-hook.ts:6:11
4 | let data;
5 | if (props.cond) {
> 6 | data = useArray();
| ^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (6:6)
| ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning)
7 | }
8 | return data;
9 | }
```

View File

@@ -22,15 +22,31 @@ function Component({item, cond}) {
## Error
```
Found 2 errors:
Error: Calling setState from useMemo may trigger an infinite loop
Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)
error.invalid-conditional-setState-in-useMemo.ts:7:6
5 | useMemo(() => {
6 | if (cond) {
> 7 | setPrevItem(item);
| ^^^^^^^^^^^ InvalidReact: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) (7:7)
InvalidReact: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) (8:8)
| ^^^^^^^^^^^ Found setState() within useMemo()
8 | setState(0);
9 | }
10 | }, [cond, key, init]);
Error: Calling setState from useMemo may trigger an infinite loop
Each time the memo callback is evaluated it will change state. This can cause a memoization dependency to change, running the memo function again and causing an infinite loop. Instead of setting state in useMemo(), prefer deriving the value during render. (https://react.dev/reference/react/useState)
error.invalid-conditional-setState-in-useMemo.ts:8:6
6 | if (cond) {
7 | setPrevItem(item);
> 8 | setState(0);
| ^^^^^^^^ Found setState() within useMemo()
9 | }
10 | }, [cond, key, init]);
11 |
```

View File

@@ -16,10 +16,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX
error.invalid-delete-computed-property-of-frozen-value.ts:5:9
3 | // freeze
4 | <div>{x}</div>;
> 5 | delete x[y];
| ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX (5:5)
| ^ This value cannot be modified
6 | return x;
7 | }
8 |

View File

@@ -16,10 +16,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX
error.invalid-delete-property-of-frozen-value.ts:5:9
3 | // freeze
4 | <div>{x}</div>;
> 5 | delete x.y;
| ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX (5:5)
| ^ This value cannot be modified
6 | return x;
7 | }
8 |

View File

@@ -13,9 +13,15 @@ function useFoo(props) {
## Error
```
Found 1 error:
Error: Cannot reassign variables declared outside of the component/hook
Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
error.invalid-destructure-assignment-to-global.ts:2:3
1 | function useFoo(props) {
> 2 | [x] = props;
| ^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (2:2)
| ^ Cannot reassign variable
3 | return {x};
4 | }
5 |

View File

@@ -15,10 +15,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Cannot reassign variables declared outside of the component/hook
Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
error.invalid-destructure-to-local-global-variables.ts:3:6
1 | function Component(props) {
2 | let a;
> 3 | [a, b] = props.value;
| ^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (3:3)
| ^ Cannot reassign variable
4 |
5 | return [a, b];
6 | }

View File

@@ -16,13 +16,19 @@ function Component() {
## Error
```
Found 1 error:
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
error.invalid-disallow-mutating-ref-in-render.ts:4:2
2 | function Component() {
3 | const ref = useRef(null);
> 4 | ref.current = false;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
5 |
6 | return <button ref={ref} />;
7 | }
```

View File

@@ -21,15 +21,31 @@ function Component() {
## Error
```
Found 2 errors:
Error: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
error.invalid-disallow-mutating-refs-in-render-transitive.ts:9:2
7 | };
8 | const changeRef = setRef;
> 9 | changeRef();
| ^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9)
| ^^^^^^^^^ This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef)
10 |
11 | return <button ref={ref} />;
12 | }
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
error.invalid-disallow-mutating-refs-in-render-transitive.ts:9:2
7 | };
8 | const changeRef = setRef;
> 9 | changeRef();
| ^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
10 |
11 | return <button ref={ref} />;
12 | }
```

View File

@@ -13,12 +13,20 @@ function Component(props) {
## Error
```
Found 1 error:
Error: The 'eval' function is not supported
Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler.
error.invalid-eval-unsupported.ts:2:2
1 | function Component(props) {
> 2 | eval('props.x = true');
| ^^^^ UnsupportedJS: The 'eval' function is not supported. Eval is an anti-pattern in JavaScript, and the code executed cannot be evaluated by React Compiler (2:2)
| ^^^^ The 'eval' function is not supported
3 | return <div />;
4 | }
5 |
```

View File

@@ -18,10 +18,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead
error.invalid-function-expression-mutates-immutable-value.ts:5:4
3 | const onChange = e => {
4 | // INVALID! should use copy-on-write and pass the new value
> 5 | x.value = e.target.value;
| ^ InvalidReact: Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead. Found mutation of `x` (5:5)
| ^ `x` cannot be modified
6 | setX(x);
7 | };
8 | return <input value={x.value} onChange={onChange} />;

View File

@@ -35,10 +35,16 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Error: Cannot reassign variables declared outside of the component/hook
Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)
error.invalid-global-reassignment-indirect.ts:9:4
7 |
8 | const setGlobal = () => {
> 9 | someGlobal = true;
| ^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (9:9)
| ^^^^^^^^^^ Cannot reassign variable
10 | };
11 | const indirectSetGlobal = () => {
12 | setGlobal();

View File

@@ -38,15 +38,28 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Error: Cannot access variable before it is declared
Variable `setState` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time
error.invalid-hoisting-setstate.ts:19:18
17 | * $2 = Function context=setState
18 | */
> 19 | useEffect(() => setState(2), []);
| ^^^^^^^^ InvalidReact: This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time. Variable `setState` is accessed before it is declared (19:19)
InvalidReact: This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time. Variable `setState` is accessed before it is declared (21:21)
| ^^^^^^^^ Variable accessed before it is declared
20 |
21 | const [state, setState] = useState(0);
22 | return <Stringify state={state} />;
error.invalid-hoisting-setstate.ts:21:16
19 | useEffect(() => setState(2), []);
20 |
> 21 | const [state, setState] = useState(0);
| ^^^^^^^^ The variable is declared here
22 | return <Stringify state={state} />;
23 | }
24 |
```

View File

@@ -17,6 +17,12 @@ function useFoo() {
## Error
```
Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-hook-function-argument-mutates-local-variable.ts:5:10
3 | function useFoo() {
4 | const cache = new Map();
> 5 | useHook(() => {
@@ -24,9 +30,16 @@ function useFoo() {
> 6 | cache.set('key', 'value');
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 7 | });
| ^^^^ InvalidReact: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead (5:7)
| ^^^^ This function may (indirectly) reassign or modify local variables after render
8 | }
9 |
InvalidReact: The function modifies a local variable here (6:6)
error.invalid-hook-function-argument-mutates-local-variable.ts:6:4
4 | const cache = new Map();
5 | useHook(() => {
> 6 | cache.set('key', 'value');
| ^^^^^ This modifies a local variable
7 | });
8 | }
9 |
```

View File

@@ -17,17 +17,43 @@ function Component() {
## Error
```
Found 3 errors:
Error: Cannot call impure function during render
`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)
error.invalid-impure-functions-in-render.ts:4:15
2 |
3 | function Component() {
> 4 | const date = Date.now();
| ^^^^^^^^^^ InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `Date.now` is an impure function whose results may change on every call (4:4)
InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `performance.now` is an impure function whose results may change on every call (5:5)
InvalidReact: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent). `Math.random` is an impure function whose results may change on every call (6:6)
| ^^^^^^^^^^ Cannot call impure function
5 | const now = performance.now();
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
Error: Cannot call impure function during render
`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)
error.invalid-impure-functions-in-render.ts:5:14
3 | function Component() {
4 | const date = Date.now();
> 5 | const now = performance.now();
| ^^^^^^^^^^^^^^^^^ Cannot call impure function
6 | const rand = Math.random();
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
Error: Cannot call impure function during render
`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)
error.invalid-impure-functions-in-render.ts:6:15
4 | const date = Date.now();
5 | const now = performance.now();
> 6 | const rand = Math.random();
| ^^^^^^^^^^^^^ Cannot call impure function
7 | return <Foo date={date} now={now} rand={rand} />;
8 | }
9 |
```

View File

@@ -50,10 +50,16 @@ export const FIXTURE_ENTRYPOINT = {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX
error.invalid-jsx-captures-context-variable.ts:22:2
20 | />
21 | );
> 22 | i = i + 1;
| ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX. Found mutation of `i` (22:22)
| ^ `i` cannot be modified
23 | items.push(
24 | <Stringify
25 | key={i}

View File

@@ -25,10 +25,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX
error.invalid-mutate-after-aliased-freeze.ts:13:2
11 | // y is MaybeFrozen at this point, since it may alias to x
12 | // (which is the above line freezes)
> 13 | y.push(props.p2);
| ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX (13:13)
| ^ This value cannot be modified
14 |
15 | return <Component x={x} y={y} />;
16 | }

View File

@@ -19,10 +19,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX
error.invalid-mutate-after-freeze.ts:7:2
5 |
6 | // x is Frozen at this point
> 7 | x.push(props.p2);
| ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX (7:7)
| ^ This value cannot be modified
8 |
9 | return <div>{_}</div>;
10 | }

View File

@@ -24,10 +24,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value returned from 'useContext()' is not allowed.
error.invalid-mutate-context-in-callback.ts:12:4
10 | // independently
11 | const onClick = () => {
> 12 | FooContext.current = true;
| ^^^^^^^^^^ InvalidReact: Mutating a value returned from 'useContext()', which should not be mutated. Found mutation of `FooContext` (12:12)
| ^^^^^^^^^^ `FooContext` cannot be modified
13 | };
14 | return <div onClick={onClick} />;
15 | }

View File

@@ -14,10 +14,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value returned from 'useContext()' is not allowed.
error.invalid-mutate-context.ts:3:2
1 | function Component(props) {
2 | const context = useContext(FooContext);
> 3 | context.value = props.value;
| ^^^^^^^ InvalidReact: Mutating a value returned from 'useContext()', which should not be mutated (3:3)
| ^^^^^^^ This value cannot be modified
4 | return context.value;
5 | }
6 |

View File

@@ -25,10 +25,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying component props or hook arguments is not allowed. Consider using a local variable instead
error.invalid-mutate-props-in-effect-fixpoint.ts:10:4
8 | let y = x;
9 | let mutateProps = () => {
> 10 | y.foo = true;
| ^ InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable instead. Found mutation of `y` (10:10)
| ^ `y` cannot be modified
11 | };
12 | let mutatePropsIndirect = () => {
13 | mutateProps();

View File

@@ -17,10 +17,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying component props or hook arguments is not allowed. Consider using a local variable instead
error.invalid-mutate-props-via-for-of-iterator.ts:4:4
2 | const items = [];
3 | for (const x of props.items) {
> 4 | x.modified = true;
| ^ InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable instead (4:4)
| ^ This value cannot be modified
5 | items.push(x);
6 | }
7 | return items;

View File

@@ -16,10 +16,16 @@ function useInvalidMutation(options) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying component props or hook arguments is not allowed. Consider using a local variable instead
error.invalid-mutation-in-closure.ts:4:4
2 | function test() {
3 | foo(options.foo); // error should not point on this line
> 4 | options.foo = 'bar';
| ^^^^^^^ InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable instead. Found mutation of `options` (4:4)
| ^^^^^^^ `options` cannot be modified
5 | }
6 | return test;
7 | }

View File

@@ -19,10 +19,16 @@ function Component(props) {
## 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-mutation-of-possible-props-phi-indirect.ts:4:4
2 | let x = cond ? someGlobal : props.foo;
3 | const mutatePhiThatCouldBeProps = () => {
> 4 | x.y = true;
| ^ InvalidReact: Writing to a variable defined outside a component or hook is not allowed. Consider using an effect. Found mutation of `x` (4:4)
| ^ `x` cannot be modified
5 | };
6 | const indirectMutateProps = () => {
7 | mutatePhiThatCouldBeProps();

View File

@@ -46,10 +46,16 @@ function Component() {
## Error
```
Found 1 error:
Error: Cannot reassign a variable after render completes
Reassigning variable `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-nested-function-reassign-local-variable-in-effect.ts:7:6
5 | // Create the reassignment function inside another function, then return it
6 | const reassignLocal = newValue => {
> 7 | local = newValue;
| ^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `local` cannot be reassigned after render (7:7)
| ^^^^^ Cannot reassign variable after render completes
8 | };
9 | return reassignLocal;
10 | };

View File

@@ -24,10 +24,16 @@ function SomeComponent() {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed
error.invalid-non-imported-reanimated-shared-value-writes.ts:11:22
9 | return (
10 | <Button
> 11 | onPress={() => (sharedVal.value = Math.random())}
| ^^^^^^^^^ InvalidReact: Updating a value returned from a hook is not allowed. Consider moving the mutation into the hook where the value is constructed. Found mutation of `sharedVal` (11:11)
| ^^^^^^^^^ `sharedVal` cannot be modified
12 | title="Randomize"
13 | />
14 | );

View File

@@ -18,6 +18,12 @@ function Component(props) {
## Error
```
Found 1 error:
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.
error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.ts:3:23
1 | // @validatePreserveExistingMemoizationGuarantees
2 | function Component(props) {
> 3 | const data = useMemo(() => {
@@ -29,7 +35,7 @@ function Component(props) {
> 6 | // deps are optional
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
> 7 | }, [props.items?.edges?.nodes]);
| ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items.edges.nodes`, but the source dependencies were [props.items?.edges?.nodes]. Inferred different dependency than source (3:7)
| ^^^^ Could not preserve existing manual memoization
8 | return <Foo data={data} />;
9 | }
10 |

View File

@@ -12,11 +12,17 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-pass-hook-as-call-arg.ts:2:13
1 | function Component(props) {
> 2 | return foo(useFoo);
| ^^^^^^ InvalidReact: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values (2:2)
| ^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
3 | }
4 |
```

View File

@@ -12,11 +12,17 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
error.invalid-pass-hook-as-prop.ts:2:21
1 | function Component(props) {
> 2 | return <Child foo={useFoo} />;
| ^^^^^^ InvalidReact: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values (2:2)
| ^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values
3 | }
4 |
```

View File

@@ -17,14 +17,27 @@ function Component() {
## Error
```
Found 1 error:
Error: Cannot modify local variables after render completes
This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-pass-mutable-function-as-prop.ts:7:18
5 | cache.set('key', 'value');
6 | };
> 7 | return <Foo fn={fn} />;
| ^^ InvalidReact: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead (7:7)
InvalidReact: The function modifies a local variable here (5:5)
| ^^ This function may (indirectly) reassign or modify local variables after render
8 | }
9 |
error.invalid-pass-mutable-function-as-prop.ts:5:4
3 | const cache = new Map();
4 | const fn = () => {
> 5 | cache.set('key', 'value');
| ^^^^^ This modifies a local variable
6 | };
7 | return <Foo fn={fn} />;
8 | }
```

View File

@@ -15,13 +15,19 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
error.invalid-pass-ref-to-function.ts:4:16
2 | function Component(props) {
3 | const ref = useRef(null);
> 4 | const x = foo(ref);
| ^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4)
| ^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
5 | return x.current;
6 | }
7 |
```

View File

@@ -18,10 +18,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying component props or hook arguments is not allowed. Consider using a local variable instead
error.invalid-prop-mutation-indirect.ts:3:4
1 | function Component(props) {
2 | const f = () => {
> 3 | props.value = true;
| ^^^^^ InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable instead. Found mutation of `props` (3:3)
| ^^^^^ `props` cannot be modified
4 | };
5 | const g = () => {
6 | f();

View File

@@ -16,10 +16,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX
error.invalid-property-store-to-frozen-value.ts:5:2
3 | // freeze
4 | <div>{x}</div>;
> 5 | x.y = true;
| ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX (5:5)
| ^ This value cannot be modified
6 | return x;
7 | }
8 |

View File

@@ -18,10 +18,16 @@ function Component(props) {
## Error
```
Found 1 error:
Error: This value cannot be modified
Modifying component props or hook arguments is not allowed. Consider using a local variable instead
error.invalid-props-mutation-in-effect-indirect.ts:3:4
1 | function Component(props) {
2 | const mutateProps = () => {
> 3 | props.value = true;
| ^^^^^ InvalidReact: Mutating component props or hook arguments is not allowed. Consider using a local variable instead. Found mutation of `props` (3:3)
| ^^^^^ `props` cannot be modified
4 | };
5 | const indirectMutateProps = () => {
6 | mutateProps();

View File

@@ -14,13 +14,19 @@ function Component({ref}) {
## Error
```
Found 1 error:
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
error.invalid-read-ref-prop-in-render-destructure.ts:3:16
1 | // @validateRefAccessDuringRender @compilationMode:"infer"
2 | function Component({ref}) {
> 3 | const value = ref.current;
| ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3)
| ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
4 | return <div>{value}</div>;
5 | }
6 |
```

View File

@@ -14,13 +14,19 @@ function Component(props) {
## Error
```
Found 1 error:
Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
error.invalid-read-ref-prop-in-render-property-load.ts:3:16
1 | // @validateRefAccessDuringRender @compilationMode:"infer"
2 | function Component(props) {
> 3 | const value = props.ref.current;
| ^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (3:3)
| ^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef)
4 | return <div>{value}</div>;
5 | }
6 |
```

View File

@@ -13,12 +13,20 @@ function Component() {
## Error
```
Found 1 error:
Error: Cannot reassign a `const` variable
`x` is declared as const.
error.invalid-reassign-const.ts:3:2
1 | function Component() {
2 | const x = 0;
> 3 | x = 1;
| ^ InvalidJS: Cannot reassign a `const` variable. `x` is declared as const (3:3)
| ^ Cannot reassign a `const` variable
4 | }
5 |
```

View File

@@ -15,10 +15,16 @@ function useFoo() {
## Error
```
Found 1 error:
Error: Cannot reassign a variable after render completes
Reassigning variable `x` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-reassign-local-in-hook-return-value.ts:4:4
2 | let x = 0;
3 | return value => {
> 4 | x = value;
| ^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `x` cannot be reassigned after render (4:4)
| ^ Cannot reassign variable after render completes
5 | };
6 | }
7 |

View File

@@ -25,13 +25,21 @@ function Component() {
## Error
```
Found 1 error:
Error: Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead
Variable `value` cannot be reassigned after render.
error.invalid-reassign-local-variable-in-async-callback.ts:8:6
6 | // after render, so this should error regardless of where this ends up
7 | // getting called
> 8 | value = result;
| ^^^^^ InvalidReact: Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `value` cannot be reassigned after render (8:8)
| ^^^^^ Reassigning a variable in an async function can cause inconsistent behavior on subsequent renders. Consider using state instead
9 | });
10 | };
11 |
```

View File

@@ -47,10 +47,16 @@ function Component() {
## Error
```
Found 1 error:
Error: Cannot reassign a variable after render completes
Reassigning variable `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-reassign-local-variable-in-effect.ts:7:4
5 |
6 | const reassignLocal = newValue => {
> 7 | local = newValue;
| ^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `local` cannot be reassigned after render (7:7)
| ^^^^^ Cannot reassign variable after render completes
8 | };
9 |
10 | const onMount = newValue => {

View File

@@ -48,10 +48,16 @@ function Component() {
## Error
```
Found 1 error:
Error: Cannot reassign a variable after render completes
Reassigning variable `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead
error.invalid-reassign-local-variable-in-hook-argument.ts:8:4
6 |
7 | const reassignLocal = newValue => {
> 8 | local = newValue;
| ^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `local` cannot be reassigned after render (8:8)
| ^^^^^ Cannot reassign variable after render completes
9 | };
10 |
11 | const callback = newValue => {

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