Skip to content

Commit b5ee05a

Browse files
committed
docs(react-x): refresh compiler diff reports
1 parent 55c10db commit b5ee05a

13 files changed

Lines changed: 518 additions & 664 deletions

File tree

Lines changed: 41 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,66 @@
11
# error-boundaries IMPL–SPEC Diff Report
22

3-
**IMPL**: `error-boundaries.ts` (ESLint rule)\
4-
**SPEC**: `error-boundaries.spec.md` (React Compiler `ValidateNoJSXInTryStatement`)
3+
## Verification metadata
54

6-
---
5+
- **IMPL**: `error-boundaries.ts` (ESLint rule)
6+
- **SPEC**: `error-boundaries.spec.md` (React Compiler `ValidateNoJSXInTryStatement`)
7+
- **Implementation commit**: `55c10db7bae04d49606792767530cc1e786dd5a0`
8+
- **React commit**: `c0c39a6b3907eaab35f43074949e2957a2a734c1`
9+
- **Last verified**: `2026-07-14`
10+
- **React package**: `compiler/packages/babel-plugin-react-compiler`
11+
- **Implementation sources/tests**:
12+
- `error-boundaries.ts`
13+
- `error-boundaries.spec.ts`
14+
- **React sources/fixtures**:
15+
- `src/Validation/ValidateNoJSXInTryStatement.ts`
16+
- `src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js`
17+
- `src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js`
18+
- `src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-try-with-finally.js`
19+
- `src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-catch-in-outer-try-with-finally.js`
720

8-
## 1. Underlying Mechanism
21+
“Source” below means behavior directly established by the files above; “fixture” means an explicit reviewed case; “inference” is kept separate.
922

10-
The SPEC operates on the React Compiler's High-level IR (HIR), using block-based CFG traversal with an `activeTryBlocks` stack. It tracks `JsxExpression` and `JsxFragment` instructions and operates on `HIRFunction` scope with `outputMode === 'lint'`.
23+
## 1. Detection mechanism and breadth
1124

12-
The IMPL operates on the ESLint AST with parent-node traversal (`Traverse.findParent`). It detects JSX-like return values via `isJsxLike(context, ret, hint)` and is explicitly scoped to function components and hooks via collectors.
25+
**Source — React pass.** `validateNoJSXInTryStatement(fn)` iterates `fn.body.blocks`, maintains an `activeTryBlocks` stack, and reports every `JsxExpression` or `JsxFragment` instruction encountered while that stack is non-empty.
1326

14-
---
27+
**Source — IMPL.** The rule collects function components and hooks, then checks JSX-like values from their collected `rets`. It does not visit every JSX creation site. Separately, it collects `use()` calls and reports those that are in a matching try body belonging to a collected component or hook.
1528

16-
## 2. Rule: No JSX in Try Block
29+
**Fixture.** React's `invalid-jsx-in-try-with-catch.js` assigns `<div />` to a variable inside a try body. This is an explicit compiler error case; it is not the return-value shape inspected by the IMPL.
1730

18-
### Detection Target
31+
**Inference.** The IMPL's JSX candidate set is narrower than the pass's instruction-level JSX scan. The IMPL also adds `tryCatchWithUse`, which has no counterpart in this React pass.
1932

20-
The SPEC flags any `JsxExpression` or `JsxFragment` instruction appearing inside a try block, including JSX assigned to variables (e.g., `el = <div />`).
33+
## 2. Try ancestry, catch, and finally
2134

22-
The IMPL only inspects `ReturnStatement` nodes with a `TryStatement` ancestor. Intermediate variable assignments inside try blocks are missed.
35+
**Source — IMPL traversal.** `getEnclosingTryBlock` uses a custom `while` parent-chain, not repeated `Traverse.findParent` calls. For each ancestor `TryStatement`, it walks from the original node toward that try and matches only if the path passes through `TryStatement.block`.
2336

24-
**Verdict**: IMPL detection is narrower. It only catches JSX returned directly from a try/catch scope.
37+
Consequently:
2538

26-
### Catch Block Handling
39+
- a node in a try body matches that try;
40+
- a node in that try's own `catch` or `finally` does not match that try;
41+
- traversal continues outward, so the same node can match an outer try when its path passes through the outer `TryStatement.block`.
2742

28-
The SPEC maintains an `activeTryBlocks` stack and explicitly removes catch handler blocks from the stack at block start. JSX inside a catch block is outside the try scope when there is no enclosing outer try.
43+
The IMPL's remaining `Traverse.findParent` use checks whether a matched `use()` try belongs to a collected component or hook; it is not the try-body ancestry algorithm.
2944

30-
The IMPL now uses repeated `Traverse.findParent` calls to determine whether a `ReturnStatement` sits inside the `try` block or inside the `catch` / `finally` block of its nearest ancestor `TryStatement`. If the node is in `catch` / `finally`, the traversal continues upward to check for an enclosing outer `TryStatement`.
45+
**Source — React pass.** At each HIR block, the pass removes that block's ID from `activeTryBlocks` before inspecting instructions. This excludes a current try's handler while retaining any still-active outer try.
3146

32-
Key behavioral difference:
47+
**Fixtures.** The reviewed React fixtures explicitly cover JSX in a try body with catch and JSX in an inner catch that remains inside an outer try. Together with the reviewed IMPL tests, these support alignment for those two cases.
3348

34-
- JSX in catch (no outer try): **Allowed** by both.
35-
- JSX in catch (nested in outer try): **Error** for both.
49+
**Source/fixtures — Finally.** The IMPL does not treat a current try's own `finally` as its try body; it can only match an enclosing outer try as described above. The React pass has no explicit finally branch, and the two reviewed finally scenarios are TODO fixtures.
3650

37-
**Verdict**: Both now align on catch-block exemptions.
51+
**Inference.** Current source and fixtures do not establish general IMPL/compiler parity for finally-related control flow.
3852

39-
### Nested Try/Catch
53+
## 3. Function scope and nested callbacks
4054

41-
The SPEC's `activeTryBlocks` stack naturally handles nesting depth. The IMPL now resolves nested structures by walking up through successive `TryStatement` ancestors, skipping those whose `catch` / `finally` blocks contain the node, until it finds one whose `try` block does.
55+
**Source — IMPL.** The rule's JSX checks are driven by `rets` supplied by the function-component and hook collectors, rather than by a recursive scan of all nested function bodies.
4256

43-
**Verdict**: Both handle nested structures correctly and align on catch-block exemptions.
57+
**Source — React pass.** One invocation scans only the blocks of the supplied `HIRFunction`; this pass does not recursively traverse lowered nested functions.
4458

45-
---
59+
**Fixture boundary.** The reviewed React fixtures cover try/catch, catch inside an outer try, and the two finally TODOs. None contains a nested callback.
4660

47-
## 3. Rule: No `use` Hook in Try/Catch
61+
**Inference boundary.** Current pass source and fixtures do not prove how the compiler pipeline handles JSX inside a nested callback. In particular, this report does not assert that the compiler must report such a case; that would require pipeline/lowering evidence or a dedicated fixture.
4862

49-
The SPEC does not include this check. The IMPL adds an extra `tryCatchWithUse` rule that reports `use()` calls inside try blocks.
63+
## 4. Message mapping
5064

51-
Rationale: the `use` hook suspends the component; its errors can only be caught by Error Boundaries, not try/catch.
52-
53-
**Verdict**: IMPL extends the compiler SPEC with an extra rule that has no SPEC counterpart.
54-
55-
---
56-
57-
## 4. Finally Blocks
58-
59-
The SPEC explicitly marks finally blocks as TODO / unsupported (`error.todo-invalid-jsx-in-try-with-finally.js`). The IMPL provides no special handling; AST traversal treats finally as a normal descendant of `TryStatement`.
60-
61-
**Verdict**: IMPL provides basic coverage for finally via AST ancestry, while the SPEC documents known unsupported cases.
62-
63-
---
64-
65-
## 5. Non-Component / Non-Hook Functions
66-
67-
The SPEC implicitly limits scope via `HIRFunction`. The IMPL explicitly filters — only function components and hooks are collected. Utility functions (e.g., lowercase `processItems`, `fetchData`) are ignored.
68-
69-
**Verdict**: Both are effectively limited to component-like functions, but the IMPL achieves this explicitly via collectors.
70-
71-
### Nested Functions and Hook Callbacks
72-
73-
A corollary of the above: the IMPL does **not** inspect `ReturnStatement` nodes inside nested function expressions, arrow functions, object methods, or hook callbacks (e.g., `useMemo(() => { try { return <div />; } catch {} })`). Because these nested functions are not themselves components or hooks, their returns are never examined for JSX-in-try violations.
74-
75-
The SPEC, operating on the full HIR of the component, would still flag JSX constructed inside a try block regardless of whether it appears in a nested callback. [NEEDS VERIFICATION]
76-
77-
**Verdict**: IMPL misses try/catch JSX inside nested callbacks; SPEC catches them uniformly.
78-
79-
---
80-
81-
## 6. Message ID Mapping
82-
83-
The SPEC defines a single error message. The IMPL splits the concept into two distinct MessageIDs:
84-
85-
- `tryCatchWithJsx` — "Avoid constructing JSX within try/catch"\
86-
Maps to the SPEC's single error.
87-
- `tryCatchWithUse`**IMPL-only**\
88-
Reports `use()` hook calls inside a `TryStatement`.
89-
90-
Error text is aligned in intent (recommending Error Boundaries) but the IMPL adds hook-specific wording for the `use` case.
91-
92-
---
93-
94-
## 7. Key Gaps and Deviations
95-
96-
1. **Narrower JSX Detection**: The IMPL only catches JSX in `ReturnStatement` nodes, missing intermediate assignments such as `el = <div />` inside try blocks.
97-
2. **`use` Hook Extension**: The IMPL adds `tryCatchWithUse`, a rule with no equivalent in the compiler SPEC.
98-
3. **Finally Handling**: The SPEC documents TODOs for finally blocks; the IMPL provides implicit coverage via AST ancestry but does not document any gaps.
65+
- `tryCatchWithJsx` corresponds in intent to the pass diagnostic, “Avoid constructing JSX within try/catch,” though the exact text differs.
66+
- `tryCatchWithUse` is IMPL-only and advises using an Error Boundary around `use()` rather than try/catch.

plugins/eslint-plugin-react-x/src/rules/globals/globals.spec.diff.md

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,21 @@
11
# globals IMPL–SPEC Diff Report
22

3-
**IMPL**: `globals.ts` (ESLint rule)\
4-
**SPEC**: `globals.spec.md` (React Compiler `InferMutationAliasingEffects`)
5-
6-
## 1. Underlying Mechanism
3+
## Verification metadata
4+
5+
- **IMPL**: `globals.ts` + `lib.ts` (ESLint rule)
6+
- **SPEC**: `globals.spec.md` (React Compiler `InferMutationAliasingEffects`)
7+
- **Implementation commit**: `55c10db7bae04d49606792767530cc1e786dd5a0`
8+
- **React commit**: `c0c39a6b3907eaab35f43074949e2957a2a734c1`
9+
- **Last verified**: `2026-07-14`
10+
- **React package**: `compiler/packages/babel-plugin-react-compiler`
11+
- **Implementation sources/tests**:
12+
- `globals.ts`
13+
- `lib.ts`
14+
- `globals.spec.ts`
15+
- **React sources/fixtures**:
16+
- `src/Inference/InferMutationAliasingEffects.ts`
17+
18+
## 1. Underlying mechanism
719

820
The SPEC runs abstract interpretation over SSA-form HIR. It computes instruction signatures, applies aliasing effects to an `InferenceState`, and iterates control flow to a fixed point. A write to a non-local binding is represented as `MutateGlobal` and reported under `ErrorCategory.Globals`.
921

@@ -15,7 +27,7 @@ The IMPL runs on the ESLint AST without type information or HIR. It mirrors the
1527

1628
This separation is important: a function may carry a global mutation effect without executing during render.
1729

18-
## 2. Mutation Effects
30+
## 2. Mutation effects
1931

2032
The IMPL creates effects for:
2133

@@ -27,7 +39,7 @@ The IMPL creates effects for:
2739

2840
Unresolved identifiers and bindings declared in global or module scope are treated as globals. Local binding reassignment is not considered a global mutation, even when the local was initialized from a global primitive.
2941

30-
## 3. Alias Handling
42+
## 3. Alias handling
3143

3244
The SPEC tracks `Assign`, `Alias`, `Capture`, and related effects through abstract values and control-flow joins.
3345

@@ -47,7 +59,7 @@ let local = moduleCount;
4759
local++; // allowed
4860
```
4961

50-
## 4. Function Effect Propagation
62+
## 4. Function effect propagation
5163

5264
Direct calls to local functions add call-graph edges. At `Program:exit`, global mutation effects are propagated from components and Hooks through those edges:
5365

@@ -71,7 +83,7 @@ return <button onClick={() => mutate()} />;
7183

7284
The IMPL currently resolves function declarations, function expressions, arrow functions, and simple identifier aliases. Calls through object properties, returned functions, or dynamic dispatch are not resolved.
7385

74-
## 5. Mutating Array Methods
86+
## 5. Mutating array methods
7587

7688
The SPEC derives receiver mutation from built-in function signatures. The IMPL uses an explicit method set because the AST alone cannot inspect function signatures:
7789

@@ -81,13 +93,13 @@ copyWithin, fill, pop, push, reverse, shift, sort, splice, unshift;
8193

8294
Computed static property names such as `items["push"]()` are supported. This remains less general than the SPEC and can neither recognize arbitrary user-defined mutators nor prove the receiver's runtime type.
8395

84-
## 6. Render Boundaries
96+
## 6. Render boundaries
8597

86-
Only functions recognized by the component and Hook collectors are analysis roots. Non-component functions are summarized but reported only when directly reachable from a render root.
98+
Only functions recognized by the component and Hook collectors are analysis roots. Non-component functions are summarized and reported when transitively reachable from a render root through recorded direct-call edges.
8799

88100
A nested function that is only stored, returned, passed to a Hook, passed to an unknown function, or used as an event callback is not considered render-executed. A nested function called synchronously from a render root is considered render-executed.
89101

90-
## 7. Error Reporting
102+
## 7. Error reporting
91103

92104
The SPEC uses one globals diagnostic category. The IMPL keeps three surface-specific messages:
93105

@@ -99,7 +111,7 @@ The SPEC uses one globals diagnostic category. The IMPL keeps three surface-spec
99111

100112
Diagnostics are reported at the original mutation site, including when the effect reaches render through helper calls.
101113

102-
## 8. Remaining Gaps
114+
## 8. Remaining gaps
103115

104116
- No SSA, control-flow fixed point, or phi-node handling.
105117
- No `ValueKind` lattice or frozen/context value validation.

plugins/eslint-plugin-react-x/src/rules/immutability/immutability.spec.diff.md

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,31 @@
11
# immutability IMPL–SPEC Diff Report
22

3-
**IMPL**: `immutability.ts` + `collect.ts` + `effects.ts` + `lib.ts` (ESLint AST rule)\
4-
**SPEC**: `immutability.spec.md` / React Compiler `ValidateNoFreezingKnownMutableFunctions`
3+
## Verification metadata
4+
5+
- **IMPL**: `immutability.ts` + `collect.ts` + `effects.ts` + `lib.ts` (ESLint AST rule)
6+
- **SPEC**: `immutability.spec.md` (React Compiler `ValidateNoFreezingKnownMutableFunctions`)
7+
- **Implementation commit**: `55c10db7bae04d49606792767530cc1e786dd5a0`
8+
- **React commit**: `c0c39a6b3907eaab35f43074949e2957a2a734c1`
9+
- **Last verified**: `2026-07-14`
10+
- **React package**: `compiler/packages/babel-plugin-react-compiler`
11+
- **Implementation sources/tests**:
12+
- `immutability.ts`
13+
- `collect.ts`
14+
- `effects.ts`
15+
- `lib.ts`
16+
- `immutability.spec.ts`
17+
- **React sources/fixtures**:
18+
- `src/Validation/ValidateNoFreezingKnownMutableFunctions.ts`
19+
- `src/Entrypoint/Pipeline.ts`
20+
- `src/HIR/Environment.ts`
21+
- `src/__tests__/fixtures/compiler/error.invalid-pass-mutable-function-as-prop.{js,expect.md}`
22+
- `src/__tests__/fixtures/compiler/error.invalid-hook-function-argument-mutates-local-variable.{js,expect.md}`
23+
- `src/__tests__/fixtures/compiler/error.invalid-return-mutable-function-from-hook.{js,expect.md}`
524

625
> Scope: this report compares the validation behavior implemented by this rule with the compiler pass described by the local SPEC. The compiler pass consumes HIR operands that have already been assigned `Freeze` effects; it does not itself decide which JSX or hook syntax receives those effects. Therefore, syntax-specific sink omissions below are confirmed IMPL boundaries, but their exact compiler behavior also depends on upstream HIR/effect inference.
726
27+
> Upstream configuration note: `Environment.ts` defines `validateNoFreezingKnownMutableFunctions` with a default of `false`, but at the verified commit `Pipeline.ts` invokes `validateNoFreezingKnownMutableFunctions(hir)` unconditionally whenever `env.enableValidations` is active. No other source read of that configuration field was found. This records the current source wiring only; it does not infer historical intent or broader user-visible behavior.
28+
829
## 1. Summary
930

1031
| Area | Alignment | Main difference |
@@ -20,7 +41,7 @@
2041

2142
Overall, the IMPL matches the SPEC's three principal use cases, but it is a syntax- and naming-based approximation rather than an effect-equivalent implementation.
2243

23-
## 2. Detection Model
44+
## 2. Detection model
2445

2546
### SPEC
2647

@@ -42,7 +63,7 @@ The ESLint rule performs one AST traversal and defers correlation until `Program
4263

4364
The mutable-function map stores one representative mutation per function. In the IMPL this is the first collected mutation in source traversal order; later captured-variable mutations in the same function do not produce additional diagnostics for that sink. The compiler pass likewise associates a known function value with one representative mutation effect, though the selected effect is determined by effect-inference order rather than ESLint AST traversal.
4465

45-
## 3. Mutation Recognition
66+
## 3. Mutation recognition
4667

4768
| Mutation form | IMPL behavior |
4869
| --------------------------------------------------------- | --------------------------------------------------------------------------------------- |
@@ -66,7 +87,7 @@ Important precision differences:
6687
- **Conditional over-approximation**: the IMPL has no equivalent of conditional aliasing effects, so any recognized mutation syntax is considered definite even when control-flow conditional.
6788
- **Global/context boundary**: unresolved globals and bindings whose origin is declared in global/module scope are ignored, matching the SPEC's function-context boundary.
6889

69-
## 4. Function Resolution and Freeze Sinks
90+
## 4. Function resolution and freeze sinks
7091

7192
### Function resolution
7293

@@ -107,7 +128,7 @@ Confirmed IMPL sink omissions:
107128

108129
The local SPEC describes JSX props, hook arguments, and hook returns at a semantic level. Whether each omitted syntax shape receives a `Freeze` effect in the compiler is determined before `ValidateNoFreezingKnownMutableFunctions` and is not established by this validation pass alone.
109130

110-
## 5. Ref Exception
131+
## 5. Ref exception
111132

112133
The SPEC exempts mutations using `isRefOrRefLikeMutableType`, which is type-based.
113134

@@ -141,7 +162,7 @@ The IMPL emits two independent ESLint reports per sink:
141162

142163
This preserves both locations but changes problem counts and grouping. If the same mutable function is used at two sinks, the IMPL emits four reports: two usage reports and two mutation reports at the same mutation location. The SPEC's reason (`Cannot modify local variables after render completes`) is not emitted as an ESLint message; `meta.docs.description` only provides a general rule description.
143164

144-
## 7. Test Coverage Notes
165+
## 7. Test coverage notes
145166

146167
`immutability.spec.ts` pins the IMPL behavior for:
147168

0 commit comments

Comments
 (0)