diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index cac897a9269..c704d44b55c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -59,7 +59,7 @@ export type CompilerDiagnosticDetail = */ { kind: 'error'; - loc: SourceLocation; + loc: SourceLocation | null; message: string; }; @@ -100,6 +100,12 @@ export class CompilerDiagnostic { this.options = options; } + static create( + options: Omit, + ): CompilerDiagnostic { + return new CompilerDiagnostic({...options, details: []}); + } + get category(): CompilerDiagnosticOptions['category'] { return this.options.category; } @@ -113,6 +119,11 @@ export class CompilerDiagnostic { 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; } @@ -127,7 +138,7 @@ export class CompilerDiagnostic { switch (detail.kind) { case 'error': { const loc = detail.loc; - if (typeof loc === 'symbol') { + if (loc == null || typeof loc === 'symbol') { continue; } let codeFrame: string; diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts index 5b5702f19c6..cd85656de24 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -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()) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts index 9c41ebcae19..569bbbdc2d6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateLocalsNotReassignedAfterRender.ts @@ -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; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts index 573db2f6b7d..b988183530e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoFreezingKnownMutableFunctions.ts @@ -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', + }), + ); } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts index 6e88773ecf0..85adb79ceb8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts @@ -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', + }), + ); } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts index 505302f7d12..eea6c0a08e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts @@ -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; } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts index e9b0fdb8870..3c810025a7e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts @@ -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', + }), + ); } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts index fc101581b30..81209d61c68 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts @@ -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; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts index c150c1fbd74..6bb59247dab 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts @@ -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 { @@ -519,14 +533,21 @@ class Visitor extends ReactiveFunctionVisitor { !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 { 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', + }), + ); } } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts index fd4d781ef3c..7c83e65dff0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts @@ -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 { } 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; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-old-inference-false-positive-ref-validation-in-use-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-old-inference-false-positive-ref-validation-in-use-effect.expect.md index 84370796a67..a33ff7ce765 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-old-inference-false-positive-ref-validation-in-use-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bug-old-inference-false-positive-ref-validation-in-use-effect.expect.md @@ -36,8 +36,10 @@ function Component() { ## Error ``` -Found 2 errors: -Error: 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 +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 | ); @@ -51,24 +53,19 @@ error.bug-old-inference-false-positive-ref-validation-in-use-effect.ts:20:12 > 23 | } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > 24 | }, [update]); - | ^^^^ 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 + | ^^^^ This function may (indirectly) reassign or modify local variables after render 25 | 26 | return 'ok'; 27 | } - -Error: The function modifies a local variable here - error.bug-old-inference-false-positive-ref-validation-in-use-effect.ts:14:6 12 | ...partialParams, 13 | }; > 14 | nextParams.param = 'value'; - | ^^^^^^^^^^ The function modifies a local variable here + | ^^^^^^^^^^ This modifies a local variable 15 | console.log(nextParams); 16 | }, 17 | [params] - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md index de50b215437..408537c5b8a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.context-variable-only-chained-assign.expect.md @@ -29,20 +29,18 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead +Error: Cannot reassign a variable after render completes -Variable `x` cannot be reassigned after render. +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); - | ^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + | ^ Cannot reassign variable after render completes 11 | return [invoke(fn1), copy2, identity(copy2)]; 12 | }; 13 | return invoke(fn2); - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md index 6823db842d2..2c1c7657f73 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.declare-reassign-variable-in-function-declaration.expect.md @@ -18,20 +18,18 @@ function Component() { ``` Found 1 error: -Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead +Error: Cannot reassign a variable after render completes -Variable `x` cannot be reassigned after render. +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; - | ^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + | ^ Cannot reassign variable after render completes 5 | } 6 | const y = bar(foo); 7 | return ; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md index 47af9952483..fba4e272ee9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.function-expression-references-variable-its-assigned-to.expect.md @@ -16,20 +16,18 @@ function Component() { ``` Found 1 error: -Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead +Error: Cannot reassign a variable after render completes -Variable `callback` cannot be reassigned after render. +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; - | ^^^^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + | ^^^^^^^^ Cannot reassign variable after render completes 4 | }; 5 | return
; 6 | } - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md index dcde3a9f83f..815a8fb2083 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md @@ -25,9 +25,9 @@ function Component(props) { ``` Found 1 error: -Memoization: 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 +Memoization: Compilation skipped because existing memoization could not be preserved -The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source. +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'; @@ -47,12 +47,10 @@ error.hoist-optional-member-expression-with-conditional-optional.ts:4:23 > 10 | return x; | ^^^^^^^^^^^^^^^^^ > 11 | }, [props?.items, props.cond]); - | ^^^^ 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 + | ^^^^ Could not preserve existing manual memoization 12 | return ( 13 | 14 | ); - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md index ea6683fd0ad..64065a7819a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional.expect.md @@ -25,9 +25,9 @@ function Component(props) { ``` Found 1 error: -Memoization: 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 +Memoization: Compilation skipped because existing memoization could not be preserved -The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source. +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'; @@ -47,12 +47,10 @@ error.hoist-optional-member-expression-with-conditional.ts:4:23 > 10 | return x; | ^^^^^^^^^^^^^^^^^ > 11 | }, [props?.items, props.cond]); - | ^^^^ 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 + | ^^^^ Could not preserve existing manual memoization 12 | return ( 13 | 14 | ); - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ReactUseMemo-async-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ReactUseMemo-async-callback.expect.md index 1c5c92d2c30..2f8fd0e6717 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ReactUseMemo-async-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ReactUseMemo-async-callback.expect.md @@ -18,6 +18,8 @@ function component(a, b) { 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 () => { @@ -25,12 +27,10 @@ error.invalid-ReactUseMemo-async-callback.ts:2:24 > 3 | await a; | ^^^^^^^^^^^^ > 4 | }, []); - | ^^^^ useMemo callbacks may not be async or generator functions + | ^^^^ Async and generator functions are not supported 5 | return x; 6 | } 7 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md index 5af5db112fb..0334a33cfee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md @@ -23,30 +23,30 @@ function Component({item, cond}) { ``` Found 2 errors: -Error: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) +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); - | ^^^^^^^^^^^ Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) + | ^^^^^^^^^^^ Found setState() within useMemo() 8 | setState(0); 9 | } 10 | }, [cond, key, init]); +Error: Calling setState from useMemo may trigger an infinite loop - -Error: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) +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); - | ^^^^^^^^ Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) + | ^^^^^^^^ Found setState() within useMemo() 9 | } 10 | }, [cond, key, init]); 11 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hook-function-argument-mutates-local-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hook-function-argument-mutates-local-variable.expect.md index d9e0ed390bb..8d9be03b427 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hook-function-argument-mutates-local-variable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hook-function-argument-mutates-local-variable.expect.md @@ -17,8 +17,10 @@ function useFoo() { ## Error ``` -Found 2 errors: -Error: 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 +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() { @@ -28,23 +30,18 @@ error.invalid-hook-function-argument-mutates-local-variable.ts:5:10 > 6 | cache.set('key', 'value'); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > 7 | }); - | ^^^^ 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 + | ^^^^ This function may (indirectly) reassign or modify local variables after render 8 | } 9 | - -Error: The function modifies a local variable here - error.invalid-hook-function-argument-mutates-local-variable.ts:6:4 4 | const cache = new Map(); 5 | useHook(() => { > 6 | cache.set('key', 'value'); - | ^^^^^ The function modifies a local variable here + | ^^^^^ This modifies a local variable 7 | }); 8 | } 9 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md index 025560bd505..073a15b7f52 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-nested-function-reassign-local-variable-in-effect.expect.md @@ -47,20 +47,18 @@ function Component() { ``` Found 1 error: -Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead +Error: Cannot reassign a variable after render completes -Variable `local` cannot be reassigned after render. +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; - | ^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + | ^^^^^ Cannot reassign variable after render completes 8 | }; 9 | return reassignLocal; 10 | }; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.expect.md index 196ce696b21..4a6cded7b8b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.expect.md @@ -19,9 +19,9 @@ function Component(props) { ``` Found 1 error: -Memoization: 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 +Memoization: Compilation skipped because existing memoization could not be preserved -The inferred dependency was `props.items.edges.nodes`, but the source dependencies were [props.items?.edges?.nodes]. Inferred different dependency than source. +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 @@ -35,12 +35,10 @@ error.invalid-optional-member-expression-as-memo-dep-non-optional-in-body.ts:3:2 > 6 | // deps are optional | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > 7 | }, [props.items?.edges?.nodes]); - | ^^^^ 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 + | ^^^^ Could not preserve existing manual memoization 8 | return ; 9 | } 10 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-mutable-function-as-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-mutable-function-as-prop.expect.md index 259a57c3e7c..50d3a9e6687 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-mutable-function-as-prop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-pass-mutable-function-as-prop.expect.md @@ -17,30 +17,27 @@ function Component() { ## Error ``` -Found 2 errors: -Error: 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 +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 ; - | ^^ 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 + | ^^ This function may (indirectly) reassign or modify local variables after render 8 | } 9 | - -Error: The function modifies a local variable here - error.invalid-pass-mutable-function-as-prop.ts:5:4 3 | const cache = new Map(); 4 | const fn = () => { > 5 | cache.set('key', 'value'); - | ^^^^^ The function modifies a local variable here + | ^^^^^ This modifies a local variable 6 | }; 7 | return ; 8 | } - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md index a8eee83aa10..02750e44720 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md @@ -16,20 +16,18 @@ function useFoo() { ``` Found 1 error: -Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead +Error: Cannot reassign a variable after render completes -Variable `x` cannot be reassigned after render. +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; - | ^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + | ^ Cannot reassign variable after render completes 5 | }; 6 | } 7 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md index 47e3509dbfd..3f8a81c106b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md @@ -48,20 +48,18 @@ function Component() { ``` Found 1 error: -Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead +Error: Cannot reassign a variable after render completes -Variable `local` cannot be reassigned after render. +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; - | ^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + | ^^^^^ Cannot reassign variable after render completes 8 | }; 9 | 10 | const onMount = newValue => { - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md index e9774d0c993..8bf6cd1ccc6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md @@ -49,20 +49,18 @@ function Component() { ``` Found 1 error: -Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead +Error: Cannot reassign a variable after render completes -Variable `local` cannot be reassigned after render. +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; - | ^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + | ^^^^^ Cannot reassign variable after render completes 9 | }; 10 | 11 | const callback = newValue => { - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index cd565964d55..ea7fd3d1d7b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -42,20 +42,18 @@ function Component() { ``` Found 1 error: -Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead +Error: Cannot reassign a variable after render completes -Variable `local` cannot be reassigned after render. +Reassigning variable `local` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead error.invalid-reassign-local-variable-in-jsx-callback.ts:5:4 3 | 4 | const reassignLocal = newValue => { > 5 | local = newValue; - | ^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + | ^^^^^ Cannot reassign variable after render completes 6 | }; 7 | 8 | const onClick = newValue => { - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-return-mutable-function-from-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-return-mutable-function-from-hook.expect.md index eb499847a36..767c05ec73b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-return-mutable-function-from-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-return-mutable-function-from-hook.expect.md @@ -19,8 +19,10 @@ function useFoo() { ## Error ``` -Found 2 errors: -Error: 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 +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-return-mutable-function-from-hook.ts:7:9 5 | useHook(); // for inference to kick in @@ -30,23 +32,18 @@ error.invalid-return-mutable-function-from-hook.ts:7:9 > 8 | cache.set('key', 'value'); | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > 9 | }; - | ^^^^ 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 + | ^^^^ This function may (indirectly) reassign or modify local variables after render 10 | } 11 | - -Error: The function modifies a local variable here - error.invalid-return-mutable-function-from-hook.ts:8:4 6 | const cache = new Map(); 7 | return () => { > 8 | cache.set('key', 'value'); - | ^^^^^ The function modifies a local variable here + | ^^^^^ This modifies a local variable 9 | }; 10 | } 11 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md index c29483ca981..156c0aff876 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md @@ -27,18 +27,18 @@ function useKeyedState({key, init}) { ``` Found 1 error: -Error: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) +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-setState-in-useMemo-indirect-useCallback.ts:13:4 11 | 12 | useMemo(() => { > 13 | fn(); - | ^^ Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) + | ^^ Found setState() within useMemo() 14 | }, [key, init]); 15 | 16 | return state; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo.expect.md index 8be4ac12a29..1cdd24d7311 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo.expect.md @@ -21,30 +21,30 @@ function useKeyedState({key, init}) { ``` Found 2 errors: -Error: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) +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-setState-in-useMemo.ts:6:4 4 | 5 | useMemo(() => { > 6 | setPrevKey(key); - | ^^^^^^^^^^ Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) + | ^^^^^^^^^^ Found setState() within useMemo() 7 | setState(init); 8 | }, [key, init]); 9 | +Error: Calling setState from useMemo may trigger an infinite loop - -Error: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) +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-setState-in-useMemo.ts:7:4 5 | useMemo(() => { 6 | setPrevKey(key); > 7 | setState(init); - | ^^^^^^^^ Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) + | ^^^^^^^^ Found setState() within useMemo() 8 | }, [key, init]); 9 | 10 | return state; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md index af590101c0f..88788bbfee3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-uncalled-function-capturing-mutable-values-memoizes-with-captures-values.expect.md @@ -47,8 +47,10 @@ hook useMemoMap( ## Error ``` -Found 2 errors: -Error: 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 +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 undefined:21:9 19 | map: TInput => TOutput @@ -86,23 +88,18 @@ undefined:21:9 > 36 | }; | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > 37 | }, [map]); - | ^^^^^^^^^^^^ 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 + | ^^^^^^^^^^^^ This function may (indirectly) reassign or modify local variables after render 38 | } 39 | - -Error: The function modifies a local variable here - undefined:33:8 31 | if (output == null) { 32 | output = map(input); > 33 | cache.set(input, output); - | ^^^^^ The function modifies a local variable here + | ^^^^^ This modifies a local variable 34 | } 35 | return output; 36 | }; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md index 0c15a0f632f..82138d8a5a8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unconditional-set-state-in-render.expect.md @@ -20,30 +20,30 @@ function Component(props) { ``` Found 2 errors: -Error: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState) +Error: Calling setState during render may trigger an infinite loop + +Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState) error.invalid-unconditional-set-state-in-render.ts:6:2 4 | const aliased = setX; 5 | > 6 | setX(1); - | ^^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState) + | ^^^^ Found setState() within useMemo() 7 | aliased(2); 8 | 9 | return x; +Error: Calling setState during render may trigger an infinite loop - -Error: This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState) +Calling setState during render will trigger another render, and can lead to infinite loops. (https://react.dev/reference/react/useState) error.invalid-unconditional-set-state-in-render.ts:7:2 5 | 6 | setX(1); > 7 | aliased(2); - | ^^^^^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState) + | ^^^^^^^ Found setState() within useMemo() 8 | 9 | return x; 10 | } - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-async-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-async-callback.expect.md index 272ed7ab8c0..7fc99985f1a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-async-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-async-callback.expect.md @@ -18,6 +18,8 @@ function component(a, b) { 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-useMemo-async-callback.ts:2:18 1 | function component(a, b) { > 2 | let x = useMemo(async () => { @@ -25,12 +27,10 @@ error.invalid-useMemo-async-callback.ts:2:18 > 3 | await a; | ^^^^^^^^^^^^ > 4 | }, []); - | ^^^^ useMemo callbacks may not be async or generator functions + | ^^^^ Async and generator functions are not supported 5 | return x; 6 | } 7 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-callback-args.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-callback-args.expect.md index 6c370909175..97996884b25 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-callback-args.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-callback-args.expect.md @@ -14,17 +14,17 @@ function component(a, b) { ``` Found 1 error: -Error: useMemo callbacks may not accept any arguments +Error: useMemo() callbacks may not accept parameters + +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. error.invalid-useMemo-callback-args.ts:2:18 1 | function component(a, b) { > 2 | let x = useMemo(c => a, []); - | ^^^^^^ useMemo callbacks may not accept any arguments + | ^ 3 | return x; 4 | } 5 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md index 151c1a20c72..e284cb78145 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md @@ -33,20 +33,18 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead +Error: Cannot reassign a variable after render completes -Variable `a` cannot be reassigned after render. +Reassigning variable `a` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead error.mutable-range-shared-inner-outer-function.ts:8:6 6 | const f = () => { 7 | if (cond) { > 8 | a = {}; - | ^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + | ^ Cannot reassign variable after render completes 9 | b = []; 10 | } else { 11 | a = {}; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-like-name-not-Ref.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-like-name-not-Ref.expect.md index a86193b84fd..838e7a5b6dc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-like-name-not-Ref.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.ref-like-name-not-Ref.expect.md @@ -32,9 +32,9 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Memoization: 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 +Memoization: Compilation skipped because existing memoization could not be preserved -The inferred dependency was `Ref.current`, but the source dependencies were []. Inferred dependency not present in source. +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `Ref.current`, but the source dependencies were []. Inferred dependency not present in source. error.ref-like-name-not-Ref.ts:11:30 9 | const Ref = useCustomRef(); @@ -44,12 +44,10 @@ error.ref-like-name-not-Ref.ts:11:30 > 12 | Ref.current?.click(); | ^^^^^^^^^^^^^^^^^^^^^^^^^ > 13 | }, []); - | ^^^^ 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 + | ^^^^ Could not preserve existing manual memoization 14 | 15 | return