From a39da6c61feb5d10c988ddb55b7451a5af165783 Mon Sep 17 00:00:00 2001 From: Joseph Savona <6425824+josephsavona@users.noreply.github.com> Date: Thu, 24 Jul 2025 15:43:08 -0700 Subject: [PATCH 001/846] [compiler] Use new diagnostics for core inference errors (#33760) Uses the new diagnostic type for errors created during mutation/aliasing inference, such as errors for mutating immutable values like props or state, reassigning globals, etc. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/33760). * #33981 * #33777 * #33767 * #33765 * __->__ #33760 --- .../src/HIR/PrintHIR.ts | 6 +- .../src/Inference/AliasingEffects.ts | 12 +- .../src/Inference/InferFunctionEffects.ts | 22 ++-- .../Inference/InferMutationAliasingEffects.ts | 123 +++++++++--------- .../Inference/InferMutationAliasingRanges.ts | 4 +- ...global-in-component-tag-function.expect.md | 8 +- ...or.assign-global-in-jsx-children.expect.md | 8 +- ...call-freezes-captured-identifier.expect.md | 8 +- ...call-freezes-captured-memberexpr.expect.md | 8 +- .../error.invalid-array-push-frozen.expect.md | 8 +- ...d-computed-store-to-frozen-value.expect.md | 8 +- ...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 +- ...pression-mutates-immutable-value.expect.md | 8 +- ...lid-global-reassignment-indirect.expect.md | 8 +- .../error.invalid-hoisting-setstate.expect.md | 17 +-- ...valid-impure-functions-in-render.expect.md | 24 ++-- ...id-jsx-captures-context-variable.expect.md | 8 +- ...alid-mutate-after-aliased-freeze.expect.md | 8 +- ...rror.invalid-mutate-after-freeze.expect.md | 8 +- ...valid-mutate-context-in-callback.expect.md | 8 +- .../error.invalid-mutate-context.expect.md | 8 +- ...-mutate-props-in-effect-fixpoint.expect.md | 8 +- ...mutate-props-via-for-of-iterator.expect.md | 8 +- ...rror.invalid-mutation-in-closure.expect.md | 8 +- ...n-of-possible-props-phi-indirect.expect.md | 8 +- ...d-reanimated-shared-value-writes.expect.md | 8 +- ...r.invalid-prop-mutation-indirect.expect.md | 8 +- ...d-property-store-to-frozen-value.expect.md | 8 +- ...rops-mutation-in-effect-indirect.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 +- .../error.mutate-function-property.expect.md | 8 +- .../error.mutate-hook-argument.expect.md | 14 +- ...rror.mutate-property-from-global.expect.md | 8 +- .../compiler/error.mutate-props.expect.md | 8 +- ...or.not-useEffect-external-mutate.expect.md | 14 +- ...r.object-capture-global-mutation.expect.md | 4 +- .../error.reassign-global-fn-arg.expect.md | 8 +- ....reassignment-to-global-indirect.expect.md | 14 +- .../error.reassignment-to-global.expect.md | 14 +- .../error.store-property-in-global.expect.md | 8 +- ...ror.update-global-should-bailout.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 +- .../no-emit/retry-no-emit.expect.md | 2 +- ...valid-impure-functions-in-render.expect.md | 24 ++-- ...rozen-hoisted-storecontext-const.expect.md | 17 +-- .../error.mutate-frozen-value.expect.md | 8 +- .../error.mutate-hook-argument.expect.md | 14 +- ...or.not-useEffect-external-mutate.expect.md | 14 +- ....reassignment-to-global-indirect.expect.md | 14 +- .../error.reassignment-to-global.expect.md | 14 +- ...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 +- 61 files changed, 312 insertions(+), 349 deletions(-) diff --git a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts index 23471a00b097..cecbb49e4436 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/HIR/PrintHIR.ts @@ -995,13 +995,13 @@ export function printAliasingEffect(effect: AliasingEffect): string { return `${effect.kind} ${printPlaceForAliasEffect(effect.value)}`; } case 'MutateFrozen': { - return `MutateFrozen ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; + return `MutateFrozen ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`; } case 'MutateGlobal': { - return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; + return `MutateGlobal ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`; } case 'Impure': { - return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.reason)}`; + return `Impure ${printPlaceForAliasEffect(effect.place)} reason=${JSON.stringify(effect.error.category)}`; } case 'Render': { return `Render ${printPlaceForAliasEffect(effect.place)}`; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts index f844129e26bd..95f4e0f5bb1a 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/AliasingEffects.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import {CompilerErrorDetailOptions} from '../CompilerError'; +import {CompilerDiagnostic} from '../CompilerError'; import { FunctionExpression, GeneratedSource, @@ -133,19 +133,19 @@ export type AliasingEffect = /** * Mutation of a value known to be immutable */ - | {kind: 'MutateFrozen'; place: Place; error: CompilerErrorDetailOptions} + | {kind: 'MutateFrozen'; place: Place; error: CompilerDiagnostic} /** * Mutation of a global */ | { kind: 'MutateGlobal'; place: Place; - error: CompilerErrorDetailOptions; + error: CompilerDiagnostic; } /** * Indicates a side-effect that is not safe during render */ - | {kind: 'Impure'; place: Place; error: CompilerErrorDetailOptions} + | {kind: 'Impure'; place: Place; error: CompilerDiagnostic} /** * Indicates that a given place is accessed during render. Used to distingush * hook arguments that are known to be called immediately vs those used for @@ -211,9 +211,9 @@ export function hashEffect(effect: AliasingEffect): string { effect.kind, effect.place.identifier.id, effect.error.severity, - effect.error.reason, + effect.error.category, effect.error.description, - printSourceLocation(effect.error.loc ?? GeneratedSource), + printSourceLocation(effect.error.primaryLocation() ?? GeneratedSource), ].join(':'); } case 'Mutate': diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts index 9b347ebb6c4c..a01ca188a0af 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferFunctionEffects.ts @@ -326,26 +326,26 @@ function isEffectSafeOutsideRender(effect: FunctionEffect): boolean { export function getWriteErrorReason(abstractValue: AbstractValue): string { if (abstractValue.reason.has(ValueReason.Global)) { - return 'Writing to a variable defined outside a component or hook is not allowed. Consider using an effect'; + return 'Modifying a variable defined outside a component or hook is not allowed. Consider using an effect'; } else if (abstractValue.reason.has(ValueReason.JsxCaptured)) { - return 'Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX'; + return 'Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX'; } else if (abstractValue.reason.has(ValueReason.Context)) { - return `Mutating a value returned from 'useContext()', which should not be mutated`; + return `Modifying a value returned from 'useContext()' is not allowed.`; } else if (abstractValue.reason.has(ValueReason.KnownReturnSignature)) { - return 'Mutating a value returned from a function whose return value should not be mutated'; + return 'Modifying a value returned from a function whose return value should not be mutated'; } else if (abstractValue.reason.has(ValueReason.ReactiveFunctionArgument)) { - return 'Mutating component props or hook arguments is not allowed. Consider using a local variable instead'; + return 'Modifying component props or hook arguments is not allowed. Consider using a local variable instead'; } else if (abstractValue.reason.has(ValueReason.State)) { - return "Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead"; + return "Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead"; } else if (abstractValue.reason.has(ValueReason.ReducerState)) { - return "Mutating a value returned from 'useReducer()', which should not be mutated. Use the dispatch function to update instead"; + return "Modifying a value returned from 'useReducer()', which should not be modified directly. Use the dispatch function to update instead"; } else if (abstractValue.reason.has(ValueReason.Effect)) { - return 'Updating a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the mutation before calling useEffect()'; + return 'Modifying a value used previously in an effect function or as an effect dependency is not allowed. Consider moving the modification before calling useEffect()'; } else if (abstractValue.reason.has(ValueReason.HookCaptured)) { - return 'Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook'; + return 'Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook'; } else if (abstractValue.reason.has(ValueReason.HookReturn)) { - return 'Updating a value returned from a hook is not allowed. Consider moving the mutation into the hook where the value is constructed'; + return 'Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed'; } else { - return 'This mutates a variable that React considers immutable'; + return 'This modifies a variable that React considers immutable'; } } 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 d02a294b5b43..678e77cf97a6 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingEffects.ts @@ -6,6 +6,7 @@ */ import { + CompilerDiagnostic, CompilerError, Effect, ErrorSeverity, @@ -446,20 +447,24 @@ function applySignature( reason: value.reason, context: new Set(), }); + const message = + effect.value.identifier.name !== null && + effect.value.identifier.name.kind === 'named' + ? `\`${effect.value.identifier.name.value}\` cannot be modified` + : 'This value cannot be modified'; effects.push({ kind: 'MutateFrozen', place: effect.value, - error: { + error: CompilerDiagnostic.create({ severity: ErrorSeverity.InvalidReact, - reason, - description: - effect.value.identifier.name !== null && - effect.value.identifier.name.kind === 'named' - ? `Found mutation of \`${effect.value.identifier.name.value}\`` - : null, - loc: effect.value.loc, + category: 'This value cannot be modified', + description: reason, suggestions: null, - }, + }).withDetail({ + kind: 'error', + loc: effect.value.loc, + message, + }), }); } } @@ -1016,30 +1021,28 @@ function applyEffect( const description = effect.value.identifier.name !== null && effect.value.identifier.name.kind === 'named' - ? `Variable \`${effect.value.identifier.name.value}\` is accessed before it is declared` - : null; + ? `Variable \`${effect.value.identifier.name.value}\`` + : 'This variable'; const hoistedAccess = context.hoistedContextDeclarations.get( effect.value.identifier.declarationId, ); + const diagnostic = CompilerDiagnostic.create({ + severity: ErrorSeverity.InvalidReact, + category: 'Cannot access variable before it is declared', + description: `${description} is accessed before it is declared, which prevents the earlier access from updating when this value changes over time`, + }); if (hoistedAccess != null && hoistedAccess.loc != effect.value.loc) { - applyEffect( - context, - state, - { - kind: 'MutateFrozen', - place: effect.value, - error: { - severity: ErrorSeverity.InvalidReact, - reason: `This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time`, - description, - loc: hoistedAccess.loc, - suggestions: null, - }, - }, - initialized, - effects, - ); + diagnostic.withDetail({ + kind: 'error', + loc: hoistedAccess.loc, + message: 'Variable accessed before it is declared', + }); } + diagnostic.withDetail({ + kind: 'error', + loc: effect.value.loc, + message: 'The variable is declared here', + }); applyEffect( context, @@ -1047,13 +1050,7 @@ function applyEffect( { kind: 'MutateFrozen', place: effect.value, - error: { - severity: ErrorSeverity.InvalidReact, - reason: `This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time`, - description, - loc: effect.value.loc, - suggestions: null, - }, + error: diagnostic, }, initialized, effects, @@ -1064,11 +1061,11 @@ function applyEffect( reason: value.reason, context: new Set(), }); - const description = + const message = effect.value.identifier.name !== null && effect.value.identifier.name.kind === 'named' - ? `Found mutation of \`${effect.value.identifier.name.value}\`` - : null; + ? `\`${effect.value.identifier.name.value}\` cannot be modified` + : 'This value cannot be modified'; applyEffect( context, state, @@ -1078,13 +1075,15 @@ function applyEffect( ? 'MutateFrozen' : 'MutateGlobal', place: effect.value, - error: { + error: CompilerDiagnostic.create({ severity: ErrorSeverity.InvalidReact, - reason, - description, + category: 'This value cannot be modified', + description: reason, + }).withDetail({ + kind: 'error', loc: effect.value.loc, - suggestions: null, - }, + message, + }), }, initialized, effects, @@ -2006,13 +2005,18 @@ function computeSignatureForInstruction( effects.push({ kind: 'MutateGlobal', place: value.value, - error: { - reason: - 'Unexpected reassignment of a variable which was defined outside of the component. Components and hooks should be pure and side-effect free, but variable reassignment is a form of side-effect. If this variable is used in rendering, use useState instead. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', - loc: instr.loc, - suggestions: null, + error: CompilerDiagnostic.create({ severity: ErrorSeverity.InvalidReact, - }, + category: + 'Cannot reassign variables declared outside of the component/hook', + description: + 'Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render)', + suggestions: null, + }).withDetail({ + kind: 'error', + loc: instr.loc, + message: 'Cannot reassign variable', + }), }); effects.push({kind: 'Assign', from: value.value, into: lvalue}); break; @@ -2102,17 +2106,20 @@ function computeEffectsForLegacySignature( effects.push({ kind: 'Impure', place: receiver, - error: { - reason: - 'Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', - description: - signature.canonicalName != null - ? `\`${signature.canonicalName}\` is an impure function whose results may change on every call` - : null, + error: CompilerDiagnostic.create({ severity: ErrorSeverity.InvalidReact, - loc, + category: 'Cannot call impure function during render', + description: + (signature.canonicalName != null + ? `\`${signature.canonicalName}\` is an impure function. ` + : '') + + 'Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent)', suggestions: null, - }, + }).withDetail({ + kind: 'error', + loc, + message: 'Cannot call impure function', + }), }); } const stores: Array = []; diff --git a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts index 79f8cf8c0e85..cd344342225c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts +++ b/compiler/packages/babel-plugin-react-compiler/src/Inference/InferMutationAliasingRanges.ts @@ -195,7 +195,7 @@ export function inferMutationAliasingRanges( effect.kind === 'MutateGlobal' || effect.kind === 'Impure' ) { - errors.push(effect.error); + errors.pushDiagnostic(effect.error); functionEffects.push(effect); } else if (effect.kind === 'Render') { renders.push({index: index++, place: effect.place}); @@ -549,7 +549,7 @@ function appendFunctionErrors(errors: CompilerError, fn: HIRFunction): void { case 'Impure': case 'MutateFrozen': case 'MutateGlobal': { - errors.push(effect.error); + errors.pushDiagnostic(effect.error); break; } } 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 d15aba19d12e..daf0071d25e1 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 @@ -16,18 +16,18 @@ function Component() { ``` 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: Cannot reassign variables declared outside of the component/hook + +Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-component-tag-function.ts:3:4 1 | function Component() { 2 | const Foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ 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) + | ^^^^^^^^^^ Cannot reassign variable 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 634d98394c3d..81c7be61ac2e 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 @@ -19,18 +19,18 @@ function Component() { ``` 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: Cannot reassign variables declared outside of the component/hook + +Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.assign-global-in-jsx-children.ts:3:4 1 | function Component() { 2 | const foo = () => { > 3 | someGlobal = true; - | ^^^^^^^^^^ 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) + | ^^^^^^^^^^ Cannot reassign variable 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.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 7174acc43d2b..aaccfe84d883 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 @@ -30,18 +30,18 @@ export const FIXTURE_ENTRYPOINT = { ``` 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: This value cannot be modified + +Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook error.hook-call-freezes-captured-identifier.ts:13:2 11 | }); 12 | > 13 | x.value += count; - | ^ Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook + | ^ This value cannot be modified 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 7a969400a33f..755aa6d68f30 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 @@ -30,18 +30,18 @@ export const FIXTURE_ENTRYPOINT = { ``` 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: This value cannot be modified + +Modifying a value previously passed as an argument to a hook is not allowed. Consider moving the modification before calling the hook error.hook-call-freezes-captured-memberexpr.ts:13:2 11 | }); 12 | > 13 | x.value += count; - | ^ Updating a value previously passed as an argument to a hook is not allowed. Consider moving the mutation before calling the hook + | ^ This value cannot be modified 14 | return ; 15 | } 16 | - - ``` \ 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 137d29cbc291..356b3b7c1063 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 @@ -16,18 +16,18 @@ function Component(props) { ``` Found 1 error: -Error: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX +Error: This value cannot be modified + +Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX error.invalid-array-push-frozen.ts:4:2 2 | const x = []; 3 |
{x}
; > 4 | x.push(props.value); - | ^ Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX + | ^ This value cannot be modified 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-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 7391ae00490e..585680500b60 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 @@ -17,18 +17,18 @@ function Component(props) { ``` Found 1 error: -Error: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX +Error: This value cannot be modified + +Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX error.invalid-computed-store-to-frozen-value.ts:5:2 3 | // freeze 4 |
{x}
; > 5 | x[0] = true; - | ^ Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX + | ^ This value cannot be modified 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-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 363d4137f45a..27c04ffc5e31 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 @@ -17,18 +17,18 @@ function Component(props) { ``` Found 1 error: -Error: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX +Error: This value cannot be modified + +Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX error.invalid-delete-computed-property-of-frozen-value.ts:5:9 3 | // freeze 4 |
{x}
; > 5 | delete x[y]; - | ^ Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX + | ^ This value cannot be modified 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 ccea30731bdc..bd3269326c43 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 @@ -17,18 +17,18 @@ function Component(props) { ``` Found 1 error: -Error: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX +Error: This value cannot be modified + +Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX error.invalid-delete-property-of-frozen-value.ts:5:9 3 | // freeze 4 |
{x}
; > 5 | delete x.y; - | ^ Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX + | ^ This value cannot be modified 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 7454d4069522..2dd40f203e26 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 @@ -14,17 +14,17 @@ function useFoo(props) { ``` 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: Cannot reassign variables declared outside of the component/hook + +Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-assignment-to-global.ts:2:3 1 | function useFoo(props) { > 2 | [x] = props; - | ^ 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) + | ^ Cannot reassign variable 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 dcb4a7af2fd8..81dd728b85df 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 @@ -16,18 +16,18 @@ function Component(props) { ``` 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: Cannot reassign variables declared outside of the component/hook + +Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-destructure-to-local-global-variables.ts:3:6 1 | function Component(props) { 2 | let a; > 3 | [a, b] = props.value; - | ^ 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) + | ^ Cannot reassign variable 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-function-expression-mutates-immutable-value.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-function-expression-mutates-immutable-value.expect.md index 5574b38ccebe..6de3c555e935 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-function-expression-mutates-immutable-value.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-function-expression-mutates-immutable-value.expect.md @@ -19,20 +19,18 @@ function Component(props) { ``` Found 1 error: -Error: Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead +Error: This value cannot be modified -Found mutation of `x`. +Modifying a value returned from 'useState()', which should not be modified directly. Use the setter function to update instead error.invalid-function-expression-mutates-immutable-value.ts:5:4 3 | const onChange = e => { 4 | // INVALID! should use copy-on-write and pass the new value > 5 | x.value = e.target.value; - | ^ Mutating a value returned from 'useState()', which should not be mutated. Use the setter function to update instead + | ^ `x` cannot be modified 6 | setX(x); 7 | }; 8 | return ; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md index 161fb3643744..a6579893b00c 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-global-reassignment-indirect.expect.md @@ -36,18 +36,18 @@ export const FIXTURE_ENTRYPOINT = { ``` 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: Cannot reassign variables declared outside of the component/hook + +Reassigning a variable declared outside of the component/hook is a form of side effect, which can cause unpredictable behavior depending on when the component happens to re-render. If this variable is used in rendering, use useState instead. Otherwise, consider updating it in an effect (https://react.dev/reference/rules/components-and-hooks-must-be-pure#side-effects-must-run-outside-of-render) error.invalid-global-reassignment-indirect.ts:9:4 7 | 8 | const setGlobal = () => { > 9 | someGlobal = true; - | ^^^^^^^^^^ 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) + | ^^^^^^^^^^ Cannot reassign variable 10 | }; 11 | const indirectSetGlobal = () => { 12 | setGlobal(); - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md index 9c8f800547cc..6573418aee53 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-hoisting-setstate.expect.md @@ -38,35 +38,28 @@ export const FIXTURE_ENTRYPOINT = { ## Error ``` -Found 2 errors: -Error: This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time +Found 1 error: +Error: Cannot access variable before it is declared -Variable `setState` is accessed before it is declared. +Variable `setState` is accessed before it is declared, which prevents the earlier access from updating when this value changes over time error.invalid-hoisting-setstate.ts:19:18 17 | * $2 = Function context=setState 18 | */ > 19 | useEffect(() => setState(2), []); - | ^^^^^^^^ This variable is accessed before it is declared, which may prevent it from updating as the assigned value changes over time + | ^^^^^^^^ Variable accessed before it is declared 20 | 21 | const [state, setState] = useState(0); 22 | return ; - -Error: This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time - -Variable `setState` is accessed before it is declared. - error.invalid-hoisting-setstate.ts:21:16 19 | useEffect(() => setState(2), []); 20 | > 21 | const [state, setState] = useState(0); - | ^^^^^^^^ This variable is accessed before it is declared, which prevents the earlier access from updating when this value changes over time + | ^^^^^^^^ The variable is declared here 22 | return ; 23 | } 24 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md index 6e87c112b2b6..c4bc7f55eee1 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-impure-functions-in-render.expect.md @@ -18,48 +18,42 @@ function Component() { ``` Found 3 errors: -Error: 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) +Error: Cannot call impure function during render -`Date.now` is an impure function whose results may change on every call. +`Date.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) error.invalid-impure-functions-in-render.ts:4:15 2 | 3 | function Component() { > 4 | const date = Date.now(); - | ^^^^^^^^^^ 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) + | ^^^^^^^^^^ Cannot call impure function 5 | const now = performance.now(); 6 | const rand = Math.random(); 7 | return ; +Error: Cannot call impure function during render - -Error: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) - -`performance.now` is an impure function whose results may change on every call. +`performance.now` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) error.invalid-impure-functions-in-render.ts:5:14 3 | function Component() { 4 | const date = Date.now(); > 5 | const now = performance.now(); - | ^^^^^^^^^^^^^^^^^ 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) + | ^^^^^^^^^^^^^^^^^ Cannot call impure function 6 | const rand = Math.random(); 7 | return ; 8 | } +Error: Cannot call impure function during render - -Error: Calling an impure function can produce unstable results. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) - -`Math.random` is an impure function whose results may change on every call. +`Math.random` is an impure function. Calling an impure function can produce unstable results that update unpredictably when the component happens to re-render. (https://react.dev/reference/rules/components-and-hooks-must-be-pure#components-and-hooks-must-be-idempotent) error.invalid-impure-functions-in-render.ts:6:15 4 | const date = Date.now(); 5 | const now = performance.now(); > 6 | const rand = Math.random(); - | ^^^^^^^^^^^^^ 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) + | ^^^^^^^^^^^^^ Cannot call impure function 7 | return ; 8 | } 9 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-captures-context-variable.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-captures-context-variable.expect.md index 66ff1b657aa7..b4a4d2df42c7 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-captures-context-variable.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-captures-context-variable.expect.md @@ -51,20 +51,18 @@ export const FIXTURE_ENTRYPOINT = { ``` Found 1 error: -Error: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX +Error: This value cannot be modified -Found mutation of `i`. +Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX error.invalid-jsx-captures-context-variable.ts:22:2 20 | /> 21 | ); > 22 | i = i + 1; - | ^ Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX + | ^ `i` cannot be modified 23 | items.push( 24 | 13 | y.push(props.p2); - | ^ Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX + | ^ This value cannot be modified 14 | 15 | return ; 16 | } - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-after-freeze.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-after-freeze.expect.md index 6d519e039d3b..4636b3432d39 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-after-freeze.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-after-freeze.expect.md @@ -20,18 +20,18 @@ function Component(props) { ``` Found 1 error: -Error: Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX +Error: This value cannot be modified + +Modifying a value used previously in JSX is not allowed. Consider moving the modification before the JSX error.invalid-mutate-after-freeze.ts:7:2 5 | 6 | // x is Frozen at this point > 7 | x.push(props.p2); - | ^ Updating a value used previously in JSX is not allowed. Consider moving the mutation before the JSX + | ^ This value cannot be modified 8 | 9 | return
{_}
; 10 | } - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context-in-callback.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context-in-callback.expect.md index ce290041a7e2..e97eb19123d0 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context-in-callback.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context-in-callback.expect.md @@ -25,20 +25,18 @@ function Component(props) { ``` Found 1 error: -Error: Mutating a value returned from 'useContext()', which should not be mutated +Error: This value cannot be modified -Found mutation of `FooContext`. +Modifying a value returned from 'useContext()' is not allowed. error.invalid-mutate-context-in-callback.ts:12:4 10 | // independently 11 | const onClick = () => { > 12 | FooContext.current = true; - | ^^^^^^^^^^ Mutating a value returned from 'useContext()', which should not be mutated + | ^^^^^^^^^^ `FooContext` cannot be modified 13 | }; 14 | return
; 15 | } - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context.expect.md index 0152f67233a8..de4c69512f7f 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-context.expect.md @@ -15,18 +15,18 @@ function Component(props) { ``` Found 1 error: -Error: Mutating a value returned from 'useContext()', which should not be mutated +Error: This value cannot be modified + +Modifying a value returned from 'useContext()' is not allowed. error.invalid-mutate-context.ts:3:2 1 | function Component(props) { 2 | const context = useContext(FooContext); > 3 | context.value = props.value; - | ^^^^^^^ Mutating a value returned from 'useContext()', which should not be mutated + | ^^^^^^^ This value cannot be modified 4 | return context.value; 5 | } 6 | - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-props-in-effect-fixpoint.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-props-in-effect-fixpoint.expect.md index 0021119ed772..795af4b75f26 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-props-in-effect-fixpoint.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-props-in-effect-fixpoint.expect.md @@ -26,20 +26,18 @@ function Component(props) { ``` Found 1 error: -Error: Mutating component props or hook arguments is not allowed. Consider using a local variable instead +Error: This value cannot be modified -Found mutation of `y`. +Modifying component props or hook arguments is not allowed. Consider using a local variable instead error.invalid-mutate-props-in-effect-fixpoint.ts:10:4 8 | let y = x; 9 | let mutateProps = () => { > 10 | y.foo = true; - | ^ Mutating component props or hook arguments is not allowed. Consider using a local variable instead + | ^ `y` cannot be modified 11 | }; 12 | let mutatePropsIndirect = () => { 13 | mutateProps(); - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-props-via-for-of-iterator.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-props-via-for-of-iterator.expect.md index 62815dd23d30..2a363a7dac81 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-props-via-for-of-iterator.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutate-props-via-for-of-iterator.expect.md @@ -18,18 +18,18 @@ function Component(props) { ``` Found 1 error: -Error: Mutating component props or hook arguments is not allowed. Consider using a local variable instead +Error: This value cannot be modified + +Modifying component props or hook arguments is not allowed. Consider using a local variable instead error.invalid-mutate-props-via-for-of-iterator.ts:4:4 2 | const items = []; 3 | for (const x of props.items) { > 4 | x.modified = true; - | ^ Mutating component props or hook arguments is not allowed. Consider using a local variable instead + | ^ This value cannot be modified 5 | items.push(x); 6 | } 7 | return items; - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-in-closure.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-in-closure.expect.md index e28dda12955e..83fb1972af0e 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-in-closure.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-in-closure.expect.md @@ -17,20 +17,18 @@ function useInvalidMutation(options) { ``` Found 1 error: -Error: Mutating component props or hook arguments is not allowed. Consider using a local variable instead +Error: This value cannot be modified -Found mutation of `options`. +Modifying component props or hook arguments is not allowed. Consider using a local variable instead error.invalid-mutation-in-closure.ts:4:4 2 | function test() { 3 | foo(options.foo); // error should not point on this line > 4 | options.foo = 'bar'; - | ^^^^^^^ Mutating component props or hook arguments is not allowed. Consider using a local variable instead + | ^^^^^^^ `options` cannot be modified 5 | } 6 | return test; 7 | } - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md index e942495b5a66..53926d826069 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-of-possible-props-phi-indirect.expect.md @@ -20,20 +20,18 @@ function Component(props) { ``` Found 1 error: -Error: Writing to a variable defined outside a component or hook is not allowed. Consider using an effect +Error: This value cannot be modified -Found mutation of `x`. +Modifying a variable defined outside a component or hook is not allowed. Consider using an effect error.invalid-mutation-of-possible-props-phi-indirect.ts:4:4 2 | let x = cond ? someGlobal : props.foo; 3 | const mutatePhiThatCouldBeProps = () => { > 4 | x.y = true; - | ^ Writing to a variable defined outside a component or hook is not allowed. Consider using an effect + | ^ `x` cannot be modified 5 | }; 6 | const indirectMutateProps = () => { 7 | mutatePhiThatCouldBeProps(); - - ``` \ No newline at end of file diff --git a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-non-imported-reanimated-shared-value-writes.expect.md b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-non-imported-reanimated-shared-value-writes.expect.md index f4f03c49122a..9aef48428fb9 100644 --- a/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-non-imported-reanimated-shared-value-writes.expect.md +++ b/compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-non-imported-reanimated-shared-value-writes.expect.md @@ -25,20 +25,18 @@ function SomeComponent() { ``` Found 1 error: -Error: Updating a value returned from a hook is not allowed. Consider moving the mutation into the hook where the value is constructed +Error: This value cannot be modified -Found mutation of `sharedVal`. +Modifying a value returned from a hook is not allowed. Consider moving the modification into the hook where the value is constructed error.invalid-non-imported-reanimated-shared-value-writes.ts:11:22 9 | return ( 10 | ; 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 4d2c55cdaa1f..520a8e4097d1 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 47d099c10163..acd4ff9395fc 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 b3f75f3ab8d7..8f2783f96909 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 d5dd79b96497..343c51787e25 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 d5e2cbcb83e2..a9960ad44dd6 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 af8103b7ae51..1224a5b9cf9c 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 7720863da34c..7bec7f73b551 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 8d218bf24b0d..e7f9ad7f30fd 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 e3bc7a5eb579..f6220645ef87 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 02e9f4f4a4b8..4a72cdd85551 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 83807391218c..7bc1e49069b6 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 7e9247c5ae9a..006d2a49c020 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 7ec5c5320f4b..8481ed2c576b 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 55c9cfcb2c12..f84686bc36bd 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 ad15e74d9707..1eb6bf66e964 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 8cb5ce3d788b..c519d43fb41c 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 c36f0b4db987..2e767c3c71e6 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 a66ddd3350ca..40c4bc5394dc 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 3f752a4a44d6..81c36a362cff 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 846816b7d478..96cea9c08f0f 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 436515da9965..4dc5336ebe01 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 0c232de9745f..dcd302bbe104 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 9515d32eb791..67410297f303 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 6e931e467bec..bff40e9649d9 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 071bfb2e7b5f..5a2bea68526f 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 51bc4e07533e..1d326012864b 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 1da7d8f57fbc..a636c5937542 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 28133aee7bc4..9d05cd1871fe 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 254962d99cce..0492c2c30c30 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 003/846] [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 02813a8d2fd0..27d2fd79890a 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 0992591183c1..5cfa56d77f74 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 7886f11e6237..36d20a0125e7 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 a800e2577329..cece2fa7c6dd 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 4b59a22e802f..ed74f4664953 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 b337f0c724dd..ee0d0f74c5f3 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 004/846] [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 27d2fd79890a..0ced1e54ed76 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 36d20a0125e7..0f8fe268ca02 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 005/846] [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 0f35215e86d7..0084911eec1b 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 0f8fe268ca02..bf7bd3eb6507 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 14a9fc94668b..f272913bce49 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 4d0369f5210c..ee341b111f1a 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 26942a29962a..6e522e16669d 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 89024fd210e5..3221f97731d9 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 1860ea1fd40a..96be8584be21 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 92a647a0c549..e19cee753263 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 97ef238a5064..9c87cafff183 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 7716ddfd105c..7077b733b0d4 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 2347acf0937e..4b2f94a263a1 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 4bb3c1eb1019..0a5cde5cd65d 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. From 5a04619f60221d9132f64f25b5a87b04c78fc7dc Mon Sep 17 00:00:00 2001 From: Josh Story Date: Thu, 24 Jul 2025 19:38:31 -0700 Subject: [PATCH 006/846] [Flight] Properly close stream when no chunks need to be written after prerender (#33982) There is an edge case when prerendering where if you have nothing to write you can end up in a state where the prerender is in status closed before you can provide a destination. In this case the destination is never closed becuase it assumes it already would have been. This condition can happen now because of the introduction of the deubg stream. Before this a request would never entere closed status if there was no active destination. When a destination was added it would perform a flush and possibly close the stream. Now, it is possible to flush without a destination because you might have debug chunks to stream and you can end up closing the stream independent of an active destination. There are a number of ways we can solve this but the one that seems to adhere best to the original design is to only set the status to CLOSED when a destination is active. This means that if you don't have an active destination when the pendingChunks count hits zero it will not enter CLOSED status until you startFlowing. --- .../src/__tests__/ReactFlightDOMNode-test.js | 42 +++++++++++++++++++ .../react-server/src/ReactFlightServer.js | 3 +- 2 files changed, 44 insertions(+), 1 deletion(-) diff --git a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js index 4c06d93bed6f..049fa39d417a 100644 --- a/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js +++ b/packages/react-server-dom-webpack/src/__tests__/ReactFlightDOMNode-test.js @@ -863,4 +863,46 @@ describe('ReactFlightDOMNode', () => { expect(ownerStack).toBeNull(); } }); + + // @gate experimental + // @gate enableHalt + it('can handle an empty prelude when prerendering', async () => { + function App() { + return null; + } + + const serverAbortController = new AbortController(); + serverAbortController.abort(); + const errors = []; + const {pendingResult} = await serverAct(async () => { + // destructure trick to avoid the act scope from awaiting the returned value + return { + pendingResult: ReactServerDOMStaticServer.unstable_prerender( + ReactServer.createElement(App, null), + webpackMap, + { + signal: serverAbortController.signal, + onError(error) { + errors.push(error); + }, + }, + ), + }; + }); + + expect(errors).toEqual([]); + + const {prelude} = await pendingResult; + + const reader = prelude.getReader(); + while (true) { + const {done} = await reader.read(); + if (done) { + break; + } + } + + // We don't really have an assertion other than to make sure + // the stream doesn't hang. + }); }); diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 688a9ef0c574..30698b8311ac 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -5762,6 +5762,7 @@ function flushCompletedChunks(request: Request): void { // TODO: If this destination is not currently flowing we'll not close it when it resumes flowing. // We should keep a separate status for this. if (request.destination !== null) { + request.status = CLOSED; close(request.destination); request.destination = null; } @@ -5779,8 +5780,8 @@ function flushCompletedChunks(request: Request): void { ); request.cacheController.abort(abortReason); } - request.status = CLOSED; if (request.destination !== null) { + request.status = CLOSED; close(request.destination); request.destination = null; } From 99be14c883c5c83c9a087d37e19d93d6afb839ed Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Fri, 25 Jul 2025 04:59:46 -0400 Subject: [PATCH 007/846] [Flight] Promote enableAsyncDebugInfo to stable without enableComponentPerformanceTrack (#33996) There's a lot of overlap between `enableComponentPerformanceTrack` and `enableAsyncDebugInfo` because they both rely on timing information. The former is mainly emit timestamps for how long server components and awaits took. The latter how long I/O took. `enableAsyncDebugInfo` is currently primarily for the component performance track but its meta data is useful for other debug tools too. This promotes that flag to stable. However, `enableComponentPerformanceTrack` needs more work due to performance concerns with Chrome DevTools so I need to separate them. This keeps doing most of the timing tracking on the server but doesn't emit the per-server component time stamps when `enableComponentPerformanceTrack` is false. --- .../react-client/src/ReactFlightClient.js | 13 +- .../src/__tests__/ReactFlight-test.js | 4 +- .../react-server/src/ReactFlightServer.js | 114 ++++++++++++------ packages/shared/ReactFeatureFlags.js | 2 +- 4 files changed, 83 insertions(+), 50 deletions(-) diff --git a/packages/react-client/src/ReactFlightClient.js b/packages/react-client/src/ReactFlightClient.js index 4403687383ae..4a13d094a826 100644 --- a/packages/react-client/src/ReactFlightClient.js +++ b/packages/react-client/src/ReactFlightClient.js @@ -3647,7 +3647,7 @@ function initializeIOInfo(response: Response, ioInfo: ReactIOInfo): void { // $FlowFixMe[cannot-write] ioInfo.end += response._timeOrigin; - if (response._replayConsole) { + if (enableComponentPerformanceTrack && response._replayConsole) { const env = response._rootEnvironmentName; const promise = ioInfo.value; if (promise) { @@ -4149,7 +4149,10 @@ function processFullStringRow( return; } case 78 /* "N" */: { - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ( + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ) { // Track the time origin for future debug info. We track it relative // to the current environment's time space. const timeOrigin: number = +row; @@ -4169,11 +4172,7 @@ function processFullStringRow( // Fallthrough to share the error with Console entries. } case 74 /* "J" */: { - if ( - enableProfilerTimer && - enableComponentPerformanceTrack && - enableAsyncDebugInfo - ) { + if (enableProfilerTimer && enableAsyncDebugInfo) { resolveIOInfo(response, id, row); return; } diff --git a/packages/react-client/src/__tests__/ReactFlight-test.js b/packages/react-client/src/__tests__/ReactFlight-test.js index 2ccf874cee91..150997c1c374 100644 --- a/packages/react-client/src/__tests__/ReactFlight-test.js +++ b/packages/react-client/src/__tests__/ReactFlight-test.js @@ -2898,7 +2898,7 @@ describe('ReactFlight', () => { ); }); - // @gate enableAsyncIterableChildren + // @gate enableAsyncIterableChildren && enableComponentPerformanceTrack it('preserves debug info for server-to-server pass through of async iterables', async () => { let resolve; const iteratorPromise = new Promise(r => (resolve = r)); @@ -3727,7 +3727,7 @@ describe('ReactFlight', () => { expect(caughtError.digest).toBe('digest("my-error")'); }); - // @gate __DEV__ && enableComponentPerformanceTrack + // @gate __DEV__ && enableComponentPerformanceTrack it('can render deep but cut off JSX in debug info', async () => { function createDeepJSX(n) { if (n <= 0) { diff --git a/packages/react-server/src/ReactFlightServer.js b/packages/react-server/src/ReactFlightServer.js index 30698b8311ac..1a5cee7ba724 100644 --- a/packages/react-server/src/ReactFlightServer.js +++ b/packages/react-server/src/ReactFlightServer.js @@ -731,7 +731,10 @@ function RequestInstance( } let timeOrigin: number; - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ( + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ) { // We start by serializing the time origin. Any future timestamps will be // emitted relatively to this origin. Instead of using performance.timeOrigin // as this origin, we use the timestamp at the start of the request. @@ -978,7 +981,10 @@ function serializeThenable( task.keyPath, // the server component sequence continues through Promise-as-a-child. task.implicitSlot, request.abortableTasks, - enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0, + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ? task.time + : 0, __DEV__ ? task.debugOwner : null, __DEV__ ? task.debugStack : null, __DEV__ ? task.debugTask : null, @@ -1048,7 +1054,10 @@ function serializeThenable( }, reason => { if (newTask.status === PENDING) { - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ( + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ) { // If this is async we need to time when this task finishes. newTask.timed = true; } @@ -1094,7 +1103,10 @@ function serializeReadableStream( task.keyPath, task.implicitSlot, request.abortableTasks, - enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0, + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ? task.time + : 0, __DEV__ ? task.debugOwner : null, __DEV__ ? task.debugStack : null, __DEV__ ? task.debugTask : null, @@ -1186,7 +1198,10 @@ function serializeAsyncIterable( task.keyPath, task.implicitSlot, request.abortableTasks, - enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0, + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ? task.time + : 0, __DEV__ ? task.debugOwner : null, __DEV__ ? task.debugStack : null, __DEV__ ? task.debugTask : null, @@ -1616,7 +1631,10 @@ function renderFunctionComponent( outlineComponentInfo(request, componentDebugInfo); // Track when we started rendering this component. - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ( + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ) { advanceTaskTime(request, task, performance.now()); } @@ -1686,12 +1704,7 @@ function renderFunctionComponent( throw null; } - if ( - __DEV__ || - (enableProfilerTimer && - enableComponentPerformanceTrack && - enableAsyncDebugInfo) - ) { + if (__DEV__ || (enableProfilerTimer && enableAsyncDebugInfo)) { // Forward any debug information for any Promises that we use():ed during the render. // We do this at the end so that we don't keep doing this for each retry. const trackedThenables = getTrackedThenablesAfterRendering(); @@ -2016,7 +2029,10 @@ function deferTask(request: Request, task: Task): ReactJSONValue { task.keyPath, // unlike outlineModel this one carries along context task.implicitSlot, request.abortableTasks, - enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0, + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ? task.time + : 0, __DEV__ ? task.debugOwner : null, __DEV__ ? task.debugStack : null, __DEV__ ? task.debugTask : null, @@ -2033,7 +2049,10 @@ function outlineTask(request: Request, task: Task): ReactJSONValue { task.keyPath, // unlike outlineModel this one carries along context task.implicitSlot, request.abortableTasks, - enableProfilerTimer && enableComponentPerformanceTrack ? task.time : 0, + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ? task.time + : 0, __DEV__ ? task.debugOwner : null, __DEV__ ? task.debugStack : null, __DEV__ ? task.debugTask : null, @@ -2482,7 +2501,10 @@ function emitAsyncSequence( } function pingTask(request: Request, task: Task): void { - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ( + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ) { // If this was async we need to emit the time when it completes. task.timed = true; } @@ -2587,7 +2609,10 @@ function createTask( | 'debugStack' | 'debugTask', >): any); - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ( + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ) { task.timed = false; task.time = lastTimestamp; } @@ -2795,7 +2820,8 @@ function outlineModel(request: Request, value: ReactClientValue): number { null, // The way we use outlining is for reusing an object. false, // It makes no sense for that use case to be contextual. request.abortableTasks, - enableProfilerTimer && enableComponentPerformanceTrack + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) ? performance.now() // TODO: This should really inherit the time from the task. : 0, null, // TODO: Currently we don't associate any debug information with @@ -3041,7 +3067,8 @@ function serializeBlob(request: Request, blob: Blob): string { null, false, request.abortableTasks, - enableProfilerTimer && enableComponentPerformanceTrack + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) ? performance.now() // TODO: This should really inherit the time from the task. : 0, null, // TODO: Currently we don't associate any debug information with @@ -3177,7 +3204,8 @@ function renderModel( task.keyPath, task.implicitSlot, request.abortableTasks, - enableProfilerTimer && enableComponentPerformanceTrack + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) ? task.time : 0, __DEV__ ? task.debugOwner : null, @@ -5130,11 +5158,7 @@ function forwardDebugInfoFromThenable( forwardDebugInfo(request, task, debugInfo); } } - if ( - enableProfilerTimer && - enableComponentPerformanceTrack && - enableAsyncDebugInfo - ) { + if (enableProfilerTimer && enableAsyncDebugInfo) { const sequence = getAsyncSequenceFromPromise(thenable); if (sequence !== null) { emitAsyncSequence(request, task, sequence, debugInfo, owner, stack); @@ -5155,11 +5179,7 @@ function forwardDebugInfoFromCurrentContext( forwardDebugInfo(request, task, debugInfo); } } - if ( - enableProfilerTimer && - enableComponentPerformanceTrack && - enableAsyncDebugInfo - ) { + if (enableProfilerTimer && enableAsyncDebugInfo) { const sequence = getCurrentAsyncSequence(); if (sequence !== null) { emitAsyncSequence(request, task, sequence, debugInfo, null, null); @@ -5182,11 +5202,7 @@ function forwardDebugInfoFromAbortedTask(request: Request, task: Task): void { forwardDebugInfo(request, task, debugInfo); } } - if ( - enableProfilerTimer && - enableComponentPerformanceTrack && - enableAsyncDebugInfo - ) { + if (enableProfilerTimer && enableAsyncDebugInfo) { let thenable: null | Thenable = null; if (typeof model.then === 'function') { thenable = (model: any); @@ -5262,7 +5278,10 @@ function advanceTaskTime( task: Task, timestamp: number, ): void { - if (!enableProfilerTimer || !enableComponentPerformanceTrack) { + if ( + !enableProfilerTimer || + (!enableComponentPerformanceTrack && !enableAsyncDebugInfo) + ) { return; } // Emits a timing chunk, if the new timestamp is higher than the previous timestamp of this task. @@ -5278,7 +5297,10 @@ function advanceTaskTime( } function markOperationEndTime(request: Request, task: Task, timestamp: number) { - if (!enableProfilerTimer || !enableComponentPerformanceTrack) { + if ( + !enableProfilerTimer || + (!enableComponentPerformanceTrack && !enableAsyncDebugInfo) + ) { return; } // This is like advanceTaskTime() but always emits a timing chunk even if it doesn't advance. @@ -5384,7 +5406,10 @@ function emitChunk( } function erroredTask(request: Request, task: Task, error: mixed): void { - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ( + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ) { if (task.timed) { markOperationEndTime(request, task, performance.now()); } @@ -5467,7 +5492,10 @@ function retryTask(request: Request, task: Task): void { } } // We've finished rendering. Log the end time. - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ( + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ) { if (task.timed) { markOperationEndTime(request, task, performance.now()); } @@ -5605,7 +5633,10 @@ function finishAbortedTask( } forwardDebugInfoFromAbortedTask(request, task); // Track when we aborted this task as its end time. - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ( + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ) { if (task.timed) { markOperationEndTime(request, task, request.abortTime); } @@ -5921,7 +5952,10 @@ export function abort(request: Request, reason: mixed): void { } try { request.status = ABORTING; - if (enableProfilerTimer && enableComponentPerformanceTrack) { + if ( + enableProfilerTimer && + (enableComponentPerformanceTrack || enableAsyncDebugInfo) + ) { request.abortTime = performance.now(); } request.cacheController.abort(reason); diff --git a/packages/shared/ReactFeatureFlags.js b/packages/shared/ReactFeatureFlags.js index 0e80e113490e..5dd612837a6b 100644 --- a/packages/shared/ReactFeatureFlags.js +++ b/packages/shared/ReactFeatureFlags.js @@ -247,7 +247,7 @@ export const enableProfilerCommitHooks = __PROFILE__; // Phase param passed to onRender callback differentiates between an "update" and a "cascading-update". export const enableProfilerNestedUpdatePhase = __PROFILE__; -export const enableAsyncDebugInfo = __EXPERIMENTAL__; +export const enableAsyncDebugInfo = true; // Track which Fiber(s) schedule render work. export const enableUpdaterTracking = __PROFILE__; From 7ca2d4cd2e263f1923d958b16797d20d3efbc194 Mon Sep 17 00:00:00 2001 From: "Sebastian \"Sebbie\" Silbermann" Date: Fri, 25 Jul 2025 12:32:30 +0200 Subject: [PATCH 008/846] Work around Chrome DevTools crash on `performance.measure` (#33997) --- .../react-client/src/ReactFlightPerformanceTrack.js | 12 ++++++------ .../src/ReactFiberPerformanceTrack.js | 10 +++++----- .../src/__tests__/ReactFlightAsyncDebugInfo-test.js | 2 +- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/react-client/src/ReactFlightPerformanceTrack.js b/packages/react-client/src/ReactFlightPerformanceTrack.js index 0832497d9445..984408500d53 100644 --- a/packages/react-client/src/ReactFlightPerformanceTrack.js +++ b/packages/react-client/src/ReactFlightPerformanceTrack.js @@ -110,7 +110,7 @@ export function logComponentRender( } debugTask.run( // $FlowFixMe[method-unbinding] - performance.measure.bind(performance, entryName, { + performance.measure.bind(performance, '\u200b' + entryName, { start: startTime < 0 ? 0 : startTime, end: childrenEndTime, detail: { @@ -125,7 +125,7 @@ export function logComponentRender( ); } else { console.timeStamp( - entryName, + '\u200b' + entryName, startTime < 0 ? 0 : startTime, childrenEndTime, trackNames[trackIdx], @@ -163,7 +163,7 @@ export function logComponentAborted( if (componentInfo.props != null) { addObjectToProperties(componentInfo.props, properties, 0, ''); } - performance.measure(entryName, { + performance.measure('\u200b' + entryName, { start: startTime < 0 ? 0 : startTime, end: childrenEndTime, detail: { @@ -220,7 +220,7 @@ export function logComponentErrored( if (componentInfo.props != null) { addObjectToProperties(componentInfo.props, properties, 0, ''); } - performance.measure(entryName, { + performance.measure('\u200b' + entryName, { start: startTime < 0 ? 0 : startTime, end: childrenEndTime, detail: { @@ -614,7 +614,7 @@ export function logIOInfoErrored( getIOLongName(ioInfo, description, ioInfo.env, rootEnv) + ' Rejected'; debugTask.run( // $FlowFixMe[method-unbinding] - performance.measure.bind(performance, entryName, { + performance.measure.bind(performance, '\u200b' + entryName, { start: startTime < 0 ? 0 : startTime, end: endTime, detail: { @@ -667,7 +667,7 @@ export function logIOInfo( ); debugTask.run( // $FlowFixMe[method-unbinding] - performance.measure.bind(performance, entryName, { + performance.measure.bind(performance, '\u200b' + entryName, { start: startTime < 0 ? 0 : startTime, end: endTime, detail: { diff --git a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js index 3f5f92c895b8..4f70a589cb08 100644 --- a/packages/react-reconciler/src/ReactFiberPerformanceTrack.js +++ b/packages/react-reconciler/src/ReactFiberPerformanceTrack.js @@ -272,7 +272,7 @@ export function logComponentRender( // $FlowFixMe[method-unbinding] performance.measure.bind( performance, - name, + '\u200b' + name, reusableComponentOptions, ), ); @@ -369,10 +369,10 @@ export function logComponentErrored( if (__DEV__ && debugTask) { debugTask.run( // $FlowFixMe[method-unbinding] - performance.measure.bind(performance, name, options), + performance.measure.bind(performance, '\u200b' + name, options), ); } else { - performance.measure(name, options); + performance.measure('\u200b' + name, options); } } else { console.timeStamp( @@ -436,10 +436,10 @@ function logComponentEffectErrored( if (debugTask) { debugTask.run( // $FlowFixMe[method-unbinding] - performance.measure.bind(performance, name, options), + performance.measure.bind(performance, '\u200b' + name, options), ); } else { - performance.measure(name, options); + performance.measure('\u200b' + name, options); } } else { console.timeStamp( diff --git a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js index 68c3431adc0c..0a98d87e363c 100644 --- a/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js +++ b/packages/react-server/src/__tests__/ReactFlightAsyncDebugInfo-test.js @@ -610,7 +610,7 @@ describe('ReactFlightAsyncDebugInfo', () => { expect(entries).toMatchInlineSnapshot(` [ { - "name": "Component", + "name": "\u200bComponent", }, { "name": "await getData (…/pulls)", From 142fd27bf6e1b46c554d436509bdf9b70f7ef042 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Markb=C3=A5ge?= Date: Fri, 25 Jul 2025 10:16:43 -0400 Subject: [PATCH 009/846] [DevTools] Add Option to Open Local Files directly in External Editor (#33983) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The `useOpenResource` hook is now used to open links. Currently, the `<>` icon for the component stacks and the link in the bottom of the components stack. But it'll also be used for many new links like stacks. If this new option is configured, and this is a local file then this is opened directly in the external editor. Otherwise it fallbacks to open in the Sources tab or whatever the standalone or inline is configured to use. Screenshot 2025-07-24 at 4 09 09 PM I prominently surface this option in the Source pane to make it discoverable. Screenshot 2025-07-24 at 4 03 48 PM When this is configured, the "Open in Editor" is hidden since that's just the default. I plan on deprecating this button to avoid having the two buttons going forward. Notably there's one exception where this doesn't work. When you click an Action or Event listener it takes you to the Sources tab and you have to open in editor from there. That's because we use the `inspect()` mechanism instead of extracting the source location. That's because we can't do the "throw trick" since these can have side-effects. The Chrome debugger protocol would solve this but it pops up an annoying dialog. We could maybe only attach the debugger only for that case. Especially if the dialog disappears before you focus on the browser again. --- .../react-devtools-core/src/standalone.js | 10 +-- .../src/main/index.js | 2 +- .../react-devtools-fusebox/src/frontend.d.ts | 17 +++- .../inspectedElementSerializer.js | 3 +- .../src/backend/fiber/renderer.js | 5 -- .../src/backend/legacy/renderer.js | 2 - .../src/backend/types.js | 3 - .../react-devtools-shared/src/backendAPI.js | 2 - .../react-devtools-shared/src/constants.js | 2 + .../views/Components/InspectedElement.js | 37 +++++--- .../Components/InspectedElementSourcePanel.js | 22 +---- .../InspectedElementViewSourceButton.js | 45 ++-------- .../src/devtools/views/DevTools.js | 10 +-- .../src/devtools/views/Editor/EditorPane.css | 18 +++- .../src/devtools/views/Editor/EditorPane.js | 47 ++++++---- .../src/devtools/views/Editor/utils.js | 4 +- .../views/Profiler/SidebarEventInfo.js | 18 ++-- .../views/Settings/CodeEditorByDefault.js | 35 ++++++++ .../views/Settings/CodeEditorOptions.js | 2 +- .../views/Settings/GeneralSettings.js | 17 ++++ .../src/devtools/views/hooks.js | 16 +++- .../src/devtools/views/useOpenResource.js | 85 +++++++++++++++++++ .../src/frontend/types.js | 3 - packages/react-devtools-shared/src/utils.js | 9 ++ 24 files changed, 274 insertions(+), 140 deletions(-) create mode 100644 packages/react-devtools-shared/src/devtools/views/Settings/CodeEditorByDefault.js create mode 100644 packages/react-devtools-shared/src/devtools/views/useOpenResource.js diff --git a/packages/react-devtools-core/src/standalone.js b/packages/react-devtools-core/src/standalone.js index 369dc43a242f..f8286783a9b4 100644 --- a/packages/react-devtools-core/src/standalone.js +++ b/packages/react-devtools-core/src/standalone.js @@ -26,7 +26,7 @@ import { import {localStorageSetItem} from 'react-devtools-shared/src/storage'; import type {FrontendBridge} from 'react-devtools-shared/src/bridge'; -import type {ReactFunctionLocation} from 'shared/ReactTypes'; +import type {ReactFunctionLocation, ReactCallSite} from 'shared/ReactTypes'; export type StatusTypes = 'server-connected' | 'devtools-connected' | 'error'; export type StatusListener = (message: string, status: StatusTypes) => void; @@ -144,8 +144,8 @@ async function fetchFileWithCaching(url: string) { } function canViewElementSourceFunction( - _source: ReactFunctionLocation, - symbolicatedSource: ReactFunctionLocation | null, + _source: ReactFunctionLocation | ReactCallSite, + symbolicatedSource: ReactFunctionLocation | ReactCallSite | null, ): boolean { if (symbolicatedSource == null) { return false; @@ -156,8 +156,8 @@ function canViewElementSourceFunction( } function viewElementSourceFunction( - _source: ReactFunctionLocation, - symbolicatedSource: ReactFunctionLocation | null, + _source: ReactFunctionLocation | ReactCallSite, + symbolicatedSource: ReactFunctionLocation | ReactCallSite | null, ): void { if (symbolicatedSource == null) { return; diff --git a/packages/react-devtools-extensions/src/main/index.js b/packages/react-devtools-extensions/src/main/index.js index d7756ca991be..5bb94ea1ccd3 100644 --- a/packages/react-devtools-extensions/src/main/index.js +++ b/packages/react-devtools-extensions/src/main/index.js @@ -326,7 +326,7 @@ function createSourcesEditorPanel() { editorPane = createdPane; createdPane.setPage('panel.html'); - createdPane.setHeight('42px'); + createdPane.setHeight('75px'); createdPane.onShown.addListener(portal => { editorPortalContainer = portal.container; diff --git a/packages/react-devtools-fusebox/src/frontend.d.ts b/packages/react-devtools-fusebox/src/frontend.d.ts index 5a06aec7e49c..a1142178f47a 100644 --- a/packages/react-devtools-fusebox/src/frontend.d.ts +++ b/packages/react-devtools-fusebox/src/frontend.d.ts @@ -34,17 +34,26 @@ export type ReactFunctionLocation = [ number, // enclosing line number number, // enclosing column number ]; +export type ReactCallSite = [ + string, // function name + string, // file name TODO: model nested eval locations as nested arrays + number, // line number + number, // column number + number, // enclosing line number + number, // enclosing column number + boolean, // async resume +]; export type ViewElementSource = ( - source: ReactFunctionLocation, - symbolicatedSource: ReactFunctionLocation | null, + source: ReactFunctionLocation | ReactCallSite, + symbolicatedSource: ReactFunctionLocation | ReactCallSite | null, ) => void; export type ViewAttributeSource = ( id: number, path: Array, ) => void; export type CanViewElementSource = ( - source: ReactFunctionLocation, - symbolicatedSource: ReactFunctionLocation | null, + source: ReactFunctionLocation | ReactCallSite, + symbolicatedSource: ReactFunctionLocation | ReactCallSite | null, ) => boolean; export type InitializationOptions = { diff --git a/packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js b/packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js index 870ad35e4b03..55029252843c 100644 --- a/packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js +++ b/packages/react-devtools-shared/src/__tests__/__serializers__/inspectedElementSerializer.js @@ -15,8 +15,7 @@ export function test(maybeInspectedElement) { hasOwnProperty('canEditFunctionProps') && hasOwnProperty('canEditHooks') && hasOwnProperty('canToggleSuspense') && - hasOwnProperty('canToggleError') && - hasOwnProperty('canViewSource') + hasOwnProperty('canToggleError') ); } diff --git a/packages/react-devtools-shared/src/backend/fiber/renderer.js b/packages/react-devtools-shared/src/backend/fiber/renderer.js index 937fe8d352fd..96c7a38863b2 100644 --- a/packages/react-devtools-shared/src/backend/fiber/renderer.js +++ b/packages/react-devtools-shared/src/backend/fiber/renderer.js @@ -4374,8 +4374,6 @@ export function attach( (fiber.alternate !== null && forceFallbackForFibers.has(fiber.alternate))), - // Can view component source location. - canViewSource, source, // Does the component have legacy context attached to it. @@ -4416,7 +4414,6 @@ export function attach( function inspectVirtualInstanceRaw( virtualInstance: VirtualInstance, ): InspectedElement | null { - const canViewSource = true; const source = getSourceForInstance(virtualInstance); const componentInfo = virtualInstance.data; @@ -4470,8 +4467,6 @@ export function attach( canToggleSuspense: supportsTogglingSuspense && hasSuspenseBoundary, - // Can view component source location. - canViewSource, source, // Does the component have legacy context attached to it. diff --git a/packages/react-devtools-shared/src/backend/legacy/renderer.js b/packages/react-devtools-shared/src/backend/legacy/renderer.js index cbcc37e319ee..cc097c837909 100644 --- a/packages/react-devtools-shared/src/backend/legacy/renderer.js +++ b/packages/react-devtools-shared/src/backend/legacy/renderer.js @@ -830,8 +830,6 @@ export function attach( // Suspense did not exist in legacy versions canToggleSuspense: false, - // Can view component source location. - canViewSource: type === ElementTypeClass || type === ElementTypeFunction, source: null, // Only legacy context exists in legacy versions. diff --git a/packages/react-devtools-shared/src/backend/types.js b/packages/react-devtools-shared/src/backend/types.js index c86298850973..c9d6284b2f42 100644 --- a/packages/react-devtools-shared/src/backend/types.js +++ b/packages/react-devtools-shared/src/backend/types.js @@ -264,9 +264,6 @@ export type InspectedElement = { // Is this Suspense, and can its value be overridden now? canToggleSuspense: boolean, - // Can view component source location. - canViewSource: boolean, - // Does the component have legacy context attached to it. hasLegacyContext: boolean, diff --git a/packages/react-devtools-shared/src/backendAPI.js b/packages/react-devtools-shared/src/backendAPI.js index bbb171bce0da..20b4e99a101e 100644 --- a/packages/react-devtools-shared/src/backendAPI.js +++ b/packages/react-devtools-shared/src/backendAPI.js @@ -222,7 +222,6 @@ export function convertInspectedElementBackendToFrontend( canToggleError, isErrored, canToggleSuspense, - canViewSource, hasLegacyContext, id, type, @@ -252,7 +251,6 @@ export function convertInspectedElementBackendToFrontend( canToggleError, isErrored, canToggleSuspense, - canViewSource, hasLegacyContext, id, key, diff --git a/packages/react-devtools-shared/src/constants.js b/packages/react-devtools-shared/src/constants.js index b08738165906..fa32ead1e934 100644 --- a/packages/react-devtools-shared/src/constants.js +++ b/packages/react-devtools-shared/src/constants.js @@ -37,6 +37,8 @@ export const LOCAL_STORAGE_OPEN_IN_EDITOR_URL = 'React::DevTools::openInEditorUrl'; export const LOCAL_STORAGE_OPEN_IN_EDITOR_URL_PRESET = 'React::DevTools::openInEditorUrlPreset'; +export const LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR = + 'React::DevTools::alwaysOpenInEditor'; export const LOCAL_STORAGE_PARSE_HOOK_NAMES_KEY = 'React::DevTools::parseHookNames'; export const SESSION_STORAGE_RECORD_CHANGE_DESCRIPTIONS_KEY = diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js index 8210e12331b1..0c980f0db484 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElement.js @@ -18,8 +18,11 @@ import Toggle from '../Toggle'; import {ElementTypeSuspense} from 'react-devtools-shared/src/frontend/types'; import InspectedElementView from './InspectedElementView'; import {InspectedElementContext} from './InspectedElementContext'; -import {getOpenInEditorURL} from '../../../utils'; -import {LOCAL_STORAGE_OPEN_IN_EDITOR_URL} from '../../../constants'; +import {getOpenInEditorURL, getAlwaysOpenInEditor} from '../../../utils'; +import { + LOCAL_STORAGE_OPEN_IN_EDITOR_URL, + LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR, +} from '../../../constants'; import FetchFileWithCachingContext from './FetchFileWithCachingContext'; import {symbolicateSourceWithCache} from 'react-devtools-shared/src/symbolicateSource'; import OpenInEditorButton from './OpenInEditorButton'; @@ -118,18 +121,26 @@ export default function InspectedElementWrapper(_: Props): React.Node { inspectedElement != null && inspectedElement.canToggleSuspense; - const editorURL = useSyncExternalStore( - function subscribe(callback) { - window.addEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback); + const alwaysOpenInEditor = useSyncExternalStore( + useCallback(function subscribe(callback) { + window.addEventListener(LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR, callback); return function unsubscribe() { - window.removeEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback); + window.removeEventListener( + LOCAL_STORAGE_ALWAYS_OPEN_IN_EDITOR, + callback, + ); }; - }, - function getState() { - return getOpenInEditorURL(); - }, + }, []), + getAlwaysOpenInEditor, ); + const editorURL = useSyncExternalStore(function subscribe(callback) { + window.addEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback); + return function unsubscribe() { + window.removeEventListener(LOCAL_STORAGE_OPEN_IN_EDITOR_URL, callback); + }; + }, getOpenInEditorURL); + const toggleErrored = useCallback(() => { if (inspectedElement == null) { return; @@ -217,7 +228,8 @@ export default function InspectedElementWrapper(_: Props): React.Node {
- {!!editorURL && + {!alwaysOpenInEditor && + !!editorURL && inspectedElement != null && inspectedElement.source != null && symbolicatedSourcePromise != null && ( @@ -271,8 +283,7 @@ export default function InspectedElementWrapper(_: Props): React.Node { {!hideViewSourceAction && ( )} diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js index 91780cdc13d7..585361cedcf4 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementSourcePanel.js @@ -8,7 +8,6 @@ */ import * as React from 'react'; -import {useCallback, useContext} from 'react'; import {copy} from 'clipboard-js'; import {toNormalUrl} from 'jsc-safe-url'; @@ -17,7 +16,7 @@ import ButtonIcon from '../ButtonIcon'; import Skeleton from './Skeleton'; import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck'; -import ViewElementSourceContext from './ViewElementSourceContext'; +import useOpenResource from '../useOpenResource'; import type {ReactFunctionLocation} from 'shared/ReactTypes'; import styles from './InspectedElementSourcePanel.css'; @@ -91,24 +90,11 @@ function CopySourceButton({source, symbolicatedSourcePromise}: Props) { function FormattedSourceString({source, symbolicatedSourcePromise}: Props) { const symbolicatedSource = React.use(symbolicatedSourcePromise); - const {canViewElementSourceFunction, viewElementSourceFunction} = useContext( - ViewElementSourceContext, + const [linkIsEnabled, viewSource] = useOpenResource( + source, + symbolicatedSource, ); - // In some cases (e.g. FB internal usage) the standalone shell might not be able to view the source. - // To detect this case, we defer to an injected helper function (if present). - const linkIsEnabled = - viewElementSourceFunction != null && - source != null && - (canViewElementSourceFunction == null || - canViewElementSourceFunction(source, symbolicatedSource)); - - const viewSource = useCallback(() => { - if (viewElementSourceFunction != null && source != null) { - viewElementSourceFunction(source, symbolicatedSource); - } - }, [source, symbolicatedSource]); - const [, sourceURL, line] = symbolicatedSource == null ? source : symbolicatedSource; diff --git a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js index 6043bb61df92..23d4cf96c827 100644 --- a/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js +++ b/packages/react-devtools-shared/src/devtools/views/Components/InspectedElementViewSourceButton.js @@ -11,79 +11,48 @@ import * as React from 'react'; import ButtonIcon from '../ButtonIcon'; import Button from '../Button'; -import ViewElementSourceContext from './ViewElementSourceContext'; import Skeleton from './Skeleton'; import type {ReactFunctionLocation} from 'shared/ReactTypes'; -import type { - CanViewElementSource, - ViewElementSource, -} from 'react-devtools-shared/src/devtools/views/DevTools'; -const {useCallback, useContext} = React; +import useOpenResource from '../useOpenResource'; type Props = { - canViewSource: ?boolean, - source: ?ReactFunctionLocation, + source: null | ReactFunctionLocation, symbolicatedSourcePromise: Promise | null, }; function InspectedElementViewSourceButton({ - canViewSource, source, symbolicatedSourcePromise, }: Props): React.Node { - const {canViewElementSourceFunction, viewElementSourceFunction} = useContext( - ViewElementSourceContext, - ); - return ( }> ); } type ActualSourceButtonProps = { - canViewSource: ?boolean, - source: ?ReactFunctionLocation, + source: null | ReactFunctionLocation, symbolicatedSourcePromise: Promise | null, - canViewElementSourceFunction: CanViewElementSource | null, - viewElementSourceFunction: ViewElementSource | null, }; function ActualSourceButton({ - canViewSource, source, symbolicatedSourcePromise, - canViewElementSourceFunction, - viewElementSourceFunction, }: ActualSourceButtonProps): React.Node { const symbolicatedSource = symbolicatedSourcePromise == null ? null : React.use(symbolicatedSourcePromise); - // In some cases (e.g. FB internal usage) the standalone shell might not be able to view the source. - // To detect this case, we defer to an injected helper function (if present). - const buttonIsEnabled = - !!canViewSource && - viewElementSourceFunction != null && - source != null && - (canViewElementSourceFunction == null || - canViewElementSourceFunction(source, symbolicatedSource)); - - const viewSource = useCallback(() => { - if (viewElementSourceFunction != null && source != null) { - viewElementSourceFunction(source, symbolicatedSource); - } - }, [source, symbolicatedSource]); - + const [buttonIsEnabled, viewSource] = useOpenResource( + source, + symbolicatedSource, + ); return ( ); + } else { + editorToolbar = ( +
+ +
+ +
+ ); } return (
- -
- + {editorToolbar} +
+ {editorURL ? ( + + ) : ( + 'Configure an external editor to open local files.' + )} +
); } diff --git a/packages/react-devtools-shared/src/devtools/views/Editor/utils.js b/packages/react-devtools-shared/src/devtools/views/Editor/utils.js index 89e697bd60c8..b239c9d213a6 100644 --- a/packages/react-devtools-shared/src/devtools/views/Editor/utils.js +++ b/packages/react-devtools-shared/src/devtools/views/Editor/utils.js @@ -7,11 +7,11 @@ * @flow */ -import type {ReactFunctionLocation} from 'shared/ReactTypes'; +import type {ReactFunctionLocation, ReactCallSite} from 'shared/ReactTypes'; export function checkConditions( editorURL: string, - source: ReactFunctionLocation, + source: ReactFunctionLocation | ReactCallSite, ): {url: URL | null, shouldDisableButton: boolean} { try { const url = new URL(editorURL); diff --git a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js index b7b031a990ca..77f0d13feb72 100644 --- a/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js +++ b/packages/react-devtools-shared/src/devtools/views/Profiler/SidebarEventInfo.js @@ -12,7 +12,6 @@ import type {SchedulingEvent} from 'react-devtools-timeline/src/types'; import * as React from 'react'; import Button from '../Button'; import ButtonIcon from '../ButtonIcon'; -import ViewElementSourceContext from '../Components/ViewElementSourceContext'; import {useContext} from 'react'; import {TimelineContext} from 'react-devtools-timeline/src/TimelineContext'; import { @@ -22,6 +21,7 @@ import { import {stackToComponentLocations} from 'react-devtools-shared/src/devtools/utils'; import {copy} from 'clipboard-js'; import {withPermissionsCheck} from 'react-devtools-shared/src/frontend/utils/withPermissionsCheck'; +import useOpenResource from '../useOpenResource'; import styles from './SidebarEventInfo.css'; @@ -32,9 +32,6 @@ type SchedulingEventProps = { }; function SchedulingEventInfo({eventInfo}: SchedulingEventProps) { - const {canViewElementSourceFunction, viewElementSourceFunction} = useContext( - ViewElementSourceContext, - ); const {componentName, timestamp} = eventInfo; const componentStack = eventInfo.componentStack || null; @@ -79,15 +76,10 @@ function SchedulingEventInfo({eventInfo}: SchedulingEventProps) { // TODO: We should support symbolication here as well, but // symbolicating the whole stack can be expensive - const canViewSource = - canViewElementSourceFunction == null || - canViewElementSourceFunction(location, null); - - const viewSource = - !canViewSource || viewElementSourceFunction == null - ? () => null - : () => viewElementSourceFunction(location, null); - + const [canViewSource, viewSource] = useOpenResource( + location, + null, + ); return (
  • +
    + +
    + +
    +
    Display density
    + +
    + {supportsTraceUpdates && (