Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,98 +1,66 @@
# error-boundaries IMPL–SPEC Diff Report

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

---
- **IMPL**: `error-boundaries.ts` (ESLint rule)
- **SPEC**: `error-boundaries.spec.md` (React Compiler `ValidateNoJSXInTryStatement`)
- **Implementation commit**: `55c10db7bae04d49606792767530cc1e786dd5a0`
- **React commit**: `c0c39a6b3907eaab35f43074949e2957a2a734c1`
- **Last verified**: `2026-07-14`
- **React package**: `compiler/packages/babel-plugin-react-compiler`
- **Implementation sources/tests**:
- `error-boundaries.ts`
- `error-boundaries.spec.ts`
- **React sources/fixtures**:
- `src/Validation/ValidateNoJSXInTryStatement.ts`
- `src/__tests__/fixtures/compiler/invalid-jsx-in-try-with-catch.js`
- `src/__tests__/fixtures/compiler/invalid-jsx-in-catch-in-outer-try-with-catch.js`
- `src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-try-with-finally.js`
- `src/__tests__/fixtures/compiler/error.todo-invalid-jsx-in-catch-in-outer-try-with-finally.js`

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

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'`.
## 1. Detection mechanism and breadth

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.
**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.

---
**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.

## 2. Rule: No JSX in Try Block
**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.

### Detection Target
**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.

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

The IMPL only inspects `ReturnStatement` nodes with a `TryStatement` ancestor. Intermediate variable assignments inside try blocks are missed.
**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`.

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

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

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.
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.

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`.
**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.

Key behavioral difference:
**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.

- JSX in catch (no outer try): **Allowed** by both.
- JSX in catch (nested in outer try): **Error** for both.
**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.

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

### Nested Try/Catch
## 3. Function scope and nested callbacks

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.
**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.

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

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

## 3. Rule: No `use` Hook in Try/Catch
**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.

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

Rationale: the `use` hook suspends the component; its errors can only be caught by Error Boundaries, not try/catch.

**Verdict**: IMPL extends the compiler SPEC with an extra rule that has no SPEC counterpart.

---

## 4. Finally Blocks

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`.

**Verdict**: IMPL provides basic coverage for finally via AST ancestry, while the SPEC documents known unsupported cases.

---

## 5. Non-Component / Non-Hook Functions

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.

**Verdict**: Both are effectively limited to component-like functions, but the IMPL achieves this explicitly via collectors.

### Nested Functions and Hook Callbacks

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.

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]

**Verdict**: IMPL misses try/catch JSX inside nested callbacks; SPEC catches them uniformly.

---

## 6. Message ID Mapping

The SPEC defines a single error message. The IMPL splits the concept into two distinct MessageIDs:

- `tryCatchWithJsx` — "Avoid constructing JSX within try/catch"\
Maps to the SPEC's single error.
- `tryCatchWithUse` — **IMPL-only**\
Reports `use()` hook calls inside a `TryStatement`.

Error text is aligned in intent (recommending Error Boundaries) but the IMPL adds hook-specific wording for the `use` case.

---

## 7. Key Gaps and Deviations

1. **Narrower JSX Detection**: The IMPL only catches JSX in `ReturnStatement` nodes, missing intermediate assignments such as `el = <div />` inside try blocks.
2. **`use` Hook Extension**: The IMPL adds `tryCatchWithUse`, a rule with no equivalent in the compiler SPEC.
3. **Finally Handling**: The SPEC documents TODOs for finally blocks; the IMPL provides implicit coverage via AST ancestry but does not document any gaps.
- `tryCatchWithJsx` corresponds in intent to the pass diagnostic, “Avoid constructing JSX within try/catch,” though the exact text differs.
- `tryCatchWithUse` is IMPL-only and advises using an Error Boundary around `use()` rather than try/catch.
Original file line number Diff line number Diff line change
@@ -1,9 +1,21 @@
# globals IMPL–SPEC Diff Report

**IMPL**: `globals.ts` (ESLint rule)\
**SPEC**: `globals.spec.md` (React Compiler `InferMutationAliasingEffects`)

## 1. Underlying Mechanism
## Verification metadata

- **IMPL**: `globals.ts` + `lib.ts` (ESLint rule)
- **SPEC**: `globals.spec.md` (React Compiler `InferMutationAliasingEffects`)
- **Implementation commit**: `55c10db7bae04d49606792767530cc1e786dd5a0`
- **React commit**: `c0c39a6b3907eaab35f43074949e2957a2a734c1`
- **Last verified**: `2026-07-14`
- **React package**: `compiler/packages/babel-plugin-react-compiler`
- **Implementation sources/tests**:
- `globals.ts`
- `lib.ts`
- `globals.spec.ts`
- **React sources/fixtures**:
- `src/Inference/InferMutationAliasingEffects.ts`

## 1. Underlying mechanism

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`.

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

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

## 2. Mutation Effects
## 2. Mutation effects

The IMPL creates effects for:

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

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.

## 3. Alias Handling
## 3. Alias handling

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

Expand All @@ -47,7 +59,7 @@ let local = moduleCount;
local++; // allowed
```

## 4. Function Effect Propagation
## 4. Function effect propagation

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

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

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.

## 5. Mutating Array Methods
## 5. Mutating array methods

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:

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

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.

## 6. Render Boundaries
## 6. Render boundaries

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.
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.

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.

## 7. Error Reporting
## 7. Error reporting

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

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

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

## 8. Remaining Gaps
## 8. Remaining gaps

- No SSA, control-flow fixed point, or phi-node handling.
- No `ValueKind` lattice or frozen/context value validation.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,31 @@
# immutability IMPL–SPEC Diff Report

**IMPL**: `immutability.ts` + `collect.ts` + `effects.ts` + `lib.ts` (ESLint AST rule)\
**SPEC**: `immutability.spec.md` / React Compiler `ValidateNoFreezingKnownMutableFunctions`
## Verification metadata

- **IMPL**: `immutability.ts` + `collect.ts` + `effects.ts` + `lib.ts` (ESLint AST rule)
- **SPEC**: `immutability.spec.md` (React Compiler `ValidateNoFreezingKnownMutableFunctions`)
- **Implementation commit**: `55c10db7bae04d49606792767530cc1e786dd5a0`
- **React commit**: `c0c39a6b3907eaab35f43074949e2957a2a734c1`
- **Last verified**: `2026-07-14`
- **React package**: `compiler/packages/babel-plugin-react-compiler`
- **Implementation sources/tests**:
- `immutability.ts`
- `collect.ts`
- `effects.ts`
- `lib.ts`
- `immutability.spec.ts`
- **React sources/fixtures**:
- `src/Validation/ValidateNoFreezingKnownMutableFunctions.ts`
- `src/Entrypoint/Pipeline.ts`
- `src/HIR/Environment.ts`
- `src/__tests__/fixtures/compiler/error.invalid-pass-mutable-function-as-prop.{js,expect.md}`
- `src/__tests__/fixtures/compiler/error.invalid-hook-function-argument-mutates-local-variable.{js,expect.md}`
- `src/__tests__/fixtures/compiler/error.invalid-return-mutable-function-from-hook.{js,expect.md}`

> 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.

> 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.

## 1. Summary

| Area | Alignment | Main difference |
Expand All @@ -20,7 +41,7 @@

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.

## 2. Detection Model
## 2. Detection model

### SPEC

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

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.

## 3. Mutation Recognition
## 3. Mutation recognition

| Mutation form | IMPL behavior |
| --------------------------------------------------------- | --------------------------------------------------------------------------------------- |
Expand All @@ -66,7 +87,7 @@ Important precision differences:
- **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.
- **Global/context boundary**: unresolved globals and bindings whose origin is declared in global/module scope are ignored, matching the SPEC's function-context boundary.

## 4. Function Resolution and Freeze Sinks
## 4. Function resolution and freeze sinks

### Function resolution

Expand Down Expand Up @@ -107,7 +128,7 @@ Confirmed IMPL sink omissions:

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.

## 5. Ref Exception
## 5. Ref exception

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

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

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.

## 7. Test Coverage Notes
## 7. Test coverage notes

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

Expand Down
Loading
Loading