Skip to content

fix(react-doctor): forward reactMajorVersion in programmatic diagnose() + cleanups#174

Merged
aidenybai merged 2 commits intomainfrom
audit-fixes-4
May 8, 2026
Merged

fix(react-doctor): forward reactMajorVersion in programmatic diagnose() + cleanups#174
aidenybai merged 2 commits intomainfrom
audit-fixes-4

Conversation

@aidenybai
Copy link
Copy Markdown
Member

@aidenybai aidenybai commented May 8, 2026

Summary

Audit pass on main after #170/#171/#172. One real regression introduced by #172, plus a stale doc, a duplicate-export alias, and a code-org cleanup.

H1 — prefer-use-effect-event silently disabled in the programmatic API (regression from #172)

diagnose() in packages/react-doctor/src/index.ts did not forward reactMajorVersion to runOxlint. After the directional version-gating change in #172, every "prefer-newer-api" rule (today: prefer-use-effect-event) was silently skipped for every programmatic API consumer (e.g. react-doctor/api), even on React 19+ projects. The CLI (scan.ts:589) was unaffected because it always passed the version explicitly.

Fix is one line + one import — mirror what scan.ts already does.

Added tests/diagnose.test.ts with:

  • a regression test that asserts prefer-use-effect-event fires on a React 19 fixture via diagnose()
  • a symmetric guard that it stays skipped when the React version can't be parsed (e.g. a github: range), proving the version-gate boundary is honored

H2 — stale docstring on runOxlint's reactMajorVersion

The doc still claimed "null means unknown — leave those rules enabled" but after #172 the null branch is directional (deprecation-warning rules stay on, prefer-newer-api rules go off). Updated to reflect the actual semantics.

M1 — duplicate alias SUB_HANDLER_DIRECT_CALLEE_NAMES

SUB_HANDLER_DIRECT_CALLEE_NAMES was just = TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES. Two names existed in different files for narrative reasons; knip flagged it. Collapsed to the canonical name and updated the one consumer (isCallExpressionWithSubHandlerCallee).

L1 — moved walkInsideStatementBlocks to plugin/helpers.ts

Already had four call sites in state-and-effects.ts. Colocated with its sibling walkAst so future rules discover it.

L2 (audit finding) — INTENTIONALLY NOT TAKEN

I proposed receiver-gating post/put/patch in EVENT_TRIGGERED_SIDE_EFFECT_CALLEES. The canonical "You Might Not Need an Effect" §6 example is post(jsonToSubmit) as a bare callee, so removing those names breaks textbook detection (3 existing tests / fixtures fail). Documented the trade-off in the constants.ts docstring instead.

Test plan

  • pnpm typecheck — clean
  • pnpm lint — 0 warnings, 0 errors
  • pnpm format:check — clean
  • pnpm test — 642 passing (640 baseline + 2 new diagnose() API regressions)

Made with Cursor


Note

Low Risk
Low risk: mostly wiring/cleanup changes plus new regression tests; behavior change is limited to version-gating which only affects which lint rules fire for programmatic diagnose() consumers.

Overview
Fixes a regression where programmatic diagnose() scans didn’t pass the detected React major version into runOxlint, causing React-version-gated “prefer-newer-api” rules (e.g. prefer-use-effect-event) to be skipped even on React 19+ projects.

Adds regression tests covering the React 19 firing case and the “unknown/unparseable version” skip case, updates the reactMajorVersion option docs to reflect directional gating semantics, and does small internal cleanups (dedupe timer/scheduler callee naming, move walkInsideStatementBlocks into shared helpers, and minor script formatting).

Reviewed by Cursor Bugbot for commit 95ae940. Bugbot is set up for automated code reviews on this repo. Configure here.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 8, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
react-doctor-website Ready Ready Preview, Comment May 8, 2026 10:51am

aidenybai and others added 2 commits May 8, 2026 03:50
…() + cleanups

H1 (regression introduced by #172) — `diagnose()` in `src/index.ts`
forgot to forward `reactMajorVersion` to `runOxlint`. After the
directional version-gating change in #172, that meant every
"prefer-newer-api" rule (today: `prefer-use-effect-event`) was silently
skipped for every programmatic API consumer, even on React 19+
projects. The CLI (`scan.ts`) was unaffected because it always passed
the version explicitly.

Fix is one line + one import — mirror what `scan.ts` already does.
Added `tests/diagnose.test.ts` with a regression test that asserts
`prefer-use-effect-event` fires on a React 19 fixture, plus a
symmetric guard that it stays skipped when the React version can't
be parsed (e.g. a github: range).

H2 — updated the stale docstring on `runOxlint`'s `reactMajorVersion`
field. The doc still claimed "`null` means unknown — leave those
rules enabled" but after #172 the null branch is directional
(deprecation-warning rules stay on, prefer-newer-api rules go off).

M1 — `SUB_HANDLER_DIRECT_CALLEE_NAMES` was just an alias of
`TIMER_AND_SCHEDULER_DIRECT_CALLEE_NAMES`. Both names existed for
narrative reasons in different files; knip flagged the duplicate
export. Collapsed to the canonical name and updated the one consumer
(`isCallExpressionWithSubHandlerCallee` in state-and-effects).

L1 — moved `walkInsideStatementBlocks` from inline in
`state-and-effects.ts` to `plugin/helpers.ts` next to its sibling
`walkAst`. It already had four call sites and is the natural
"synchronous-only" walker for any rule asking what runs inside an
effect's own body — colocating with `walkAst` makes future rules
discover it.

L2 (audit finding) — proposed receiver-gating
`post`/`put`/`patch` in `EVENT_TRIGGERED_SIDE_EFFECT_CALLEES`
INTENTIONALLY NOT TAKEN. The canonical "You Might Not Need an Effect"
§6 example is `post(jsonToSubmit)` as a bare callee, so removing
those names breaks textbook detection (3 existing tests / fixtures).
Documented the trade-off in the constants.ts docstring.

Validation
- 642 tests passing (640 baseline + 2 new diagnose-API regressions)
- typecheck / lint / format clean

Co-authored-by: Cursor <cursoragent@cursor.com>
Pre-existing formatting issue introduced in 6afdc04 — the script was
committed unformatted, breaking `pnpm format:check` on every PR
branched off main since. Picked up by rebasing this PR.

Co-authored-by: Cursor <cursoragent@cursor.com>
@aidenybai aidenybai merged commit 4fb4d27 into main May 8, 2026
6 checks passed
@aidenybai aidenybai deleted the audit-fixes-4 branch May 8, 2026 10:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant