From 3082604bdc03cfd8cee35e54dbb10caef956937e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 24 Jul 2025 17:38:12 -0400 Subject: [PATCH 01/13] [DevTools] Feature detect sources panel (#33987) I broke Firefox DevTools extension in #33968. It turns out the Firefox has a placeholder object for the sources panel which is empty. We need to detect the actual event handler. --- packages/react-devtools-extensions/src/main/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js index a82df1e226338..46c42418c331c 100644 --- a/packages/react-devtools-extensions/src/main/index.js +++ b/packages/react-devtools-extensions/src/main/index.js @@ -111,7 +111,7 @@ function createBridge() { chrome.devtools.panels.elements.onSelectionChanged.removeListener( onBrowserElementSelectionChanged, ); - if (sourcesPanel) { + if (sourcesPanel && sourcesPanel.onSelectionChanged) { currentSelectedSource = null; sourcesPanel.onSelectionChanged.removeListener( onBrowserSourceSelectionChanged, @@ -124,7 +124,7 @@ function createBridge() { chrome.devtools.panels.elements.onSelectionChanged.addListener( onBrowserElementSelectionChanged, ); - if (sourcesPanel) { + if (sourcesPanel && sourcesPanel.onSelectionChanged) { sourcesPanel.onSelectionChanged.addListener( onBrowserSourceSelectionChanged, ); From 5020d48d2809c33db980f20726d1d0a7b2c8e31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Thu, 24 Jul 2025 17:42:06 -0400 Subject: [PATCH 02/13] [DevTools] Feature detect createSidebarPane (#33988) Same as #33987 but for the sidebar pane creation. --- packages/react-devtools-extensions/src/main/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js index 46c42418c331c..d7756ca991bef 100644 --- a/packages/react-devtools-extensions/src/main/index.js +++ b/packages/react-devtools-extensions/src/main/index.js @@ -317,7 +317,7 @@ function createSourcesEditorPanel() { } const sourcesPanel = chrome.devtools.panels.sources; - if (!sourcesPanel) { + if (!sourcesPanel || !sourcesPanel.createSidebarPane) { // Firefox doesn't currently support extending the source panel. return; } From 448f781a52d62042341d2411d1352d705ce2cbfe Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:16:28 -0700 Subject: [PATCH 03/13] [compiler] Fix for false positive mutation of destructured spread object (#33786) When destructuring, spread creates a new mutable object that _captures_ part of the original rvalue. This new value is safe to modify. When making this change I realized that we weren't inferring array pattern spread as creating an array (in type inference) so I also added that here. --- .../src/HIR/visitors.ts | 45 ++++++++ .../Inference/InferMutationAliasingEffects.ts | 27 ++++- .../src/TypeInference/InferTypes.ts | 6 + ...ray-pattern-spread-creates-array.expect.md | 104 ++++++++++++++++++ .../array-pattern-spread-creates-array.js | 28 +++++ ...ew-object-from-destructured-prop.expect.md | 62 +++++++++++ ...on-of-new-object-from-destructured-prop.js | 14 +++ ...ession-of-jsxexpressioncontainer.expect.md | 28 ++--- 8 files changed, 291 insertions(+), 23 deletions(-) create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-pattern-spread-creates-array.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-pattern-spread-creates-array.js create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-local-mutation-of-new-object-from-destructured-prop.expect.md create mode 100644 compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-local-mutation-of-new-object-from-destructured-prop.js diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts index 52bbefc732856..88786bc5dd2bb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/visitors.ts @@ -345,6 +345,51 @@ export function* eachPatternOperand(pattern: Pattern): Iterable { } } +export function* eachPatternItem( + pattern: Pattern, +): Iterable { + switch (pattern.kind) { + case 'ArrayPattern': { + for (const item of pattern.items) { + if (item.kind === 'Identifier') { + yield item; + } else if (item.kind === 'Spread') { + yield item; + } else if (item.kind === 'Hole') { + continue; + } else { + assertExhaustive( + item, + `Unexpected item kind \`${(item as any).kind}\``, + ); + } + } + break; + } + case 'ObjectPattern': { + for (const property of pattern.properties) { + if (property.kind === 'ObjectProperty') { + yield property.place; + } else if (property.kind === 'Spread') { + yield property; + } else { + assertExhaustive( + property, + `Unexpected item kind \`${(property as any).kind}\``, + ); + } + } + break; + } + default: { + assertExhaustive( + pattern, + `Unexpected pattern kind \`${(pattern as any).kind}\``, + ); + } + } +} + export function mapInstructionLValues( instr: Instruction, fn: (place: Place) => Place, diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts index b91b606d507e6..d02a294b5b43c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -36,8 +36,8 @@ import { ValueReason, } from '../HIR'; import { - eachInstructionValueLValue, eachInstructionValueOperand, + eachPatternItem, eachTerminalOperand, eachTerminalSuccessor, } from '../HIR/visitors'; @@ -1864,19 +1864,34 @@ function computeSignatureForInstruction( break; } case 'Destructure': { - for (const patternLValue of eachInstructionValueLValue(value)) { - if (isPrimitiveType(patternLValue.identifier)) { + for (const patternItem of eachPatternItem(value.lvalue.pattern)) { + const place = + patternItem.kind === 'Identifier' ? patternItem : patternItem.place; + if (isPrimitiveType(place.identifier)) { effects.push({ kind: 'Create', - into: patternLValue, + into: place, value: ValueKind.Primitive, reason: ValueReason.Other, }); - } else { + } else if (patternItem.kind === 'Identifier') { effects.push({ kind: 'CreateFrom', from: value.value, - into: patternLValue, + into: place, + }); + } else { + // Spread creates a new object/array that captures from the RValue + effects.push({ + kind: 'Create', + into: place, + reason: ValueReason.Other, + value: ValueKind.Mutable, + }); + effects.push({ + kind: 'Capture', + from: value.value, + into: place, }); } } diff --git a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts index 859c871c263ad..6a764d674012f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/TypeInference/InferTypes.ts @@ -360,6 +360,12 @@ function* generateInstructionTypes( value: makePropertyLiteral(propertyName), }, }); + } else if (item.kind === 'Spread') { + // Array pattern spread always creates an array + yield equation(item.place.identifier.type, { + kind: 'Object', + shapeId: BuiltInArrayId, + }); } else { break; } diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-pattern-spread-creates-array.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-pattern-spread-creates-array.expect.md new file mode 100644 index 0000000000000..9994a6536f419 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-pattern-spread-creates-array.expect.md @@ -0,0 +1,104 @@ + +## Input + +```javascript +// @validatePreserveExistingMemoizationGuarantees +import {useMemo} from 'react'; +import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime'; + +function Component(props) { + // Should memoize independently + const x = useMemo(() => makeObject_Primitives(), []); + + const rest = useMemo(() => { + const [_, ...rest] = props.array; + + // Should be inferred as Array.proto.push which doesn't mutate input + rest.push(x); + return rest; + }); + + return ( + <> + + + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{array: [0, 1, 2]}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; // @validatePreserveExistingMemoizationGuarantees +import { useMemo } from "react"; +import { makeObject_Primitives, ValidateMemoization } from "shared-runtime"; + +function Component(props) { + const $ = _c(9); + let t0; + if ($[0] === Symbol.for("react.memo_cache_sentinel")) { + t0 = makeObject_Primitives(); + $[0] = t0; + } else { + t0 = $[0]; + } + const x = t0; + let rest; + if ($[1] !== props.array) { + [, ...rest] = props.array; + + rest.push(x); + $[1] = props.array; + $[2] = rest; + } else { + rest = $[2]; + } + const rest_0 = rest; + let t1; + if ($[3] === Symbol.for("react.memo_cache_sentinel")) { + t1 = ; + $[3] = t1; + } else { + t1 = $[3]; + } + let t2; + if ($[4] !== props.array) { + t2 = [props.array]; + $[4] = props.array; + $[5] = t2; + } else { + t2 = $[5]; + } + let t3; + if ($[6] !== rest_0 || $[7] !== t2) { + t3 = ( + <> + {t1} + + + ); + $[6] = rest_0; + $[7] = t2; + $[8] = t3; + } else { + t3 = $[8]; + } + return t3; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ array: [0, 1, 2] }], +}; + +``` + +### Eval output +(kind: ok)
{"inputs":[],"output":{"a":0,"b":"value1","c":true}}
{"inputs":[[0,1,2]],"output":[1,2,{"a":0,"b":"value1","c":true}]}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-pattern-spread-creates-array.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-pattern-spread-creates-array.js new file mode 100644 index 0000000000000..888bdbcb8b935 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/array-pattern-spread-creates-array.js @@ -0,0 +1,28 @@ +// @validatePreserveExistingMemoizationGuarantees +import {useMemo} from 'react'; +import {makeObject_Primitives, ValidateMemoization} from 'shared-runtime'; + +function Component(props) { + // Should memoize independently + const x = useMemo(() => makeObject_Primitives(), []); + + const rest = useMemo(() => { + const [_, ...rest] = props.array; + + // Should be inferred as Array.proto.push which doesn't mutate input + rest.push(x); + return rest; + }); + + return ( + <> + + + + ); +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{array: [0, 1, 2]}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-local-mutation-of-new-object-from-destructured-prop.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-local-mutation-of-new-object-from-destructured-prop.expect.md new file mode 100644 index 0000000000000..b1a0f6cbb34ce --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-local-mutation-of-new-object-from-destructured-prop.expect.md @@ -0,0 +1,62 @@ + +## Input + +```javascript +import {Stringify} from 'shared-runtime'; + +function Component(props) { + const {a} = props; + const {b, ...rest} = a; + // Local mutation of `rest` is allowed since it is a newly allocated object + rest.value = props.value; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {b: 0, other: 'other'}, value: 42}], +}; + +``` + +## Code + +```javascript +import { c as _c } from "react/compiler-runtime"; +import { Stringify } from "shared-runtime"; + +function Component(props) { + const $ = _c(5); + const { a } = props; + let rest; + if ($[0] !== a || $[1] !== props.value) { + const { b, ...t0 } = a; + rest = t0; + + rest.value = props.value; + $[0] = a; + $[1] = props.value; + $[2] = rest; + } else { + rest = $[2]; + } + let t0; + if ($[3] !== rest) { + t0 = ; + $[3] = rest; + $[4] = t0; + } else { + t0 = $[4]; + } + return t0; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{ a: { b: 0, other: "other" }, value: 42 }], +}; + +``` + +### Eval output +(kind: ok)
{"rest":{"other":"other","value":42}}
\ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-local-mutation-of-new-object-from-destructured-prop.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-local-mutation-of-new-object-from-destructured-prop.js new file mode 100644 index 0000000000000..a77de3177d0f5 --- /dev/null +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-local-mutation-of-new-object-from-destructured-prop.js @@ -0,0 +1,14 @@ +import {Stringify} from 'shared-runtime'; + +function Component(props) { + const {a} = props; + const {b, ...rest} = a; + // Local mutation of `rest` is allowed since it is a newly allocated object + rest.value = props.value; + return ; +} + +export const FIXTURE_ENTRYPOINT = { + fn: Component, + params: [{a: {b: 0, other: 'other'}, value: 42}], +}; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md index 3e71c05a2efbf..9d41b7de21f53 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/repro-undefined-expression-of-jsxexpressioncontainer.expect.md @@ -48,32 +48,26 @@ import { c as _c } from "react/compiler-runtime"; import { StaticText1, Stringify, Text } from "shared-runtime"; function Component(props) { - const $ = _c(6); + const $ = _c(4); const { buttons } = props; - let nonPrimaryButtons; - if ($[0] !== buttons) { - [, ...nonPrimaryButtons] = buttons; - $[0] = buttons; - $[1] = nonPrimaryButtons; - } else { - nonPrimaryButtons = $[1]; - } let t0; - if ($[2] !== nonPrimaryButtons) { + if ($[0] !== buttons) { + const [, ...nonPrimaryButtons] = buttons; + t0 = nonPrimaryButtons.map(_temp); - $[2] = nonPrimaryButtons; - $[3] = t0; + $[0] = buttons; + $[1] = t0; } else { - t0 = $[3]; + t0 = $[1]; } const renderedNonPrimaryButtons = t0; let t1; - if ($[4] !== renderedNonPrimaryButtons) { + if ($[2] !== renderedNonPrimaryButtons) { t1 = {renderedNonPrimaryButtons}; - $[4] = renderedNonPrimaryButtons; - $[5] = t1; + $[2] = renderedNonPrimaryButtons; + $[3] = t1; } else { - t1 = $[5]; + t1 = $[3]; } return t1; } From 6f4294af9b97c7082289f067315affe2ccf0ed7a Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:36:40 -0700 Subject: [PATCH 04/13] [compiler] Validate against setState in all effect types (#33753) --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33753). * #33981 * #33777 * #33767 * #33765 * #33760 * #33759 * #33758 * #33751 * #33752 * __->__ #33753 --- .../src/Entrypoint/Pipeline.ts | 6 +++--- .../src/HIR/Environment.ts | 4 ++-- ...siveEffects.ts => ValidateNoSetStateInEffects.ts} | 12 +++++++++--- ...nvalid-setState-in-useEffect-transitive.expect.md | 8 ++++---- .../invalid-setState-in-useEffect-transitive.js | 2 +- .../compiler/invalid-setState-in-useEffect.expect.md | 8 ++++---- .../compiler/invalid-setState-in-useEffect.js | 2 +- ...tState-in-useEffect-listener-transitive.expect.md | 4 ++-- ...alid-setState-in-useEffect-listener-transitive.js | 2 +- .../valid-setState-in-useEffect-listener.expect.md | 4 ++-- .../compiler/valid-setState-in-useEffect-listener.js | 2 +- .../src/__tests__/parseConfigPragma-test.ts | 6 +++--- 12 files changed, 33 insertions(+), 27 deletions(-) rename compiler/packages/babel-plugin-react-compiler/src/Validation/{ValidateNoSetStateInPassiveEffects.ts => ValidateNoSetStateInEffects.ts} (92%) diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts index c5ca3434b1b54..648ff01ba713c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts @@ -94,7 +94,7 @@ import {validateLocalsNotReassignedAfterRender} from '../Validation/ValidateLoca import {outlineFunctions} from '../Optimization/OutlineFunctions'; import {propagatePhiTypes} from '../TypeInference/PropagatePhiTypes'; import {lowerContextAccess} from '../Optimization/LowerContextAccess'; -import {validateNoSetStateInPassiveEffects} from '../Validation/ValidateNoSetStateInPassiveEffects'; +import {validateNoSetStateInEffects} from '../Validation/ValidateNoSetStateInEffects'; import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryStatement'; import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR'; import {outlineJSX} from '../Optimization/OutlineJsx'; @@ -292,8 +292,8 @@ function runWithEnvironment( validateNoSetStateInRender(hir).unwrap(); } - if (env.config.validateNoSetStateInPassiveEffects) { - env.logErrors(validateNoSetStateInPassiveEffects(hir)); + if (env.config.validateNoSetStateInEffects) { + env.logErrors(validateNoSetStateInEffects(hir)); } if (env.config.validateNoJSXInTryStatements) { diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts index a552803171ad5..5b5b78fc5202c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/Environment.ts @@ -318,10 +318,10 @@ export const EnvironmentConfigSchema = z.object({ validateNoSetStateInRender: z.boolean().default(true), /** - * Validates that setState is not called directly within a passive effect (useEffect). + * Validates that setState is not called synchronously within an effect (useEffect and friends). * Scheduling a setState (with an event listener, subscription, etc) is valid. */ - validateNoSetStateInPassiveEffects: z.boolean().default(false), + validateNoSetStateInEffects: z.boolean().default(false), /** * Validates against creating JSX within a try block and recommends using an error boundary diff --git a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInPassiveEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts similarity index 92% rename from compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInPassiveEffects.ts rename to compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts index a36c347faa001..e9b0fdb8870ee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInPassiveEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInEffects.ts @@ -11,20 +11,22 @@ import { IdentifierId, isSetStateType, isUseEffectHookType, + isUseInsertionEffectHookType, + isUseLayoutEffectHookType, Place, } from '../HIR'; import {eachInstructionValueOperand} from '../HIR/visitors'; import {Result} from '../Utils/Result'; /** - * Validates against calling setState in the body of a *passive* effect (useEffect), + * Validates against calling setState in the body of an effect (useEffect and friends), * while allowing calling setState in callbacks scheduled by the effect. * * Calling setState during execution of a useEffect triggers a re-render, which is * often bad for performance and frequently has more efficient and straightforward * alternatives. See https://react.dev/learn/you-might-not-need-an-effect for examples. */ -export function validateNoSetStateInPassiveEffects( +export function validateNoSetStateInEffects( fn: HIRFunction, ): Result { const setStateFunctions: Map = new Map(); @@ -79,7 +81,11 @@ export function validateNoSetStateInPassiveEffects( instr.value.kind === 'MethodCall' ? instr.value.receiver : instr.value.callee; - if (isUseEffectHookType(callee.identifier)) { + if ( + isUseEffectHookType(callee.identifier) || + isUseLayoutEffectHookType(callee.identifier) || + isUseInsertionEffectHookType(callee.identifier) + ) { const arg = instr.value.args[0]; if (arg !== undefined && arg.kind === 'Identifier') { const setState = setStateFunctions.get(arg.identifier.id); 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 e116fb4fd988f..b09fd352f24f7 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 @@ -2,7 +2,7 @@ ## Input ```javascript -// @loggerTestOnly @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInEffects import {useEffect, useState} from 'react'; function Component() { @@ -24,7 +24,7 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInPassiveEffects +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInEffects import { useEffect, useState } from "react"; function Component() { @@ -65,8 +65,8 @@ 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":272},"end":{"line":13,"column":5,"index":273},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":99},"end":{"line":16,"column":1,"index":300},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"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":"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} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js index bbf976f0bd4ed..50007c0bd13a4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js @@ -1,4 +1,4 @@ -// @loggerTestOnly @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInEffects import {useEffect, useState} from 'react'; function Component() { 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 202071455b74c..41390e5ce0a22 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 @@ -2,7 +2,7 @@ ## Input ```javascript -// @loggerTestOnly @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInEffects import {useEffect, useState} from 'react'; function Component() { @@ -18,7 +18,7 @@ function Component() { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInPassiveEffects +import { c as _c } from "react/compiler-runtime"; // @loggerTestOnly @validateNoSetStateInEffects import { useEffect, useState } from "react"; function Component() { @@ -45,8 +45,8 @@ 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":187},"end":{"line":7,"column":12,"index":195},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null} -{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":99},"end":{"line":10,"column":1,"index":232},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0} +{"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":"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} ``` ### Eval output diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js index e526ad82e987e..a95d3642cb8de 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js @@ -1,4 +1,4 @@ -// @loggerTestOnly @validateNoSetStateInPassiveEffects +// @loggerTestOnly @validateNoSetStateInEffects import {useEffect, useState} from 'react'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener-transitive.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener-transitive.expect.md index dd48adcda7158..ac55bd0469935 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener-transitive.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener-transitive.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateNoSetStateInPassiveEffects +// @validateNoSetStateInEffects import {useEffect, useState} from 'react'; function Component() { @@ -26,7 +26,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @validateNoSetStateInPassiveEffects +import { c as _c } from "react/compiler-runtime"; // @validateNoSetStateInEffects import { useEffect, useState } from "react"; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener-transitive.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener-transitive.js index 8b1e159071ef3..525f3e97d1911 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener-transitive.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener-transitive.js @@ -1,4 +1,4 @@ -// @validateNoSetStateInPassiveEffects +// @validateNoSetStateInEffects import {useEffect, useState} from 'react'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener.expect.md index 7fdd01fd0a76e..a7deed9afb09e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener.expect.md @@ -2,7 +2,7 @@ ## Input ```javascript -// @validateNoSetStateInPassiveEffects +// @validateNoSetStateInEffects import {useEffect, useState} from 'react'; function Component() { @@ -23,7 +23,7 @@ export const FIXTURE_ENTRYPOINT = { ## Code ```javascript -import { c as _c } from "react/compiler-runtime"; // @validateNoSetStateInPassiveEffects +import { c as _c } from "react/compiler-runtime"; // @validateNoSetStateInEffects import { useEffect, useState } from "react"; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener.js b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener.js index ba9720cba9b70..723e4841f6d3a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener.js +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/valid-setState-in-useEffect-listener.js @@ -1,4 +1,4 @@ -// @validateNoSetStateInPassiveEffects +// @validateNoSetStateInEffects import {useEffect, useState} from 'react'; function Component() { diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts index 903afe4c20b9f..0ee50a0e761f0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/parseConfigPragma-test.ts @@ -15,11 +15,11 @@ describe('parseConfigPragmaForTests()', () => { // Validate defaults first to make sure that the parser is getting the value from the pragma, // and not just missing it and getting the default value expect(defaultConfig.enableUseTypeAnnotations).toBe(false); - expect(defaultConfig.validateNoSetStateInPassiveEffects).toBe(false); + expect(defaultConfig.validateNoSetStateInEffects).toBe(false); expect(defaultConfig.validateNoSetStateInRender).toBe(true); const config = parseConfigPragmaForTests( - '@enableUseTypeAnnotations @validateNoSetStateInPassiveEffects:true @validateNoSetStateInRender:false', + '@enableUseTypeAnnotations @validateNoSetStateInEffects:true @validateNoSetStateInRender:false', {compilationMode: defaultOptions.compilationMode}, ); expect(config).toEqual({ @@ -28,7 +28,7 @@ describe('parseConfigPragmaForTests()', () => { environment: { ...defaultOptions.environment, enableUseTypeAnnotations: true, - validateNoSetStateInPassiveEffects: true, + validateNoSetStateInEffects: true, validateNoSetStateInRender: false, enableResetCacheOnSourceFileChanges: false, }, From 0d39496eab710b5a3efc07d34c0db3f2475c04f8 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:36:54 -0700 Subject: [PATCH 05/13] [compiler] Enable additional lints by default (#33752) Enable more validations to help catch bad patterns, but only in the linter. These rules are already enabled by default in the compiler _if_ violations could produce unsafe output. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33752). * #33981 * #33777 * #33767 * #33765 * #33760 * #33759 * #33758 * #33751 * __->__ #33752 * #33753 --- .../src/rules/ReactCompilerRule.ts | 6 ++++++ .../eslint-plugin-react-hooks/src/rules/ReactCompiler.ts | 6 ++++++ 2 files changed, 12 insertions(+) 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 e9eee26bdabc6..fbe7f5b507d16 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -107,6 +107,12 @@ const COMPILER_OPTIONS: Partial = { flowSuppressions: false, environment: validateEnvironmentConfig({ validateRefAccessDuringRender: false, + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, }), }; diff --git a/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts index 67d5745a1c7ea..35c515ad948a0 100644 --- a/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts +++ b/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts @@ -109,6 +109,12 @@ const COMPILER_OPTIONS: Partial = { flowSuppressions: false, environment: validateEnvironmentConfig({ validateRefAccessDuringRender: false, + validateNoSetStateInRender: true, + validateNoSetStateInEffects: true, + validateNoJSXInTryStatements: true, + validateNoImpureFunctionsInRender: true, + validateStaticComponents: true, + validateNoFreezingKnownMutableFunctions: true, }), }; From 707e321f8f1ba3f69d27df861caf630fe48aade6 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:37:06 -0700 Subject: [PATCH 06/13] [compiler][wip] Improve diagnostic infra (#33751) Work in progress, i'm experimenting with revamping our diagnostic infra. Starting with a better format for representing errors, with an ability to point ot multiple locations, along with better printing of errors. Of course, Babel still controls the printing in the majority case so this still needs more work. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33751). * #33981 * #33777 * #33767 * #33765 * #33760 * #33759 * #33758 * __->__ #33751 * #33752 * #33753 --- .../src/Babel/BabelPlugin.ts | 96 ++++---- .../src/CompilerError.ts | 209 +++++++++++++++++- .../src/Entrypoint/Options.ts | 9 +- .../src/Entrypoint/Program.ts | 2 +- .../ValidateNoUntransformedReferences.ts | 62 +++--- .../src/HIR/BuildHIR.ts | 21 +- .../src/HIR/HIRBuilder.ts | 17 +- .../src/__tests__/envConfig-test.ts | 4 +- ...odo.computed-lval-in-destructure.expect.md | 8 +- ...global-in-component-tag-function.expect.md | 8 +- ...or.assign-global-in-jsx-children.expect.md | 8 +- ...n-global-in-jsx-spread-attribute.expect.md | 8 +- ...rror.bailout-on-flow-suppression.expect.md | 10 +- ...ut-on-suppression-of-custom-rule.expect.md | 26 ++- ...ive-ref-validation-in-use-effect.expect.md | 22 +- ...-destructuring-asignment-complex.expect.md | 8 +- ...apitalized-function-call-aliased.expect.md | 10 +- .../error.capitalized-function-call.expect.md | 10 +- .../error.capitalized-method-call.expect.md | 10 +- .../error.capture-ref-for-mutation.expect.md | 44 +++- ...ook-unknown-hook-react-namespace.expect.md | 8 +- ...conditional-hooks-as-method-call.expect.md | 8 +- ...ext-variable-only-chained-assign.expect.md | 10 +- ...variable-in-function-declaration.expect.md | 10 +- ...ror.default-param-accesses-local.expect.md | 8 +- ...rror.dont-hoist-inline-reference.expect.md | 10 +- ...r.emit-freeze-conflicting-global.expect.md | 10 +- ...erences-variable-its-assigned-to.expect.md | 10 +- ...ession-with-conditional-optional.expect.md | 10 +- ...mber-expression-with-conditional.expect.md | 10 +- ...ting-simple-function-declaration.expect.md | 8 +- ...call-freezes-captured-identifier.expect.md | 8 +- ...call-freezes-captured-memberexpr.expect.md | 8 +- ...or.hook-property-load-local-hook.expect.md | 22 +- .../compiler/error.hook-ref-value.expect.md | 20 +- ...alid-ReactUseMemo-async-callback.expect.md | 8 +- ...invalid-access-ref-during-render.expect.md | 8 +- ...-callback-invoked-during-render-.expect.md | 8 +- .../error.invalid-array-push-frozen.expect.md | 8 +- ...ror.invalid-assign-hook-to-local.expect.md | 8 +- ...d-computed-store-to-frozen-value.expect.md | 8 +- ...itional-call-aliased-hook-import.expect.md | 8 +- ...ditional-call-aliased-react-hook.expect.md | 8 +- ...l-call-non-hook-imported-as-hook.expect.md | 8 +- ...-conditional-setState-in-useMemo.expect.md | 22 +- ...omputed-property-of-frozen-value.expect.md | 8 +- ...-delete-property-of-frozen-value.expect.md | 8 +- ...destructure-assignment-to-global.expect.md | 8 +- ...ucture-to-local-global-variables.expect.md | 8 +- ...-disallow-mutating-ref-in-render.expect.md | 8 +- ...tating-refs-in-render-transitive.expect.md | 20 +- .../error.invalid-eval-unsupported.expect.md | 10 +- ...pression-mutates-immutable-value.expect.md | 10 +- ...lid-global-reassignment-indirect.expect.md | 8 +- .../error.invalid-hoisting-setstate.expect.md | 26 ++- ...-argument-mutates-local-variable.expect.md | 20 +- ...valid-impure-functions-in-render.expect.md | 40 +++- ...id-jsx-captures-context-variable.expect.md | 10 +- ...alid-mutate-after-aliased-freeze.expect.md | 8 +- ...rror.invalid-mutate-after-freeze.expect.md | 8 +- ...valid-mutate-context-in-callback.expect.md | 10 +- .../error.invalid-mutate-context.expect.md | 8 +- ...-mutate-props-in-effect-fixpoint.expect.md | 10 +- ...mutate-props-via-for-of-iterator.expect.md | 8 +- ...rror.invalid-mutation-in-closure.expect.md | 10 +- ...n-of-possible-props-phi-indirect.expect.md | 10 +- ...eassign-local-variable-in-effect.expect.md | 10 +- ...d-reanimated-shared-value-writes.expect.md | 10 +- ...as-memo-dep-non-optional-in-body.expect.md | 10 +- ...or.invalid-pass-hook-as-call-arg.expect.md | 8 +- .../error.invalid-pass-hook-as-prop.expect.md | 8 +- ...id-pass-mutable-function-as-prop.expect.md | 22 +- ...ror.invalid-pass-ref-to-function.expect.md | 8 +- ...r.invalid-prop-mutation-indirect.expect.md | 10 +- ...d-property-store-to-frozen-value.expect.md | 8 +- ...rops-mutation-in-effect-indirect.expect.md | 10 +- ...d-ref-prop-in-render-destructure.expect.md | 8 +- ...ref-prop-in-render-property-load.expect.md | 8 +- .../error.invalid-reassign-const.expect.md | 10 +- ...ssign-local-in-hook-return-value.expect.md | 10 +- ...local-variable-in-async-callback.expect.md | 10 +- ...eassign-local-variable-in-effect.expect.md | 10 +- ...-local-variable-in-hook-argument.expect.md | 10 +- ...n-local-variable-in-jsx-callback.expect.md | 10 +- ...n-callback-invoked-during-render.expect.md | 8 +- ...error.invalid-ref-value-as-props.expect.md | 8 +- ...eturn-mutable-function-from-hook.expect.md | 20 +- ...d-set-and-read-ref-during-render.expect.md | 21 +- ...ef-nested-property-during-render.expect.md | 21 +- ...-in-useMemo-indirect-useCallback.expect.md | 8 +- ...rror.invalid-setState-in-useMemo.expect.md | 22 +- ....invalid-sketchy-code-use-forget.expect.md | 26 ++- ...invalid-ternary-with-hook-values.expect.md | 41 +++- ...name-not-typed-as-hook-namespace.expect.md | 10 +- ...ider-hook-name-not-typed-as-hook.expect.md | 10 +- ...hooklike-module-default-not-hook.expect.md | 10 +- ...vider-nonhook-name-typed-as-hook.expect.md | 10 +- ...es-memoizes-with-captures-values.expect.md | 22 +- ...alid-unclosed-eslint-suppression.expect.md | 10 +- ...nconditional-set-state-in-render.expect.md | 22 +- ...f-added-to-dep-without-type-info.expect.md | 20 +- ...-memoized-bc-range-overlaps-hook.expect.md | 8 +- ...valid-useEffect-dep-not-memoized.expect.md | 8 +- ...InsertionEffect-dep-not-memoized.expect.md | 8 +- ...useLayoutEffect-dep-not-memoized.expect.md | 8 +- ...r.invalid-useMemo-async-callback.expect.md | 8 +- ...or.invalid-useMemo-callback-args.expect.md | 8 +- ...rite-but-dont-read-ref-in-render.expect.md | 8 +- ...invalid-write-ref-prop-in-render.expect.md | 8 +- .../compiler/error.modify-state-2.expect.md | 8 +- .../compiler/error.modify-state.expect.md | 8 +- .../error.modify-useReducer-state.expect.md | 8 +- ...ange-shared-inner-outer-function.expect.md | 10 +- .../error.mutate-function-property.expect.md | 8 +- ...lobal-increment-op-invalid-react.expect.md | 8 +- .../error.mutate-hook-argument.expect.md | 21 +- ...rror.mutate-property-from-global.expect.md | 8 +- .../compiler/error.mutate-props.expect.md | 8 +- .../error.nomemo-and-change-detect.expect.md | 3 +- ...or.not-useEffect-external-mutate.expect.md | 22 +- ...r.object-capture-global-mutation.expect.md | 8 +- .../error.propertyload-hook.expect.md | 21 +- .../error.reassign-global-fn-arg.expect.md | 8 +- ....reassignment-to-global-indirect.expect.md | 22 +- .../error.reassignment-to-global.expect.md | 21 +- ...ror.ref-initialization-arbitrary.expect.md | 22 +- .../error.ref-initialization-call-2.expect.md | 8 +- .../error.ref-initialization-call.expect.md | 8 +- .../error.ref-initialization-linear.expect.md | 8 +- .../error.ref-initialization-nonif.expect.md | 24 +- .../error.ref-initialization-other.expect.md | 8 +- ...ref-initialization-post-access-2.expect.md | 8 +- ...r.ref-initialization-post-access.expect.md | 8 +- .../error.ref-like-name-not-Ref.expect.md | 10 +- .../error.ref-like-name-not-a-ref.expect.md | 10 +- .../compiler/error.ref-optional.expect.md | 8 +- .../error.repro-ref-mutable-range.expect.md | 8 +- ...ror.sketchy-code-exhaustive-deps.expect.md | 10 +- ...rror.sketchy-code-rules-of-hooks.expect.md | 10 +- .../error.store-property-in-global.expect.md | 8 +- .../error.todo-for-await-loops.expect.md | 8 +- ...p-with-context-variable-iterator.expect.md | 8 +- ...p-with-context-variable-iterator.expect.md | 8 +- ...ences-later-variable-declaration.expect.md | 10 +- ...error.todo-functiondecl-hoisting.expect.md | 8 +- ...andle-update-context-identifiers.expect.md | 8 +- .../error.todo-hoist-function-decls.expect.md | 8 +- ...ted-function-in-unreachable-code.expect.md | 8 +- ...-hoisting-simple-var-declaration.expect.md | 8 +- ...ok-call-spreads-mutable-iterator.expect.md | 8 +- ...-catch-in-outer-try-with-finally.expect.md | 8 +- ...-invalid-jsx-in-try-with-finally.expect.md | 8 +- .../compiler/error.todo-kitchensink.expect.md | 164 ++++++++++++-- ...ical-expression-within-try-catch.expect.md | 8 +- ...wer-property-load-into-temporary.expect.md | 8 +- ...or.todo-new-target-meta-property.expect.md | 8 +- ...after-construction-sequence-expr.expect.md | 8 +- ...dified-during-after-construction.expect.md | 8 +- ...te-key-while-constructing-object.expect.md | 8 +- ...odo-object-expression-get-syntax.expect.md | 8 +- ...ject-expression-member-expr-call.expect.md | 8 +- ...odo-object-expression-set-syntax.expect.md | 8 +- ...ional-call-chain-in-logical-expr.expect.md | 8 +- ...-optional-call-chain-in-optional.expect.md | 8 +- ...o-optional-call-chain-in-ternary.expect.md | 8 +- .../error.todo-reassign-const.expect.md | 8 +- ...-declaration-for-all-identifiers.expect.md | 8 +- ...ed-function-inferred-as-mutation.expect.md | 8 +- ...from-inferred-mutation-in-logger.expect.md | 50 ++++- ...on-with-shadowed-local-same-name.expect.md | 10 +- ...ack-captured-in-context-variable.expect.md | 8 +- ...ified-later-preserve-memoization.expect.md | 8 +- ...todo-valid-functiondecl-hoisting.expect.md | 8 +- .../error.todo.try-catch-with-throw.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 +- ...ror.update-global-should-bailout.expect.md | 8 +- ...ia-function-preserve-memoization.expect.md | 20 +- ...operty-dont-preserve-memoization.expect.md | 8 +- ...error.useMemo-callback-generator.expect.md | 8 +- ...ror.useMemo-non-literal-depslist.expect.md | 8 +- ...ror.validate-blocklisted-imports.expect.md | 10 +- ...ffect-deps-invalidated-dep-value.expect.md | 8 +- ...alidate-mutate-ref-arg-in-render.expect.md | 8 +- .../fbt/error.todo-fbt-as-local.expect.md | 8 +- ...rror.todo-fbt-unknown-enum-value.expect.md | 17 +- .../error.todo-locally-require-fbt.expect.md | 8 +- .../error.todo-multiple-fbt-plural.expect.md | 17 +- .../dynamic-gating-bailout-nopanic.expect.md | 2 +- .../dynamic-gating-invalid-multiple.expect.md | 2 +- ...ntifier-nopanic-required-feature.expect.md | 8 +- ...ynamic-gating-invalid-identifier.expect.md | 10 +- ...e-in-non-react-fn-default-import.expect.md | 8 +- .../error.callsite-in-non-react-fn.expect.md | 8 +- .../error.non-inlined-effect-fn.expect.md | 8 +- .../error.todo-dynamic-gating.expect.md | 8 +- .../bailout-retry/error.todo-gating.expect.md | 8 +- ...mport-default-property-useEffect.expect.md | 8 +- .../bailout-retry/error.todo-syntax.expect.md | 8 +- .../bailout-retry/error.use-no-memo.expect.md | 8 +- ...e-after-useeffect-optional-chain.expect.md | 2 +- ...utate-after-useeffect-ref-access.expect.md | 2 +- .../mutate-after-useeffect.expect.md | 2 +- .../error.wrong-index-no-func.expect.md | 8 +- .../error.wrong-index.expect.md | 8 +- .../no-emit/retry-no-emit.expect.md | 2 +- ...valid-impure-functions-in-render.expect.md | 40 +++- ...n-local-variable-in-jsx-callback.expect.md | 10 +- ...rozen-hoisted-storecontext-const.expect.md | 26 ++- ...back-captures-reassigned-context.expect.md | 20 +- .../error.mutate-frozen-value.expect.md | 8 +- .../error.mutate-hook-argument.expect.md | 21 +- ...or.not-useEffect-external-mutate.expect.md | 22 +- ....reassignment-to-global-indirect.expect.md | 22 +- .../error.reassignment-to-global.expect.md | 21 +- ...on-with-shadowed-local-same-name.expect.md | 10 +- ...e-after-useeffect-optional-chain.expect.md | 2 +- ...utate-after-useeffect-ref-access.expect.md | 2 +- .../mutate-after-useeffect.expect.md | 2 +- .../new-mutability/retry-no-emit.expect.md | 2 +- ...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 | 10 +- ...ck-infer-conditional-value-block.expect.md | 40 +++- ...back-captures-reassigned-context.expect.md | 20 +- ...nvalid-useCallback-read-maybeRef.expect.md | 10 +- ...be-invalid-useMemo-read-maybeRef.expect.md | 10 +- ....maybe-mutable-ref-not-preserved.expect.md | 8 +- ...ve-use-memo-ref-missing-reactive.expect.md | 10 +- ...back-captures-invalidating-value.expect.md | 8 +- .../error.useCallback-aliased-var.expect.md | 10 +- ...lback-conditional-access-noAlloc.expect.md | 10 +- ...less-specific-conditional-access.expect.md | 10 +- ...or.useCallback-property-call-dep.expect.md | 10 +- .../error.useMemo-aliased-var.expect.md | 10 +- ...less-specific-conditional-access.expect.md | 10 +- ...specific-conditional-value-block.expect.md | 39 +++- ...emo-property-call-chained-object.expect.md | 10 +- .../error.useMemo-property-call-dep.expect.md | 10 +- ...o-unrelated-mutation-in-depslist.expect.md | 10 +- .../error.useMemo-with-refs.flow.expect.md | 8 +- ....validate-useMemo-named-function.expect.md | 8 +- ...-optional-call-chain-in-optional.expect.md | 8 +- ...ession-with-conditional-optional.expect.md | 10 +- ...mber-expression-with-conditional.expect.md | 10 +- ...bail.rules-of-hooks-3d692676194b.expect.md | 10 +- ...bail.rules-of-hooks-8503ca76d6f8.expect.md | 10 +- ...r.invalid-call-phi-possibly-hook.expect.md | 31 ++- ...nally-call-local-named-like-hook.expect.md | 8 +- ...onally-call-prop-named-like-hook.expect.md | 8 +- ...dcall-hooklike-property-of-local.expect.md | 8 +- ...-call-hooklike-property-of-local.expect.md | 8 +- ...-dynamic-hook-via-hooklike-local.expect.md | 8 +- ....invalid-hook-after-early-return.expect.md | 8 +- ...invalid-hook-as-conditional-test.expect.md | 8 +- .../error.invalid-hook-as-prop.expect.md | 8 +- .../error.invalid-hook-for.expect.md | 22 +- ...or.invalid-hook-from-hook-return.expect.md | 8 +- ...hook-from-property-of-other-hook.expect.md | 8 +- .../error.invalid-hook-if-alternate.expect.md | 8 +- ...error.invalid-hook-if-consequent.expect.md | 8 +- ...ion-expression-object-expression.expect.md | 10 +- ...lid-hook-in-nested-object-method.expect.md | 10 +- ...invalid-hook-optional-methodcall.expect.md | 8 +- ...r.invalid-hook-optional-property.expect.md | 8 +- .../error.invalid-hook-optionalcall.expect.md | 8 +- ...d-hook-reassigned-in-conditional.expect.md | 31 ++- ...alid-rules-of-hooks-1b9527f967f3.expect.md | 46 +++- ...alid-rules-of-hooks-2aabd222fc6a.expect.md | 8 +- ...alid-rules-of-hooks-49d341e5d68f.expect.md | 8 +- ...alid-rules-of-hooks-79128a755612.expect.md | 8 +- ...alid-rules-of-hooks-9718e30b856c.expect.md | 8 +- ...alid-rules-of-hooks-9bf17c174134.expect.md | 21 +- ...alid-rules-of-hooks-b4dcda3d60ed.expect.md | 8 +- ...alid-rules-of-hooks-c906cace44e9.expect.md | 8 +- ...alid-rules-of-hooks-d740d54e9c21.expect.md | 8 +- ...alid-rules-of-hooks-d85c144bdf40.expect.md | 22 +- ...alid-rules-of-hooks-ea7c2fb545a9.expect.md | 8 +- ...alid-rules-of-hooks-f3d6c5e9c83d.expect.md | 8 +- ...alid-rules-of-hooks-f69800950ff0.expect.md | 35 ++- ...alid-rules-of-hooks-0a1dbff27ba0.expect.md | 10 +- ...alid-rules-of-hooks-0de1224ce64b.expect.md | 26 ++- ...alid-rules-of-hooks-449a37146a83.expect.md | 10 +- ...alid-rules-of-hooks-76a74b4666e9.expect.md | 10 +- ...alid-rules-of-hooks-d842d36db450.expect.md | 10 +- ...alid-rules-of-hooks-d952b82c2597.expect.md | 10 +- ...alid-rules-of-hooks-368024110a58.expect.md | 8 +- ...alid-rules-of-hooks-8566f9a360e2.expect.md | 8 +- ...alid-rules-of-hooks-a0058f0b446d.expect.md | 8 +- ...rror.rules-of-hooks-27c18dc8dad2.expect.md | 8 +- ...rror.rules-of-hooks-d0935abedc42.expect.md | 8 +- ...rror.rules-of-hooks-e29c874aa913.expect.md | 8 +- ...rror.object-pattern-computed-key.expect.md | 8 +- .../bailout-retry/error.todo-syntax.expect.md | 8 +- ...ror.untransformed-fire-reference.expect.md | 8 +- .../bailout-retry/error.use-no-memo.expect.md | 8 +- ...ror.invalid-mix-fire-and-no-fire.expect.md | 10 +- .../error.invalid-multiple-args.expect.md | 10 +- .../error.invalid-nested-use-effect.expect.md | 10 +- .../error.invalid-not-call.expect.md | 10 +- .../error.invalid-outside-effect.expect.md | 26 ++- ...id-rewrite-deps-no-array-literal.expect.md | 10 +- ...rror.invalid-rewrite-deps-spread.expect.md | 10 +- .../error.invalid-spread.expect.md | 10 +- .../error.todo-method.expect.md | 10 +- .../babel-plugin-react-compiler/src/index.ts | 3 + .../__tests__/ReactCompilerRule-test.ts | 20 +- .../ReactCompilerRuleTypescript-test.ts | 3 +- .../src/rules/ReactCompilerRule.ts | 77 ++++--- compiler/packages/snap/src/runner-worker.ts | 23 -- .../__tests__/ReactCompilerRule-test.ts | 50 ++++- .../ReactCompilerRuleTypescript-test.ts | 5 +- .../src/rules/ReactCompiler.ts | 45 ++-- 317 files changed, 3560 insertions(+), 626 deletions(-) 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 58167194249e5..18946703a03e4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Babel/BabelPlugin.ts @@ -12,6 +12,7 @@ import { pipelineUsesReanimatedPlugin, } from '../Entrypoint/Reanimated'; import validateNoUntransformedReferences from '../Entrypoint/ValidateNoUntransformedReferences'; +import {CompilerError} from '..'; const ENABLE_REACT_COMPILER_TIMINGS = process.env['ENABLE_REACT_COMPILER_TIMINGS'] === '1'; @@ -34,51 +35,58 @@ export default function BabelPluginReactCompiler( */ Program: { enter(prog, pass): void { - const filename = pass.filename ?? 'unknown'; - if (ENABLE_REACT_COMPILER_TIMINGS === true) { - performance.mark(`${filename}:start`, { - detail: 'BabelPlugin:Program:start', - }); - } - let opts = parsePluginOptions(pass.opts); - const isDev = - (typeof __DEV__ !== 'undefined' && __DEV__ === true) || - process.env['NODE_ENV'] === 'development'; - if ( - opts.enableReanimatedCheck === true && - pipelineUsesReanimatedPlugin(pass.file.opts.plugins) - ) { - opts = injectReanimatedFlag(opts); - } - if ( - opts.environment.enableResetCacheOnSourceFileChanges !== false && - isDev - ) { - opts = { - ...opts, - environment: { - ...opts.environment, - enableResetCacheOnSourceFileChanges: true, - }, - }; - } - const result = compileProgram(prog, { - opts, - filename: pass.filename ?? null, - comments: pass.file.ast.comments ?? [], - code: pass.file.code, - }); - validateNoUntransformedReferences( - prog, - pass.filename ?? null, - opts.logger, - opts.environment, - result, - ); - if (ENABLE_REACT_COMPILER_TIMINGS === true) { - performance.mark(`${filename}:end`, { - detail: 'BabelPlugin:Program:end', + try { + const filename = pass.filename ?? 'unknown'; + if (ENABLE_REACT_COMPILER_TIMINGS === true) { + performance.mark(`${filename}:start`, { + detail: 'BabelPlugin:Program:start', + }); + } + let opts = parsePluginOptions(pass.opts); + const isDev = + (typeof __DEV__ !== 'undefined' && __DEV__ === true) || + process.env['NODE_ENV'] === 'development'; + if ( + opts.enableReanimatedCheck === true && + pipelineUsesReanimatedPlugin(pass.file.opts.plugins) + ) { + opts = injectReanimatedFlag(opts); + } + if ( + opts.environment.enableResetCacheOnSourceFileChanges !== false && + isDev + ) { + opts = { + ...opts, + environment: { + ...opts.environment, + enableResetCacheOnSourceFileChanges: true, + }, + }; + } + const result = compileProgram(prog, { + opts, + filename: pass.filename ?? null, + comments: pass.file.ast.comments ?? [], + code: pass.file.code, }); + validateNoUntransformedReferences( + prog, + pass.filename ?? null, + opts.logger, + opts.environment, + result, + ); + if (ENABLE_REACT_COMPILER_TIMINGS === true) { + performance.mark(`${filename}:end`, { + detail: 'BabelPlugin:Program:end', + }); + } + } catch (e) { + if (e instanceof CompilerError) { + throw new Error(e.printErrorMessage(pass.file.code)); + } + throw e; } }, exit(_, pass): void { diff --git a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts index 75e01abaefa8f..cac897a9269be 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import {codeFrameColumns} from '@babel/code-frame'; import type {SourceLocation} from './HIR'; import {Err, Ok, Result} from './Utils/Result'; import {assertExhaustive} from './Utils/utils'; @@ -44,6 +45,24 @@ export enum ErrorSeverity { Invariant = 'Invariant', } +export type CompilerDiagnosticOptions = { + severity: ErrorSeverity; + category: string; + description: string; + details: Array; + suggestions?: Array | null | undefined; +}; + +export type CompilerDiagnosticDetail = + /** + * A/the source of the error + */ + { + kind: 'error'; + loc: SourceLocation; + message: string; + }; + export enum CompilerSuggestionOperation { InsertBefore, InsertAfter, @@ -74,6 +93,94 @@ export type CompilerErrorDetailOptions = { suggestions?: Array | null | undefined; }; +export class CompilerDiagnostic { + options: CompilerDiagnosticOptions; + + constructor(options: CompilerDiagnosticOptions) { + this.options = options; + } + + get category(): CompilerDiagnosticOptions['category'] { + return this.options.category; + } + get description(): CompilerDiagnosticOptions['description'] { + return this.options.description; + } + get severity(): CompilerDiagnosticOptions['severity'] { + return this.options.severity; + } + get suggestions(): CompilerDiagnosticOptions['suggestions'] { + return this.options.suggestions; + } + + primaryLocation(): SourceLocation | null { + return this.options.details.filter(d => d.kind === 'error')[0]?.loc ?? null; + } + + printErrorMessage(source: string): string { + const buffer = [ + printErrorSummary(this.severity, this.category), + '\n\n', + this.description, + ]; + for (const detail of this.options.details) { + switch (detail.kind) { + case 'error': { + const loc = detail.loc; + if (typeof loc === 'symbol') { + continue; + } + let codeFrame: string; + try { + codeFrame = codeFrameColumns( + source, + { + start: { + line: loc.start.line, + column: loc.start.column + 1, + }, + end: { + line: loc.end.line, + column: loc.end.column + 1, + }, + }, + { + message: detail.message, + }, + ); + } catch (e) { + codeFrame = detail.message; + } + buffer.push( + `\n\n${loc.filename}:${loc.start.line}:${loc.start.column}\n`, + ); + buffer.push(codeFrame); + break; + } + default: { + assertExhaustive( + detail.kind, + `Unexpected detail kind ${(detail as any).kind}`, + ); + } + } + } + return buffer.join(''); + } + + toString(): string { + const buffer = [printErrorSummary(this.severity, this.category)]; + if (this.description != null) { + buffer.push(`. ${this.description}.`); + } + const loc = this.primaryLocation(); + if (loc != null && typeof loc !== 'symbol') { + buffer.push(` (${loc.start.line}:${loc.start.column})`); + } + return buffer.join(''); + } +} + /* * Each bailout or invariant in HIR lowering creates an {@link CompilerErrorDetail}, which is then * aggregated into a single {@link CompilerError} later. @@ -101,24 +208,62 @@ export class CompilerErrorDetail { return this.options.suggestions; } - printErrorMessage(): string { - const buffer = [`${this.severity}: ${this.reason}`]; + primaryLocation(): SourceLocation | null { + return this.loc; + } + + printErrorMessage(source: string): string { + const buffer = [printErrorSummary(this.severity, this.reason)]; if (this.description != null) { - buffer.push(`. ${this.description}`); + buffer.push(`\n\n${this.description}.`); } - if (this.loc != null && typeof this.loc !== 'symbol') { - buffer.push(` (${this.loc.start.line}:${this.loc.end.line})`); + const loc = this.loc; + if (loc != null && typeof loc !== 'symbol') { + let codeFrame: string; + try { + codeFrame = codeFrameColumns( + source, + { + start: { + line: loc.start.line, + column: loc.start.column + 1, + }, + end: { + line: loc.end.line, + column: loc.end.column + 1, + }, + }, + { + message: this.reason, + }, + ); + } catch (e) { + codeFrame = ''; + } + buffer.push( + `\n\n${loc.filename}:${loc.start.line}:${loc.start.column}\n`, + ); + buffer.push(codeFrame); + buffer.push('\n\n'); } return buffer.join(''); } toString(): string { - return this.printErrorMessage(); + const buffer = [printErrorSummary(this.severity, this.reason)]; + if (this.description != null) { + buffer.push(`. ${this.description}.`); + } + const loc = this.loc; + if (loc != null && typeof loc !== 'symbol') { + buffer.push(` (${loc.start.line}:${loc.start.column})`); + } + return buffer.join(''); } } export class CompilerError extends Error { - details: Array = []; + details: Array = []; static invariant( condition: unknown, @@ -136,6 +281,12 @@ export class CompilerError extends Error { } } + static throwDiagnostic(options: CompilerDiagnosticOptions): never { + const errors = new CompilerError(); + errors.pushDiagnostic(new CompilerDiagnostic(options)); + throw errors; + } + static throwTodo( options: Omit, ): never { @@ -210,6 +361,21 @@ export class CompilerError extends Error { return this.name; } + printErrorMessage(source: string): string { + return ( + `Found ${this.details.length} error${this.details.length === 1 ? '' : 's'}:\n` + + this.details.map(detail => detail.printErrorMessage(source)).join('\n') + ); + } + + merge(other: CompilerError): void { + this.details.push(...other.details); + } + + pushDiagnostic(diagnostic: CompilerDiagnostic): void { + this.details.push(diagnostic); + } + push(options: CompilerErrorDetailOptions): CompilerErrorDetail { const detail = new CompilerErrorDetail({ reason: options.reason, @@ -260,3 +426,32 @@ export class CompilerError extends Error { }); } } + +function printErrorSummary(severity: ErrorSeverity, message: string): string { + let severityCategory: string; + switch (severity) { + case ErrorSeverity.InvalidConfig: + case ErrorSeverity.InvalidJS: + case ErrorSeverity.InvalidReact: + case ErrorSeverity.UnsupportedJS: { + severityCategory = 'Error'; + break; + } + case ErrorSeverity.CannotPreserveMemoization: { + severityCategory = 'Memoization'; + break; + } + case ErrorSeverity.Invariant: { + severityCategory = 'Invariant'; + break; + } + case ErrorSeverity.Todo: { + severityCategory = 'Todo'; + break; + } + default: { + assertExhaustive(severity, `Unexpected severity '${severity}'`); + } + } + return `${severityCategory}: ${message}`; +} diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts index 0c23ceb345296..c13940ed10a21 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Options.ts @@ -7,7 +7,12 @@ import * as t from '@babel/types'; import {z} from 'zod'; -import {CompilerError, CompilerErrorDetailOptions} from '../CompilerError'; +import { + CompilerDiagnostic, + CompilerError, + CompilerErrorDetail, + CompilerErrorDetailOptions, +} from '../CompilerError'; import { EnvironmentConfig, ExternalFunction, @@ -224,7 +229,7 @@ export type LoggerEvent = export type CompileErrorEvent = { kind: 'CompileError'; fnLoc: t.SourceLocation | null; - detail: CompilerErrorDetailOptions; + detail: CompilerErrorDetail | CompilerDiagnostic; }; export type CompileDiagnosticEvent = { kind: 'CompileDiagnostic'; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts index de8d16fb12a6e..79bbee37a565c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts @@ -181,7 +181,7 @@ function logError( context.opts.logger.logEvent(context.filename, { kind: 'CompileError', fnLoc, - detail: detail.options, + detail, }); } } else { diff --git a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts index d363e11831d3d..16d7c3713c6dd 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Entrypoint/ValidateNoUntransformedReferences.ts @@ -8,32 +8,27 @@ import {NodePath} from '@babel/core'; import * as t from '@babel/types'; -import { - CompilerError, - CompilerErrorDetailOptions, - EnvironmentConfig, - ErrorSeverity, - Logger, -} from '..'; +import {CompilerError, EnvironmentConfig, ErrorSeverity, Logger} from '..'; import {getOrInsertWith} from '../Utils/utils'; -import {Environment} from '../HIR'; +import {Environment, GeneratedSource} from '../HIR'; import {DEFAULT_EXPORT} from '../HIR/Environment'; import {CompileProgramMetadata} from './Program'; +import {CompilerDiagnostic, CompilerDiagnosticOptions} from '../CompilerError'; function throwInvalidReact( - options: Omit, + options: Omit, {logger, filename}: TraversalState, ): never { - const detail: CompilerErrorDetailOptions = { - ...options, + const detail: CompilerDiagnosticOptions = { severity: ErrorSeverity.InvalidReact, + ...options, }; logger?.logEvent(filename, { kind: 'CompileError', fnLoc: null, - detail, + detail: new CompilerDiagnostic(detail), }); - CompilerError.throw(detail); + CompilerError.throwDiagnostic(detail); } function isAutodepsSigil( @@ -97,14 +92,18 @@ function assertValidEffectImportReference( */ throwInvalidReact( { - reason: - '[InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. ' + - 'This will break your build! ' + - 'To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.', - description: maybeErrorDiagnostic - ? `(Bailout reason: ${maybeErrorDiagnostic})` - : null, - loc: parent.node.loc ?? null, + category: + 'Cannot infer dependencies of this effect. This will break your build!', + description: + 'To resolve, either pass a dependency array or fix reported compiler bailout diagnostics.' + + (maybeErrorDiagnostic ? ` ${maybeErrorDiagnostic}` : ''), + details: [ + { + kind: 'error', + message: 'Cannot infer dependencies', + loc: parent.node.loc ?? GeneratedSource, + }, + ], }, context, ); @@ -124,13 +123,20 @@ function assertValidFireImportReference( ); throwInvalidReact( { - reason: - '[Fire] Untransformed reference to compiler-required feature. ' + - 'Either remove this `fire` call or ensure it is successfully transformed by the compiler', - description: maybeErrorDiagnostic - ? `(Bailout reason: ${maybeErrorDiagnostic})` - : null, - loc: paths[0].node.loc ?? null, + category: + '[Fire] Untransformed reference to compiler-required feature.', + description: + 'Either remove this `fire` call or ensure it is successfully transformed by the compiler' + + maybeErrorDiagnostic + ? ` ${maybeErrorDiagnostic}` + : '', + details: [ + { + kind: 'error', + message: 'Untransformed `fire` call', + loc: paths[0].node.loc ?? GeneratedSource, + }, + ], }, context, ); 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 cd7d6a7a35b35..5b5702f19c62b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/BuildHIR.ts @@ -2271,11 +2271,17 @@ function lowerExpression( }); for (const [name, locations] of Object.entries(fbtLocations)) { if (locations.length > 1) { - CompilerError.throwTodo({ - reason: `Support <${tagName}> tags with multiple <${tagName}:${name}> values`, - loc: locations.at(-1) ?? GeneratedSource, - description: null, - suggestions: null, + CompilerError.throwDiagnostic({ + severity: ErrorSeverity.Todo, + category: 'Support duplicate fbt tags', + description: `Support \`<${tagName}>\` tags with multiple \`<${tagName}:${name}>\` values`, + details: locations.map(loc => { + return { + kind: 'error', + message: `Multiple \`<${tagName}:${name}>\` tags found`, + loc, + }; + }), }); } } @@ -3503,9 +3509,8 @@ function lowerFunction( ); let loweredFunc: HIRFunction; if (lowering.isErr()) { - lowering - .unwrapErr() - .details.forEach(detail => builder.errors.pushErrorDetail(detail)); + const functionErrors = lowering.unwrapErr(); + builder.errors.merge(functionErrors); return null; } loweredFunc = lowering.unwrap(); diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts index c3a6c18d3aaff..81959ea361564 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/HIRBuilder.ts @@ -7,7 +7,7 @@ import {Binding, NodePath} from '@babel/traverse'; import * as t from '@babel/types'; -import {CompilerError} from '../CompilerError'; +import {CompilerError, ErrorSeverity} from '../CompilerError'; import {Environment} from './Environment'; import { BasicBlock, @@ -308,9 +308,18 @@ export default class HIRBuilder { resolveBinding(node: t.Identifier): Identifier { if (node.name === 'fbt') { - CompilerError.throwTodo({ - reason: 'Support local variables named "fbt"', - loc: node.loc ?? null, + CompilerError.throwDiagnostic({ + severity: ErrorSeverity.Todo, + category: 'Support local variables named `fbt`', + description: + 'Local variables named `fbt` may conflict with the fbt plugin and are not yet supported', + details: [ + { + kind: 'error', + message: 'Rename to avoid conflict with fbt plugin', + loc: node.loc ?? GeneratedSource, + }, + ], }); } const originalName = node.name; diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts b/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts index 500edab8f20f5..f8a6330977fe8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/envConfig-test.ts @@ -20,7 +20,7 @@ describe('parseConfigPragma()', () => { validateHooksUsage: 1, } as any); }).toThrowErrorMatchingInlineSnapshot( - `"InvalidConfig: Could not validate environment config. Update React Compiler config to fix the error. Validation error: Expected boolean, received number at "validateHooksUsage""`, + `"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: Expected boolean, received number at "validateHooksUsage"."`, ); }); @@ -38,7 +38,7 @@ describe('parseConfigPragma()', () => { ], } as any); }).toThrowErrorMatchingInlineSnapshot( - `"InvalidConfig: Could not validate environment config. Update React Compiler config to fix the error. Validation error: autodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex""`, + `"Error: Could not validate environment config. Update React Compiler config to fix the error. Validation error: autodepsIndex must be > 0 at "inferEffectDependencies[0].autodepsIndex"."`, ); }); diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error._todo.computed-lval-in-destructure.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error._todo.computed-lval-in-destructure.expect.md index f44ae83b2cee0..eaa480f7c5f62 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error._todo.computed-lval-in-destructure.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error._todo.computed-lval-in-destructure.expect.md @@ -15,13 +15,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Todo: (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern + +error._todo.computed-lval-in-destructure.ts:3:9 1 | function Component(props) { 2 | const computedKey = props.key; > 3 | const {[computedKey]: x} = props.val; - | ^^^^^^^^^^^^^^^^ Todo: (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern (3:3) + | ^^^^^^^^^^^^^^^^ (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern 4 | 5 | return x; 6 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md index 5553f235a0847..d15aba19d12ee 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-component-tag-function.expect.md @@ -15,13 +15,19 @@ function Component() { ## Error ``` +Found 1 error: +Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) + +error.assign-global-in-component-tag-function.ts:3:4 1 | function Component() { 2 | const Foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (3:3) + | ^^^^^^^^^^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) 4 | }; 5 | return ; 6 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md index d380137836cc1..634d98394c3da 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-children.expect.md @@ -18,13 +18,19 @@ function Component() { ## Error ``` +Found 1 error: +Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) + +error.assign-global-in-jsx-children.ts:3:4 1 | function Component() { 2 | const foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (3:3) + | ^^^^^^^^^^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) 4 | }; 5 | // Children are generally access/called during render, so 6 | // modifying a global in a children function is almost + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md index 3f0b5530ee2d2..2bcf5a49f8e6e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.assign-global-in-jsx-spread-attribute.expect.md @@ -16,13 +16,19 @@ function Component() { ## Error ``` +Found 1 error: +Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) + +error.assign-global-in-jsx-spread-attribute.ts:4:4 2 | function Component() { 3 | const foo = () => { > 4 | someGlobal = true; - | ^^^^^^^^^^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (4:4) + | ^^^^^^^^^^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) 5 | }; 6 | return
; 7 | } + + ``` \ No newline at end of file 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 1d5b4abdf7d42..988a8dbab8d03 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 @@ -16,13 +16,21 @@ function Foo(props) { ## Error ``` +Found 1 error: +Error: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior + +$FlowFixMe[react-rule-hook]. + +error.bailout-on-flow-suppression.ts:4:2 2 | 3 | function Foo(props) { > 4 | // $FlowFixMe[react-rule-hook] - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. $FlowFixMe[react-rule-hook] (4:4) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React rule violations were reported by Flow. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior 5 | useX(); 6 | return null; 7 | } + + ``` \ No newline at end of file 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 d74ebd119c345..c6653177a7a53 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 @@ -19,15 +19,35 @@ function lowercasecomponent() { ## Error ``` +Found 2 errors: +Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior + +eslint-disable my-app/react-rule. + +error.bailout-on-suppression-of-custom-rule.ts:3:0 1 | // @eslintSuppressionRules:["my-app","react-rule"] 2 | > 3 | /* eslint-disable my-app/react-rule */ - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. eslint-disable my-app/react-rule (3:3) - -InvalidReact: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior. eslint-disable-next-line my-app/react-rule (7:7) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior 4 | function lowercasecomponent() { 5 | 'use forget'; 6 | const x = []; + + +Error: React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior + +eslint-disable-next-line my-app/react-rule. + +error.bailout-on-suppression-of-custom-rule.ts:7:2 + 5 | 'use forget'; + 6 | const x = []; +> 7 | // eslint-disable-next-line my-app/react-rule + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ React Compiler has skipped optimizing this component because one or more React ESLint rules were disabled. React Compiler only works when your components follow all the rules of React, disabling them may result in unexpected or incorrect behavior + 8 | return
{x}
; + 9 | } + 10 | /* eslint-enable my-app/react-rule */ + + ``` \ No newline at end of file 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 e1cebb00df56f..84370796a6724 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,6 +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 + +error.bug-old-inference-false-positive-ref-validation-in-use-effect.ts:20:12 18 | ); 19 | const ref = useRef(null); > 20 | useEffect(() => { @@ -47,12 +51,24 @@ function Component() { > 23 | } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ > 24 | }, [update]); - | ^^^^ InvalidReact: This argument is a function which may reassign or mutate local variables after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead (20:24) - -InvalidReact: The function modifies a local variable here (14:14) + | ^^^^ This 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 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 + 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.call-args-destructuring-asignment-complex.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.call-args-destructuring-asignment-complex.expect.md index cb2ce1a20df0a..fea112547e445 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.call-args-destructuring-asignment-complex.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.call-args-destructuring-asignment-complex.expect.md @@ -14,13 +14,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Invariant: Const declaration cannot be referenced as an expression + +error.call-args-destructuring-asignment-complex.ts:3:9 1 | function Component(props) { 2 | let x = makeObject(); > 3 | x.foo(([[x]] = makeObject())); - | ^^^^^ Invariant: Const declaration cannot be referenced as an expression (3:3) + | ^^^^^ Const declaration cannot be referenced as an expression 4 | return x; 5 | } 6 | + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-function-call-aliased.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-function-call-aliased.expect.md index 94b3ae1035ea2..dad64bcbd8dc4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-function-call-aliased.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-function-call-aliased.expect.md @@ -14,12 +14,20 @@ function Foo() { ## Error ``` +Found 1 error: +Error: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config + +Bar may be a component.. + +error.capitalized-function-call-aliased.ts:4:2 2 | function Foo() { 3 | let x = Bar; > 4 | x(); // ERROR - | ^^^ InvalidReact: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config. Bar may be a component. (4:4) + | ^^^ Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config 5 | } 6 | + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-function-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-function-call.expect.md index d8b0f8facfed0..e2894b6efd21b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-function-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-function-call.expect.md @@ -15,13 +15,21 @@ function Component() { ## Error ``` +Found 1 error: +Error: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config + +SomeFunc may be a component.. + +error.capitalized-function-call.ts:3:12 1 | // @validateNoCapitalizedCalls 2 | function Component() { > 3 | const x = SomeFunc(); - | ^^^^^^^^^^ InvalidReact: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config. SomeFunc may be a component. (3:3) + | ^^^^^^^^^^ Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config 4 | 5 | return x; 6 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-method-call.expect.md index 39dc43e4a563c..ecc0303692ea5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capitalized-method-call.expect.md @@ -15,13 +15,21 @@ function Component() { ## Error ``` +Found 1 error: +Error: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config + +SomeFunc may be a component.. + +error.capitalized-method-call.ts:3:12 1 | // @validateNoCapitalizedCalls 2 | function Component() { > 3 | const x = someGlobal.SomeFunc(); - | ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config. SomeFunc may be a component. (3:3) + | ^^^^^^^^^^^^^^^^^^^^^ Capitalized functions are reserved for components, which must be invoked with JSX. If this is a component, render it with JSX. Otherwise, ensure that it has no hook calls and rename it to begin with a lowercase letter. Alternatively, if you know for a fact that this function is not a component, you can allowlist it via the compiler config 4 | 5 | return x; 6 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md index cff34e3449376..9c9cd94dbd1f5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.capture-ref-for-mutation.expect.md @@ -32,19 +32,55 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` +Found 4 errors: +Error: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) + +error.capture-ref-for-mutation.ts:12:13 10 | }; 11 | const moveLeft = { > 12 | handler: handleKey('left')(), - | ^^^^^^^^^^^^^^^^^ InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (12:12) + | ^^^^^^^^^^^^^^^^^ This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) + 13 | }; + 14 | const moveRight = { + 15 | handler: handleKey('right')(), -InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (12:12) -InvalidReact: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) (15:15) +Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) -InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (15:15) +error.capture-ref-for-mutation.ts:12:13 + 10 | }; + 11 | const moveLeft = { +> 12 | handler: handleKey('left')(), + | ^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) 13 | }; 14 | const moveRight = { 15 | handler: handleKey('right')(), + + +Error: This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) + +error.capture-ref-for-mutation.ts:15:13 + 13 | }; + 14 | const moveRight = { +> 15 | handler: handleKey('right')(), + | ^^^^^^^^^^^^^^^^^^ This function accesses a ref value (the `current` property), which may not be accessed during render. (https://react.dev/reference/react/useRef) + 16 | }; + 17 | return [moveLeft, moveRight]; + 18 | } + + +Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) + +error.capture-ref-for-mutation.ts:15:13 + 13 | }; + 14 | const moveRight = { +> 15 | handler: handleKey('right')(), + | ^^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) + 16 | }; + 17 | return [moveLeft, moveRight]; + 18 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.conditional-hook-unknown-hook-react-namespace.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.conditional-hook-unknown-hook-react-namespace.expect.md index 7ea8ae9809370..86af804221926 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.conditional-hook-unknown-hook-react-namespace.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.conditional-hook-unknown-hook-react-namespace.expect.md @@ -16,13 +16,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) + +error.conditional-hook-unknown-hook-react-namespace.ts:4:8 2 | let x = null; 3 | if (props.cond) { > 4 | x = React.useNonexistentHook(); - | ^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4) + | ^^^^^^^^^^^^^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) 5 | } 6 | return x; 7 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.conditional-hooks-as-method-call.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.conditional-hooks-as-method-call.expect.md index c2ad547414aa9..427a573dc789c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.conditional-hooks-as-method-call.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.conditional-hooks-as-method-call.expect.md @@ -16,13 +16,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) + +error.conditional-hooks-as-method-call.ts:4:8 2 | let x = null; 3 | if (props.cond) { > 4 | x = Foo.useFoo(); - | ^^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (4:4) + | ^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) 5 | } 6 | return x; 7 | } + + ``` \ 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 0318fa9525fda..de50b21543742 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 @@ -28,13 +28,21 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` +Found 1 error: +Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + +Variable `x` cannot be reassigned after render. + +error.context-variable-only-chained-assign.ts:10:19 8 | }; 9 | const fn2 = () => { > 10 | const copy2 = (x = 4); - | ^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `x` cannot be reassigned after render (10:10) + | ^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead 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 2a6dce11f242e..6823db842d2a1 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 @@ -17,13 +17,21 @@ function Component() { ## Error ``` +Found 1 error: +Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + +Variable `x` cannot be reassigned after render. + +error.declare-reassign-variable-in-function-declaration.ts:4:4 2 | let x = null; 3 | function foo() { > 4 | x = 9; - | ^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `x` cannot be reassigned after render (4:4) + | ^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead 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.default-param-accesses-local.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.default-param-accesses-local.expect.md index dbf084466d80d..02e06c7a82fda 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.default-param-accesses-local.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.default-param-accesses-local.expect.md @@ -22,6 +22,10 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` +Found 1 error: +Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `ArrowFunctionExpression` cannot be safely reordered + +error.default-param-accesses-local.ts:3:6 1 | function Component( 2 | x, > 3 | y = () => { @@ -29,10 +33,12 @@ export const FIXTURE_ENTRYPOINT = { > 4 | return x; | ^^^^^^^^^^^^^ > 5 | } - | ^^^^ Todo: (BuildHIR::node.lowerReorderableExpression) Expression type `ArrowFunctionExpression` cannot be safely reordered (3:5) + | ^^^^ (BuildHIR::node.lowerReorderableExpression) Expression type `ArrowFunctionExpression` cannot be safely reordered 6 | ) { 7 | return y(); 8 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.dont-hoist-inline-reference.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.dont-hoist-inline-reference.expect.md index b08d151be64c4..c0bd287e12e87 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.dont-hoist-inline-reference.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.dont-hoist-inline-reference.expect.md @@ -19,13 +19,21 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` +Found 1 error: +Todo: [hoisting] EnterSSA: Expected identifier to be defined before being used + +Identifier x$1 is undefined. + +error.dont-hoist-inline-reference.ts:3:2 1 | import {identity} from 'shared-runtime'; 2 | function useInvalid() { > 3 | const x = identity(x); - | ^^^^^^^^^^^^^^^^^^^^^^ Todo: [hoisting] EnterSSA: Expected identifier to be defined before being used. Identifier x$1 is undefined (3:3) + | ^^^^^^^^^^^^^^^^^^^^^^ [hoisting] EnterSSA: Expected identifier to be defined before being used 4 | return x; 5 | } 6 | + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md index a54cc98708f1e..d1e1476535335 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.emit-freeze-conflicting-global.expect.md @@ -15,13 +15,21 @@ function useFoo(props) { ## Error ``` +Found 1 error: +Todo: Encountered conflicting global in generated program + +Conflict from local binding __DEV__. + +error.emit-freeze-conflicting-global.ts:3:8 1 | // @enableEmitFreeze @instrumentForget 2 | function useFoo(props) { > 3 | const __DEV__ = 'conflicting global'; - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Todo: Encountered conflicting global in generated program. Conflict from local binding __DEV__ (3:3) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Encountered conflicting global in generated program 4 | console.log(__DEV__); 5 | return foo(props.x); 6 | } + + ``` \ 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 76ac6d77a2774..47af99524836a 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 @@ -15,13 +15,21 @@ function Component() { ## Error ``` +Found 1 error: +Error: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead + +Variable `callback` cannot be reassigned after render. + +error.function-expression-references-variable-its-assigned-to.ts:3:4 1 | function Component() { 2 | let callback = () => { > 3 | callback = null; - | ^^^^^^^^ InvalidReact: Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead. Variable `callback` cannot be reassigned after render (3:3) + | ^^^^^^^^ Reassigning a variable after render has completed can cause inconsistent behavior on subsequent renders. Consider using state instead 4 | }; 5 | return
; 6 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoist-optional-member-expression-with-conditional-optional.expect.md index 048fee7ee1d58..dcde3a9f83f57 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 @@ -24,6 +24,12 @@ function Component(props) { ## Error ``` +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 + +The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source. + +error.hoist-optional-member-expression-with-conditional-optional.ts:4:23 2 | import {ValidateMemoization} from 'shared-runtime'; 3 | function Component(props) { > 4 | const data = useMemo(() => { @@ -41,10 +47,12 @@ function Component(props) { > 10 | return x; | ^^^^^^^^^^^^^^^^^ > 11 | }, [props?.items, props.cond]); - | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source (4:11) + | ^^^^ 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 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 ca3ee2ae1388e..ea6683fd0ad80 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 @@ -24,6 +24,12 @@ function Component(props) { ## Error ``` +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 + +The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source. + +error.hoist-optional-member-expression-with-conditional.ts:4:23 2 | import {ValidateMemoization} from 'shared-runtime'; 3 | function Component(props) { > 4 | const data = useMemo(() => { @@ -41,10 +47,12 @@ function Component(props) { > 10 | return x; | ^^^^^^^^^^^^^^^^^ > 11 | }, [props?.items, props.cond]); - | ^^^^ CannotPreserveMemoization: React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `props.items`, but the source dependencies were [props?.items, props.cond]. Inferred different dependency than source (4:11) + | ^^^^ 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 12 | return ( 13 | 14 | ); + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md index 1ba0d59e17265..c3ab81ba38beb 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hoisting-simple-function-declaration.expect.md @@ -24,6 +24,10 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` +Found 1 error: +Todo: Support functions with unreachable code that may contain hoisted declarations + +error.hoisting-simple-function-declaration.ts:6:2 4 | } 5 | return baz(); // OK: FuncDecls are HoistableDeclarations that have both declaration and value hoisting > 6 | function baz() { @@ -31,10 +35,12 @@ export const FIXTURE_ENTRYPOINT = { > 7 | return bar(); | ^^^^^^^^^^^^^^^^^ > 8 | } - | ^^^^ Todo: Support functions with unreachable code that may contain hoisted declarations (6:8) + | ^^^^ Support functions with unreachable code that may contain hoisted declarations 9 | } 10 | 11 | export const FIXTURE_ENTRYPOINT = { + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.expect.md index 5e0a9886272bc..7174acc43d2b5 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-identifier.expect.md @@ -29,13 +29,19 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` +Found 1 error: +Error: Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook + +error.hook-call-freezes-captured-identifier.ts:13:2 11 | }); 12 | > 13 | x.value += count; - | ^ InvalidReact: Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook (13:13) + | ^ Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook 14 | return ; 15 | } 16 | + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md index c5af59d642426..7a969400a33f7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md @@ -29,13 +29,19 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` +Found 1 error: +Error: Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook + +error.hook-call-freezes-captured-memberexpr.ts:13:2 11 | }); 12 | > 13 | x.value += count; - | ^ InvalidReact: Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook (13:13) + | ^ Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook 14 | return ; 15 | } 16 | + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-property-load-local-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-property-load-local-hook.expect.md index 0949fb3072fcb..f3716d810c5a0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-property-load-local-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-property-load-local-hook.expect.md @@ -23,15 +23,31 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` +Found 2 errors: +Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values + +error.hook-property-load-local-hook.ts:7:12 5 | 6 | function Foo() { > 7 | let bar = useFoo.useBar; - | ^^^^^^^^^^^^^ InvalidReact: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values (7:7) - -InvalidReact: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values (8:8) + | ^^^^^^^^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values 8 | return bar(); 9 | } 10 | + + +Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values + +error.hook-property-load-local-hook.ts:8:9 + 6 | function Foo() { + 7 | let bar = useFoo.useBar; +> 8 | return bar(); + | ^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values + 9 | } + 10 | + 11 | export const FIXTURE_ENTRYPOINT = { + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md index d92d918fe9f3c..abf18e43e3f3d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-ref-value.expect.md @@ -20,15 +20,31 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` +Found 2 errors: +Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) + +error.hook-ref-value.ts:5:23 3 | function Component(props) { 4 | const ref = useRef(); > 5 | useEffect(() => {}, [ref.current]); - | ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5) + | ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) + 6 | } + 7 | + 8 | export const FIXTURE_ENTRYPOINT = { + + +Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) -InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (5:5) +error.hook-ref-value.ts:5:23 + 3 | function Component(props) { + 4 | const ref = useRef(); +> 5 | useEffect(() => {}, [ref.current]); + | ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) 6 | } 7 | 8 | export const FIXTURE_ENTRYPOINT = { + + ``` \ 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 db616600e80dc..1c5c92d2c305a 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 @@ -15,16 +15,22 @@ function component(a, b) { ## Error ``` +Found 1 error: +Error: useMemo callbacks may not be async or generator functions + +error.invalid-ReactUseMemo-async-callback.ts:2:24 1 | function component(a, b) { > 2 | let x = React.useMemo(async () => { | ^^^^^^^^^^^^^ > 3 | await a; | ^^^^^^^^^^^^ > 4 | }, []); - | ^^^^ InvalidReact: useMemo callbacks may not be async or generator functions (2:4) + | ^^^^ useMemo callbacks may not be async or generator functions 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-access-ref-during-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md index 02748366456fb..d3dd7317ef90e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-access-ref-during-render.expect.md @@ -15,13 +15,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) + +error.invalid-access-ref-during-render.ts:4:16 2 | function Component(props) { 3 | const ref = useRef(null); > 4 | const value = ref.current; - | ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4) + | ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) 5 | return value; 6 | } 7 | + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-aliased-ref-in-callback-invoked-during-render-.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-aliased-ref-in-callback-invoked-during-render-.expect.md index e2ce2cceae3b9..7d7a0dafcee74 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-aliased-ref-in-callback-invoked-during-render-.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-aliased-ref-in-callback-invoked-during-render-.expect.md @@ -19,12 +19,18 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) + +error.invalid-aliased-ref-in-callback-invoked-during-render-.ts:9:33 7 | return ; 8 | }; > 9 | return {props.items.map(item => renderItem(item))}; - | ^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (9:9) + | ^^^^^^^^^^^^^^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) 10 | } 11 | + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-array-push-frozen.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-array-push-frozen.expect.md index 0440117adbfc7..137d29cbc2916 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-array-push-frozen.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-array-push-frozen.expect.md @@ -15,13 +15,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX + +error.invalid-array-push-frozen.ts:4:2 2 | const x = []; 3 |
{x}
; > 4 | x.push(props.value); - | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX (4:4) + | ^ Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX 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-assign-hook-to-local.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-assign-hook-to-local.expect.md index a4327cf961bfc..6abdb5b2efc04 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-assign-hook-to-local.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-assign-hook-to-local.expect.md @@ -14,12 +14,18 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values + +error.invalid-assign-hook-to-local.ts:2:12 1 | function Component(props) { > 2 | const x = useState; - | ^^^^^^^^ InvalidReact: Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values (2:2) + | ^^^^^^^^ Hooks may not be referenced as normal values, they must be called. See https://react.dev/reference/rules/react-calls-components-and-hooks#never-pass-around-hooks-as-regular-values 3 | const state = x(null); 4 | return state[0]; 5 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-computed-store-to-frozen-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-computed-store-to-frozen-value.expect.md index 2318d38feb80f..7391ae00490e7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-computed-store-to-frozen-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-computed-store-to-frozen-value.expect.md @@ -16,13 +16,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX + +error.invalid-computed-store-to-frozen-value.ts:5:2 3 | // freeze 4 |
{x}
; > 5 | x[0] = true; - | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX (5:5) + | ^ Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX 6 | return x; 7 | } 8 | + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-aliased-hook-import.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-aliased-hook-import.expect.md index 14bf83054607e..0f2a99872b37b 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-aliased-hook-import.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-aliased-hook-import.expect.md @@ -18,13 +18,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) + +error.invalid-conditional-call-aliased-hook-import.ts:6:11 4 | let data; 5 | if (props.cond) { > 6 | data = readFragment(); - | ^^^^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (6:6) + | ^^^^^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) 7 | } 8 | return data; 9 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-aliased-react-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-aliased-react-hook.expect.md index 6c81f3d2be5b0..8ac4baa899ff6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-aliased-react-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-aliased-react-hook.expect.md @@ -18,13 +18,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) + +error.invalid-conditional-call-aliased-react-hook.ts:6:10 4 | let s; 5 | if (props.cond) { > 6 | [s] = state(); - | ^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (6:6) + | ^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) 7 | } 8 | return s; 9 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-non-hook-imported-as-hook.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-non-hook-imported-as-hook.expect.md index d0fb92e751c86..8b70421efd28d 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-non-hook-imported-as-hook.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-call-non-hook-imported-as-hook.expect.md @@ -18,13 +18,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) + +error.invalid-conditional-call-non-hook-imported-as-hook.ts:6:11 4 | let data; 5 | if (props.cond) { > 6 | data = useArray(); - | ^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (6:6) + | ^^^^^^^^ Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) 7 | } 8 | return data; 9 | } + + ``` \ 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 f1666cc4013f7..5af5db112fb1b 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 @@ -22,15 +22,31 @@ function Component({item, cond}) { ## Error ``` +Found 2 errors: +Error: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) + +error.invalid-conditional-setState-in-useMemo.ts:7:6 5 | useMemo(() => { 6 | if (cond) { > 7 | setPrevItem(item); - | ^^^^^^^^^^^ InvalidReact: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) (7:7) - -InvalidReact: Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) (8:8) + | ^^^^^^^^^^^ Calling setState from useMemo may trigger an infinite loop. (https://react.dev/reference/react/useState) 8 | setState(0); 9 | } 10 | }, [cond, key, init]); + + +Error: Calling setState from useMemo may trigger an infinite loop. (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) + 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-delete-computed-property-of-frozen-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-delete-computed-property-of-frozen-value.expect.md index 7116e4d197154..363d4137f45ae 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-delete-computed-property-of-frozen-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-delete-computed-property-of-frozen-value.expect.md @@ -16,13 +16,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX + +error.invalid-delete-computed-property-of-frozen-value.ts:5:9 3 | // freeze 4 |
{x}
; > 5 | delete x[y]; - | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX (5:5) + | ^ Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX 6 | return x; 7 | } 8 | + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-delete-property-of-frozen-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-delete-property-of-frozen-value.expect.md index c6176d1afc5d9..ccea30731bdc8 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-delete-property-of-frozen-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-delete-property-of-frozen-value.expect.md @@ -16,13 +16,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX + +error.invalid-delete-property-of-frozen-value.ts:5:9 3 | // freeze 4 |
{x}
; > 5 | delete x.y; - | ^ InvalidReact: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX (5:5) + | ^ Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX 6 | return x; 7 | } 8 | + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md index b3471873eb079..7454d40695221 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-assignment-to-global.expect.md @@ -13,12 +13,18 @@ function useFoo(props) { ## Error ``` +Found 1 error: +Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) + +error.invalid-destructure-assignment-to-global.ts:2:3 1 | function useFoo(props) { > 2 | [x] = props; - | ^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (2:2) + | ^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) 3 | return {x}; 4 | } 5 | + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md index b3303fa189a33..dcb4a7af2fd82 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-destructure-to-local-global-variables.expect.md @@ -15,13 +15,19 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) + +error.invalid-destructure-to-local-global-variables.ts:3:6 1 | function Component(props) { 2 | let a; > 3 | [a, b] = props.value; - | ^ InvalidReact: Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) (3:3) + | ^ Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) 4 | 5 | return [a, b]; 6 | } + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-ref-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-ref-in-render.expect.md index b5547a1328629..ee3619c3dd2be 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-ref-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-disallow-mutating-ref-in-render.expect.md @@ -16,13 +16,19 @@ function Component() { ## Error ``` +Found 1 error: +Error: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) + +error.invalid-disallow-mutating-ref-in-render.ts:4:2 2 | function Component() { 3 | const ref = useRef(null); > 4 | ref.current = false; - | ^^^^^^^^^^^ InvalidReact: Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) (4:4) + | ^^^^^^^^^^^ Ref values (the `current` property) may not be accessed during render. (https://react.dev/reference/react/useRef) 5 | 6 | return ; 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 fabbf9b089a22..4d2c55cdaa1f0 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 @@ -20,13 +20,19 @@ const MemoizedButton = memo(function (props) { ## Error ``` +Found 1 error: +Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) + +todo.error.invalid-rules-of-hooks-8566f9a360e2.ts:8:4 6 | const MemoizedButton = memo(function (props) { 7 | if (props.fancy) { > 8 | useCustomHook(); - | ^^^^^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (8:8) + | ^^^^^^^^^^^^^ 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) 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 b6e240e26c37d..47d099c101633 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 @@ -19,13 +19,19 @@ function ComponentWithConditionalHook() { ## Error ``` +Found 1 error: +Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) + +todo.error.invalid-rules-of-hooks-a0058f0b446d.ts:8:4 6 | function ComponentWithConditionalHook() { 7 | if (cond) { > 8 | Namespace.useConditionalHook(); - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (8:8) + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 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) 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 83e94b7616692..b3f75f3ab8d74 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 @@ -20,13 +20,19 @@ const FancyButton = React.forwardRef((props, ref) => { ## Error ``` +Found 1 error: +Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) + +todo.error.rules-of-hooks-27c18dc8dad2.ts:8:4 6 | const FancyButton = React.forwardRef((props, ref) => { 7 | if (props.fancy) { > 8 | useCustomHook(); - | ^^^^^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (8:8) + | ^^^^^^^^^^^^^ 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) 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 a96e8e0878bc3..d5dd79b964976 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 @@ -19,13 +19,19 @@ React.unknownFunction((foo, bar) => { ## Error ``` +Found 1 error: +Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) + +todo.error.rules-of-hooks-d0935abedc42.ts:8:4 6 | React.unknownFunction((foo, bar) => { 7 | if (foo) { > 8 | useNotAHook(bar); - | ^^^^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (8:8) + | ^^^^^^^^^^^ 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) 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 6ce7fc2c8bcdf..d5e2cbcb83e27 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 @@ -20,13 +20,19 @@ function useHook() { ## Error ``` +Found 1 error: +Error: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) + +todo.error.rules-of-hooks-e29c874aa913.ts:9:4 7 | try { 8 | f(); > 9 | useState(); - | ^^^^^^^^ InvalidReact: Hooks must always be called in a consistent order, and may not be called conditionally. See the Rules of Hooks (https://react.dev/warnings/invalid-hook-call-warning) (9:9) + | ^^^^^^^^ 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) 10 | } catch {} 11 | } 12 | + + ``` \ No newline at end of file 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 1856784ce0eb7..83807391218cf 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 @@ -21,13 +21,19 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` +Found 1 error: +Todo: (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern + +todo.error.object-pattern-computed-key.ts:5:9 3 | const SCALE = 2; 4 | function Component(props) { > 5 | const {[props.name]: value} = props; - | ^^^^^^^^^^^^^^^^^^^ Todo: (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern (5:5) + | ^^^^^^^^^^^^^^^^^^^ (BuildHIR::lowerAssignment) Handle computed properties in ObjectPattern 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 aa3d989296a77..7e9247c5ae9a1 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 @@ -29,10 +29,16 @@ function Component({prop1}) { ## Error ``` +Found 1 error: +Error: [Fire] Untransformed reference to compiler-required feature. + + Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:4) + +error.todo-syntax.ts:18:4 16 | }; 17 | useEffect(() => { > 18 | fire(foo()); - | ^^^^ InvalidReact: [Fire] Untransformed reference to compiler-required feature. Either remove this `fire` call or ensure it is successfully transformed by the compiler. (Bailout reason: Todo: (BuildHIR::lowerStatement) Handle TryStatement without a catch clause (11:15)) (18:18) + | ^^^^ Untransformed `fire` call 19 | }); 20 | } 21 | 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 0141ffb8adb9a..7ec5c5320f4be 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 @@ -13,10 +13,16 @@ console.log(fire == null); ## Error ``` +Found 1 error: +Error: [Fire] Untransformed reference to compiler-required feature. + + null + +error.untransformed-fire-reference.ts:4:12 2 | import {fire} from 'react'; 3 | > 4 | console.log(fire == null); - | ^^^^ InvalidReact: [Fire] Untransformed reference to compiler-required feature. Either remove this `fire` call or ensure it is successfully transformed by the compiler (4:4) + | ^^^^ Untransformed `fire` call 5 | ``` 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 275012351c25b..55c9cfcb2c127 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 @@ -30,10 +30,16 @@ function Component({props, bar}) { ## Error ``` +Found 1 error: +Error: [Fire] Untransformed reference to compiler-required feature. + + null + +error.use-no-memo.ts:15:4 13 | }; 14 | useEffect(() => { > 15 | fire(foo(props)); - | ^^^^ InvalidReact: [Fire] Untransformed reference to compiler-required feature. Either remove this `fire` call or ensure it is successfully transformed by the compiler (15:15) + | ^^^^ Untransformed `fire` call 16 | fire(foo()); 17 | fire(bar()); 18 | }); 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 e73451a896ee4..ad15e74d97079 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 @@ -27,13 +27,21 @@ function Component(props) { ## Error ``` +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. + +error.invalid-mix-fire-and-no-fire.ts:11:6 9 | function nested() { 10 | fire(foo(props)); > 11 | foo(props); - | ^^^ InvalidReact: 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 (11:11) + | ^^^ Cannot compile `fire` 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 8329717cb3939..8cb5ce3d788b4 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 @@ -22,13 +22,21 @@ function Component({bar, baz}) { ## Error ``` +Found 1 error: +Error: Cannot compile `fire` + +fire() can only take in a single call expression as an argument but received multiple arguments. + +error.invalid-multiple-args.ts:9:4 7 | }; 8 | useEffect(() => { > 9 | fire(foo(bar), baz); - | ^^^^^^^^^^^^^^^^^^^ InvalidReact: Cannot compile `fire`. fire() can only take in a single call expression as an argument but received multiple arguments (9:9) + | ^^^^^^^^^^^^^^^^^^^ Cannot compile `fire` 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 1e1ff49b372cb..c36f0b4db987d 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 @@ -28,13 +28,21 @@ function Component(props) { ## Error ``` +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. + +error.invalid-nested-use-effect.ts:9:4 7 | }; 8 | useEffect(() => { > 9 | useEffect(() => { - | ^^^^^^^^^ InvalidReact: 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 (9:9) + | ^^^^^^^^^ 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) 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 855c7b7d706cb..a66ddd3350caf 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 @@ -22,13 +22,21 @@ function Component(props) { ## Error ``` +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. + +error.invalid-not-call.ts:9:4 7 | }; 8 | useEffect(() => { > 9 | fire(props); - | ^^^^^^^^^^^ InvalidReact: Cannot compile `fire`. `fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed (9:9) + | ^^^^^^^^^^^ Cannot compile `fire` 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 687a21f98cdb4..3f752a4a44d65 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 @@ -24,15 +24,35 @@ function Component({props, bar}) { ## Error ``` +Found 2 errors: +Invariant: Cannot compile `fire` + +Cannot use `fire` outside of a useEffect function. + +error.invalid-outside-effect.ts:8:2 6 | console.log(props); 7 | }; > 8 | fire(foo(props)); - | ^^^^ Invariant: Cannot compile `fire`. Cannot use `fire` outside of a useEffect function (8:8) - -Invariant: Cannot compile `fire`. Cannot use `fire` outside of a useEffect function (11:11) + | ^^^^ Cannot compile `fire` 9 | 10 | useCallback(() => { 11 | fire(foo(props)); + + +Invariant: Cannot compile `fire` + +Cannot use `fire` outside of a useEffect function. + +error.invalid-outside-effect.ts:11:4 + 9 | + 10 | useCallback(() => { +> 11 | fire(foo(props)); + | ^^^^ Cannot compile `fire` + 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 dcd9312bb2e53..846816b7d478e 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 @@ -25,13 +25,21 @@ function Component(props) { ## Error ``` +Found 1 error: +Invariant: Cannot compile `fire` + +You must use an array literal for an effect dependency array when that effect uses `fire()`. + +error.invalid-rewrite-deps-no-array-literal.ts:13:5 11 | useEffect(() => { 12 | fire(foo(props)); > 13 | }, deps); - | ^^^^ Invariant: Cannot compile `fire`. You must use an array literal for an effect dependency array when that effect uses `fire()` (13:13) + | ^^^^ Cannot compile `fire` 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 91c5523564cdd..436515da99651 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 @@ -28,13 +28,21 @@ function Component(props) { ## Error ``` +Found 1 error: +Invariant: Cannot compile `fire` + +You must use an array literal for an effect dependency array when that effect uses `fire()`. + +error.invalid-rewrite-deps-spread.ts:15:7 13 | fire(foo(props)); 14 | }, > 15 | ...deps - | ^^^^ Invariant: Cannot compile `fire`. You must use an array literal for an effect dependency array when that effect uses `fire()` (15:15) + | ^^^^ Cannot compile `fire` 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 c0b797fc14471..0c232de9745ff 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 @@ -22,13 +22,21 @@ function Component(props) { ## Error ``` +Found 1 error: +Error: Cannot compile `fire` + +fire() can only take in a single call expression as an argument but received a spread argument. + +error.invalid-spread.ts:9:4 7 | }; 8 | useEffect(() => { > 9 | fire(...foo); - | ^^^^^^^^^^^^ InvalidReact: Cannot compile `fire`. fire() can only take in a single call expression as an argument but received a spread argument (9:9) + | ^^^^^^^^^^^^ Cannot compile `fire` 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 3f237cfc6f364..9515d32eb7914 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 @@ -22,13 +22,21 @@ function Component(props) { ## Error ``` +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. + +error.todo-method.ts:9:4 7 | }; 8 | useEffect(() => { > 9 | fire(props.foo()); - | ^^^^^^^^^^^^^^^^^ InvalidReact: Cannot compile `fire`. `fire()` can only receive a function call such as `fire(fn(a,b)). Method calls and other expressions are not allowed (9:9) + | ^^^^^^^^^^^^^^^^^ Cannot compile `fire` 10 | }); 11 | 12 | return null; + + ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/index.ts b/compiler/packages/babel-plugin-react-compiler/src/index.ts index bbd814b2b60e3..34244892756b4 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/index.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/index.ts @@ -9,9 +9,12 @@ export {runBabelPluginReactCompiler} from './Babel/RunReactCompilerBabelPlugin'; export { CompilerError, CompilerErrorDetail, + CompilerDiagnostic, CompilerSuggestionOperation, ErrorSeverity, type CompilerErrorDetailOptions, + type CompilerDiagnosticOptions, + type CompilerDiagnosticDetail, } from './CompilerError'; export { compileFn as compile, 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 39c5dc2ea8e8c..6e931e467bec0 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRule-test.ts @@ -104,8 +104,7 @@ const tests: CompilerTestCases = { }`, errors: [ { - message: - '(BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration', + message: /Handle var kinds in VariableDeclaration/, }, ], }, @@ -119,8 +118,7 @@ const tests: CompilerTestCases = { }`, errors: [ { - message: - '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', + message: /React Compiler has skipped optimizing this component/, suggestions: [ { output: normalizeIndent` @@ -158,12 +156,10 @@ const tests: CompilerTestCases = { }`, errors: [ { - message: - '(BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration', + message: /Handle var kinds in VariableDeclaration/, }, { - message: - 'Mutating component props or hook arguments is not allowed. Consider using a local variable instead', + message: /Mutating component props or hook arguments is not allowed/, }, ], }, @@ -182,8 +178,7 @@ const tests: CompilerTestCases = { }`, errors: [ { - message: - '[ReactCompilerBailout] (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration (@:3:2)', + message: /Handle var kinds in VariableDeclaration/, }, ], }, @@ -200,7 +195,7 @@ const tests: CompilerTestCases = { errors: [ { message: - 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + /Unexpected reassignment of a variable which was defined outside of the component/, }, ], }, @@ -274,8 +269,7 @@ const tests: CompilerTestCases = { ], errors: [ { - message: - '[InferEffectDependencies] React Compiler is unable to infer dependencies of this effect. This will break your build! To resolve, either pass your own dependency array or fix reported compiler bailout diagnostics.', + message: /Cannot infer dependencies of this effect/, }, ], }, 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 f67ff673cbbbb..071bfb2e7b5f4 100644 --- a/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/compiler/packages/eslint-plugin-react-compiler/__tests__/ReactCompilerRuleTypescript-test.ts @@ -61,8 +61,7 @@ const tests: CompilerTestCases = { `, errors: [ { - message: - "Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead", + message: /Mutating 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 fbe7f5b507d16..51bc4e07533e4 100644 --- a/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts +++ b/compiler/packages/eslint-plugin-react-compiler/src/rules/ReactCompilerRule.ts @@ -10,6 +10,9 @@ import {transformFromAstSync} from '@babel/core'; import PluginProposalPrivateMethods from '@babel/plugin-proposal-private-methods'; import type {SourceLocation as BabelSourceLocation} from '@babel/types'; import BabelPluginReactCompiler, { + CompilerDiagnostic, + CompilerDiagnosticOptions, + CompilerErrorDetail, CompilerErrorDetailOptions, CompilerSuggestionOperation, ErrorSeverity, @@ -18,15 +21,11 @@ import BabelPluginReactCompiler, { OPT_OUT_DIRECTIVES, type PluginOptions, } from 'babel-plugin-react-compiler/src'; -import {Logger} from 'babel-plugin-react-compiler/src/Entrypoint'; +import {Logger, LoggerEvent} from 'babel-plugin-react-compiler/src/Entrypoint'; import type {Rule} from 'eslint'; import {Statement} from 'estree'; import * as HermesParser from 'hermes-parser'; -type CompilerErrorDetailWithLoc = Omit & { - loc: BabelSourceLocation; -}; - function assertExhaustive(_: never, errorMsg: string): never { throw new Error(errorMsg); } @@ -38,19 +37,15 @@ const DEFAULT_REPORTABLE_LEVELS = new Set([ let reportableLevels = DEFAULT_REPORTABLE_LEVELS; function isReportableDiagnostic( - detail: CompilerErrorDetailOptions, -): detail is CompilerErrorDetailWithLoc { - return ( - reportableLevels.has(detail.severity) && - detail.loc != null && - typeof detail.loc !== 'symbol' - ); + detail: CompilerErrorDetail | CompilerDiagnostic, +): boolean { + return reportableLevels.has(detail.severity); } function makeSuggestions( - detail: CompilerErrorDetailOptions, + detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, ): Array { - let suggest: Array = []; + const suggest: Array = []; if (Array.isArray(detail.suggestions)) { for (const suggestion of detail.suggestions) { switch (suggestion.op) { @@ -134,10 +129,10 @@ const rule: Rule.RuleModule = { const filename = context.filename ?? context.getFilename(); const userOpts = context.options[0] ?? {}; if ( - userOpts['reportableLevels'] != null && - userOpts['reportableLevels'] instanceof Set + userOpts.reportableLevels != null && + userOpts.reportableLevels instanceof Set ) { - reportableLevels = userOpts['reportableLevels']; + reportableLevels = userOpts.reportableLevels; } else { reportableLevels = DEFAULT_REPORTABLE_LEVELS; } @@ -150,11 +145,11 @@ const rule: Rule.RuleModule = { */ let __unstable_donotuse_reportAllBailouts: boolean = false; if ( - userOpts['__unstable_donotuse_reportAllBailouts'] != null && - typeof userOpts['__unstable_donotuse_reportAllBailouts'] === 'boolean' + userOpts.__unstable_donotuse_reportAllBailouts != null && + typeof userOpts.__unstable_donotuse_reportAllBailouts === 'boolean' ) { __unstable_donotuse_reportAllBailouts = - userOpts['__unstable_donotuse_reportAllBailouts']; + userOpts.__unstable_donotuse_reportAllBailouts; } let shouldReportUnusedOptOutDirective = true; @@ -168,16 +163,17 @@ const rule: Rule.RuleModule = { }); const userLogger: Logger | null = options.logger; options.logger = { - logEvent: (filename, event): void => { - userLogger?.logEvent(filename, event); + logEvent: (eventFilename, event): void => { + userLogger?.logEvent(eventFilename, event); if (event.kind === 'CompileError') { shouldReportUnusedOptOutDirective = false; const detail = event.detail; - const suggest = makeSuggestions(detail); + const suggest = makeSuggestions(detail.options); if (__unstable_donotuse_reportAllBailouts && event.fnLoc != null) { + const loc = detail.primaryLocation(); const locStr = - detail.loc != null && typeof detail.loc !== 'symbol' - ? ` (@:${detail.loc.start.line}:${detail.loc.start.column})` + loc != null && typeof loc !== 'symbol' + ? ` (@:${loc.start.line}:${loc.start.column})` : ''; /** * Report bailouts with a smaller span (just the first line). @@ -193,10 +189,10 @@ const rule: Rule.RuleModule = { endLoc = { line: event.fnLoc.start.line, // Babel loc line numbers are 1-indexed - column: sourceCode.text.split( - /\r?\n|\r|\n/g, - event.fnLoc.start.line, - )[event.fnLoc.start.line - 1].length, + column: + sourceCode.text.split(/\r?\n|\r|\n/g)[ + event.fnLoc.start.line - 1 + ]?.length ?? 0, }; } const firstLineLoc = { @@ -204,29 +200,30 @@ const rule: Rule.RuleModule = { end: endLoc, }; context.report({ - message: `[ReactCompilerBailout] ${detail.reason}${locStr}`, + message: `${detail.printErrorMessage(sourceCode.text)} ${locStr}`, loc: firstLineLoc, suggest, }); } - if (!isReportableDiagnostic(detail)) { + const loc = detail.primaryLocation(); + if ( + !isReportableDiagnostic(detail) || + loc == null || + typeof loc === 'symbol' + ) { return; } if ( - hasFlowSuppression(detail.loc, 'react-rule-hook') || - hasFlowSuppression(detail.loc, 'react-rule-unsafe-ref') + hasFlowSuppression(loc, 'react-rule-hook') || + hasFlowSuppression(loc, 'react-rule-unsafe-ref') ) { // If Flow already caught this error, we don't need to report it again. return; } - const loc = - detail.loc == null || typeof detail.loc == 'symbol' - ? event.fnLoc - : detail.loc; if (loc != null) { context.report({ - message: detail.reason, + message: detail.printErrorMessage(sourceCode.text), loc, suggest, }); @@ -239,8 +236,8 @@ const rule: Rule.RuleModule = { options.environment = validateEnvironmentConfig( options.environment ?? {}, ); - } catch (err) { - options.logger?.logEvent('', err); + } catch (err: unknown) { + options.logger?.logEvent('', err as LoggerEvent); } function hasFlowSuppression( diff --git a/compiler/packages/snap/src/runner-worker.ts b/compiler/packages/snap/src/runner-worker.ts index fd4763b20322e..554348534e305 100644 --- a/compiler/packages/snap/src/runner-worker.ts +++ b/compiler/packages/snap/src/runner-worker.ts @@ -145,29 +145,6 @@ async function compile( console.error(e.stack); } error = e.message.replace(/\u001b[^m]*m/g, ''); - const loc = e.details?.[0]?.loc; - if (loc != null) { - try { - error = codeFrameColumns( - input, - { - start: { - line: loc.start.line, - column: loc.start.column + 1, - }, - end: { - line: loc.end.line, - column: loc.end.column + 1, - }, - }, - { - message: e.message, - }, - ); - } catch { - // In case the location data isn't valid, skip printing a code frame. - } - } } // Promote console errors so they can be recorded in fixture output diff --git a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts index 30762e5819535..1da7d8f57fbc7 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts +++ b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRule-test.ts @@ -106,8 +106,7 @@ const tests: CompilerTestCases = { }`, errors: [ { - message: - '(BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration', + message: /Handle var kinds in VariableDeclaration/, }, ], }, @@ -121,8 +120,7 @@ const tests: CompilerTestCases = { }`, errors: [ { - message: - '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', + message: /React Compiler has skipped optimizing this component/, suggestions: [ { output: normalizeIndent` @@ -160,12 +158,10 @@ const tests: CompilerTestCases = { }`, errors: [ { - message: - '(BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration', + message: /Handle var kinds in VariableDeclaration/, }, { - message: - 'Mutating component props or hook arguments is not allowed. Consider using a local variable instead', + message: /Mutating component props or hook arguments is not allowed/, }, ], }, @@ -184,8 +180,7 @@ const tests: CompilerTestCases = { }`, errors: [ { - message: - '[ReactCompilerBailout] (BuildHIR::lowerStatement) Handle var kinds in VariableDeclaration (@:3:2)', + message: /Handle var kinds in VariableDeclaration/, }, ], }, @@ -202,7 +197,7 @@ const tests: CompilerTestCases = { errors: [ { message: - 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + /Unexpected reassignment of a variable which was defined outside of the component/, }, ], }, @@ -248,6 +243,37 @@ const tests: CompilerTestCases = { }, ], }, + { + name: 'Pipeline errors are reported', + code: normalizeIndent` + import useMyEffect from 'useMyEffect'; + function Component({a}) { + 'use no memo'; + useMyEffect(() => console.log(a.b)); + return
Hello world
; + } + `, + options: [ + { + environment: { + inferEffectDependencies: [ + { + function: { + source: 'useMyEffect', + importSpecifierName: 'default', + }, + numRequiredArgs: 1, + }, + ], + }, + }, + ], + errors: [ + { + message: /Cannot infer dependencies of this effect/, + }, + ], + }, ], }; @@ -259,4 +285,4 @@ const eslintTester = new ESLintTesterV8({ enableExperimentalComponentSyntax: true, }, }); -eslintTester.run('react-compiler - eslint: v8', ReactCompilerRule, tests); +eslintTester.run('react-compiler', ReactCompilerRule, tests); diff --git a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts index 2efe6c7a38419..28133aee7bc4f 100644 --- a/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts +++ b/packages/eslint-plugin-react-hooks/__tests__/ReactCompilerRuleTypescript-test.ts @@ -63,8 +63,7 @@ const tests: CompilerTestCases = { `, errors: [ { - message: - "Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead", + message: /Mutating a value returned from 'useState\(\)'/, line: 7, }, ], @@ -75,4 +74,4 @@ const tests: CompilerTestCases = { const eslintTester = new ESLintTesterV8({ parser: require.resolve('@typescript-eslint/parser-v5'), }); -eslintTester.run('react-compiler - eslint: v8', ReactCompilerRule, tests); +eslintTester.run('react-compiler', ReactCompilerRule, tests); diff --git a/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts b/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts index 35c515ad948a0..254962d99ccec 100644 --- a/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts +++ b/packages/eslint-plugin-react-hooks/src/rules/ReactCompiler.ts @@ -11,7 +11,10 @@ import {transformFromAstSync} from '@babel/core'; import PluginProposalPrivateMethods from '@babel/plugin-transform-private-methods'; import type {SourceLocation as BabelSourceLocation} from '@babel/types'; import BabelPluginReactCompiler, { + type CompilerErrorDetail, type CompilerErrorDetailOptions, + type CompilerDiagnostic, + type CompilerDiagnosticOptions, CompilerSuggestionOperation, ErrorSeverity, parsePluginOptions, @@ -25,10 +28,6 @@ import type {Rule} from 'eslint'; import {Statement} from 'estree'; import * as HermesParser from 'hermes-parser'; -type CompilerErrorDetailWithLoc = Omit & { - loc: BabelSourceLocation; -}; - function assertExhaustive(_: never, errorMsg: string): never { throw new Error(errorMsg); } @@ -40,17 +39,13 @@ const DEFAULT_REPORTABLE_LEVELS = new Set([ let reportableLevels = DEFAULT_REPORTABLE_LEVELS; function isReportableDiagnostic( - detail: CompilerErrorDetailOptions, -): detail is CompilerErrorDetailWithLoc { - return ( - reportableLevels.has(detail.severity) && - detail.loc != null && - typeof detail.loc !== 'symbol' - ); + detail: CompilerErrorDetail | CompilerDiagnostic, +): boolean { + return reportableLevels.has(detail.severity); } function makeSuggestions( - detail: CompilerErrorDetailOptions, + detail: CompilerErrorDetailOptions | CompilerDiagnosticOptions, ): Array { const suggest: Array = []; if (Array.isArray(detail.suggestions)) { @@ -175,11 +170,12 @@ const rule: Rule.RuleModule = { if (event.kind === 'CompileError') { shouldReportUnusedOptOutDirective = false; const detail = event.detail; - const suggest = makeSuggestions(detail); + const suggest = makeSuggestions(detail.options); if (__unstable_donotuse_reportAllBailouts && event.fnLoc != null) { + const loc = detail.primaryLocation(); const locStr = - detail.loc != null && typeof detail.loc !== 'symbol' - ? ` (@:${detail.loc.start.line}:${detail.loc.start.column})` + loc != null && typeof loc !== 'symbol' + ? ` (@:${loc.start.line}:${loc.start.column})` : ''; /** * Report bailouts with a smaller span (just the first line). @@ -206,29 +202,30 @@ const rule: Rule.RuleModule = { end: endLoc, }; context.report({ - message: `[ReactCompilerBailout] ${detail.reason}${locStr}`, + message: `${detail.printErrorMessage(sourceCode.text)} ${locStr}`, loc: firstLineLoc, suggest, }); } - if (!isReportableDiagnostic(detail)) { + const loc = detail.primaryLocation(); + if ( + !isReportableDiagnostic(detail) || + loc == null || + typeof loc === 'symbol' + ) { return; } if ( - hasFlowSuppression(detail.loc, 'react-rule-hook') || - hasFlowSuppression(detail.loc, 'react-rule-unsafe-ref') + hasFlowSuppression(loc, 'react-rule-hook') || + hasFlowSuppression(loc, 'react-rule-unsafe-ref') ) { // If Flow already caught this error, we don't need to report it again. return; } - const loc = - detail.loc == null || typeof detail.loc === 'symbol' - ? event.fnLoc - : detail.loc; if (loc != null) { context.report({ - message: detail.reason, + message: detail.printErrorMessage(sourceCode.text), loc, suggest, }); 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 07/13] [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 cac897a9269be..c704d44b55c98 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 5b5702f19c62b..cd85656de24f8 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 9c41ebcae19f6..569bbbdc2d6e3 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 573db2f6b7d00..b988183530ee4 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 6e88773ecf0bd..85adb79ceb859 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 505302f7d12c2..eea6c0a08e951 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 e9b0fdb8870ee..3c810025a7e5c 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 fc101581b30b0..81209d61c6853 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 fd4d781ef3c6f..7c83e65dff0fd 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 84370796a6724..a33ff7ce76582 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 de50b21543742..408537c5b8a39 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 6823db842d2a1..2c1c7657f731f 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 47af99524836a..fba4e272ee9d9 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 1c5c92d2c305a..2f8fd0e671727 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 5af5db112fb1b..0334a33cfee91 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 d9e0ed390bbfb..8d9be03b42773 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 025560bd50515..073a15b7f52c4 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 259a57c3e7c37..50d3a9e668761 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 a8eee83aa10b2..02750e447205a 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 47e3509dbfd4d..3f8a81c106b02 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 e9774d0c9932a..8bf6cd1ccc6b7 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 cd565964d5532..ea7fd3d1d7b07 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 eb499847a3669..767c05ec73b00 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 c29483ca98156..156c0aff8762c 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 8be4ac12a297c..1cdd24d7311b5 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 af590101c0fc1..88788bbfee3ca 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 0c15a0f632f45..82138d8a5a859 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 272ed7ab8c0f0..7fc99985f1a50 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 6c37090917550..97996884b25f4 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 151c1a20c7254..e284cb78145e1 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 ce4814b172ef4..f151b624e9a95 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 a90a9fd423beb..1a51d17fb491e 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 65c1a8caae8b7..903ee395f4ab3 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 c2f70e67caeaa..de829f66fc2d3 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 12332ae299b06..447183b79c898 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 4d43240997b32..dcd643edb241f 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 9f4d85de707d4..6583f0a4e2dcf 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 9fe89f25eeea9..b6e9e87ada427 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 b09fd352f24f7..4e9e1f2c3a799 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 41390e5ce0a22..8d902c2c7d649 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 cca5515ab78a7..2050ea107c64d 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 08/13] [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 c150c1fbd7486..6bb59247dab7c 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 dcde3a9f83f57..815a8fb208327 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 ea6683fd0ad80..64065a7819a5b 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 196ce696b2123..4a6cded7b8ba7 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 a86193b84fd6c..838e7a5b6dcfa 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 4d2c55cdaa1f0..520a8e4097d14 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 47d099c101633..acd4ff9395fc6 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 b3f75f3ab8d74..8f2783f96909e 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 d5dd79b964976..343c51787e259 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 d5e2cbcb83e27..a9960ad44dd68 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 af8103b7ae515..1224a5b9cf9ca 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 7720863da34c2..7bec7f73b551a 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 8d218bf24b0de..e7f9ad7f30fd6 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 e3bc7a5eb5796..f6220645ef87b 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 02e9f4f4a4b8d..4a72cdd855516 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 83807391218cf..7bc1e49069b6a 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 7e9247c5ae9a1..006d2a49c0203 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 7ec5c5320f4be..8481ed2c576b0 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 55c9cfcb2c127..f84686bc36bd2 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 ad15e74d97079..1eb6bf66e9642 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 8cb5ce3d788b4..c519d43fb41c0 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 c36f0b4db987d..2e767c3c71e61 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 a66ddd3350caf..40c4bc5394dce 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 3f752a4a44d65..81c36a362cff0 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 846816b7d478e..96cea9c08f0f2 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 436515da99651..4dc5336ebe011 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 0c232de9745ff..dcd302bbe104c 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 9515d32eb7914..67410297f3032 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 6e931e467bec0..bff40e9649d96 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 071bfb2e7b5f4..5a2bea68526fb 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 51bc4e07533e4..1d326012864b9 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 1da7d8f57fbc7..a636c59375425 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 28133aee7bc4f..9d05cd1871fe9 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 254962d99ccec..0492c2c30c309 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 2ae8b3dacf2cd93900d86bc11f22768d507ddce7 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:47:56 -0700 Subject: [PATCH 11/13] [compiler] Use new diagnostic printing in playground (#33767) Per title --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33767). * #33981 * #33777 * __->__ #33767 --- .../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 02813a8d2fd01..27d2fd79890aa 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 0992591183c15..5cfa56d77f743 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 7886f11e62370..36d20a0125e73 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 a800e25773295..cece2fa7c6dd3 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 4b59a22e802ff..ed74f4664953b 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 b337f0c724dda..ee0d0f74c5f3c 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 bcea86945cd5324f1a7f324a48fd2c7cb36e569b Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:52:45 -0700 Subject: [PATCH 12/13] [compiler][rfc] Enable more validations in playground. (#33777) 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? --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33777). * #33981 * __->__ #33777 --- .../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 27d2fd79890aa..0ced1e54ed76d 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 36d20a0125e73..0f8fe268ca020 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 129aa85e1621f31d382d3b8bf7a5aa456daf3d59 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:54:24 -0700 Subject: [PATCH 13/13] [compiler] Use diagnostic for "found suppression" error (#33981) --- .../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 0f35215e86d71..0084911eec1b7 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 0f8fe268ca020..bf7bd3eb65078 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 14a9fc94668bd..f272913bce49a 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 4d0369f5210ca..ee341b111f1ae 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 26942a29962a4..6e522e16669dc 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 89024fd210e5a..3221f97731d9a 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 1860ea1fd40ad..96be8584be216 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 92a647a0c549b..e19cee7532631 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 97ef238a50645..9c87cafff1832 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 7716ddfd105c4..7077b733b0d46 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 2347acf0937e8..4b2f94a263a18 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 4bb3c1eb10198..0a5cde5cd65dc 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.