@samitouri / QOS-React / commits / e3c06424ae

[compiler] Refactor validations to return Result and log where appropriate

Updates ~all of our validations to return a Result, and then updates callers to either unwrap() if they should bailout or else just log. ghstack-source-id: 418b5f5aa2b7dd49ca76b3f98a48a35150691d7e Pull Request resolved: https://github.com/facebook/react/pull/32688

Joe Savona committed Mar 20, 2025 at 11:02 UTC e3c06424ae1162319d786a76371d649dee412c29
25 files changed +331 -200
compiler/packages/babel-plugin-react-compiler/src/CompilerError.ts
+5
@@ -6,6 +6,7 @@
6 */
7
8 import type {SourceLocation} from './HIR';
9 +import {Err, Ok, Result} from './Utils/Result';
10 import {assertExhaustive} from './Utils/utils';
11
12 export enum ErrorSeverity {
@@ -224,6 +225,10 @@ export class CompilerError extends Error {
225 return this.details.length > 0;
226 }
227
228 + asResult(): Result<void, CompilerError> {
229 + return this.hasErrors() ? Err(this) : Ok(undefined);
230 + }
231 +
232 /*
233 * An error is critical if it means the compiler has entered into a broken state and cannot
234 * continue safely. Other expected errors such as Todos mean that we can skip over that component
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+11 -11
@@ -100,7 +100,7 @@ import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHI
100 import {outlineJSX} from '../Optimization/OutlineJsx';
101 import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
102 import {transformFire} from '../Transform';
103 -import {validateNoImpureFunctionsInRender} from '../Validation/ValiateNoImpureFunctionsInRender';
103 +import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender';
104 import {CompilerError} from '..';
105 import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
106
@@ -162,7 +162,7 @@ function runWithEnvironment(
162 log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
163
164 validateContextVariableLValues(hir);
165 - validateUseMemo(hir);
165 + validateUseMemo(hir).unwrap();
166
167 if (
168 env.isInferredMemoEnabled &&
@@ -203,10 +203,10 @@ function runWithEnvironment(
203
204 if (env.isInferredMemoEnabled) {
205 if (env.config.validateHooksUsage) {
206 - validateHooksUsage(hir);
206 + validateHooksUsage(hir).unwrap();
207 }
208 if (env.config.validateNoCapitalizedCalls) {
209 - validateNoCapitalizedCalls(hir);
209 + validateNoCapitalizedCalls(hir).unwrap();
210 }
211 }
212
@@ -256,23 +256,23 @@ function runWithEnvironment(
256 }
257
258 if (env.config.validateRefAccessDuringRender) {
259 - validateNoRefAccessInRender(hir);
259 + validateNoRefAccessInRender(hir).unwrap();
260 }
261
262 if (env.config.validateNoSetStateInRender) {
263 - validateNoSetStateInRender(hir);
263 + validateNoSetStateInRender(hir).unwrap();
264 }
265
266 if (env.config.validateNoSetStateInPassiveEffects) {
267 - validateNoSetStateInPassiveEffects(hir);
267 + env.logErrors(validateNoSetStateInPassiveEffects(hir));
268 }
269
270 if (env.config.validateNoJSXInTryStatements) {
271 - validateNoJSXInTryStatement(hir);
271 + env.logErrors(validateNoJSXInTryStatement(hir));
272 }
273
274 if (env.config.validateNoImpureFunctionsInRender) {
275 - validateNoImpureFunctionsInRender(hir);
275 + validateNoImpureFunctionsInRender(hir).unwrap();
276 }
277 }
278
@@ -514,14 +514,14 @@ function runWithEnvironment(
514 });
515
516 if (env.config.validateMemoizedEffectDependencies) {
517 - validateMemoizedEffectDependencies(reactiveFunction);
517 + validateMemoizedEffectDependencies(reactiveFunction).unwrap();
518 }
519
520 if (
521 env.config.enablePreserveExistingMemoizationGuarantees ||
522 env.config.validatePreserveExistingMemoizationGuarantees
523 ) {
524 - validatePreservedManualMemoization(reactiveFunction);
524 + validatePreservedManualMemoization(reactiveFunction).unwrap();
525 }
526
527 const ast = codegenFunction(reactiveFunction, {
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateHooksUsage.ts
+5 -4
@@ -26,6 +26,7 @@ import {
26 eachTerminalOperand,
27 } from '../HIR/visitors';
28 import {assertExhaustive} from '../Utils/utils';
29 +import {Result} from '../Utils/Result';
30
31 /**
32 * Represents the possible kinds of value which may be stored at a given Place during
@@ -87,7 +88,9 @@ function joinKinds(a: Kind, b: Kind): Kind {
88 * may not appear as the callee of a conditional call.
89 * See the note for Kind.PotentialHook for sources of potential hooks
90 */
90 -export function validateHooksUsage(fn: HIRFunction): void {
91 +export function validateHooksUsage(
92 + fn: HIRFunction,
93 +): Result<void, CompilerError> {
94 const unconditionalBlocks = computeUnconditionalBlocks(fn);
95
96 const errors = new CompilerError();
@@ -423,9 +426,7 @@ export function validateHooksUsage(fn: HIRFunction): void {
426 for (const [, error] of errorsByPlace) {
427 errors.push(error);
428 }
426 - if (errors.hasErrors()) {
427 - throw errors;
428 - }
429 + return errors.asResult();
430 }
431
432 function visitFunctionExpression(errors: CompilerError, fn: HIRFunction): void {
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateMemoizedEffectDependencies.ts
+5 -4
@@ -22,6 +22,7 @@ import {
22 ReactiveFunctionVisitor,
23 visitReactiveFunction,
24 } from '../ReactiveScopes/visitors';
25 +import {Result} from '../Utils/Result';
26
27 /**
28 * Validates that all known effect dependencies are memoized. The algorithm checks two things:
@@ -47,12 +48,12 @@ import {
48 * mutate(object); // ... mutable range ends here after this mutation
49 * ```
50 */
50 -export function validateMemoizedEffectDependencies(fn: ReactiveFunction): void {
51 +export function validateMemoizedEffectDependencies(
52 + fn: ReactiveFunction,
53 +): Result<void, CompilerError> {
54 const errors = new CompilerError();
55 visitReactiveFunction(fn, new Visitor(), errors);
53 - if (errors.hasErrors()) {
54 - throw errors;
55 - }
56 + return errors.asResult();
57 }
58
59 class Visitor extends ReactiveFunctionVisitor<CompilerError> {
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoCapitalizedCalls.ts
+9 -3
@@ -4,11 +4,14 @@
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7 -import {CompilerError, EnvironmentConfig} from '..';
7 +import {CompilerError, EnvironmentConfig, ErrorSeverity} from '..';
8 import {HIRFunction, IdentifierId} from '../HIR';
9 import {DEFAULT_GLOBALS} from '../HIR/Globals';
10 +import {Result} from '../Utils/Result';
11
11 -export function validateNoCapitalizedCalls(fn: HIRFunction): void {
12 +export function validateNoCapitalizedCalls(
13 + fn: HIRFunction,
14 +): Result<void, CompilerError> {
15 const envConfig: EnvironmentConfig = fn.env.config;
16 const ALLOW_LIST = new Set([
17 ...DEFAULT_GLOBALS.keys(),
@@ -26,6 +29,7 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
29 );
30 };
31
32 + const errors = new CompilerError();
33 const capitalLoadGlobals = new Map<IdentifierId, string>();
34 const capitalizedProperties = new Map<IdentifierId, string>();
35 const reason =
@@ -73,7 +77,8 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
77 const propertyIdentifier = value.property.identifier.id;
78 const propertyName = capitalizedProperties.get(propertyIdentifier);
79 if (propertyName != null) {
76 - CompilerError.throwInvalidReact({
80 + errors.push({
81 + severity: ErrorSeverity.InvalidReact,
82 reason,
83 description: `${propertyName} may be a component.`,
84 loc: value.loc,
@@ -85,4 +90,5 @@ export function validateNoCapitalizedCalls(fn: HIRFunction): void {
90 }
91 }
92 }
93 + return errors.asResult();
94 }
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoImpureFunctionsInRender.ts renamed
+5 -4
@@ -8,6 +8,7 @@
8 import {CompilerError, ErrorSeverity} from '..';
9 import {HIRFunction} from '../HIR';
10 import {getFunctionCallSignature} from '../Inference/InferReferenceEffects';
11 +import {Result} from '../Utils/Result';
12
13 /**
14 * Checks that known-impure functions are not called during render. Examples of invalid functions to
@@ -18,7 +19,9 @@ import {getFunctionCallSignature} from '../Inference/InferReferenceEffects';
19 * this in several of our validation passes and should unify those analyses into a reusable helper
20 * and use it here.
21 */
21 -export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
22 +export function validateNoImpureFunctionsInRender(
23 + fn: HIRFunction,
24 +): Result<void, CompilerError> {
25 const errors = new CompilerError();
26 for (const [, block] of fn.body.blocks) {
27 for (const instr of block.instructions) {
@@ -46,7 +49,5 @@ export function validateNoImpureFunctionsInRender(fn: HIRFunction): void {
49 }
50 }
51 }
49 - if (errors.hasErrors()) {
50 - throw errors;
51 - }
52 + return errors.asResult();
53 }
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoJSXInTryStatement.ts
+5 -4
@@ -7,6 +7,7 @@
7
8 import {CompilerError, ErrorSeverity} from '..';
9 import {BlockId, HIRFunction} from '../HIR';
10 +import {Result} from '../Utils/Result';
11 import {retainWhere} from '../Utils/utils';
12
13 /**
@@ -19,7 +20,9 @@ import {retainWhere} from '../Utils/utils';
20 * created within a try block. JSX is allowed within a catch statement, unless that catch
21 * is itself nested inside an outer try.
22 */
22 -export function validateNoJSXInTryStatement(fn: HIRFunction): void {
23 +export function validateNoJSXInTryStatement(
24 + fn: HIRFunction,
25 +): Result<void, CompilerError> {
26 const activeTryBlocks: Array<BlockId> = [];
27 const errors = new CompilerError();
28 for (const [, block] of fn.body.blocks) {
@@ -46,7 +49,5 @@ export function validateNoJSXInTryStatement(fn: HIRFunction): void {
49 activeTryBlocks.push(block.terminal.handler);
50 }
51 }
49 - if (errors.hasErrors()) {
50 - throw errors;
51 - }
52 + return errors.asResult();
53 }
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoRefAccesInRender.ts
+4 -2
@@ -99,9 +99,11 @@ class Env extends Map<IdentifierId, RefAccessType> {
99 }
100 }
101
102 -export function validateNoRefAccessInRender(fn: HIRFunction): void {
102 +export function validateNoRefAccessInRender(
103 + fn: HIRFunction,
104 +): Result<void, CompilerError> {
105 const env = new Env();
104 - validateNoRefAccessInRenderImpl(fn, env).unwrap();
106 + return validateNoRefAccessInRenderImpl(fn, env).map(_ => undefined);
107 }
108
109 function refTypeOfType(place: Place): RefAccessType {
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInPassiveEffects.ts
+5 -4
@@ -14,6 +14,7 @@ import {
14 Place,
15 } from '../HIR';
16 import {eachInstructionValueOperand} from '../HIR/visitors';
17 +import {Result} from '../Utils/Result';
18
19 /**
20 * Validates against calling setState in the body of a *passive* effect (useEffect),
@@ -23,7 +24,9 @@ import {eachInstructionValueOperand} from '../HIR/visitors';
24 * often bad for performance and frequently has more efficient and straightforward
25 * alternatives. See https://react.dev/learn/you-might-not-need-an-effect for examples.
26 */
26 -export function validateNoSetStateInPassiveEffects(fn: HIRFunction): void {
27 +export function validateNoSetStateInPassiveEffects(
28 + fn: HIRFunction,
29 +): Result<void, CompilerError> {
30 const setStateFunctions: Map<IdentifierId, Place> = new Map();
31 const errors = new CompilerError();
32 for (const [, block] of fn.body.blocks) {
@@ -98,9 +101,7 @@ export function validateNoSetStateInPassiveEffects(fn: HIRFunction): void {
101 }
102 }
103
101 - if (errors.hasErrors()) {
102 - throw errors;
103 - }
104 + return errors.asResult();
105 }
106
107 function getSetStateCall(
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateNoSetStateInRender.ts
+6 -8
@@ -9,7 +9,7 @@ import {CompilerError, ErrorSeverity} from '../CompilerError';
9 import {HIRFunction, IdentifierId, isSetStateType} from '../HIR';
10 import {computeUnconditionalBlocks} from '../HIR/ComputeUnconditionalBlocks';
11 import {eachInstructionValueOperand} from '../HIR/visitors';
12 -import {Err, Ok, Result} from '../Utils/Result';
12 +import {Result} from '../Utils/Result';
13
14 /**
15 * Validates that the given function does not have an infinite update loop
@@ -39,9 +39,11 @@ import {Err, Ok, Result} from '../Utils/Result';
39 * y();
40 * ```
41 */
42 -export function validateNoSetStateInRender(fn: HIRFunction): void {
42 +export function validateNoSetStateInRender(
43 + fn: HIRFunction,
44 +): Result<void, CompilerError> {
45 const unconditionalSetStateFunctions: Set<IdentifierId> = new Set();
44 - validateNoSetStateInRenderImpl(fn, unconditionalSetStateFunctions).unwrap();
46 + return validateNoSetStateInRenderImpl(fn, unconditionalSetStateFunctions);
47 }
48
49 function validateNoSetStateInRenderImpl(
@@ -145,9 +147,5 @@ function validateNoSetStateInRenderImpl(
147 }
148 }
149
148 - if (errors.hasErrors()) {
149 - return Err(errors);
150 - } else {
151 - return Ok(undefined);
152 - }
150 + return errors.asResult();
151 }
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidatePreservedManualMemoization.ts
+7 -5
@@ -5,9 +5,10 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import {CompilerError, Effect, ErrorSeverity} from '..';
8 +import {CompilerError, ErrorSeverity} from '../CompilerError';
9 import {
10 DeclarationId,
11 + Effect,
12 GeneratedSource,
13 Identifier,
14 IdentifierId,
@@ -30,6 +31,7 @@ import {
31 ReactiveFunctionVisitor,
32 visitReactiveFunction,
33 } from '../ReactiveScopes/visitors';
34 +import {Result} from '../Utils/Result';
35 import {getOrInsertDefault} from '../Utils/utils';
36
37 /**
@@ -39,15 +41,15 @@ import {getOrInsertDefault} from '../Utils/utils';
41 * This can occur if a value's mutable range somehow extended to include a hook and
42 * was pruned.
43 */
42 -export function validatePreservedManualMemoization(fn: ReactiveFunction): void {
44 +export function validatePreservedManualMemoization(
45 + fn: ReactiveFunction,
46 +): Result<void, CompilerError> {
47 const state = {
48 errors: new CompilerError(),
49 manualMemoState: null,
50 };
51 visitReactiveFunction(fn, new Visitor(), state);
48 - if (state.errors.hasErrors()) {
49 - throw state.errors;
50 - }
52 + return state.errors.asResult();
53 }
54
55 const DEBUG = false;
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateStaticComponents.ts
+2 -6
@@ -7,7 +7,7 @@
7
8 import {CompilerError, ErrorSeverity} from '../CompilerError';
9 import {HIRFunction, IdentifierId, SourceLocation} from '../HIR';
10 -import {Err, Ok, Result} from '../Utils/Result';
10 +import {Result} from '../Utils/Result';
11
12 /**
13 * Validates against components that are created dynamically and whose identity is not guaranteed
@@ -79,9 +79,5 @@ export function validateStaticComponents(
79 }
80 }
81 }
82 - if (error.hasErrors()) {
83 - return Err(error);
84 - } else {
85 - return Ok(undefined);
86 - }
82 + return error.asResult();
83 }
compiler/packages/babel-plugin-react-compiler/src/Validation/ValidateUseMemo.ts
+9 -4
@@ -5,10 +5,12 @@
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 -import {CompilerError} from '..';
8 +import {CompilerError, ErrorSeverity} from '..';
9 import {FunctionExpression, HIRFunction, IdentifierId} from '../HIR';
10 +import {Result} from '../Utils/Result';
11
11 -export function validateUseMemo(fn: HIRFunction): void {
12 +export function validateUseMemo(fn: HIRFunction): Result<void, CompilerError> {
13 + const errors = new CompilerError();
14 const useMemos = new Set<IdentifierId>();
15 const react = new Set<IdentifierId>();
16 const functions = new Map<IdentifierId, FunctionExpression>();
@@ -61,7 +63,8 @@ export function validateUseMemo(fn: HIRFunction): void {
63 }
64
65 if (body.loweredFunc.func.params.length > 0) {
64 - CompilerError.throwInvalidReact({
66 + errors.push({
67 + severity: ErrorSeverity.InvalidReact,
68 reason: 'useMemo callbacks may not accept any arguments',
69 description: null,
70 loc: body.loc,
@@ -70,7 +73,8 @@ export function validateUseMemo(fn: HIRFunction): void {
73 }
74
75 if (body.loweredFunc.func.async || body.loweredFunc.func.generator) {
73 - CompilerError.throwInvalidReact({
76 + errors.push({
77 + severity: ErrorSeverity.InvalidReact,
78 reason:
79 'useMemo callbacks may not be async or generator functions',
80 description: null,
@@ -84,4 +88,5 @@ export function validateUseMemo(fn: HIRFunction): void {
88 }
89 }
90 }
91 + return errors.asResult();
92 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-catch-in-outer-try-with-catch.expect.md deleted
-38
@@ -1,38 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @validateNoJSXInTryStatements
6 -import {identity} from 'shared-runtime';
7 -
8 -function Component(props) {
9 - let el;
10 - try {
11 - let value;
12 - try {
13 - value = identity(props.foo);
14 - } catch {
15 - el = <div value={value} />;
16 - }
17 - } catch {
18 - return null;
19 - }
20 - return el;
21 -}
22 -
23 -```
24 -
25 -
26 -## Error
27 -
28 -```
29 - 9 | value = identity(props.foo);
30 - 10 | } catch {
31 -> 11 | el = <div value={value} />;
32 - | ^^^^^^^^^^^^^^^^^^^^^ InvalidReact: Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) (11:11)
33 - 12 | }
34 - 13 | } catch {
35 - 14 | return null;
36 -```
37 -
38 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-jsx-in-try-with-catch.expect.md deleted
-31
@@ -1,31 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @validateNoJSXInTryStatements
6 -function Component(props) {
7 - let el;
8 - try {
9 - el = <div />;
10 - } catch {
11 - return null;
12 - }
13 - return el;
14 -}
15 -
16 -```
17 -
18 -
19 -## Error
20 -
21 -```
22 - 3 | let el;
23 - 4 | try {
24 -> 5 | el = <div />;
25 - | ^^^^^^^ InvalidReact: Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary) (5:5)
26 - 6 | } catch {
27 - 7 | return null;
28 - 8 | }
29 -```
30 -
31 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useEffect-transitive.expect.md deleted
-37
@@ -1,37 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @validateNoSetStateInPassiveEffects
6 -import {useEffect, useState} from 'react';
7 -
8 -function Component() {
9 - const [state, setState] = useState(0);
10 - const f = () => {
11 - setState(s => s + 1);
12 - };
13 - const g = () => {
14 - f();
15 - };
16 - useEffect(() => {
17 - g();
18 - });
19 - return state;
20 -}
21 -
22 -```
23 -
24 -
25 -## Error
26 -
27 -```
28 - 11 | };
29 - 12 | useEffect(() => {
30 -> 13 | g();
31 - | ^ InvalidReact: Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect) (13:13)
32 - 14 | });
33 - 15 | return state;
34 - 16 | }
35 -```
36 -
37 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useEffect.expect.md deleted
-31
@@ -1,31 +0,0 @@
1 -
2 -## Input
3 -
4 -```javascript
5 -// @validateNoSetStateInPassiveEffects
6 -import {useEffect, useState} from 'react';
7 -
8 -function Component() {
9 - const [state, setState] = useState(0);
10 - useEffect(() => {
11 - setState(s => s + 1);
12 - });
13 - return state;
14 -}
15 -
16 -```
17 -
18 -
19 -## Error
20 -
21 -```
22 - 5 | const [state, setState] = useState(0);
23 - 6 | useEffect(() => {
24 -> 7 | setState(s => s + 1);
25 - | ^^^^^^^^ InvalidReact: Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect) (7:7)
26 - 8 | });
27 - 9 | return state;
28 - 10 | }
29 -```
30 -
31 -
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.expect.md new
+73
@@ -0,0 +1,73 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @logger @validateNoJSXInTryStatements
6 +import {identity} from 'shared-runtime';
7 +
8 +function Component(props) {
9 + let el;
10 + try {
11 + let value;
12 + try {
13 + value = identity(props.foo);
14 + } catch {
15 + el = <div value={value} />;
16 + }
17 + } catch {
18 + return null;
19 + }
20 + return el;
21 +}
22 +
23 +```
24 +
25 +## Code
26 +
27 +```javascript
28 +import { c as _c } from "react/compiler-runtime"; // @logger @validateNoJSXInTryStatements
29 +import { identity } from "shared-runtime";
30 +
31 +function Component(props) {
32 + const $ = _c(4);
33 + let el;
34 + try {
35 + let value;
36 + try {
37 + let t0;
38 + if ($[0] !== props.foo) {
39 + t0 = identity(props.foo);
40 + $[0] = props.foo;
41 + $[1] = t0;
42 + } else {
43 + t0 = $[1];
44 + }
45 + value = t0;
46 + } catch {
47 + let t0;
48 + if ($[2] !== value) {
49 + t0 = <div value={value} />;
50 + $[2] = value;
51 + $[3] = t0;
52 + } else {
53 + t0 = $[3];
54 + }
55 + el = t0;
56 + }
57 + } catch {
58 + return null;
59 + }
60 + return el;
61 +}
62 +
63 +```
64 +
65 +## Logs
66 +
67 +```
68 +{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":11,"column":11,"index":214},"end":{"line":11,"column":32,"index":235},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"}}},"fnLoc":null}
69 +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":83},"end":{"line":17,"column":1,"index":290},"filename":"invalid-jsx-in-catch-in-outer-try-with-catch.ts"},"fnName":"Component","memoSlots":4,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0}
70 +```
71 +
72 +### Eval output
73 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js renamed
+1 -1
@@ -1,4 +1,4 @@
1 -// @validateNoJSXInTryStatements
1 +// @logger @validateNoJSXInTryStatements
2 import {identity} from 'shared-runtime';
3
4 function Component(props) {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.expect.md new
+50
@@ -0,0 +1,50 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @logger @validateNoJSXInTryStatements
6 +function Component(props) {
7 + let el;
8 + try {
9 + el = <div />;
10 + } catch {
11 + return null;
12 + }
13 + return el;
14 +}
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +import { c as _c } from "react/compiler-runtime"; // @logger @validateNoJSXInTryStatements
22 +function Component(props) {
23 + const $ = _c(1);
24 + let el;
25 + try {
26 + let t0;
27 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
28 + t0 = <div />;
29 + $[0] = t0;
30 + } else {
31 + t0 = $[0];
32 + }
33 + el = t0;
34 + } catch {
35 + return null;
36 + }
37 + return el;
38 +}
39 +
40 +```
41 +
42 +## Logs
43 +
44 +```
45 +{"kind":"CompileError","detail":{"options":{"reason":"Unexpected JSX element within a try statement. To catch errors in rendering a given component, wrap that component in an error boundary. (https://react.dev/reference/react/Component#catching-rendering-errors-with-an-error-boundary)","description":null,"severity":"InvalidReact","loc":{"start":{"line":5,"column":9,"index":96},"end":{"line":5,"column":16,"index":103},"filename":"invalid-jsx-in-try-with-catch.ts"}}},"fnLoc":null}
46 +{"kind":"CompileSuccess","fnLoc":{"start":{"line":2,"column":0,"index":41},"end":{"line":10,"column":1,"index":152},"filename":"invalid-jsx-in-try-with-catch.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
47 +```
48 +
49 +### Eval output
50 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js renamed
+1 -1
@@ -1,4 +1,4 @@
1 -// @validateNoJSXInTryStatements
1 +// @logger @validateNoJSXInTryStatements
2 function Component(props) {
3 let el;
4 try {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.expect.md new
+73
@@ -0,0 +1,73 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @logger @validateNoSetStateInPassiveEffects
6 +import {useEffect, useState} from 'react';
7 +
8 +function Component() {
9 + const [state, setState] = useState(0);
10 + const f = () => {
11 + setState(s => s + 1);
12 + };
13 + const g = () => {
14 + f();
15 + };
16 + useEffect(() => {
17 + g();
18 + });
19 + return state;
20 +}
21 +
22 +```
23 +
24 +## Code
25 +
26 +```javascript
27 +import { c as _c } from "react/compiler-runtime"; // @logger @validateNoSetStateInPassiveEffects
28 +import { useEffect, useState } from "react";
29 +
30 +function Component() {
31 + const $ = _c(2);
32 + const [state, setState] = useState(0);
33 + let t0;
34 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
35 + const f = () => {
36 + setState(_temp);
37 + };
38 +
39 + t0 = () => {
40 + f();
41 + };
42 + $[0] = t0;
43 + } else {
44 + t0 = $[0];
45 + }
46 + const g = t0;
47 + let t1;
48 + if ($[1] === Symbol.for("react.memo_cache_sentinel")) {
49 + t1 = () => {
50 + g();
51 + };
52 + $[1] = t1;
53 + } else {
54 + t1 = $[1];
55 + }
56 + useEffect(t1);
57 + return state;
58 +}
59 +function _temp(s) {
60 + return s + 1;
61 +}
62 +
63 +```
64 +
65 +## Logs
66 +
67 +```
68 +{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":13,"column":4,"index":264},"end":{"line":13,"column":5,"index":265},"filename":"invalid-setState-in-useEffect-transitive.ts","identifierName":"g"}}},"fnLoc":null}
69 +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":16,"column":1,"index":292},"filename":"invalid-setState-in-useEffect-transitive.ts"},"fnName":"Component","memoSlots":2,"memoBlocks":2,"memoValues":2,"prunedMemoBlocks":0,"prunedMemoValues":0}
70 +```
71 +
72 +### Eval output
73 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect-transitive.js renamed
+1 -1
@@ -1,4 +1,4 @@
1 -// @validateNoSetStateInPassiveEffects
1 +// @logger @validateNoSetStateInPassiveEffects
2 import {useEffect, useState} from 'react';
3
4 function Component() {
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.expect.md new
+53
@@ -0,0 +1,53 @@
1 +
2 +## Input
3 +
4 +```javascript
5 +// @logger @validateNoSetStateInPassiveEffects
6 +import {useEffect, useState} from 'react';
7 +
8 +function Component() {
9 + const [state, setState] = useState(0);
10 + useEffect(() => {
11 + setState(s => s + 1);
12 + });
13 + return state;
14 +}
15 +
16 +```
17 +
18 +## Code
19 +
20 +```javascript
21 +import { c as _c } from "react/compiler-runtime"; // @logger @validateNoSetStateInPassiveEffects
22 +import { useEffect, useState } from "react";
23 +
24 +function Component() {
25 + const $ = _c(1);
26 + const [state, setState] = useState(0);
27 + let t0;
28 + if ($[0] === Symbol.for("react.memo_cache_sentinel")) {
29 + t0 = () => {
30 + setState(_temp);
31 + };
32 + $[0] = t0;
33 + } else {
34 + t0 = $[0];
35 + }
36 + useEffect(t0);
37 + return state;
38 +}
39 +function _temp(s) {
40 + return s + 1;
41 +}
42 +
43 +```
44 +
45 +## Logs
46 +
47 +```
48 +{"kind":"CompileError","detail":{"options":{"reason":"Calling setState directly within a useEffect causes cascading renders and is not recommended. Consider alternatives to useEffect. (https://react.dev/learn/you-might-not-need-an-effect)","description":null,"severity":"InvalidReact","suggestions":null,"loc":{"start":{"line":7,"column":4,"index":179},"end":{"line":7,"column":12,"index":187},"filename":"invalid-setState-in-useEffect.ts","identifierName":"setState"}}},"fnLoc":null}
49 +{"kind":"CompileSuccess","fnLoc":{"start":{"line":4,"column":0,"index":91},"end":{"line":10,"column":1,"index":224},"filename":"invalid-setState-in-useEffect.ts"},"fnName":"Component","memoSlots":1,"memoBlocks":1,"memoValues":1,"prunedMemoBlocks":0,"prunedMemoValues":0}
50 +```
51 +
52 +### Eval output
53 +(kind: exception) Fixture not implemented
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/invalid-setState-in-useEffect.js renamed
+1 -1
@@ -1,4 +1,4 @@
1 -// @validateNoSetStateInPassiveEffects
1 +// @logger @validateNoSetStateInPassiveEffects
2 import {useEffect, useState} from 'react';
3
4 function Component() {