From 72848027a5525d7beebeccb0a485f4f211a1a101 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:39:42 -0700 Subject: [PATCH 1/7] [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 --- .../src/CompilerError.ts | 15 ++++- .../src/HIR/BuildHIR.ts | 56 ++++++++++++------- .../ValidateLocalsNotReassignedAfterRender.ts | 25 +++++---- ...ValidateNoFreezingKnownMutableFunctions.ts | 30 ++++++---- .../ValidateNoImpureFunctionsInRender.ts | 29 ++++++---- .../Validation/ValidateNoJSXInTryStatement.ts | 18 ++++-- .../Validation/ValidateNoSetStateInEffects.ts | 29 +++++++--- .../Validation/ValidateNoSetStateInRender.ts | 50 +++++++++++------ .../src/Validation/ValidateUseMemo.ts | 53 ++++++++++++------ ...ive-ref-validation-in-use-effect.expect.md | 15 ++--- ...ext-variable-only-chained-assign.expect.md | 8 +-- ...variable-in-function-declaration.expect.md | 8 +-- ...erences-variable-its-assigned-to.expect.md | 8 +-- ...alid-ReactUseMemo-async-callback.expect.md | 6 +- ...-conditional-setState-in-useMemo.expect.md | 14 ++--- ...-argument-mutates-local-variable.expect.md | 15 ++--- ...eassign-local-variable-in-effect.expect.md | 8 +-- ...id-pass-mutable-function-as-prop.expect.md | 15 ++--- ...ssign-local-in-hook-return-value.expect.md | 8 +-- ...eassign-local-variable-in-effect.expect.md | 8 +-- ...-local-variable-in-hook-argument.expect.md | 8 +-- ...n-local-variable-in-jsx-callback.expect.md | 8 +-- ...eturn-mutable-function-from-hook.expect.md | 15 ++--- ...-in-useMemo-indirect-useCallback.expect.md | 8 +-- ...rror.invalid-setState-in-useMemo.expect.md | 14 ++--- ...es-memoizes-with-captures-values.expect.md | 15 ++--- ...nconditional-set-state-in-render.expect.md | 14 ++--- ...r.invalid-useMemo-async-callback.expect.md | 6 +- ...or.invalid-useMemo-callback-args.expect.md | 8 +-- ...ange-shared-inner-outer-function.expect.md | 8 +-- ...ences-later-variable-declaration.expect.md | 8 +-- ...state-in-render-after-loop-break.expect.md | 8 +-- ...l-set-state-in-render-after-loop.expect.md | 8 +-- ...-state-in-render-with-loop-throw.expect.md | 8 +-- ...r.unconditional-set-state-lambda.expect.md | 8 +-- ...tate-nested-function-expressions.expect.md | 8 +-- ...in-catch-in-outer-try-with-catch.expect.md | 2 +- .../invalid-jsx-in-try-with-catch.expect.md | 2 +- ...setState-in-useEffect-transitive.expect.md | 2 +- .../invalid-setState-in-useEffect.expect.md | 2 +- ...n-local-variable-in-jsx-callback.expect.md | 8 +-- 41 files changed, 324 insertions(+), 262 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index cac897a926..c704d44b55 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 5b5702f19c..cd85656de2 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 9c41ebcae1..569bbbdc2d 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 573db2f6b7..b988183530 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 6e88773ecf..85adb79ceb 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 505302f7d1..eea6c0a08e 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 e9b0fdb887..3c810025a7 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 fc101581b3..81209d61c6 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/ValidateUseMemo.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts index fd4d781ef3..7c83e65dff 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 84370796a6..a33ff7ce76 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 de50b21543..408537c5b8 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 6823db842d..2c1c7657f7 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 47af995248..fba4e272ee 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.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 1c5c92d2c3..2f8fd0e671 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 5af5db112f..0334a33cfe 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 d9e0ed390b..8d9be03b42 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 025560bd50..073a15b7f5 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-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 259a57c3e7..50d3a9e668 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 a8eee83aa1..02750e4472 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 47e3509dbf..3f8a81c106 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 e9774d0c99..8bf6cd1ccc 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 cd565964d5..ea7fd3d1d7 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 eb499847a3..767c05ec73 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 c29483ca98..156c0aff87 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 8be4ac12a2..1cdd24d731 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 af590101c0..88788bbfee 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 0c15a0f632..82138d8a5a 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 272ed7ab8c..7fc99985f1 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 6c37090917..97996884b2 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 151c1a20c7..e284cb7814 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.todo-function-expression-references-later-variable-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md index ce4814b172..f151b624e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-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 `onClick` cannot be reassigned after render. +Reassigning variable `onClick` after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead error.todo-function-expression-references-later-variable-declaration.ts:3:4 1 | function Component() { 2 | let callback = () => { > 3 | onClick = () => {}; - | ^^^^^^^ 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 | let onClick; 6 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md index a90a9fd423..1a51d17fb4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop-break.expect.md @@ -23,18 +23,18 @@ function Component(props) { ``` Found 1 error: -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.unconditional-set-state-in-render-after-loop-break.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState) + | ^^^^^^^^ Found setState() within useMemo() 12 | return state; 13 | } 14 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md index 65c1a8caae..903ee395f4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-after-loop.expect.md @@ -18,18 +18,18 @@ function Component(props) { ``` Found 1 error: -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.unconditional-set-state-in-render-after-loop.ts:6:2 4 | for (const _ of props) { 5 | } > 6 | setState(true); - | ^^^^^^^^ 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 | return state; 8 | } 9 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md index c2f70e67ca..de829f66fc 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-in-render-with-loop-throw.expect.md @@ -23,18 +23,18 @@ function Component(props) { ``` Found 1 error: -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.unconditional-set-state-in-render-with-loop-throw.ts:11:2 9 | } 10 | } > 11 | setState(true); - | ^^^^^^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState) + | ^^^^^^^^ Found setState() within useMemo() 12 | return state; 13 | } 14 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md index 12332ae299..447183b79c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-lambda.expect.md @@ -21,18 +21,18 @@ function Component(props) { ``` Found 1 error: -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.unconditional-set-state-lambda.ts:8:2 6 | setX(1); 7 | }; > 8 | foo(); - | ^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState) + | ^^^ Found setState() within useMemo() 9 | 10 | return [x]; 11 | } - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md index 4d43240997..dcd643edb2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.unconditional-set-state-nested-function-expressions.expect.md @@ -29,18 +29,18 @@ function Component(props) { ``` Found 1 error: -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.unconditional-set-state-nested-function-expressions.ts:16:2 14 | bar(); 15 | }; > 16 | baz(); - | ^^^ This is an unconditional set state during render, which will trigger an infinite loop. (https://react.dev/reference/react/useState) + | ^^^ Found setState() within useMemo() 17 | 18 | return [x]; 19 | } - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md index 9f4d85de70..6583f0a4e2 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md @@ -65,7 +65,7 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"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)","description":null,"severity":"InvalidReact","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"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)","details":[{"kind":"error","loc":{"start":{"line":11,"column":11,"index":222},"end":{"line":11,"column":32,"index":243},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":17,"column":1,"index":298},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md index 9fe89f25ee..b6e9e87ada 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md @@ -42,7 +42,7 @@ function Component(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"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)","description":null,"severity":"InvalidReact","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"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)","details":[{"kind":"error","loc":{"start":{"line":5,"column":9,"index":104},"end":{"line":5,"column":16,"index":111},"filename":"invalid-jsx-in-try-with-catch.ts"},"message":"Avoid constructing JSX within try/catch"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":49},"end":{"line":10,"column":1,"index":160},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md index b09fd352f2..4e9e1f2c3a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md @@ -65,7 +65,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"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":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"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":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":13,"column":4,"index":265},"end":{"line":13,"column":5,"index":266},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"},"message":"Avoid calling setState() in the top-level of an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":16,"column":1,"index":293},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md index 41390e5ce0..8d902c2c7d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md @@ -45,7 +45,7 @@ function _temp(s) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"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":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"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":"InvalidReact","suggestions":null,"details":[{"kind":"error","loc":{"start":{"line":7,"column":4,"index":180},"end":{"line":7,"column":12,"index":188},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"},"message":"Avoid calling setState() in the top-level of an effect"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":92},"end":{"line":10,"column":1,"index":225},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md index cca5515ab7..2050ea107c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md @@ -43,20 +43,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:6:4 4 | 5 | const reassignLocal = newValue => { > 6 | 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 7 | }; 8 | 9 | const onClick = newValue => { - - ``` \ No newline at end of file From 48bc166428404c948315d98d02fe69533956e319 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:39:53 -0700 Subject: [PATCH 2/7] [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 --- .../ValidatePreservedManualMemoization.ts | 110 +++++++++++------- ...ession-with-conditional-optional.expect.md | 8 +- ...mber-expression-with-conditional.expect.md | 8 +- ...as-memo-dep-non-optional-in-body.expect.md | 8 +- .../error.ref-like-name-not-Ref.expect.md | 8 +- .../error.ref-like-name-not-a-ref.expect.md | 8 +- ...ed-function-inferred-as-mutation.expect.md | 8 +- ...from-inferred-mutation-in-logger.expect.md | 20 ++-- ...ack-captured-in-context-variable.expect.md | 8 +- .../dynamic-gating-bailout-nopanic.expect.md | 2 +- ...back-captures-reassigned-context.expect.md | 14 +-- ...ropped-infer-always-invalidating.expect.md | 8 +- ...sitive-useMemo-infer-mutate-deps.expect.md | 8 +- ...-positive-useMemo-overlap-scopes.expect.md | 8 +- ...ack-conditional-access-own-scope.expect.md | 8 +- ...ck-infer-conditional-value-block.expect.md | 16 +-- ...back-captures-reassigned-context.expect.md | 14 +-- ...nvalid-useCallback-read-maybeRef.expect.md | 8 +- ...be-invalid-useMemo-read-maybeRef.expect.md | 8 +- ...ve-use-memo-ref-missing-reactive.expect.md | 8 +- ...back-captures-invalidating-value.expect.md | 8 +- .../error.useCallback-aliased-var.expect.md | 8 +- ...lback-conditional-access-noAlloc.expect.md | 8 +- ...less-specific-conditional-access.expect.md | 8 +- ...or.useCallback-property-call-dep.expect.md | 8 +- .../error.useMemo-aliased-var.expect.md | 8 +- ...less-specific-conditional-access.expect.md | 8 +- ...specific-conditional-value-block.expect.md | 16 +-- ...emo-property-call-chained-object.expect.md | 8 +- .../error.useMemo-property-call-dep.expect.md | 8 +- ...o-unrelated-mutation-in-depslist.expect.md | 8 +- ...ession-with-conditional-optional.expect.md | 8 +- ...mber-expression-with-conditional.expect.md | 8 +- 33 files changed, 191 insertions(+), 209 deletions(-) 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 c150c1fbd7..6bb59247da 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/__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 dcde3a9f83..815a8fb208 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 ea6683fd0a..64065a7819 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-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 196ce696b2..4a6cded7b8 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.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 a86193b84f..838e7a5b6d 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 ; 11 | }); - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.invalid-rules-of-hooks-8566f9a360e2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.invalid-rules-of-hooks-8566f9a360e2.expect.md index 4d2c55cdaa..520a8e4097 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.invalid-rules-of-hooks-8566f9a360e2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.invalid-rules-of-hooks-8566f9a360e2.expect.md @@ -21,6 +21,7 @@ const MemoizedButton = memo(function (props) { ``` 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) todo.error.invalid-rules-of-hooks-8566f9a360e2.ts:8:4 @@ -31,8 +32,6 @@ todo.error.invalid-rules-of-hooks-8566f9a360e2.ts:8:4 9 | } 10 | return ; 11 | }); - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.invalid-rules-of-hooks-a0058f0b446d.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.invalid-rules-of-hooks-a0058f0b446d.expect.md index 47d099c101..acd4ff9395 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.invalid-rules-of-hooks-a0058f0b446d.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.invalid-rules-of-hooks-a0058f0b446d.expect.md @@ -20,6 +20,7 @@ function ComponentWithConditionalHook() { ``` 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) todo.error.invalid-rules-of-hooks-a0058f0b446d.ts:8:4 @@ -30,8 +31,6 @@ todo.error.invalid-rules-of-hooks-a0058f0b446d.ts:8:4 9 | } 10 | } 11 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-27c18dc8dad2.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-27c18dc8dad2.expect.md index b3f75f3ab8..8f2783f969 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-27c18dc8dad2.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-27c18dc8dad2.expect.md @@ -21,6 +21,7 @@ const FancyButton = React.forwardRef((props, ref) => { ``` 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) todo.error.rules-of-hooks-27c18dc8dad2.ts:8:4 @@ -31,8 +32,6 @@ todo.error.rules-of-hooks-27c18dc8dad2.ts:8:4 9 | } 10 | return ; 11 | }); - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-d0935abedc42.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-d0935abedc42.expect.md index d5dd79b964..343c51787e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-d0935abedc42.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-d0935abedc42.expect.md @@ -20,6 +20,7 @@ React.unknownFunction((foo, bar) => { ``` 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) todo.error.rules-of-hooks-d0935abedc42.ts:8:4 @@ -30,8 +31,6 @@ todo.error.rules-of-hooks-d0935abedc42.ts:8:4 9 | } 10 | }); 11 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-e29c874aa913.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-e29c874aa913.expect.md index d5e2cbcb83..a9960ad44d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-e29c874aa913.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/todo.error.rules-of-hooks-e29c874aa913.expect.md @@ -21,6 +21,7 @@ function useHook() { ``` 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) todo.error.rules-of-hooks-e29c874aa913.ts:9:4 @@ -31,8 +32,6 @@ todo.error.rules-of-hooks-e29c874aa913.ts:9:4 10 | } catch {} 11 | } 12 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md index af8103b7ae..1224a5b9cf 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-conditionally-assigned-dynamically-constructed-component-in-render.expect.md @@ -50,8 +50,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":9,"column":10,"index":202},"end":{"line":9,"column":19,"index":211},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":5,"column":16,"index":124},"end":{"line":5,"column":33,"index":141},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":9,"column":10,"index":202},"end":{"line":9,"column":19,"index":211},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":5,"column":16,"index":124},"end":{"line":5,"column":33,"index":141},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":10,"column":1,"index":217},"filename":"invalid-conditionally-assigned-dynamically-constructed-component-in-render.ts"},"fnName":"Example","memoSlots":3,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md index 7720863da3..7bec7f73b5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-construct-component-in-render.expect.md @@ -32,8 +32,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":120},"end":{"line":4,"column":19,"index":129},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":37,"index":108},"filename":"invalid-dynamically-construct-component-in-render.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":120},"end":{"line":4,"column":19,"index":129},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":37,"index":108},"filename":"invalid-dynamically-construct-component-in-render.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":135},"filename":"invalid-dynamically-construct-component-in-render.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md index 8d218bf24b..e7f9ad7f30 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-function.expect.md @@ -37,8 +37,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":6,"column":10,"index":130},"end":{"line":6,"column":19,"index":139},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":2,"index":73},"end":{"line":5,"column":3,"index":119},"filename":"invalid-dynamically-constructed-component-function.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":6,"column":10,"index":130},"end":{"line":6,"column":19,"index":139},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":2,"index":73},"end":{"line":5,"column":3,"index":119},"filename":"invalid-dynamically-constructed-component-function.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":7,"column":1,"index":145},"filename":"invalid-dynamically-constructed-component-function.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md index e3bc7a5eb5..f6220645ef 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-method-call.expect.md @@ -41,8 +41,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":118},"end":{"line":4,"column":19,"index":127},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":35,"index":106},"filename":"invalid-dynamically-constructed-component-method-call.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":118},"end":{"line":4,"column":19,"index":127},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":35,"index":106},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":133},"filename":"invalid-dynamically-constructed-component-method-call.ts"},"fnName":"Example","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md index 02e9f4f4a4..4a72cdd855 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/static-components/invalid-dynamically-constructed-component-new.expect.md @@ -32,8 +32,7 @@ function Example(props) { ## Logs ``` -{"kind":"CompileError","detail":{"options":{"reason":"Components created during render will reset their state each time they are created. Declare components outside of render. ","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":4,"column":10,"index":125},"end":{"line":4,"column":19,"index":134},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} -{"kind":"CompileError","detail":{"options":{"reason":"The component may be created during render","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":42,"index":113},"filename":"invalid-dynamically-constructed-component-new.ts"}}},"fnLoc":null} +{"kind":"CompileError","detail":{"options":{"severity":"InvalidReact","category":"Cannot create components during render","description":"Components created during render will reset their state each time they are created. Declare components outside of render. ","details":[{"kind":"error","loc":{"start":{"line":4,"column":10,"index":125},"end":{"line":4,"column":19,"index":134},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"This component is created during render"},{"kind":"error","loc":{"start":{"line":3,"column":20,"index":91},"end":{"line":3,"column":42,"index":113},"filename":"invalid-dynamically-constructed-component-new.ts"},"message":"The component is created during render here"}]}},"fnLoc":null} {"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":45},"end":{"line":5,"column":1,"index":140},"filename":"invalid-dynamically-constructed-component-new.ts"},"fnName":"Example","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} ``` diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.error.object-pattern-computed-key.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.error.object-pattern-computed-key.expect.md index 8380739121..7bc1e49069 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.error.object-pattern-computed-key.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/todo.error.object-pattern-computed-key.expect.md @@ -22,6 +22,7 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: + Todo: (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern todo.error.object-pattern-computed-key.ts:5:9 @@ -32,8 +33,6 @@ todo.error.object-pattern-computed-key.ts:5:9 6 | return value; 7 | } 8 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md index 7e9247c5ae..006d2a49c0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.todo-syntax.expect.md @@ -30,6 +30,7 @@ function Component({prop1}) { ``` Found 1 error: + Error: [Fire] Untransformed reference to compiler-required feature. Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:4) diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md index 7ec5c5320f..8481ed2c57 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.untransformed-fire-reference.expect.md @@ -14,6 +14,7 @@ console.log(fire == null); ``` Found 1 error: + Error: [Fire] Untransformed reference to compiler-required feature. null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md index 55c9cfcb2c..f84686bc36 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/bailout-retry/error.use-no-memo.expect.md @@ -31,6 +31,7 @@ function Component({props, bar}) { ``` Found 1 error: + Error: [Fire] Untransformed reference to compiler-required feature. null diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-mix-fire-and-no-fire.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-mix-fire-and-no-fire.expect.md index ad15e74d97..1eb6bf66e9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-mix-fire-and-no-fire.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-mix-fire-and-no-fire.expect.md @@ -28,6 +28,7 @@ function Component(props) { ``` Found 1 error: + Error: Cannot compile `fire` All uses of foo must be either used with a fire() call in this effect or not used with a fire() call at all. foo was used with fire() on line 10:10 in this effect. @@ -40,8 +41,6 @@ error.invalid-mix-fire-and-no-fire.ts:11:6 12 | } 13 | 14 | nested(); - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-multiple-args.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-multiple-args.expect.md index 8cb5ce3d78..c519d43fb4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-multiple-args.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-multiple-args.expect.md @@ -23,6 +23,7 @@ function Component({bar, baz}) { ``` Found 1 error: + Error: Cannot compile `fire` fire() can only take in a single call expression as an argument but received multiple arguments. @@ -35,8 +36,6 @@ error.invalid-multiple-args.ts:9:4 10 | }); 11 | 12 | return null; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-nested-use-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-nested-use-effect.expect.md index c36f0b4db9..2e767c3c71 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-nested-use-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-nested-use-effect.expect.md @@ -29,6 +29,7 @@ function Component(props) { ``` Found 1 error: + Error: Hooks must be called at the top level in the body of a function component or custom hook, and may not be called within function expressions. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) Cannot call useEffect within a function expression. @@ -41,8 +42,6 @@ error.invalid-nested-use-effect.ts:9:4 10 | function nested() { 11 | fire(foo(props)); 12 | } - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-not-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-not-call.expect.md index a66ddd3350..40c4bc5394 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-not-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-not-call.expect.md @@ -23,6 +23,7 @@ function Component(props) { ``` Found 1 error: + Error: Cannot compile `fire` `fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed. @@ -35,8 +36,6 @@ error.invalid-not-call.ts:9:4 10 | }); 11 | 12 | return null; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md index 3f752a4a44..81c36a362c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-outside-effect.expect.md @@ -25,6 +25,7 @@ function Component({props, bar}) { ``` Found 2 errors: + Invariant: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. @@ -38,7 +39,6 @@ error.invalid-outside-effect.ts:8:2 10 | useCallback(() => { 11 | fire(foo(props)); - Invariant: Cannot compile `fire` Cannot use `fire` outside of a useEffect function. @@ -51,8 +51,6 @@ error.invalid-outside-effect.ts:11:4 12 | }, [foo, props]); 13 | 14 | return null; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md index 846816b7d4..96cea9c08f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-no-array-literal.expect.md @@ -26,6 +26,7 @@ function Component(props) { ``` Found 1 error: + Invariant: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. @@ -38,8 +39,6 @@ error.invalid-rewrite-deps-no-array-literal.ts:13:5 14 | 15 | return null; 16 | } - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md index 436515da99..4dc5336ebe 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-rewrite-deps-spread.expect.md @@ -29,6 +29,7 @@ function Component(props) { ``` Found 1 error: + Invariant: Cannot compile `fire` You must use an array literal for an effect dependency array when that effect uses `fire()`. @@ -41,8 +42,6 @@ error.invalid-rewrite-deps-spread.ts:15:7 16 | ); 17 | 18 | return null; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-spread.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-spread.expect.md index 0c232de974..dcd302bbe1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-spread.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.invalid-spread.expect.md @@ -23,6 +23,7 @@ function Component(props) { ``` Found 1 error: + Error: Cannot compile `fire` fire() can only take in a single call expression as an argument but received a spread argument. @@ -35,8 +36,6 @@ error.invalid-spread.ts:9:4 10 | }); 11 | 12 | return null; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.todo-method.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.todo-method.expect.md index 9515d32eb7..67410297f3 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.todo-method.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/transform-fire/error.todo-method.expect.md @@ -23,6 +23,7 @@ function Component(props) { ``` Found 1 error: + Error: Cannot compile `fire` `fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed. @@ -35,8 +36,6 @@ error.todo-method.ts:9:4 10 | }); 11 | 12 | return null; - - ``` \ No newline at end of file diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts index 6e931e467b..bff40e9649 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts @@ -159,7 +159,7 @@ const tests: CompilerTestCases = { message: /Handle var kinds in VariableDeclaration/, }, { - message: /Mutating component props or hook arguments is not allowed/, + message: /Modifying component props or hook arguments is not allowed/, }, ], }, @@ -195,7 +195,7 @@ const tests: CompilerTestCases = { errors: [ { message: - /Unexpected reassignment of a variable which was defined outside of the component/, + /Cannot reassign variables declared outside of the component\/hook/, }, ], }, diff --git a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts index 071bfb2e7b..5a2bea6852 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts @@ -61,7 +61,7 @@ const tests: CompilerTestCases = { `, errors: [ { - message: /Mutating a value returned from 'useState\(\)'/, + message: /Modifying a value returned from 'useState\(\)'/, line: 7, }, ], diff --git a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts index 51bc4e0753..1d32601286 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -200,7 +200,7 @@ const rule: Rule.RuleModule = { end: endLoc, }; context.report({ - message: `${detail.printErrorMessage(sourceCode.text)} ${locStr}`, + message: `${detail.printErrorMessage(sourceCode.text, {eslint: true})} ${locStr}`, loc: firstLineLoc, suggest, }); @@ -223,7 +223,9 @@ const rule: Rule.RuleModule = { } if (loc != null) { context.report({ - message: detail.printErrorMessage(sourceCode.text), + message: detail.printErrorMessage(sourceCode.text, { + eslint: true, + }), loc, suggest, }); diff --git a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts index 1da7d8f57f..a636c59375 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts +++ b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts @@ -161,7 +161,7 @@ const tests: CompilerTestCases = { message: /Handle var kinds in VariableDeclaration/, }, { - message: /Mutating component props or hook arguments is not allowed/, + message: /Modifying component props or hook arguments is not allowed/, }, ], }, @@ -197,7 +197,7 @@ const tests: CompilerTestCases = { errors: [ { message: - /Unexpected reassignment of a variable which was defined outside of the component/, + /Cannot reassign variables declared outside of the component\/hook/, }, ], }, diff --git a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts index 28133aee7b..9d05cd1871 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts @@ -63,7 +63,7 @@ const tests: CompilerTestCases = { `, errors: [ { - message: /Mutating a value returned from 'useState\(\)'/, + message: /Modifying a value returned from 'useState\(\)'/, line: 7, }, ], diff --git a/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts index 254962d99c..0492c2c30c 100644 --- a/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts +++ b/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts @@ -202,7 +202,7 @@ const rule: Rule.RuleModule = { end: endLoc, }; context.report({ - message: `${detail.printErrorMessage(sourceCode.text)} ${locStr}`, + message: `${detail.printErrorMessage(sourceCode.text, {eslint: true})} ${locStr}`, loc: firstLineLoc, suggest, }); @@ -225,7 +225,9 @@ const rule: Rule.RuleModule = { } if (loc != null) { context.report({ - message: detail.printErrorMessage(sourceCode.text), + message: detail.printErrorMessage(sourceCode.text, { + eslint: true, + }), loc, suggest, }); From 8c29a45fb68c096c442b32ee7449f995240d6025 Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Thu, 24 Jul 2025 15:41:42 -0700 Subject: [PATCH 5/7] [compiler] Use new diagnostic printing in playground Per title --- .../components/Editor/EditorImpl.tsx | 9 +++-- .../playground/components/Editor/Input.tsx | 9 ++++- .../playground/components/Editor/Output.tsx | 38 ++++++++++++------- .../lib/reactCompilerMonacoDiagnostics.ts | 32 +++++++++++----- .../src/Babel/BabelPlugin.ts | 4 +- .../src/CompilerError.ts | 14 ++++++- 6 files changed, 72 insertions(+), 34 deletions(-) diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 02813a8d2f..27d2fd7989 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -11,6 +11,7 @@ import * as t from '@babel/types'; import BabelPluginReactCompiler, { CompilerError, CompilerErrorDetail, + CompilerDiagnostic, Effect, ErrorSeverity, parseConfigPragmaForTests, @@ -144,7 +145,7 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { const results = new Map>(); const error = new CompilerError(); - const otherErrors: Array = []; + const otherErrors: Array = []; const upsert: (result: PrintedCompilerPipelineValue) => void = result => { const entry = results.get(result.name); if (Array.isArray(entry)) { @@ -214,7 +215,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { debugLogIRs: logIR, logEvent: (_filename: string | null, event: LoggerEvent) => { if (event.kind === 'CompileError') { - otherErrors.push(new CompilerErrorDetail(event.detail)); + otherErrors.push(event.detail); } }, }, @@ -226,7 +227,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { * (i.e. object shape that is not CompilerError) */ if (err instanceof CompilerError && err.details.length > 0) { - error.details.push(...err.details); + error.merge(err); } else { /** * Handle unexpected failures by logging (to get a stack trace) @@ -245,7 +246,7 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { } // Only include logger errors if there weren't other errors if (!error.hasErrors() && otherErrors.length !== 0) { - otherErrors.forEach(e => error.push(e)); + otherErrors.forEach(e => error.details.push(e)); } if (error.hasErrors()) { return [{kind: 'err', results, error: error}, language]; diff --git a/compiler/apps/playground/components/Editor/Input.tsx b/compiler/apps/playground/components/Editor/Input.tsx index 0992591183..5cfa56d77f 100644 --- a/compiler/apps/playground/components/Editor/Input.tsx +++ b/compiler/apps/playground/components/Editor/Input.tsx @@ -36,13 +36,18 @@ export default function Input({errors, language}: Props): JSX.Element { const uri = monaco.Uri.parse(`file:///index.js`); const model = monaco.editor.getModel(uri); invariant(model, 'Model must exist for the selected input file.'); - renderReactCompilerMarkers({monaco, model, details: errors}); + renderReactCompilerMarkers({ + monaco, + model, + details: errors, + source: store.source, + }); /** * N.B. that `tabSize` is a model property, not an editor property. * So, the tab size has to be set per model. */ model.updateOptions({tabSize: 2}); - }, [monaco, errors]); + }, [monaco, errors, store.source]); useEffect(() => { /** diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 7886f11e62..36d20a0125 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -142,6 +142,17 @@ async function tabify( , ); } + } else if (compilerOutput.kind === 'err') { + const errors = compilerOutput.error.printErrorMessage(source, { + eslint: false, + }); + reorderedTabs.set( + 'Errors', + , + ); } tabs.forEach((tab, name) => { reorderedTabs.set(name, tab); @@ -166,6 +177,19 @@ function Output({store, compilerOutput}: Props): JSX.Element { const [tabs, setTabs] = useState>( () => new Map(), ); + + /* + * Update the active tab back to the output or errors tab when the compilation state + * changes between success/failure. + */ + const [previousOutputKind, setPreviousOutputKind] = useState( + compilerOutput.kind, + ); + if (compilerOutput.kind !== previousOutputKind) { + setPreviousOutputKind(compilerOutput.kind); + setTabsOpen(new Set([compilerOutput.kind === 'ok' ? 'JS' : 'Errors'])); + } + useEffect(() => { tabify(store.source, compilerOutput).then(tabs => { setTabs(tabs); @@ -196,20 +220,6 @@ function Output({store, compilerOutput}: Props): JSX.Element { tabs={tabs} changedPasses={changedPasses} /> - {compilerOutput.kind === 'err' ? ( -
-
-

COMPILER ERRORS

-
-
-            {compilerOutput.error.toString()}
-          
-
- ) : null} ); } diff --git a/compiler/apps/playground/lib/reactCompilerMonacoDiagnostics.ts b/compiler/apps/playground/lib/reactCompilerMonacoDiagnostics.ts index a800e25773..cece2fa7c6 100644 --- a/compiler/apps/playground/lib/reactCompilerMonacoDiagnostics.ts +++ b/compiler/apps/playground/lib/reactCompilerMonacoDiagnostics.ts @@ -6,7 +6,11 @@ */ import {Monaco} from '@monaco-editor/react'; -import {CompilerErrorDetail, ErrorSeverity} from 'babel-plugin-react-compiler'; +import { + CompilerDiagnostic, + CompilerErrorDetail, + ErrorSeverity, +} from 'babel-plugin-react-compiler'; import {MarkerSeverity, type editor} from 'monaco-editor'; function mapReactCompilerSeverityToMonaco( @@ -22,38 +26,46 @@ function mapReactCompilerSeverityToMonaco( } function mapReactCompilerDiagnosticToMonacoMarker( - detail: CompilerErrorDetail, + detail: CompilerErrorDetail | CompilerDiagnostic, monaco: Monaco, + source: string, ): editor.IMarkerData | null { - if (detail.loc == null || typeof detail.loc === 'symbol') { + const loc = detail.primaryLocation(); + if (loc == null || typeof loc === 'symbol') { return null; } const severity = mapReactCompilerSeverityToMonaco(detail.severity, monaco); - let message = detail.printErrorMessage(); + let message = detail.printErrorMessage(source, {eslint: true}); return { severity, message, - startLineNumber: detail.loc.start.line, - startColumn: detail.loc.start.column + 1, - endLineNumber: detail.loc.end.line, - endColumn: detail.loc.end.column + 1, + startLineNumber: loc.start.line, + startColumn: loc.start.column + 1, + endLineNumber: loc.end.line, + endColumn: loc.end.column + 1, }; } type ReactCompilerMarkerConfig = { monaco: Monaco; model: editor.ITextModel; - details: Array; + details: Array; + source: string; }; let decorations: Array = []; export function renderReactCompilerMarkers({ monaco, model, details, + source, }: ReactCompilerMarkerConfig): void { const markers: Array = []; for (const detail of details) { - const marker = mapReactCompilerDiagnosticToMonacoMarker(detail, monaco); + const marker = mapReactCompilerDiagnosticToMonacoMarker( + detail, + monaco, + source, + ); if (marker == null) { continue; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts index 4b59a22e80..ed74f46649 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts @@ -84,9 +84,7 @@ export default function BabelPluginReactCompiler( } } catch (e) { if (e instanceof CompilerError) { - throw new Error( - e.printErrorMessage(pass.file.code, {eslint: false}), - ); + throw e.withPrintedMessage(pass.file.code, {eslint: false}); } throw e; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index b337f0c724..ee0d0f74c5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -262,6 +262,7 @@ export class CompilerErrorDetail { export class CompilerError extends Error { details: Array = []; + printedMessage: string | null = null; static invariant( condition: unknown, @@ -347,18 +348,29 @@ export class CompilerError extends Error { } override get message(): string { - return this.toString(); + return this.printedMessage ?? this.toString(); } override set message(_message: string) {} override toString(): string { + if (this.printedMessage) { + return this.printedMessage; + } if (Array.isArray(this.details)) { return this.details.map(detail => detail.toString()).join('\n\n'); } return this.name; } + withPrintedMessage( + source: string, + options: PrintErrorMessageOptions, + ): CompilerError { + this.printedMessage = this.printErrorMessage(source, options); + return this; + } + printErrorMessage(source: string, options: PrintErrorMessageOptions): string { if (options.eslint && this.details.length === 1) { return this.details[0].printErrorMessage(source, options); From d5e2c0ee1fb984490f3c1705d7052871df20a6af Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Thu, 24 Jul 2025 15:41:42 -0700 Subject: [PATCH 6/7] [compiler][rfc] Enable more validations in playground. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is mostly to kick off conversation, i think we should go with a modified version of the implemented approach that i'll describe here. The playground currently serves two roles. The primary one we think about is for verifying compiler output. We use it for this sometimes, and developers frequently use it for this, including to send us repros if they have a potential bug. The second mode is to help developers learn about React. Part of that includes learning how to use React correctly — where it's helpful to see feedback about problematic code — and also to understand what kind of tools we provide compared to other frameworks, to make an informed choice about what tools they want to use. Currently we primarily think about the first role, but I think we should emphasize the second more. In this PR i'm doing the worst of both: enabling all the validations used by both the compiler and the linter by default. This means that code that would actually compile can fail with validations, which isn't great. What I think we should actually do is compile twice, one in "compilation" mode and once in "linter" mode, and combine the results as follows: * If "compilation" mode succeeds, show the compiled output _and_ any linter errors. * If "compilation" mode fails, show only the compilation mode failures. We should also distinguish which case it is when we show errors: "Compilation succeeded", "Compilation succeeded with linter errors", "Compilation failed". This lets developers continue to verify compiler output, while also turning the playground into a much more useful tool for learning React. Thoughts? --- .../components/Editor/EditorImpl.tsx | 55 +++++++++++++++---- .../playground/components/Editor/Output.tsx | 52 +++++++++++++++--- .../src/Utils/TestUtils.ts | 13 ++++- 3 files changed, 99 insertions(+), 21 deletions(-) diff --git a/compiler/apps/playground/components/Editor/EditorImpl.tsx b/compiler/apps/playground/components/Editor/EditorImpl.tsx index 27d2fd7989..0ced1e54ed 100644 --- a/compiler/apps/playground/components/Editor/EditorImpl.tsx +++ b/compiler/apps/playground/components/Editor/EditorImpl.tsx @@ -142,7 +142,10 @@ const COMMON_HOOKS: Array<[string, Hook]> = [ ], ]; -function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { +function compile( + source: string, + mode: 'compiler' | 'linter', +): [CompilerOutput, 'flow' | 'typescript'] { const results = new Map>(); const error = new CompilerError(); const otherErrors: Array = []; @@ -204,6 +207,22 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { }; const parsedOptions = parseConfigPragmaForTests(pragma, { compilationMode: 'infer', + environment: + mode === 'linter' + ? { + // enabled in compiler + validateRefAccessDuringRender: false, + // enabled in linter + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, + } + : { + /* use defaults for compiler mode */ + }, }); const opts: PluginOptions = parsePluginOptions({ ...parsedOptions, @@ -249,9 +268,12 @@ function compile(source: string): [CompilerOutput, 'flow' | 'typescript'] { otherErrors.forEach(e => error.details.push(e)); } if (error.hasErrors()) { - return [{kind: 'err', results, error: error}, language]; + return [{kind: 'err', results, error}, language]; } - return [{kind: 'ok', results, transformOutput}, language]; + return [ + {kind: 'ok', results, transformOutput, errors: error.details}, + language, + ]; } export default function Editor(): JSX.Element { @@ -260,7 +282,11 @@ export default function Editor(): JSX.Element { const dispatchStore = useStoreDispatch(); const {enqueueSnackbar} = useSnackbar(); const [compilerOutput, language] = useMemo( - () => compile(deferredStore.source), + () => compile(deferredStore.source, 'compiler'), + [deferredStore.source], + ); + const [linterOutput] = useMemo( + () => compile(deferredStore.source, 'linter'), [deferredStore.source], ); @@ -286,19 +312,26 @@ export default function Editor(): JSX.Element { }); }); + let mergedOutput: CompilerOutput; + let errors: Array; + if (compilerOutput.kind === 'ok') { + errors = linterOutput.kind === 'ok' ? [] : linterOutput.error.details; + mergedOutput = { + ...compilerOutput, + errors, + }; + } else { + mergedOutput = compilerOutput; + errors = compilerOutput.error.details; + } return ( <>
- +
- +
diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 36d20a0125..0f8fe268ca 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -11,7 +11,11 @@ import { InformationCircleIcon, } from '@heroicons/react/outline'; import MonacoEditor, {DiffEditor} from '@monaco-editor/react'; -import {type CompilerError} from 'babel-plugin-react-compiler'; +import { + CompilerErrorDetail, + CompilerDiagnostic, + type CompilerError, +} from 'babel-plugin-react-compiler'; import parserBabel from 'prettier/plugins/babel'; import * as prettierPluginEstree from 'prettier/plugins/estree'; import * as prettier from 'prettier/standalone'; @@ -44,6 +48,7 @@ export type CompilerOutput = kind: 'ok'; transformOutput: CompilerTransformOutput; results: Map>; + errors: Array; } | { kind: 'err'; @@ -123,10 +128,36 @@ async function tabify( parser: transformOutput.language === 'flow' ? 'babel-flow' : 'babel-ts', plugins: [parserBabel, prettierPluginEstree], }); + + let output: string; + let language: string; + if (compilerOutput.errors.length === 0) { + output = code; + language = 'javascript'; + } else { + language = 'markdown'; + output = ` +# Output + +React Compiler compiled this function sucessfully, but there are lint errors that indicate potential issues with the original code. + +## ${compilerOutput.errors.length} Lint Errors + +${compilerOutput.errors.map(e => e.printErrorMessage(source, {eslint: false})).join('\n\n')} + +## Output + +\`\`\`js +${code} +\`\`\` +`.trim(); + } + reorderedTabs.set( - 'JS', + 'Output', , ); @@ -147,9 +178,10 @@ async function tabify( eslint: false, }); reorderedTabs.set( - 'Errors', + 'Output', , ); @@ -173,7 +205,9 @@ function getSourceMapUrl(code: string, map: string): string | null { } function Output({store, compilerOutput}: Props): JSX.Element { - const [tabsOpen, setTabsOpen] = useState>(() => new Set(['JS'])); + const [tabsOpen, setTabsOpen] = useState>( + () => new Set(['Output']), + ); const [tabs, setTabs] = useState>( () => new Map(), ); @@ -187,7 +221,7 @@ function Output({store, compilerOutput}: Props): JSX.Element { ); if (compilerOutput.kind !== previousOutputKind) { setPreviousOutputKind(compilerOutput.kind); - setTabsOpen(new Set([compilerOutput.kind === 'ok' ? 'JS' : 'Errors'])); + setTabsOpen(new Set(['Output'])); } useEffect(() => { @@ -196,7 +230,7 @@ function Output({store, compilerOutput}: Props): JSX.Element { }); }, [store.source, compilerOutput]); - const changedPasses: Set = new Set(['JS', 'HIR']); // Initial and final passes should always be bold + const changedPasses: Set = new Set(['Output', 'HIR']); // Initial and final passes should always be bold let lastResult: string = ''; for (const [passName, results] of compilerOutput.results) { for (const result of results) { @@ -228,10 +262,12 @@ function TextTabContent({ output, diff, showInfoPanel, + language, }: { output: string; diff: string | null; showInfoPanel: boolean; + language: string; }): JSX.Element { const [diffMode, setDiffMode] = useState(false); return ( @@ -282,7 +318,7 @@ function TextTabContent({ /> ) : ( > = {}; + // throw early if the defaults are invalid + EnvironmentConfigSchema.parse(defaultConfig); + + const maybeConfig: Partial> = + defaultConfig; for (const {key, value: val} of splitPragma(pragma)) { if (!hasOwnProperty(EnvironmentConfigSchema.shape, key)) { @@ -174,9 +179,13 @@ export function parseConfigPragmaForTests( pragma: string, defaults: { compilationMode: CompilationMode; + environment?: PartialEnvironmentConfig; }, ): PluginOptions { - const environment = parseConfigPragmaEnvironmentForTest(pragma); + const environment = parseConfigPragmaEnvironmentForTest( + pragma, + defaults.environment ?? {}, + ); const options: Record = { ...defaultOptions, panicThreshold: 'all_errors', From 1b4eab90c2a8be7073c01b89fd864a8604c55cfb Mon Sep 17 00:00:00 2001 From: Joe Savona Date: Thu, 24 Jul 2025 15:41:42 -0700 Subject: [PATCH 7/7] [compiler] Use diagnostic for "found suppression" error --- .../page.spec.ts/compilationMode-all-output.txt | 2 +- .../apps/playground/components/Editor/Output.tsx | 6 +++--- .../playground/components/Editor/monacoOptions.ts | 2 +- .../src/Entrypoint/Suppression.ts | 15 +++++++++------ .../error.bailout-on-flow-suppression.expect.md | 6 +++--- ...ailout-on-suppression-of-custom-rule.expect.md | 12 ++++++------ ...rror.invalid-sketchy-code-use-forget.expect.md | 12 ++++++------ ....invalid-unclosed-eslint-suppression.expect.md | 6 +++--- .../error.sketchy-code-exhaustive-deps.expect.md | 6 +++--- .../error.sketchy-code-rules-of-hooks.expect.md | 6 +++--- .../error.wrong-index-no-func.expect.md | 1 + .../error.wrong-index.expect.md | 1 + 12 files changed, 40 insertions(+), 35 deletions(-) diff --git a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt index 0f35215e86..0084911eec 100644 --- a/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt +++ b/compiler/apps/playground/__tests__/e2e/__snapshots__/page.spec.ts/compilationMode-all-output.txt @@ -1,5 +1,5 @@ import { c as _c } from "react/compiler-runtime"; //  -        @compilationMode:"all" +@compilationMode:"all" function nonReactFn() {   const $ = _c(1);   let t0; diff --git a/compiler/apps/playground/components/Editor/Output.tsx b/compiler/apps/playground/components/Editor/Output.tsx index 0f8fe268ca..bf7bd3eb65 100644 --- a/compiler/apps/playground/components/Editor/Output.tsx +++ b/compiler/apps/playground/components/Editor/Output.tsx @@ -137,9 +137,9 @@ async function tabify( } else { language = 'markdown'; output = ` -# Output +# Summary -React Compiler compiled this function sucessfully, but there are lint errors that indicate potential issues with the original code. +React Compiler compiled this function successfully, but there are lint errors that indicate potential issues with the original code. ## ${compilerOutput.errors.length} Lint Errors @@ -181,7 +181,7 @@ ${code} 'Output', , ); diff --git a/compiler/apps/playground/components/Editor/monacoOptions.ts b/compiler/apps/playground/components/Editor/monacoOptions.ts index 14a9fc9466..f272913bce 100644 --- a/compiler/apps/playground/components/Editor/monacoOptions.ts +++ b/compiler/apps/playground/components/Editor/monacoOptions.ts @@ -28,5 +28,5 @@ export const monacoOptions: Partial = { automaticLayout: true, wordWrap: 'on', - wrappingIndent: 'deepIndent', + wrappingIndent: 'same', }; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts index 4d0369f521..ee341b111f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Suppression.ts @@ -8,8 +8,8 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; import { + CompilerDiagnostic, CompilerError, - CompilerErrorDetail, CompilerSuggestionOperation, ErrorSeverity, } from '../CompilerError'; @@ -181,12 +181,11 @@ export function suppressionsToCompilerError( 'Unhandled suppression source', ); } - error.pushErrorDetail( - new CompilerErrorDetail({ - reason: `${reason}. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior`, - description: suppressionRange.disableComment.value.trim(), + error.pushDiagnostic( + CompilerDiagnostic.create({ + category: reason, + description: `React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression \`${suppressionRange.disableComment.value.trim()}\``, severity: ErrorSeverity.InvalidReact, - loc: suppressionRange.disableComment.loc ?? null, suggestions: [ { description: suggestion, @@ -197,6 +196,10 @@ export function suppressionsToCompilerError( op: CompilerSuggestionOperation.Remove, }, ], + }).withDetail({ + kind: 'error', + loc: suppressionRange.disableComment.loc ?? null, + message: 'Found React rule suppression', }), ); } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md index 26942a2996..6e522e1666 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-flow-suppression.expect.md @@ -18,15 +18,15 @@ function Foo(props) { ``` 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 +Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow -$FlowFixMe[react-rule-hook]. +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `$FlowFixMe[react-rule-hook]` error.bailout-on-flow-suppression.ts:4:2 2 | 3 | function Foo(props) { > 4 | // $FlowFixMe[react-rule-hook] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression 5 | useX(); 6 | return null; 7 | } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md index 89024fd210..3221f97731 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.bailout-on-suppression-of-custom-rule.expect.md @@ -21,28 +21,28 @@ function lowercasecomponent() { ``` 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 +Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -eslint-disable my-app/react-rule. +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `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 */ - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression 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 +Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -eslint-disable-next-line my-app/react-rule. +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression 8 | return
{x}
; 9 | } 10 | /* eslint-enable my-app/react-rule */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md index 1860ea1fd4..96be8584be 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-sketchy-code-use-forget.expect.md @@ -19,26 +19,26 @@ function lowercasecomponent() { ``` 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 +Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -eslint-disable react-hooks/rules-of-hooks. +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression 2 | function lowercasecomponent() { 3 | 'use forget'; 4 | 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 +Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -eslint-disable-next-line react-hooks/rules-of-hooks. +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/rules-of-hooks` error.invalid-sketchy-code-use-forget.ts:5:2 3 | 'use forget'; 4 | const x = []; > 5 | // eslint-disable-next-line react-hooks/rules-of-hooks - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression 6 | return
{x}
; 7 | } 8 | /* eslint-enable react-hooks/rules-of-hooks */ diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md index 92a647a0c5..e19cee7532 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-unclosed-eslint-suppression.expect.md @@ -38,14 +38,14 @@ function CrimesAgainstReact() { ``` Found 1 error: -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 +Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -eslint-disable react-hooks/rules-of-hooks. +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` error.invalid-unclosed-eslint-suppression.ts:2:0 1 | // Note: Everything below this is sketchy > 2 | /* eslint-disable react-hooks/rules-of-hooks */ - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression 3 | function lowercasecomponent() { 4 | 'use forget'; 5 | const x = []; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md index 97ef238a50..9c87cafff1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-exhaustive-deps.expect.md @@ -22,15 +22,15 @@ function Component() { ``` Found 1 error: -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 +Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -eslint-disable-next-line react-hooks/exhaustive-deps. +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable-next-line react-hooks/exhaustive-deps` error.sketchy-code-exhaustive-deps.ts:6:7 4 | () => { 5 | item.push(1); > 6 | }, // eslint-disable-next-line react-hooks/exhaustive-deps - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression 7 | [] 8 | ); 9 | diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md index 7716ddfd10..7077b733b0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.sketchy-code-rules-of-hooks.expect.md @@ -23,13 +23,13 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -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 +Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled -eslint-disable react-hooks/rules-of-hooks. +React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. Found suppression `eslint-disable react-hooks/rules-of-hooks` error.sketchy-code-rules-of-hooks.ts:1:0 > 1 | /* eslint-disable react-hooks/rules-of-hooks */ - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Found React rule suppression 2 | function lowercasecomponent() { 3 | const x = []; 4 | return
{x}
; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index-no-func.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index-no-func.expect.md index 2347acf093..4b2f94a263 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index-no-func.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index-no-func.expect.md @@ -16,6 +16,7 @@ function Component({foo}) { ``` Found 1 error: + Error: Cannot infer dependencies of this effect. This will break your build! To resolve, either pass a dependency array or fix reported compiler bailout diagnostics. diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.expect.md index 4bb3c1eb10..0a5cde5cd6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/infer-effect-dependencies/error.wrong-index.expect.md @@ -23,6 +23,7 @@ function Component({foo}) { ``` Found 1 error: + Error: Cannot infer dependencies of this effect. This will break your build! To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.