feat: port the React Compiler's HIR + validators into react-doctor#164
Draft
aidenybai wants to merge 2 commits into
Draft
feat: port the React Compiler's HIR + validators into react-doctor#164aidenybai wants to merge 2 commits into
aidenybai wants to merge 2 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
cursor Bot
pushed a commit
that referenced
this pull request
May 8, 2026
Five issues found on a fresh deep review of the HIR port (PR #164): 1. inferTypes conflated useState/useMemo/useContext returns under ReactType "Object". The PropertyLoad index-0/index-1 → StateValue/ StateSetter branch keyed off "Object", so a destructure like `const [n, runIt] = useMemo(...)` would tag `runIt` as a StateSetter and the validators would treat `runIt()` as a setState call. Fix: add a distinct `StateTuple` ReactType, gate the PropertyLoad branch on it. Regression test added. 2. The HIR rules duplicated `noDerivedStateEffect` on the canonical single-setter-call shape (article §1 fullName example), producing two diagnostics on the same line. Fix: `hir-no-derived-computations-in-effects` now defers when the effect body has no intermediate local bindings (StoreLocal in the inner HIR), preserving the multi-statement-with-locals path the AST walker can't see through. `hir-no-set-state-in-effect` stays unscoped because distinguishing direct vs aliased setter calls reliably from the HIR is not workable at v1; the duplicate on the simplest shape is documented as a known v1 limitation. 3. `lowerExpression` silently dropped `SpreadElement` arguments (`f(...args)` lowered to a CallExpression with the spread missing). Extracted `lowerCallArguments` that unwraps the spread's `argument` so the operand identity is still threaded through — losing only the spread shape (not modeled in v1) but keeping setState/alias propagation working through spread args. 4. `lowerStatement` only handled VariableDeclaration, ExpressionStatement, ReturnStatement, BlockStatement, IfStatement, and the function- declaration shapes. `for`, `while`, `do-while`, `for-of`, `for-in`, `switch`, `try`, `throw`, `labeled` all silently fell through, so a useEffect (or any hook call) inside any of these blocks was invisible to validators. Added recursive descent for all of them — control flow collapses into the surrounding block (no real CFG terminals yet) but the bodies get lowered. 5. The hir-unit test for the article §1 example used the single- setter-call shape that the round-2 scoping fix now defers on; updated to the multi-statement-with-locals form to keep the coverage on the validator's unique path. All 490 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
cursor Bot
pushed a commit
that referenced
this pull request
May 8, 2026
Five issues found on a fresh deep review of the HIR port (PR #164): 1. inferTypes conflated useState/useMemo/useContext returns under ReactType "Object". The PropertyLoad index-0/index-1 → StateValue/ StateSetter branch keyed off "Object", so a destructure like `const [n, runIt] = useMemo(...)` would tag `runIt` as a StateSetter and the validators would treat `runIt()` as a setState call. Fix: add a distinct `StateTuple` ReactType, gate the PropertyLoad branch on it. Regression test added. 2. The HIR rules duplicated `noDerivedStateEffect` on the canonical single-setter-call shape (article §1 fullName example), producing two diagnostics on the same line. Fix: `hir-no-derived-computations-in-effects` now defers when the effect body has no intermediate local bindings (StoreLocal in the inner HIR), preserving the multi-statement-with-locals path the AST walker can't see through. `hir-no-set-state-in-effect` stays unscoped because distinguishing direct vs aliased setter calls reliably from the HIR is not workable at v1; the duplicate on the simplest shape is documented as a known v1 limitation. 3. `lowerExpression` silently dropped `SpreadElement` arguments (`f(...args)` lowered to a CallExpression with the spread missing). Extracted `lowerCallArguments` that unwraps the spread's `argument` so the operand identity is still threaded through — losing only the spread shape (not modeled in v1) but keeping setState/alias propagation working through spread args. 4. `lowerStatement` only handled VariableDeclaration, ExpressionStatement, ReturnStatement, BlockStatement, IfStatement, and the function- declaration shapes. `for`, `while`, `do-while`, `for-of`, `for-in`, `switch`, `try`, `throw`, `labeled` all silently fell through, so a useEffect (or any hook call) inside any of these blocks was invisible to validators. Added recursive descent for all of them — control flow collapses into the surrounding block (no real CFG terminals yet) but the bodies get lowered. 5. The hir-unit test for the article §1 example used the single- setter-call shape that the round-2 scoping fix now defers on; updated to the multi-statement-with-locals form to keep the coverage on the validator's unique path. All 490 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
c101b26 to
29c79d0
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 8, 2026
Five issues found on a fresh deep review of the HIR port (PR #164): 1. inferTypes conflated useState/useMemo/useContext returns under ReactType "Object". The PropertyLoad index-0/index-1 → StateValue/ StateSetter branch keyed off "Object", so a destructure like `const [n, runIt] = useMemo(...)` would tag `runIt` as a StateSetter and the validators would treat `runIt()` as a setState call. Fix: add a distinct `StateTuple` ReactType, gate the PropertyLoad branch on it. Regression test added. 2. The HIR rules duplicated `noDerivedStateEffect` on the canonical single-setter-call shape (article §1 fullName example), producing two diagnostics on the same line. Fix: `hir-no-derived-computations-in-effects` now defers when the effect body has no intermediate local bindings (StoreLocal in the inner HIR), preserving the multi-statement-with-locals path the AST walker can't see through. `hir-no-set-state-in-effect` stays unscoped because distinguishing direct vs aliased setter calls reliably from the HIR is not workable at v1; the duplicate on the simplest shape is documented as a known v1 limitation. 3. `lowerExpression` silently dropped `SpreadElement` arguments (`f(...args)` lowered to a CallExpression with the spread missing). Extracted `lowerCallArguments` that unwraps the spread's `argument` so the operand identity is still threaded through — losing only the spread shape (not modeled in v1) but keeping setState/alias propagation working through spread args. 4. `lowerStatement` only handled VariableDeclaration, ExpressionStatement, ReturnStatement, BlockStatement, IfStatement, and the function- declaration shapes. `for`, `while`, `do-while`, `for-of`, `for-in`, `switch`, `try`, `throw`, `labeled` all silently fell through, so a useEffect (or any hook call) inside any of these blocks was invisible to validators. Added recursive descent for all of them — control flow collapses into the surrounding block (no real CFG terminals yet) but the bodies get lowered. 5. The hir-unit test for the article §1 example used the single- setter-call shape that the round-2 scoping fix now defers on; updated to the multi-statement-with-locals form to keep the coverage on the validator's unique path. All 490 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
29c79d0 to
e6c2397
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 8, 2026
Five issues found on a fresh deep review of the HIR port (PR #164): 1. inferTypes conflated useState/useMemo/useContext returns under ReactType "Object". The PropertyLoad index-0/index-1 → StateValue/ StateSetter branch keyed off "Object", so a destructure like `const [n, runIt] = useMemo(...)` would tag `runIt` as a StateSetter and the validators would treat `runIt()` as a setState call. Fix: add a distinct `StateTuple` ReactType, gate the PropertyLoad branch on it. Regression test added. 2. The HIR rules duplicated `noDerivedStateEffect` on the canonical single-setter-call shape (article §1 fullName example), producing two diagnostics on the same line. Fix: `hir-no-derived-computations-in-effects` now defers when the effect body has no intermediate local bindings (StoreLocal in the inner HIR), preserving the multi-statement-with-locals path the AST walker can't see through. `hir-no-set-state-in-effect` stays unscoped because distinguishing direct vs aliased setter calls reliably from the HIR is not workable at v1; the duplicate on the simplest shape is documented as a known v1 limitation. 3. `lowerExpression` silently dropped `SpreadElement` arguments (`f(...args)` lowered to a CallExpression with the spread missing). Extracted `lowerCallArguments` that unwraps the spread's `argument` so the operand identity is still threaded through — losing only the spread shape (not modeled in v1) but keeping setState/alias propagation working through spread args. 4. `lowerStatement` only handled VariableDeclaration, ExpressionStatement, ReturnStatement, BlockStatement, IfStatement, and the function- declaration shapes. `for`, `while`, `do-while`, `for-of`, `for-in`, `switch`, `try`, `throw`, `labeled` all silently fell through, so a useEffect (or any hook call) inside any of these blocks was invisible to validators. Added recursive descent for all of them — control flow collapses into the surrounding block (no real CFG terminals yet) but the bodies get lowered. 5. The hir-unit test for the article §1 example used the single- setter-call shape that the round-2 scoping fix now defers on; updated to the multi-statement-with-locals form to keep the coverage on the validator's unique path. All 490 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
e6c2397 to
e6211a0
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 10, 2026
Five issues found on a fresh deep review of the HIR port (PR #164): 1. inferTypes conflated useState/useMemo/useContext returns under ReactType "Object". The PropertyLoad index-0/index-1 → StateValue/ StateSetter branch keyed off "Object", so a destructure like `const [n, runIt] = useMemo(...)` would tag `runIt` as a StateSetter and the validators would treat `runIt()` as a setState call. Fix: add a distinct `StateTuple` ReactType, gate the PropertyLoad branch on it. Regression test added. 2. The HIR rules duplicated `noDerivedStateEffect` on the canonical single-setter-call shape (article §1 fullName example), producing two diagnostics on the same line. Fix: `hir-no-derived-computations-in-effects` now defers when the effect body has no intermediate local bindings (StoreLocal in the inner HIR), preserving the multi-statement-with-locals path the AST walker can't see through. `hir-no-set-state-in-effect` stays unscoped because distinguishing direct vs aliased setter calls reliably from the HIR is not workable at v1; the duplicate on the simplest shape is documented as a known v1 limitation. 3. `lowerExpression` silently dropped `SpreadElement` arguments (`f(...args)` lowered to a CallExpression with the spread missing). Extracted `lowerCallArguments` that unwraps the spread's `argument` so the operand identity is still threaded through — losing only the spread shape (not modeled in v1) but keeping setState/alias propagation working through spread args. 4. `lowerStatement` only handled VariableDeclaration, ExpressionStatement, ReturnStatement, BlockStatement, IfStatement, and the function- declaration shapes. `for`, `while`, `do-while`, `for-of`, `for-in`, `switch`, `try`, `throw`, `labeled` all silently fell through, so a useEffect (or any hook call) inside any of these blocks was invisible to validators. Added recursive descent for all of them — control flow collapses into the surrounding block (no real CFG terminals yet) but the bodies get lowered. 5. The hir-unit test for the article §1 example used the single- setter-call shape that the round-2 scoping fix now defers on; updated to the multi-statement-with-locals form to keep the coverage on the validator's unique path. All 490 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
e6211a0 to
5359e36
Compare
|
🔴 React Review — 0/100 (unchanged) · Copy prompt for agentReviewed by react-review for commit 02e1bbd. Configure here. |
cursor Bot
pushed a commit
that referenced
this pull request
May 12, 2026
Five issues found on a fresh deep review of the HIR port (PR #164): 1. inferTypes conflated useState/useMemo/useContext returns under ReactType "Object". The PropertyLoad index-0/index-1 → StateValue/ StateSetter branch keyed off "Object", so a destructure like `const [n, runIt] = useMemo(...)` would tag `runIt` as a StateSetter and the validators would treat `runIt()` as a setState call. Fix: add a distinct `StateTuple` ReactType, gate the PropertyLoad branch on it. Regression test added. 2. The HIR rules duplicated `noDerivedStateEffect` on the canonical single-setter-call shape (article §1 fullName example), producing two diagnostics on the same line. Fix: `hir-no-derived-computations-in-effects` now defers when the effect body has no intermediate local bindings (StoreLocal in the inner HIR), preserving the multi-statement-with-locals path the AST walker can't see through. `hir-no-set-state-in-effect` stays unscoped because distinguishing direct vs aliased setter calls reliably from the HIR is not workable at v1; the duplicate on the simplest shape is documented as a known v1 limitation. 3. `lowerExpression` silently dropped `SpreadElement` arguments (`f(...args)` lowered to a CallExpression with the spread missing). Extracted `lowerCallArguments` that unwraps the spread's `argument` so the operand identity is still threaded through — losing only the spread shape (not modeled in v1) but keeping setState/alias propagation working through spread args. 4. `lowerStatement` only handled VariableDeclaration, ExpressionStatement, ReturnStatement, BlockStatement, IfStatement, and the function- declaration shapes. `for`, `while`, `do-while`, `for-of`, `for-in`, `switch`, `try`, `throw`, `labeled` all silently fell through, so a useEffect (or any hook call) inside any of these blocks was invisible to validators. Added recursive descent for all of them — control flow collapses into the surrounding block (no real CFG terminals yet) but the bodies get lowered. 5. The hir-unit test for the article §1 example used the single- setter-call shape that the round-2 scoping fix now defers on; updated to the multi-statement-with-locals form to keep the coverage on the validator's unique path. All 490 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
5359e36 to
d6a3ce3
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 13, 2026
Five issues found on a fresh deep review of the HIR port (PR #164): 1. inferTypes conflated useState/useMemo/useContext returns under ReactType "Object". The PropertyLoad index-0/index-1 → StateValue/ StateSetter branch keyed off "Object", so a destructure like `const [n, runIt] = useMemo(...)` would tag `runIt` as a StateSetter and the validators would treat `runIt()` as a setState call. Fix: add a distinct `StateTuple` ReactType, gate the PropertyLoad branch on it. Regression test added. 2. The HIR rules duplicated `noDerivedStateEffect` on the canonical single-setter-call shape (article §1 fullName example), producing two diagnostics on the same line. Fix: `hir-no-derived-computations-in-effects` now defers when the effect body has no intermediate local bindings (StoreLocal in the inner HIR), preserving the multi-statement-with-locals path the AST walker can't see through. `hir-no-set-state-in-effect` stays unscoped because distinguishing direct vs aliased setter calls reliably from the HIR is not workable at v1; the duplicate on the simplest shape is documented as a known v1 limitation. 3. `lowerExpression` silently dropped `SpreadElement` arguments (`f(...args)` lowered to a CallExpression with the spread missing). Extracted `lowerCallArguments` that unwraps the spread's `argument` so the operand identity is still threaded through — losing only the spread shape (not modeled in v1) but keeping setState/alias propagation working through spread args. 4. `lowerStatement` only handled VariableDeclaration, ExpressionStatement, ReturnStatement, BlockStatement, IfStatement, and the function- declaration shapes. `for`, `while`, `do-while`, `for-of`, `for-in`, `switch`, `try`, `throw`, `labeled` all silently fell through, so a useEffect (or any hook call) inside any of these blocks was invisible to validators. Added recursive descent for all of them — control flow collapses into the surrounding block (no real CFG terminals yet) but the bodies get lowered. 5. The hir-unit test for the article §1 example used the single- setter-call shape that the round-2 scoping fix now defers on; updated to the multi-statement-with-locals form to keep the coverage on the validator's unique path. All 490 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
d6a3ce3 to
2fe2042
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 13, 2026
Five issues found on a fresh deep review of the HIR port (PR #164): 1. inferTypes conflated useState/useMemo/useContext returns under ReactType "Object". The PropertyLoad index-0/index-1 → StateValue/ StateSetter branch keyed off "Object", so a destructure like `const [n, runIt] = useMemo(...)` would tag `runIt` as a StateSetter and the validators would treat `runIt()` as a setState call. Fix: add a distinct `StateTuple` ReactType, gate the PropertyLoad branch on it. Regression test added. 2. The HIR rules duplicated `noDerivedStateEffect` on the canonical single-setter-call shape (article §1 fullName example), producing two diagnostics on the same line. Fix: `hir-no-derived-computations-in-effects` now defers when the effect body has no intermediate local bindings (StoreLocal in the inner HIR), preserving the multi-statement-with-locals path the AST walker can't see through. `hir-no-set-state-in-effect` stays unscoped because distinguishing direct vs aliased setter calls reliably from the HIR is not workable at v1; the duplicate on the simplest shape is documented as a known v1 limitation. 3. `lowerExpression` silently dropped `SpreadElement` arguments (`f(...args)` lowered to a CallExpression with the spread missing). Extracted `lowerCallArguments` that unwraps the spread's `argument` so the operand identity is still threaded through — losing only the spread shape (not modeled in v1) but keeping setState/alias propagation working through spread args. 4. `lowerStatement` only handled VariableDeclaration, ExpressionStatement, ReturnStatement, BlockStatement, IfStatement, and the function- declaration shapes. `for`, `while`, `do-while`, `for-of`, `for-in`, `switch`, `try`, `throw`, `labeled` all silently fell through, so a useEffect (or any hook call) inside any of these blocks was invisible to validators. Added recursive descent for all of them — control flow collapses into the surrounding block (no real CFG terminals yet) but the bodies get lowered. 5. The hir-unit test for the article §1 example used the single- setter-call shape that the round-2 scoping fix now defers on; updated to the multi-statement-with-locals form to keep the coverage on the validator's unique path. All 490 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2fe2042 to
be5aabb
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 13, 2026
Rebased PR #164 onto main after a chain of refactors (#218, #220, #221, #223, #224, #226, #227) split the monolithic src/utils + oxlint-config.ts into per-feature modules. Re-applied the HIR additions to the new locations: - packages/react-doctor/src/plugin/hir/{types,lower,infer-types, runner,validators,index}.ts — unchanged from prior revision - Rule registration moved from src/oxlint-config.ts to src/core/runners/oxlint/rule-maps.ts (GLOBAL_REACT_DOCTOR_RULES) - Help / category metadata moved to src/core/runners/run-oxlint.ts - Plugin index import + rules map updated for the new export paths - Tests now import from src/core/runners/run-oxlint.js All 744 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
be5aabb to
ebd49c9
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 13, 2026
Rebased PR #164 onto main after a chain of refactors (#218, #220, oxlint-config.ts into per-feature modules. Re-applied the HIR additions to the new locations: - packages/react-doctor/src/plugin/hir/{types,lower,infer-types, runner,validators,index}.ts — unchanged from prior revision - Rule registration moved from src/oxlint-config.ts to src/core/runners/oxlint/rule-maps.ts (GLOBAL_REACT_DOCTOR_RULES) - Help / category metadata moved to src/core/runners/run-oxlint.ts - Plugin index import + rules map updated for the new export paths - Tests now import from src/core/runners/run-oxlint.js All 744 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
ebd49c9 to
3edfd31
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 14, 2026
Rebased PR #164 onto main after a chain of refactors (#218, #220, oxlint-config.ts into per-feature modules. Re-applied the HIR additions to the new locations: - packages/react-doctor/src/plugin/hir/{types,lower,infer-types, runner,validators,index}.ts — unchanged from prior revision - Rule registration moved from src/oxlint-config.ts to src/core/runners/oxlint/rule-maps.ts (GLOBAL_REACT_DOCTOR_RULES) - Help / category metadata moved to src/core/runners/run-oxlint.ts - Plugin index import + rules map updated for the new export paths - Tests now import from src/core/runners/run-oxlint.js All 744 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
a6c2c8e to
c189609
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 14, 2026
Rebased PR #164 onto main after a chain of refactors (#218, #220, oxlint-config.ts into per-feature modules. Re-applied the HIR additions to the new locations: - packages/react-doctor/src/plugin/hir/{types,lower,infer-types, runner,validators,index}.ts — unchanged from prior revision - Rule registration moved from src/oxlint-config.ts to src/core/runners/oxlint/rule-maps.ts (GLOBAL_REACT_DOCTOR_RULES) - Help / category metadata moved to src/core/runners/run-oxlint.ts - Plugin index import + rules map updated for the new export paths - Tests now import from src/core/runners/run-oxlint.js All 744 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
c189609 to
aa13731
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 14, 2026
Rebased PR #164 onto main after a chain of refactors (#218, #220, oxlint-config.ts into per-feature modules. Re-applied the HIR additions to the new locations: - packages/react-doctor/src/plugin/hir/{types,lower,infer-types, runner,validators,index}.ts — unchanged from prior revision - Rule registration moved from src/oxlint-config.ts to src/core/runners/oxlint/rule-maps.ts (GLOBAL_REACT_DOCTOR_RULES) - Help / category metadata moved to src/core/runners/run-oxlint.ts - Plugin index import + rules map updated for the new export paths - Tests now import from src/core/runners/run-oxlint.js All 744 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
aa13731 to
b56e4c2
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 14, 2026
Rebased PR #164 onto main after a chain of refactors (#218, #220, oxlint-config.ts into per-feature modules. Re-applied the HIR additions to the new locations: - packages/react-doctor/src/plugin/hir/{types,lower,infer-types, runner,validators,index}.ts — unchanged from prior revision - Rule registration moved from src/oxlint-config.ts to src/core/runners/oxlint/rule-maps.ts (GLOBAL_REACT_DOCTOR_RULES) - Help / category metadata moved to src/core/runners/run-oxlint.ts - Plugin index import + rules map updated for the new export paths - Tests now import from src/core/runners/run-oxlint.js All 744 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
b56e4c2 to
c1c6323
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 15, 2026
Rebased PR #164 onto main after a chain of refactors (#218, #220, oxlint-config.ts into per-feature modules. Re-applied the HIR additions to the new locations: - packages/react-doctor/src/plugin/hir/{types,lower,infer-types, runner,validators,index}.ts — unchanged from prior revision - Rule registration moved from src/oxlint-config.ts to src/core/runners/oxlint/rule-maps.ts (GLOBAL_REACT_DOCTOR_RULES) - Help / category metadata moved to src/core/runners/run-oxlint.ts - Plugin index import + rules map updated for the new export paths - Tests now import from src/core/runners/run-oxlint.js All 744 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
c1c6323 to
4b03893
Compare
cursor Bot
pushed a commit
that referenced
this pull request
May 15, 2026
Rebased PR #164 onto main after a chain of refactors (#218, #220, oxlint-config.ts into per-feature modules. Re-applied the HIR additions to the new locations: - packages/react-doctor/src/plugin/hir/{types,lower,infer-types, runner,validators,index}.ts — unchanged from prior revision - Rule registration moved from src/oxlint-config.ts to src/core/runners/oxlint/rule-maps.ts (GLOBAL_REACT_DOCTOR_RULES) - Help / category metadata moved to src/core/runners/run-oxlint.ts - Plugin index import + rules map updated for the new export paths - Tests now import from src/core/runners/run-oxlint.js All 744 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
4b03893 to
0d12d1e
Compare
Rebased PR #164 onto main after a chain of refactors (#218, #220, oxlint-config.ts into per-feature modules. Re-applied the HIR additions to the new locations: - packages/react-doctor/src/plugin/hir/{types,lower,infer-types, runner,validators,index}.ts — unchanged from prior revision - Rule registration moved from src/oxlint-config.ts to src/core/runners/oxlint/rule-maps.ts (GLOBAL_REACT_DOCTOR_RULES) - Help / category metadata moved to src/core/runners/run-oxlint.ts - Plugin index import + rules map updated for the new export paths - Tests now import from src/core/runners/run-oxlint.js All 744 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
PR #235 dropped the [key: string]: any escape hatch from EsTreeNode, so the HIR lower pass — which walks ESTree as a generic graph and reads properties dynamically — no longer type-checks against the narrowed Node type. Two changes: 1. lower.ts: introduce a local `AnyNode = EsTreeNode & Record<string, any>` alias and use it throughout the lowering passes. Runtime `node.type === "X"` checks still gate every branch — only the compiler-time access is widened. 2. runner.ts: tighten the visitor signatures to EsTreeNodeOfType<"..."> so the per-visitor calls compile against the narrowed shape. All 744 tests pass; lint, typecheck, format clean. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
0d12d1e to
02e1bbd
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Ports the React Compiler's High-level IR (HIR) infrastructure into
react-doctoras a foundation for more precise rule analysis, plus two initial validators ported from the compiler:hir-no-set-state-in-effect: flagssetStatecalls insideuseEffectbodies, including when the setter is propagated throughconst x = setXaliasing or wrapped in auseEffectEvent/ innerFunctionExpression.hir-no-derived-computations-in-effects: flags effects whose captures are entirely deps + setters and whose body has at least one intermediateconst(the unique path the AST walker can't see through).What got ported
plugin/hir/types.ts):HIRFunction,BasicBlock,Instruction,Place,Identifier,ReactType(≈20 React-aware tags including the round-5StateTuple), withoriginNodeonPlacefor diagnostic precision.plugin/hir/lower.ts): ESTree → HIR, parent-chained scope environments,FunctionExpression.capturedPlaces, full statement coverage (round-5 addedfor/while/do-while/for-of/for-in/switch/try/throw/labeled),SpreadElementunwrapping in call args.plugin/hir/infer-types.ts): hook recognition viaLoadGlobalname → ReactType, propagation throughLoadLocal/StoreLocal/PropertyLoad. Round-5 introducesStateTupleso onlyuseStatereturns get the indexedStateValue/StateSettertagging —useMemo/useContextreturns no longer leak into that branch.plugin/hir/validators/): two ports, with shape-defer logic that lets the existing AST walker handle the simple cases.plugin/hir/runner.ts): per-componentWeakMap<EsTreeNode, HIRFunction>cache +originNode-based diagnostic anchoring so reports point at the offendingsetX(...)call site.Round 5 — deep code review fixes
Five issues found on a fresh review of the round-4 code:
inferTypesconflateduseState/useMemo/useContextreturns asObject; the indexed-PropertyLoad branch tagged[a, b] = useMemo(...)as[StateValue, StateSetter], leading to false-positive setState detectionStateTupleReactType gates the indexed-PropertyLoad branch touseStateonly; regression test foruseMemotuple destructurehir-no-derived-computations-in-effectsduplicatednoDerivedStateEffecton the article §1 fullName exampleStoreLocal(single-setter-call shape) — keeps the multi-statement-with-locals path that uniquely needs HIR.hir-no-set-state-in-effectleft at warn with a known-overlap note inoxlint-config.ts, since reliably distinguishing direct vs aliased setters at v1 isn't workablelowerExpressionsilently droppedSpreadElementargumentslowerCallArgumentsunwraps the spread'sargumentso operand identity is preserved through setState propagationlowerStatementignoredfor/while/do-while/for-of/for-in/switch/try/throw/labeled— hooks inside any of these blocks were invisiblehir-unit.test.tsarticle-§1 case used the single-setter-call shape that fix #2 now defers onValidation
pnpm lintcleanpnpm typecheckcleanpnpm formatcleanKnown v1 limitations (documented in code)
letreassignments are not modeled as non-reactive (HIR is non-SSA).import { useState as useS }is not recognized — name-based heuristic onLoadGlobal.hir-no-set-state-in-effectoverlapsnoDerivedStateEffecton the simplest shape; users can disable either viareact-doctor.config.json.Architecture summary
Future ports (no/lazy-init-in-render, no-create-ref-during-render, deeper effect-event analyses) plug in as additional validators reading the same HIR.