fix: rules-of-hooks FP on forwardRef/memo callbacks under non-PascalCase bindings#794
Conversation
…non-PascalCase bindings Co-Authored-By: Aiden Bai <aiden.bai05@gmail.com>
🤖 Devin AI EngineerI'll be helping with this pull request! Here's what you should know: ✅ I will automatically:
Note: I can only respond to comments from users who have write access to this repository. ⚙️ Control Options:
|
commit: |
|
No React Doctor issues found. 🎉 Reviewed by React Doctor for commit |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f9849a5. Configure here.
|
Test results — built the Escalation: I could not locate the RDE harness (OSS recording-repo manifest from
Test 1 — exact reported shape (CLI E2E)Fixture main CLI: branch CLI: Zero diagnostics on the HoC callbacks; Test 2 — OSS no-regression scan
No branch-only diagnostics, no disappearances. None of these repos contain the non-PascalCase HoC-binding shape in source (it's typical of compiled/generated bundles), hence the fixture in Test 1 carries the FP→0 proof; Test 3 — Regression: unit suite parityFull CI: 18 passed, 0 failed. Devin session |
… it with exhaustive-deps Review fixes for the forwardRef/memo non-PascalCase binding FP fix: - Only the FIRST argument of memo/forwardRef is promoted to a component — memo's second argument is the props comparator, and promoting it silenced hooks-in-comparator violations that fired before this branch. - Restore `hasResolvedName`'s documented meaning (true iff a name was inferred) by checking `isComponentOrHook` before the anonymous walk-out instead of overloading the flag for HoC callbacks. - Extract `isReactHocCallbackArgument` into a shared util and apply it to exhaustive-deps' `findEnclosingComponentOrHookFunction`, which had the same FP: factory-scope captures inside a `const _Wrapped = forwardRef(...)` callback were reported as missing deps. - Strengthen the still-flags regression tests to assert the exact diagnostic message and cover the memo-comparator case.
|
Pushed review fixes in 480f578:
Verification: all 787 rules-of-hooks + exhaustive-deps tests pass; the full plugin suite's 39 failures are byte-identical to this branch's pre-fix baseline (same preexisting set); typecheck, lint, and format:check pass. |

Summary
rules-of-hooksflagged hooks inside React HoC render callbacks whenever the resulting binding wasn't PascalCase — the exact shape compiled/generated mobile code uses:The component-or-hook decision keyed only on the inferred binding name (
inferFunctionNamesees throughforwardRef/memoto_Wrapped, andisReactComponentNamerejects_-prefixed names). But the callback passed directly tomemo(...)/forwardRef(...)(incl.React.*forms, and nestedmemo(forwardRef(...))) is a component by construction, regardless of the binding name — same stance as upstream eslint-plugin-react-hooks'isForwardRefCallback/isMemoCallback.findEnclosingComponentOrHookFunction(useEffectEvent same-scope check) gets the same treatment. Arbitrary non-React wrappers (trackEvents(fn)) are NOT promoted, so a genuinely non-component_helper()calling hooks still fires.Eval results
Full
oxlint-plugin-react-doctorsuite: 39 failures on this branch — byte-identical to the 39 preexisting failures onmain(no new misses on the adversarial suite). New regression tests inrules-of-hooks.regressions.test.tscover the forwardRef/memo/React.forwardRef/nested shapes plus both still-fires cases.Test plan
vp test run src/plugin/rules/react-builtins/rules-of-hooks*.test.ts(167 passed)pnpm --filter oxlint-plugin-react-doctor typecheckpnpm lintLink to Devin session: https://app.devin.ai/sessions/9f543a0266b74767b52eb6b246c652d4
Requested by: @aidenybai
Note
Low Risk
Lint-rule heuristic change with targeted regression tests; no runtime or auth/data impact, and real violations (memo comparator, non-React HoCs) remain covered.
Overview
Fixes false positives in
rules-of-hooksandexhaustive-depswhen hooks run insideforwardRef/memorender callbacks assigned to non-PascalCase names (e.g.const _Wrapped = forwardRef(...)).Adds
isReactHocCallbackArgument, which treats the first argument to known React HoCs (memo,forwardRef, includingReact.*) as a component by construction.rules-of-hooksuses it infindEnclosingFunctionInfoandfindEnclosingComponentOrHookFunction, and tightens the non-component branch so HoC callbacks are not misclassified via inferred binding names.exhaustive-depsuses the same helper so factory-scope captures (e.g.logger) are not reported as missing deps when the component boundary was wrong.memo’s second-argument comparator, arbitrary wrappers, and plain_helperfunctions are unchanged and still report. New regression tests cover the fixed and still-firing cases.Reviewed by Cursor Bugbot for commit 480f578. Bugbot is set up for automated code reviews on this repo. Configure here.