@samitouri / QOS-React-1 / commits / 426a394845

[compiler] Phase 2+7: Wrap pipeline passes in tryRecord for fault tolerance (#35874)

- Change runWithEnvironment/run/compileFn to return Result<CodegenFunction, CompilerError> - Wrap all pipeline passes in env.tryRecord() to catch and record CompilerErrors - Record inference pass errors via env.recordErrors() instead of throwing - Handle codegen Result explicitly, returning Err on failure - Add final error check: return Err(env.aggregateErrors()) if any errors accumulated - Update tryCompileFunction and retryCompileFunction in Program.ts to handle Result - Keep lint-only passes using env.logErrors() (non-blocking) - Update 52 test fixture expectations that now report additional errors This is the core integration that enables fault tolerance: errors are caught, recorded, and the pipeline continues to discover more errors. --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/35874). * #35888 * #35884 * #35883 * #35882 * #35881 * #35880 * #35879 * #35878 * #35877 * #35876 * #35875 * __->__ #35874

Joseph Savona committed Feb 23, 2026 at 15:26 UTC 426a394845e3e471c020543f3560046c74549c13
26 files changed +634 -90
compiler/fault-tolerance-overview.md
+18 -11
@@ -51,20 +51,20 @@ Add error accumulation to the `Environment` class so that any pass can record er
51
52 Change `runWithEnvironment` to run all passes and check for errors at the end instead of letting exceptions propagate.
53
54 -- [ ] **2.1 Change `runWithEnvironment` return type** (`src/Entrypoint/Pipeline.ts`)
54 +- [x] **2.1 Change `runWithEnvironment` return type** (`src/Entrypoint/Pipeline.ts`)
55 - Change return type from `CodegenFunction` to `Result<CodegenFunction, CompilerError>`
56 - At the end of the pipeline, check `env.hasErrors()`:
57 - If no errors: return `Ok(ast)`
58 - If errors: return `Err(env.aggregateErrors())`
59
60 -- [ ] **2.2 Update `compileFn` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
60 +- [x] **2.2 Update `compileFn` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
61 - Change `compileFn` return type from `CodegenFunction` to `Result<CodegenFunction, CompilerError>`
62 - Propagate the Result from `runWithEnvironment`
63
64 -- [ ] **2.3 Update `run` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
64 +- [x] **2.3 Update `run` to propagate the Result** (`src/Entrypoint/Pipeline.ts`)
65 - Same change for the internal `run` function
66
67 -- [ ] **2.4 Update callers in Program.ts** (`src/Entrypoint/Program.ts`)
67 +- [x] **2.4 Update callers in Program.ts** (`src/Entrypoint/Program.ts`)
68 - In `tryCompileFunction`, change from try/catch around `compileFn` to handling the `Result`:
69 - If `Ok(codegenFn)`: return the compiled function
70 - If `Err(compilerError)`: return `{kind: 'error', error: compilerError}`
@@ -248,31 +248,31 @@ The inference passes are the most critical to handle correctly because they prod
248
249 Walk through `runWithEnvironment` and wrap each pass call site. This is the integration work tying Phases 3-6 together.
250
251 -- [ ] **7.1 Wrap `lower()` call** (line 163)
251 +- [x] **7.1 Wrap `lower()` call** (line 163)
252 - Change from `lower(func, env).unwrap()` to `lower(func, env)` (direct return after Phase 3.1)
253
254 -- [ ] **7.2 Wrap validation calls that use `.unwrap()`** (lines 169-303)
254 +- [x] **7.2 Wrap validation calls that use `.unwrap()`** (lines 169-303)
255 - Remove `.unwrap()` from all validation calls after they're updated in Phase 4
256 - For validations guarded by `env.enableValidations`, keep the guard but remove the `.unwrap()`
257
258 -- [ ] **7.3 Wrap inference calls** (lines 233-267)
258 +- [x] **7.3 Wrap inference calls** (lines 233-267)
259 - After Phase 5, `inferMutationAliasingEffects` and `inferMutationAliasingRanges` record errors directly
260 - Remove the `mutabilityAliasingErrors` / `mutabilityAliasingRangeErrors` variables and their conditional throw logic
261
262 -- [ ] **7.4 Wrap `env.logErrors()` calls** (lines 286-331)
262 +- [x] **7.4 Wrap `env.logErrors()` calls** (lines 286-331)
263 - After Phase 4.13-4.16, these passes record on env directly
264 - Remove the `env.logErrors()` wrapper calls
265
266 -- [ ] **7.5 Wrap codegen** (lines 575-578)
266 +- [x] **7.5 Wrap codegen** (lines 575-578)
267 - After Phase 6.1, `codegenFunction` returns directly
268 - Remove the `.unwrap()`
269
270 -- [ ] **7.6 Add final error check** (end of `runWithEnvironment`)
270 +- [x] **7.6 Add final error check** (end of `runWithEnvironment`)
271 - After all passes complete, check `env.hasErrors()`
272 - If no errors: return `Ok(ast)`
273 - If errors: return `Err(env.aggregateErrors())`
274
275 -- [ ] **7.7 Consider wrapping each pass in `env.tryRecord()`** as a safety net
275 +- [x] **7.7 Consider wrapping each pass in `env.tryRecord()`** as a safety net
276 - Even after individual passes are updated, wrapping each pass call in `env.tryRecord()` provides defense-in-depth
277 - If a pass unexpectedly throws a CompilerError (e.g., from a code path we missed), it gets caught and recorded rather than aborting the pipeline
278 - Non-CompilerError exceptions and invariants still propagate immediately
@@ -318,3 +318,10 @@ Walk through `runWithEnvironment` and wrap each pass call site. This is the inte
318 - The `assertConsistentIdentifiers`, `assertTerminalSuccessorsExist`, `assertTerminalPredsExist`, `assertValidBlockNesting`, `assertValidMutableRanges`, `assertWellFormedBreakTargets`, `assertScopeInstructionsWithinScopes` assertion functions should continue to throw — they are invariant checks on internal data structure consistency
319 - The `panicThreshold` mechanism in Program.ts should continue to work — it now operates on the aggregated error from the Result rather than a caught exception, but the behavior is the same
320
321 +## Key Learnings
322 +
323 +* **Phase 2+7 (Pipeline tryRecord wrapping) was sufficient for basic fault tolerance.** Wrapping all passes in `env.tryRecord()` immediately enabled the compiler to continue past errors that previously threw. This caused 52 test fixtures to produce additional errors that were previously masked by the first error bailing out. For example, `error.todo-reassign-const` previously reported only "Support destructuring of context variables" but now also reports the immutability violation.
324 +* **Lint-only passes (Pattern B: `env.logErrors()`) should not use `tryRecord()`/`recordError()`** because those errors are intentionally non-blocking. They are reported via the logger only and should not cause the pipeline to return `Err`. The `logErrors` pattern was kept for `validateNoDerivedComputationsInEffects_exp`, `validateNoSetStateInEffects`, `validateNoJSXInTryStatement`, and `validateStaticComponents`.
325 +* **Inference passes that return `Result` with validation errors** (`inferMutationAliasingEffects`, `inferMutationAliasingRanges`) were changed to record errors via `env.recordErrors()` instead of throwing, allowing subsequent passes to proceed.
326 +* **Value-producing passes** (`memoizeFbtAndMacroOperandsInSameScope`, `renameVariables`, `buildReactiveFunction`) need safe default values when wrapped in `tryRecord()` since the callback can't return values. We initialize with empty defaults (e.g., `new Set()`) before the `tryRecord()` call.
327 +
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Pipeline.ts
+64 -29
@@ -9,8 +9,11 @@ import {NodePath} from '@babel/traverse';
9 import * as t from '@babel/types';
10 import prettyFormat from 'pretty-format';
11 import {CompilerOutputMode, Logger, ProgramContext} from '.';
12 +import {CompilerError} from '../CompilerError';
13 +import {Err, Ok, Result} from '../Utils/Result';
14 import {
15 HIRFunction,
16 + IdentifierId,
17 ReactiveFunction,
18 assertConsistentIdentifiers,
19 assertTerminalPredsExist,
@@ -89,7 +92,6 @@ import {validateNoJSXInTryStatement} from '../Validation/ValidateNoJSXInTryState
92 import {propagateScopeDependenciesHIR} from '../HIR/PropagateScopeDependenciesHIR';
93 import {outlineJSX} from '../Optimization/OutlineJsx';
94 import {optimizePropsMethodCalls} from '../Optimization/OptimizePropsMethodCalls';
92 -import {validateNoImpureFunctionsInRender} from '../Validation/ValidateNoImpureFunctionsInRender';
95 import {validateStaticComponents} from '../Validation/ValidateStaticComponents';
96 import {validateNoFreezingKnownMutableFunctions} from '../Validation/ValidateNoFreezingKnownMutableFunctions';
97 import {inferMutationAliasingEffects} from '../Inference/InferMutationAliasingEffects';
@@ -118,7 +120,7 @@ function run(
120 logger: Logger | null,
121 filename: string | null,
122 code: string | null,
121 -): CodegenFunction {
123 +): Result<CodegenFunction, CompilerError> {
124 const contextIdentifiers = findContextIdentifiers(func);
125 const env = new Environment(
126 func.scope,
@@ -149,7 +151,7 @@ function runWithEnvironment(
151 t.FunctionDeclaration | t.ArrowFunctionExpression | t.FunctionExpression
152 >,
153 env: Environment,
152 -): CodegenFunction {
154 +): Result<CodegenFunction, CompilerError> {
155 const log = (value: CompilerPipelineValue): void => {
156 env.logger?.debugLogIRs?.(value);
157 };
@@ -159,11 +161,17 @@ function runWithEnvironment(
161 pruneMaybeThrows(hir);
162 log({kind: 'hir', name: 'PruneMaybeThrows', value: hir});
163
162 - validateContextVariableLValues(hir);
163 - validateUseMemo(hir).unwrap();
164 + env.tryRecord(() => {
165 + validateContextVariableLValues(hir);
166 + });
167 + env.tryRecord(() => {
168 + validateUseMemo(hir).unwrap();
169 + });
170
171 if (env.enableDropManualMemoization) {
166 - dropManualMemoization(hir).unwrap();
172 + env.tryRecord(() => {
173 + dropManualMemoization(hir).unwrap();
174 + });
175 log({kind: 'hir', name: 'DropManualMemoization', value: hir});
176 }
177
@@ -196,10 +204,14 @@ function runWithEnvironment(
204
205 if (env.enableValidations) {
206 if (env.config.validateHooksUsage) {
199 - validateHooksUsage(hir).unwrap();
207 + env.tryRecord(() => {
208 + validateHooksUsage(hir).unwrap();
209 + });
210 }
211 if (env.config.validateNoCapitalizedCalls) {
202 - validateNoCapitalizedCalls(hir).unwrap();
212 + env.tryRecord(() => {
213 + validateNoCapitalizedCalls(hir).unwrap();
214 + });
215 }
216 }
217
@@ -213,7 +225,7 @@ function runWithEnvironment(
225 log({kind: 'hir', name: 'InferMutationAliasingEffects', value: hir});
226 if (env.enableValidations) {
227 if (mutabilityAliasingErrors.isErr()) {
216 - throw mutabilityAliasingErrors.unwrapErr();
228 + env.recordErrors(mutabilityAliasingErrors.unwrapErr());
229 }
230 }
231
@@ -234,9 +246,11 @@ function runWithEnvironment(
246 log({kind: 'hir', name: 'InferMutationAliasingRanges', value: hir});
247 if (env.enableValidations) {
248 if (mutabilityAliasingRangeErrors.isErr()) {
237 - throw mutabilityAliasingRangeErrors.unwrapErr();
249 + env.recordErrors(mutabilityAliasingRangeErrors.unwrapErr());
250 }
239 - validateLocalsNotReassignedAfterRender(hir);
251 + env.tryRecord(() => {
252 + validateLocalsNotReassignedAfterRender(hir);
253 + });
254 }
255
256 if (env.enableValidations) {
@@ -245,11 +259,15 @@ function runWithEnvironment(
259 }
260
261 if (env.config.validateRefAccessDuringRender) {
248 - validateNoRefAccessInRender(hir).unwrap();
262 + env.tryRecord(() => {
263 + validateNoRefAccessInRender(hir).unwrap();
264 + });
265 }
266
267 if (env.config.validateNoSetStateInRender) {
252 - validateNoSetStateInRender(hir).unwrap();
268 + env.tryRecord(() => {
269 + validateNoSetStateInRender(hir).unwrap();
270 + });
271 }
272
273 if (
@@ -258,7 +276,9 @@ function runWithEnvironment(
276 ) {
277 env.logErrors(validateNoDerivedComputationsInEffects_exp(hir));
278 } else if (env.config.validateNoDerivedComputationsInEffects) {
261 - validateNoDerivedComputationsInEffects(hir);
279 + env.tryRecord(() => {
280 + validateNoDerivedComputationsInEffects(hir);
281 + });
282 }
283
284 if (env.config.validateNoSetStateInEffects && env.outputMode === 'lint') {
@@ -269,11 +289,9 @@ function runWithEnvironment(
289 env.logErrors(validateNoJSXInTryStatement(hir));
290 }
291
272 - if (env.config.validateNoImpureFunctionsInRender) {
273 - validateNoImpureFunctionsInRender(hir).unwrap();
274 - }
275 -
276 - validateNoFreezingKnownMutableFunctions(hir).unwrap();
292 + env.tryRecord(() => {
293 + validateNoFreezingKnownMutableFunctions(hir).unwrap();
294 + });
295 }
296
297 inferReactivePlaces(hir);
@@ -285,7 +303,9 @@ function runWithEnvironment(
303 env.config.validateExhaustiveEffectDependencies
304 ) {
305 // NOTE: this relies on reactivity inference running first
288 - validateExhaustiveDependencies(hir).unwrap();
306 + env.tryRecord(() => {
307 + validateExhaustiveDependencies(hir).unwrap();
308 + });
309 }
310 }
311
@@ -314,7 +334,8 @@ function runWithEnvironment(
334 log({kind: 'hir', name: 'InferReactiveScopeVariables', value: hir});
335 }
336
317 - const fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
337 + let fbtOperands: Set<IdentifierId> = new Set();
338 + fbtOperands = memoizeFbtAndMacroOperandsInSameScope(hir);
339 log({
340 kind: 'hir',
341 name: 'MemoizeFbtAndMacroOperandsInSameScope',
@@ -406,7 +427,8 @@ function runWithEnvironment(
427 value: hir,
428 });
429
409 - const reactiveFunction = buildReactiveFunction(hir);
430 + let reactiveFunction!: ReactiveFunction;
431 + reactiveFunction = buildReactiveFunction(hir);
432 log({
433 kind: 'reactive',
434 name: 'BuildReactiveFunction',
@@ -493,7 +515,8 @@ function runWithEnvironment(
515 value: reactiveFunction,
516 });
517
496 - const uniqueIdentifiers = renameVariables(reactiveFunction);
518 + let uniqueIdentifiers: Set<string> = new Set();
519 + uniqueIdentifiers = renameVariables(reactiveFunction);
520 log({
521 kind: 'reactive',
522 name: 'RenameVariables',
@@ -511,20 +534,29 @@ function runWithEnvironment(
534 env.config.enablePreserveExistingMemoizationGuarantees ||
535 env.config.validatePreserveExistingMemoizationGuarantees
536 ) {
514 - validatePreservedManualMemoization(reactiveFunction).unwrap();
537 + env.tryRecord(() => {
538 + validatePreservedManualMemoization(reactiveFunction).unwrap();
539 + });
540 }
541
517 - const ast = codegenFunction(reactiveFunction, {
542 + const codegenResult = codegenFunction(reactiveFunction, {
543 uniqueIdentifiers,
544 fbtOperands,
520 - }).unwrap();
545 + });
546 + if (codegenResult.isErr()) {
547 + env.recordErrors(codegenResult.unwrapErr());
548 + return Err(env.aggregateErrors());
549 + }
550 + const ast = codegenResult.unwrap();
551 log({kind: 'ast', name: 'Codegen', value: ast});
552 for (const outlined of ast.outlined) {
553 log({kind: 'ast', name: 'Codegen (outlined)', value: outlined.fn});
554 }
555
556 if (env.config.validateSourceLocations) {
527 - validateSourceLocations(func, ast).unwrap();
557 + env.tryRecord(() => {
558 + validateSourceLocations(func, ast).unwrap();
559 + });
560 }
561
562 /**
@@ -536,7 +568,10 @@ function runWithEnvironment(
568 throw new Error('unexpected error');
569 }
570
539 - return ast;
571 + if (env.hasErrors()) {
572 + return Err(env.aggregateErrors());
573 + }
574 + return Ok(ast);
575 }
576
577 export function compileFn(
@@ -550,7 +585,7 @@ export function compileFn(
585 logger: Logger | null,
586 filename: string | null,
587 code: string | null,
553 -): CodegenFunction {
588 +): Result<CodegenFunction, CompilerError> {
589 return run(
590 func,
591 config,
compiler/packages/babel-plugin-react-compiler/src/Entrypoint/Program.ts
+15 -13
@@ -697,19 +697,21 @@ function tryCompileFunction(
697 }
698
699 try {
700 - return {
701 - kind: 'compile',
702 - compiledFn: compileFn(
703 - fn,
704 - programContext.opts.environment,
705 - fnType,
706 - outputMode,
707 - programContext,
708 - programContext.opts.logger,
709 - programContext.filename,
710 - programContext.code,
711 - ),
712 - };
700 + const result = compileFn(
701 + fn,
702 + programContext.opts.environment,
703 + fnType,
704 + outputMode,
705 + programContext,
706 + programContext.opts.logger,
707 + programContext.filename,
708 + programContext.code,
709 + );
710 + if (result.isOk()) {
711 + return {kind: 'compile', compiledFn: result.unwrap()};
712 + } else {
713 + return {kind: 'error', error: result.unwrapErr()};
714 + }
715 } catch (err) {
716 return {kind: 'error', error: err};
717 }
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.hook-call-freezes-captured-memberexpr.expect.md
+27 -1
@@ -29,7 +29,7 @@ export const FIXTURE_ENTRYPOINT = {
29 ## Error
30
31 ```
32 -Found 1 error:
32 +Found 2 errors:
33
34 Error: This value cannot be modified
35
@@ -43,6 +43,32 @@ error.hook-call-freezes-captured-memberexpr.ts:13:2
43 14 | return <Stringify x={x} cb={cb} />;
44 15 | }
45 16 |
46 +
47 +Error: Cannot modify local variables after render completes
48 +
49 +This argument is a function which may reassign or mutate `x` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
50 +
51 +error.hook-call-freezes-captured-memberexpr.ts:9:25
52 + 7 | * After this custom hook call, it's no longer valid to mutate x.
53 + 8 | */
54 +> 9 | const cb = useIdentity(() => {
55 + | ^^^^^^^
56 +> 10 | x.value++;
57 + | ^^^^^^^^^^^^^^
58 +> 11 | });
59 + | ^^^^ This function may (indirectly) reassign or modify `x` after render
60 + 12 |
61 + 13 | x.value += count;
62 + 14 | return <Stringify x={x} cb={cb} />;
63 +
64 +error.hook-call-freezes-captured-memberexpr.ts:10:4
65 + 8 | */
66 + 9 | const cb = useIdentity(() => {
67 +> 10 | x.value++;
68 + | ^ This modifies `x`
69 + 11 | });
70 + 12 |
71 + 13 | x.value += count;
72 ```
73
74
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-ReactUseMemo-async-callback.expect.md
+32 -1
@@ -15,7 +15,7 @@ function component(a, b) {
15 ## Error
16
17 ```
18 -Found 1 error:
18 +Found 3 errors:
19
20 Error: useMemo() callbacks may not be async or generator functions
21
@@ -32,6 +32,37 @@ error.invalid-ReactUseMemo-async-callback.ts:2:24
32 5 | return x;
33 6 | }
34 7 |
35 +
36 +Error: Found missing memoization dependencies
37 +
38 +Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
39 +
40 +error.invalid-ReactUseMemo-async-callback.ts:3:10
41 + 1 | function component(a, b) {
42 + 2 | let x = React.useMemo(async () => {
43 +> 3 | await a;
44 + | ^ Missing dependency `a`
45 + 4 | }, []);
46 + 5 | return x;
47 + 6 | }
48 +
49 +Inferred dependencies: `[a]`
50 +
51 +Compilation Skipped: Existing memoization could not be preserved
52 +
53 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source.
54 +
55 +error.invalid-ReactUseMemo-async-callback.ts:2:24
56 + 1 | function component(a, b) {
57 +> 2 | let x = React.useMemo(async () => {
58 + | ^^^^^^^^^^^^^
59 +> 3 | await a;
60 + | ^^^^^^^^^^^^
61 +> 4 | }, []);
62 + | ^^^^ Could not preserve existing manual memoization
63 + 5 | return x;
64 + 6 | }
65 + 7 |
66 ```
67
68
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-conditional-setState-in-useMemo.expect.md
+34 -1
@@ -22,7 +22,7 @@ function Component({item, cond}) {
22 ## Error
23
24 ```
25 -Found 2 errors:
25 +Found 3 errors:
26
27 Error: Calling setState from useMemo may trigger an infinite loop
28
@@ -49,6 +49,39 @@ error.invalid-conditional-setState-in-useMemo.ts:8:6
49 9 | }
50 10 | }, [cond, key, init]);
51 11 |
52 +
53 +Error: Found missing/extra memoization dependencies
54 +
55 +Missing dependencies can cause a value to update less often than it should, resulting in stale UI. Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
56 +
57 +error.invalid-conditional-setState-in-useMemo.ts:7:18
58 + 5 | useMemo(() => {
59 + 6 | if (cond) {
60 +> 7 | setPrevItem(item);
61 + | ^^^^ Missing dependency `item`
62 + 8 | setState(0);
63 + 9 | }
64 + 10 | }, [cond, key, init]);
65 +
66 +error.invalid-conditional-setState-in-useMemo.ts:10:12
67 + 8 | setState(0);
68 + 9 | }
69 +> 10 | }, [cond, key, init]);
70 + | ^^^ Unnecessary dependency `key`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change
71 + 11 |
72 + 12 | return state;
73 + 13 | }
74 +
75 +error.invalid-conditional-setState-in-useMemo.ts:10:17
76 + 8 | setState(0);
77 + 9 | }
78 +> 10 | }, [cond, key, init]);
79 + | ^^^^ Unnecessary dependency `init`. Values declared outside of a component/hook should not be listed as dependencies as the component will not re-render if they change
80 + 11 |
81 + 12 | return state;
82 + 13 | }
83 +
84 +Inferred dependencies: `[cond, item]`
85 ```
86
87
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-mutation-in-closure.expect.md
+22 -1
@@ -16,7 +16,7 @@ function useInvalidMutation(options) {
16 ## Error
17
18 ```
19 -Found 1 error:
19 +Found 2 errors:
20
21 Error: This value cannot be modified
22
@@ -30,6 +30,27 @@ error.invalid-mutation-in-closure.ts:4:4
30 5 | }
31 6 | return test;
32 7 | }
33 +
34 +Error: Cannot modify local variables after render completes
35 +
36 +This argument is a function which may reassign or mutate `options` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
37 +
38 +error.invalid-mutation-in-closure.ts:6:9
39 + 4 | options.foo = 'bar';
40 + 5 | }
41 +> 6 | return test;
42 + | ^^^^ This function may (indirectly) reassign or modify `options` after render
43 + 7 | }
44 + 8 |
45 +
46 +error.invalid-mutation-in-closure.ts:4:4
47 + 2 | function test() {
48 + 3 | foo(options.foo); // error should not point on this line
49 +> 4 | options.foo = 'bar';
50 + | ^^^^^^^ This modifies `options`
51 + 5 | }
52 + 6 | return test;
53 + 7 | }
54 ```
55
56
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-in-hook-return-value.expect.md
+26 -1
@@ -15,7 +15,7 @@ function useFoo() {
15 ## Error
16
17 ```
18 -Found 1 error:
18 +Found 2 errors:
19
20 Error: Cannot reassign variable after render completes
21
@@ -29,6 +29,31 @@ error.invalid-reassign-local-in-hook-return-value.ts:4:4
29 5 | };
30 6 | }
31 7 |
32 +
33 +Error: Cannot modify local variables after render completes
34 +
35 +This argument is a function which may reassign or mutate `x` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
36 +
37 +error.invalid-reassign-local-in-hook-return-value.ts:3:9
38 + 1 | function useFoo() {
39 + 2 | let x = 0;
40 +> 3 | return value => {
41 + | ^^^^^^^^^^
42 +> 4 | x = value;
43 + | ^^^^^^^^^^^^^^
44 +> 5 | };
45 + | ^^^^ This function may (indirectly) reassign or modify `x` after render
46 + 6 | }
47 + 7 |
48 +
49 +error.invalid-reassign-local-in-hook-return-value.ts:4:4
50 + 2 | let x = 0;
51 + 3 | return value => {
52 +> 4 | x = value;
53 + | ^ This modifies `x`
54 + 5 | };
55 + 6 | }
56 + 7 |
57 ```
58
59
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-effect.expect.md
+27 -1
@@ -47,7 +47,7 @@ function Component() {
47 ## Error
48
49 ```
50 -Found 1 error:
50 +Found 2 errors:
51
52 Error: Cannot reassign variable after render completes
53
@@ -61,6 +61,32 @@ error.invalid-reassign-local-variable-in-effect.ts:7:4
61 8 | };
62 9 |
63 10 | const onMount = newValue => {
64 +
65 +Error: Cannot modify local variables after render completes
66 +
67 +This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
68 +
69 +error.invalid-reassign-local-variable-in-effect.ts:33:12
70 + 31 | };
71 + 32 |
72 +> 33 | useEffect(() => {
73 + | ^^^^^^^
74 +> 34 | onMount();
75 + | ^^^^^^^^^^^^^^
76 +> 35 | }, [onMount]);
77 + | ^^^^ This function may (indirectly) reassign or modify `local` after render
78 + 36 |
79 + 37 | return 'ok';
80 + 38 | }
81 +
82 +error.invalid-reassign-local-variable-in-effect.ts:7:4
83 + 5 |
84 + 6 | const reassignLocal = newValue => {
85 +> 7 | local = newValue;
86 + | ^^^^^ This modifies `local`
87 + 8 | };
88 + 9 |
89 + 10 | const onMount = newValue => {
90 ```
91
92
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-hook-argument.expect.md
+27 -1
@@ -48,7 +48,7 @@ function Component() {
48 ## Error
49
50 ```
51 -Found 1 error:
51 +Found 2 errors:
52
53 Error: Cannot reassign variable after render completes
54
@@ -62,6 +62,32 @@ error.invalid-reassign-local-variable-in-hook-argument.ts:8:4
62 9 | };
63 10 |
64 11 | const callback = newValue => {
65 +
66 +Error: Cannot modify local variables after render completes
67 +
68 +This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
69 +
70 +error.invalid-reassign-local-variable-in-hook-argument.ts:34:14
71 + 32 | };
72 + 33 |
73 +> 34 | useIdentity(() => {
74 + | ^^^^^^^
75 +> 35 | callback();
76 + | ^^^^^^^^^^^^^^^
77 +> 36 | });
78 + | ^^^^ This function may (indirectly) reassign or modify `local` after render
79 + 37 |
80 + 38 | return 'ok';
81 + 39 | }
82 +
83 +error.invalid-reassign-local-variable-in-hook-argument.ts:8:4
84 + 6 |
85 + 7 | const reassignLocal = newValue => {
86 +> 8 | local = newValue;
87 + | ^^^^^ This modifies `local`
88 + 9 | };
89 + 10 |
90 + 11 | const callback = newValue => {
91 ```
92
93
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-reassign-local-variable-in-jsx-callback.expect.md
+22 -1
@@ -41,7 +41,7 @@ function Component() {
41 ## Error
42
43 ```
44 -Found 1 error:
44 +Found 2 errors:
45
46 Error: Cannot reassign variable after render completes
47
@@ -55,6 +55,27 @@ error.invalid-reassign-local-variable-in-jsx-callback.ts:5:4
55 6 | };
56 7 |
57 8 | const onClick = newValue => {
58 +
59 +Error: Cannot modify local variables after render completes
60 +
61 +This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
62 +
63 +error.invalid-reassign-local-variable-in-jsx-callback.ts:31:26
64 + 29 | };
65 + 30 |
66 +> 31 | return <button onClick={onClick}>Submit</button>;
67 + | ^^^^^^^ This function may (indirectly) reassign or modify `local` after render
68 + 32 | }
69 + 33 |
70 +
71 +error.invalid-reassign-local-variable-in-jsx-callback.ts:5:4
72 + 3 |
73 + 4 | const reassignLocal = newValue => {
74 +> 5 | local = newValue;
75 + | ^^^^^ This modifies `local`
76 + 6 | };
77 + 7 |
78 + 8 | const onClick = newValue => {
79 ```
80
81
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-setState-in-useMemo-indirect-useCallback.expect.md
+56 -1
@@ -26,7 +26,7 @@ function useKeyedState({key, init}) {
26 ## Error
27
28 ```
29 -Found 1 error:
29 +Found 3 errors:
30
31 Error: Calling setState from useMemo may trigger an infinite loop
32
@@ -40,6 +40,61 @@ error.invalid-setState-in-useMemo-indirect-useCallback.ts:13:4
40 14 | }, [key, init]);
41 15 |
42 16 | return state;
43 +
44 +Error: Found missing memoization dependencies
45 +
46 +Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
47 +
48 +error.invalid-setState-in-useMemo-indirect-useCallback.ts:9:13
49 + 7 | const fn = useCallback(() => {
50 + 8 | setPrevKey(key);
51 +> 9 | setState(init);
52 + | ^^^^ Missing dependency `init`
53 + 10 | });
54 + 11 |
55 + 12 | useMemo(() => {
56 +
57 +error.invalid-setState-in-useMemo-indirect-useCallback.ts:8:15
58 + 6 |
59 + 7 | const fn = useCallback(() => {
60 +> 8 | setPrevKey(key);
61 + | ^^^ Missing dependency `key`
62 + 9 | setState(init);
63 + 10 | });
64 + 11 |
65 +
66 +Error: Found missing/extra memoization dependencies
67 +
68 +Missing dependencies can cause a value to update less often than it should, resulting in stale UI. Extra dependencies can cause a value to update more often than it should, resulting in performance problems such as excessive renders or effects firing too often.
69 +
70 +error.invalid-setState-in-useMemo-indirect-useCallback.ts:13:4
71 + 11 |
72 + 12 | useMemo(() => {
73 +> 13 | fn();
74 + | ^^ Missing dependency `fn`
75 + 14 | }, [key, init]);
76 + 15 |
77 + 16 | return state;
78 +
79 +error.invalid-setState-in-useMemo-indirect-useCallback.ts:14:6
80 + 12 | useMemo(() => {
81 + 13 | fn();
82 +> 14 | }, [key, init]);
83 + | ^^^ Unnecessary dependency `key`
84 + 15 |
85 + 16 | return state;
86 + 17 | }
87 +
88 +error.invalid-setState-in-useMemo-indirect-useCallback.ts:14:11
89 + 12 | useMemo(() => {
90 + 13 | fn();
91 +> 14 | }, [key, init]);
92 + | ^^^^ Unnecessary dependency `init`
93 + 15 |
94 + 16 | return state;
95 + 17 | }
96 +
97 +Inferred dependencies: `[fn]`
98 ```
99
100
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-async-callback.expect.md
+32 -1
@@ -15,7 +15,7 @@ function component(a, b) {
15 ## Error
16
17 ```
18 -Found 1 error:
18 +Found 3 errors:
19
20 Error: useMemo() callbacks may not be async or generator functions
21
@@ -32,6 +32,37 @@ error.invalid-useMemo-async-callback.ts:2:18
32 5 | return x;
33 6 | }
34 7 |
35 +
36 +Error: Found missing memoization dependencies
37 +
38 +Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
39 +
40 +error.invalid-useMemo-async-callback.ts:3:10
41 + 1 | function component(a, b) {
42 + 2 | let x = useMemo(async () => {
43 +> 3 | await a;
44 + | ^ Missing dependency `a`
45 + 4 | }, []);
46 + 5 | return x;
47 + 6 | }
48 +
49 +Inferred dependencies: `[a]`
50 +
51 +Compilation Skipped: Existing memoization could not be preserved
52 +
53 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source.
54 +
55 +error.invalid-useMemo-async-callback.ts:2:18
56 + 1 | function component(a, b) {
57 +> 2 | let x = useMemo(async () => {
58 + | ^^^^^^^^^^^^^
59 +> 3 | await a;
60 + | ^^^^^^^^^^^^
61 +> 4 | }, []);
62 + | ^^^^ Could not preserve existing manual memoization
63 + 5 | return x;
64 + 6 | }
65 + 7 |
66 ```
67
68
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.invalid-useMemo-callback-args.expect.md
+27 -1
@@ -13,7 +13,7 @@ function component(a, b) {
13 ## Error
14
15 ```
16 -Found 1 error:
16 +Found 3 errors:
17
18 Error: useMemo() callbacks may not accept parameters
19
@@ -26,6 +26,32 @@ error.invalid-useMemo-callback-args.ts:2:18
26 3 | return x;
27 4 | }
28 5 |
29 +
30 +Error: Found missing memoization dependencies
31 +
32 +Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
33 +
34 +error.invalid-useMemo-callback-args.ts:2:23
35 + 1 | function component(a, b) {
36 +> 2 | let x = useMemo(c => a, []);
37 + | ^ Missing dependency `a`
38 + 3 | return x;
39 + 4 | }
40 + 5 |
41 +
42 +Inferred dependencies: `[a]`
43 +
44 +Compilation Skipped: Existing memoization could not be preserved
45 +
46 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `a`, but the source dependencies were []. Inferred dependency not present in source.
47 +
48 +error.invalid-useMemo-callback-args.ts:2:18
49 + 1 | function component(a, b) {
50 +> 2 | let x = useMemo(c => a, []);
51 + | ^^^^^^ Could not preserve existing manual memoization
52 + 3 | return x;
53 + 4 | }
54 + 5 |
55 ```
56
57
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.mutable-range-shared-inner-outer-function.expect.md
+23 -1
@@ -32,7 +32,7 @@ export const FIXTURE_ENTRYPOINT = {
32 ## Error
33
34 ```
35 -Found 1 error:
35 +Found 2 errors:
36
37 Error: Cannot reassign variable after render completes
38
@@ -46,6 +46,28 @@ error.mutable-range-shared-inner-outer-function.ts:8:6
46 9 | b = [];
47 10 | } else {
48 11 | a = {};
49 +
50 +Error: Cannot modify local variables after render completes
51 +
52 +This argument is a function which may reassign or mutate `a` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
53 +
54 +error.mutable-range-shared-inner-outer-function.ts:17:23
55 + 15 | b.push(false);
56 + 16 | };
57 +> 17 | return <div onClick={f} />;
58 + | ^ This function may (indirectly) reassign or modify `a` after render
59 + 18 | }
60 + 19 |
61 + 20 | export const FIXTURE_ENTRYPOINT = {
62 +
63 +error.mutable-range-shared-inner-outer-function.ts:8:6
64 + 6 | const f = () => {
65 + 7 | if (cond) {
66 +> 8 | a = {};
67 + | ^ This modifies `a`
68 + 9 | b = [];
69 + 10 | } else {
70 + 11 | a = {};
71 ```
72
73
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-allow-assigning-to-inferred-ref-prop-in-callback.expect.md
+22 -1
@@ -29,7 +29,7 @@ function useHook(parentRef) {
29 ## Error
30
31 ```
32 -Found 1 error:
32 +Found 2 errors:
33
34 Error: This value cannot be modified
35
@@ -43,6 +43,27 @@ error.todo-allow-assigning-to-inferred-ref-prop-in-callback.ts:15:8
43 16 | }
44 17 | }
45 18 | };
46 +
47 +Error: Cannot modify local variables after render completes
48 +
49 +This argument is a function which may reassign or mutate `parentRef` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
50 +
51 +error.todo-allow-assigning-to-inferred-ref-prop-in-callback.ts:19:9
52 + 17 | }
53 + 18 | };
54 +> 19 | return handler;
55 + | ^^^^^^^ This function may (indirectly) reassign or modify `parentRef` after render
56 + 20 | }
57 + 21 |
58 +
59 +error.todo-allow-assigning-to-inferred-ref-prop-in-callback.ts:15:8
60 + 13 | } else {
61 + 14 | // So this assignment fails since we don't know its a ref
62 +> 15 | parentRef.current = instance;
63 + | ^^^^^^^^^ This modifies `parentRef`
64 + 16 | }
65 + 17 | }
66 + 18 | };
67 ```
68
69
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-function-expression-references-later-variable-declaration.expect.md
+22 -1
@@ -17,7 +17,7 @@ function Component() {
17 ## Error
18
19 ```
20 -Found 1 error:
20 +Found 2 errors:
21
22 Error: Cannot reassign variable after render completes
23
@@ -31,6 +31,27 @@ error.todo-function-expression-references-later-variable-declaration.ts:3:4
31 4 | };
32 5 | let onClick;
33 6 |
34 +
35 +Error: Cannot modify local variables after render completes
36 +
37 +This argument is a function which may reassign or mutate `onClick` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
38 +
39 +error.todo-function-expression-references-later-variable-declaration.ts:7:23
40 + 5 | let onClick;
41 + 6 |
42 +> 7 | return <div onClick={callback} />;
43 + | ^^^^^^^^ This function may (indirectly) reassign or modify `onClick` after render
44 + 8 | }
45 + 9 |
46 +
47 +error.todo-function-expression-references-later-variable-declaration.ts:3:4
48 + 1 | function Component() {
49 + 2 | let callback = () => {
50 +> 3 | onClick = () => {};
51 + | ^^^^^^^ This modifies `onClick`
52 + 4 | };
53 + 5 | let onClick;
54 + 6 |
55 ```
56
57
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/error.todo-reassign-const.expect.md
+14 -1
@@ -21,7 +21,7 @@ function Component({foo}) {
21 ## Error
22
23 ```
24 -Found 1 error:
24 +Found 2 errors:
25
26 Todo: Support destructuring of context variables
27
@@ -33,6 +33,19 @@ error.todo-reassign-const.ts:3:20
33 4 | let bar = foo.bar;
34 5 | return (
35 6 | <Stringify
36 +
37 +Error: This value cannot be modified
38 +
39 +Modifying component props or hook arguments is not allowed. Consider using a local variable instead.
40 +
41 +error.todo-reassign-const.ts:8:8
42 + 6 | <Stringify
43 + 7 | handler={() => {
44 +> 8 | foo = true;
45 + | ^^^ `foo` cannot be modified
46 + 9 | }}
47 + 10 | />
48 + 11 | );
49 ```
50
51
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-exhaustive-deps.expect.md
+43 -1
@@ -51,7 +51,7 @@ function Component({x, y, z}) {
51 ## Error
52
53 ```
54 -Found 4 errors:
54 +Found 6 errors:
55
56 Error: Found missing/extra memoization dependencies
57
@@ -157,6 +157,48 @@ error.invalid-exhaustive-deps.ts:37:13
157 40 | }, []);
158
159 Inferred dependencies: `[ref]`
160 +
161 +Compilation Skipped: Existing memoization could not be preserved
162 +
163 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `x.y.z.a.b`, but the source dependencies were [x?.y.z.a?.b.z]. Inferred different dependency than source.
164 +
165 +error.invalid-exhaustive-deps.ts:14:20
166 + 12 | // ok, not our job to type check nullability
167 + 13 | }, [x.y.z.a]);
168 +> 14 | const c = useMemo(() => {
169 + | ^^^^^^^
170 +> 15 | return x?.y.z.a?.b;
171 + | ^^^^^^^^^^^^^^^^^^^^^^^
172 +> 16 | // error: too precise
173 + | ^^^^^^^^^^^^^^^^^^^^^^^
174 +> 17 | }, [x?.y.z.a?.b.z]);
175 + | ^^^^ Could not preserve existing manual memoization
176 + 18 | const d = useMemo(() => {
177 + 19 | return x?.y?.[(console.log(y), z?.b)];
178 + 20 | // ok
179 +
180 +Compilation Skipped: Existing memoization could not be preserved
181 +
182 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `ref`, but the source dependencies were []. Inferred dependency not present in source.
183 +
184 +error.invalid-exhaustive-deps.ts:35:21
185 + 33 | const ref2 = useRef(null);
186 + 34 | const ref = z ? ref1 : ref2;
187 +> 35 | const cb = useMemo(() => {
188 + | ^^^^^^^
189 +> 36 | return () => {
190 + | ^^^^^^^^^^^^^^^^^^
191 +> 37 | return ref.current;
192 + | ^^^^^^^^^^^^^^^^^^
193 +> 38 | };
194 + | ^^^^^^^^^^^^^^^^^^
195 +> 39 | // error: ref is a stable type but reactive
196 + | ^^^^^^^^^^^^^^^^^^
197 +> 40 | }, []);
198 + | ^^^^ Could not preserve existing manual memoization
199 + 41 | return <Stringify results={[a, b, c, d, e, f, cb]} />;
200 + 42 | }
201 + 43 |
202 ```
203
204
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/exhaustive-deps/error.invalid-missing-nonreactive-dep-unmemoized.expect.md
+14 -1
@@ -22,7 +22,7 @@ function useHook() {
22 ## Error
23
24 ```
25 -Found 1 error:
25 +Found 2 errors:
26
27 Error: Found missing memoization dependencies
28
@@ -38,6 +38,19 @@ error.invalid-missing-nonreactive-dep-unmemoized.ts:11:31
38 14 |
39
40 Inferred dependencies: `[object]`
41 +
42 +Compilation Skipped: Existing memoization could not be preserved
43 +
44 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `object`, but the source dependencies were []. Inferred dependency not present in source.
45 +
46 +error.invalid-missing-nonreactive-dep-unmemoized.ts:11:24
47 + 9 | useIdentity();
48 + 10 | object.x = 0;
49 +> 11 | const array = useMemo(() => [object], []);
50 + | ^^^^^^^^^^^^^^ Could not preserve existing manual memoization
51 + 12 | return array;
52 + 13 | }
53 + 14 |
54 ```
55
56
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-reassign-local-variable-in-jsx-callback.expect.md
+22 -1
@@ -42,7 +42,7 @@ function Component() {
42 ## Error
43
44 ```
45 -Found 1 error:
45 +Found 2 errors:
46
47 Error: Cannot reassign variable after render completes
48
@@ -56,6 +56,27 @@ error.invalid-reassign-local-variable-in-jsx-callback.ts:6:4
56 7 | };
57 8 |
58 9 | const onClick = newValue => {
59 +
60 +Error: Cannot modify local variables after render completes
61 +
62 +This argument is a function which may reassign or mutate `local` after render, which can cause inconsistent behavior on subsequent renders. Consider using state instead.
63 +
64 +error.invalid-reassign-local-variable-in-jsx-callback.ts:32:26
65 + 30 | };
66 + 31 |
67 +> 32 | return <button onClick={onClick}>Submit</button>;
68 + | ^^^^^^^ This function may (indirectly) reassign or modify `local` after render
69 + 33 | }
70 + 34 |
71 +
72 +error.invalid-reassign-local-variable-in-jsx-callback.ts:6:4
73 + 4 |
74 + 5 | const reassignLocal = newValue => {
75 +> 6 | local = newValue;
76 + | ^^^^^ This modifies `local`
77 + 7 | };
78 + 8 |
79 + 9 | const onClick = newValue => {
80 ```
81
82
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/new-mutability/error.invalid-referencing-frozen-hoisted-storecontext-const.expect.md
+13 -1
@@ -31,7 +31,7 @@ function Component({content, refetch}) {
31 ## Error
32
33 ```
34 -Found 1 error:
34 +Found 2 errors:
35
36 Error: Cannot access variable before it is declared
37
@@ -52,6 +52,18 @@ Error: Cannot access variable before it is declared
52 20 |
53 21 | return <Foo data={data} onSubmit={onSubmit} />;
54 22 | }
55 +
56 +Error: Found missing memoization dependencies
57 +
58 +Missing dependencies can cause a value to update less often than it should, resulting in stale UI.
59 +
60 + 9 | // TDZ violation!
61 + 10 | const onRefetch = useCallback(() => {
62 +> 11 | refetch(data);
63 + | ^^^^ Missing dependency `data`
64 + 12 | }, [refetch]);
65 + 13 |
66 + 14 | // The context variable gets frozen here since it's passed to a hook
67 ```
68
69
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/preserve-memo-validation/error.useMemo-unrelated-mutation-in-depslist.expect.md
+18 -1
@@ -30,7 +30,7 @@ function useFoo(input1) {
30 ## Error
31
32 ```
33 -Found 1 error:
33 +Found 2 errors:
34
35 Error: Found missing memoization dependencies
36
@@ -46,6 +46,23 @@ error.useMemo-unrelated-mutation-in-depslist.ts:18:14
46 21 | }
47
48 Inferred dependencies: `[x, y]`
49 +
50 +Compilation Skipped: Existing memoization could not be preserved
51 +
52 +React Compiler has skipped optimizing this component because the existing manual memoization could not be preserved. The inferred dependencies did not match the manually specified dependencies, which could cause the value to change more or less frequently than expected. The inferred dependency was `input1`, but the source dependencies were [y]. Inferred different dependency than source.
53 +
54 +error.useMemo-unrelated-mutation-in-depslist.ts:16:27
55 + 14 | const x = {};
56 + 15 | const y = [input1];
57 +> 16 | const memoized = useMemo(() => {
58 + | ^^^^^^^
59 +> 17 | return [y];
60 + | ^^^^^^^^^^^^^^^
61 +> 18 | }, [(mutate(x), y)]);
62 + | ^^^^ Could not preserve existing manual memoization
63 + 19 |
64 + 20 | return [x, memoized];
65 + 21 | }
66 ```
67
68
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/rules-of-hooks/error.invalid-hook-for.expect.md
+11 -16
@@ -16,29 +16,24 @@ function Component(props) {
16 ## Error
17
18 ```
19 -Found 2 errors:
19 +Found 1 error:
20
21 -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)
21 +Invariant: Unexpected empty block with `goto` terminal
22
23 -error.invalid-hook-for.ts:4:9
24 - 2 | let i = 0;
25 - 3 | for (let x = 0; useHook(x) < 10; useHook(i), x++) {
26 -> 4 | i += useHook(x);
27 - | ^^^^^^^ 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)
28 - 5 | }
29 - 6 | return i;
30 - 7 | }
23 +Block bb5 is empty.
24
32 -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)
33 -
34 -error.invalid-hook-for.ts:3:35
25 +error.invalid-hook-for.ts:3:2
26 1 | function Component(props) {
27 2 | let i = 0;
28 > 3 | for (let x = 0; useHook(x) < 10; useHook(i), x++) {
38 - | ^^^^^^^ 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)
39 - 4 | i += useHook(x);
40 - 5 | }
29 + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
30 +> 4 | i += useHook(x);
31 + | ^^^^^^^^^^^^^^^^^^^^
32 +> 5 | }
33 + | ^^^^ Unexpected empty block with `goto` terminal
34 6 | return i;
35 + 7 | }
36 + 8 |
37 ```
38
39
\ No newline at end of file
compiler/packages/babel-plugin-react-compiler/src/__tests__/fixtures/compiler/use-no-forget-multiple-with-eslint-suppression.expect.md
+1
@@ -25,6 +25,7 @@ export const FIXTURE_ENTRYPOINT = {
25 ## Code
26
27 ```javascript
28 +import { c as _c } from "react/compiler-runtime";
29 import { useRef } from "react";
30
31 const useControllableState = (options) => {};
compiler/packages/eslint-plugin-react-compiler/__tests__/PluginTest-test.ts
+2 -1
@@ -57,7 +57,6 @@ testRule('plugin-recommended', TestRecommendedRules, {
57 ],
58 invalid: [
59 {
60 - // TODO: actually return multiple diagnostics in this case
60 name: 'Multiple diagnostic kinds from the same function are surfaced',
61 code: normalizeIndent`
62 import Child from './Child';
@@ -70,6 +69,7 @@ testRule('plugin-recommended', TestRecommendedRules, {
69 `,
70 errors: [
71 makeTestCaseError('Hooks must always be called in a consistent order'),
72 + makeTestCaseError('Capitalized functions are reserved for components'),
73 ],
74 },
75 {
@@ -128,6 +128,7 @@ testRule('plugin-recommended', TestRecommendedRules, {
128 makeTestCaseError(
129 'Calling setState from useMemo may trigger an infinite loop',
130 ),
131 + makeTestCaseError('Found extra memoization dependencies'),
132 ],
133 },
134 ],