Skip to content

chore: triage-driven rule severity and scope adjustments#536

Merged
NisargIO merged 7 commits into
mainfrom
chore/triage-rule-tuning-may-27
May 29, 2026
Merged

chore: triage-driven rule severity and scope adjustments#536
NisargIO merged 7 commits into
mainfrom
chore/triage-rule-tuning-may-27

Conversation

@NisargIO

@NisargIO NisargIO commented May 28, 2026

Copy link
Copy Markdown
Member

Summary

Four independent rule tuning changes from a triage session, each scoped to one commit on the branch:

  • fix(a11y): skip alt-text on Next.js metadata image route filesopengraph-image.tsx / twitter-image.tsx / icon.tsx / apple-icon.tsx (with optional numeric suffix) rasterize JSX into a static image via next/og; DOM-flavoured a11y semantics don't apply. Extracts a shared isNextjsMetadataImageRouteFilename util so future rules can opt in, and ships a regression suite for the basenames, extensions, numeric suffixes, and the negative case (my-opengraph-image.tsx still flags).
  • chore(architecture): demote only-export-components to warn — co-located stable constants are a Fast Refresh hint, not a hard failure. Severity now reflects the actual cost.
  • chore(architecture): unregister react-compiler-destructure-method — the memoization premise didn't hold up for the three hooks it targeted (useRouter, useSearchParams, useNavigation) — all of which return stable references. On Pages Router (next/router), destructuring push captures a stale reference. Adds a RULE_IDS_TO_SKIP_REGISTRATION skiplist in the registry generator (with a required justification per entry) so we can park a rule without deleting it; the implementation, regression suite (describe.skip'd), and fixture seed all stay so re-enabling is a one-line edit.
  • fix(state-and-effects): promote no-adjust-state-on-prop-change to error — the pattern always forces an extra render with a stale UI between the two commits; there is no benign instance. Promotes to error, rewrites the recommendation + message in the authoritative "what's wrong → why → fix" shape used by sibling error-level effect rules, and documents the intentional divergence from upstream (type: "suggestion") in effect/SOURCE.md.

Test plan

  • pnpm format:check (auto-formatted the new util)
  • pnpm lint — no new errors; existing fixture warnings unchanged
  • pnpm typecheck — green
  • Targeted vp test run on the touched files:
    • alt-text + alt-text.regressions + only-export-components + no-adjust-state-on-prop-change → 253 passed, 4 skipped
    • scan-resilience + architecture-rules + vercel-skill-parity → 55 passed, 3 skipped (the describe.skip'd react-compiler-destructure-method suite)
  • CI to confirm no downstream surprises

Made with Cursor


Note

Medium Risk
Changes default lint severities and stops shipping one rule, which can shift CI pass/fail and diagnostic counts for consumers on default configs.

Overview
This PR tunes lint severity and scope across four areas after triage.

Next.js metadata image routes: alt-text now no-ops on App Router files whose basenames match opengraph-image, twitter-image, icon, or apple-icon (optional numeric suffix, common extensions) via new isNextjsMetadataImageRouteFilename, with regression tests for skip vs. still-flag cases.

Architecture: only-export-components is demoted from error → warn (Fast Refresh guidance). react-compiler-destructure-method is removed from the generated registry through a new RULE_IDS_TO_SKIP_REGISTRATION skiplist in generate-rule-registry.mjs while keeping source, skipped integration tests, and fixture scaffolding for easy re-enable.

State & effects: no-adjust-state-on-prop-change is promoted to error with clearer recommendation/message copy; scan-resilience now expects that rule at error while the other seven ported effect rules stay warn, documented in effect/SOURCE.md.

Fixtures and parity tests are updated so react-compiler-destructure-method is no longer expected in default scans.

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

NisargIO and others added 4 commits May 28, 2026 00:08
Next.js App Router treats `opengraph-image.tsx`, `twitter-image.tsx`,
`icon.tsx`, and `apple-icon.tsx` (with optional numeric suffix) as
metadata image routes. Their default export returns an `ImageResponse`
from `next/og` that rasterizes JSX into a static PNG/JPG — no browser
DOM, no screen reader, so `alt` / `aria-*` are unactionable noise.

Extract `isNextjsMetadataImageRouteFilename` as a shared util (so other
DOM-flavoured a11y rules can opt in later) and early-return from
`alt-text.create()` when the filename matches. Adds a regression suite
covering both extensions, numeric suffixes, and the negative case
(`my-opengraph-image.tsx` still flags).

Co-authored-by: Cursor <cursoragent@cursor.com>
Mixing non-component exports in a component module is a Fast Refresh
hint, not a hard failure — projects routinely co-locate stable
constants alongside components and ship fine. Drop from `error` to
`warn` so triage output reflects the actual severity gradient.

Co-authored-by: Cursor <cursoragent@cursor.com>
The React-Compiler memoization premise didn't hold up for the three
canonical hooks the rule targeted: `useRouter`, `useSearchParams`, and
`useNavigation` all return stable references, so destructuring their
methods produces no measurable compiler win. On Pages Router
(`next/router`) destructuring `push` is actively wrong — it captures
a stale reference.

Introduce a `RULE_IDS_TO_SKIP_REGISTRATION` skiplist in the registry
generator (with a required justification comment per entry) so we can
park a rule without deleting its implementation. Drops the rule from
the registered set (297 → 296), `describe.skip`'s the regression
suite, and prunes the rule from the vercel-skill parity table. The
fixture seed is kept so re-enabling is a one-line skiplist edit.

Co-authored-by: Cursor <cursoragent@cursor.com>
Adjusting derived state inside a useEffect when a prop changes always
forces an extra render with a stale UI between the two commits —
there is no benign instance of this pattern, so a warning understates
the cost. Promote to `severity: "error"` and rewrite the recommendation
and message in the authoritative "what's wrong → why → fix" shape used
by the rest of the error-level effect rules.

Upstream `eslint-plugin-react-you-might-not-need-an-effect` ships this
as a softened `type: "suggestion"`. Document the intentional divergence
(severity + copy only — detector logic is still a 1:1 port) in
`effect/SOURCE.md`, and update the registered-severity assertion in
`scan-resilience.test.ts` to expect `error` for this id.

Co-authored-by: Cursor <cursoragent@cursor.com>
@NisargIO NisargIO requested review from aidenybai and rayhanadev and removed request for aidenybai May 28, 2026 22:47
@rayhanadev

Copy link
Copy Markdown
Member

bugbot run

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 8c93b0a. Configure here.

@rayhanadev rayhanadev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lgtm just small style nits, toss at claude and ask to fix, feel free to merge once done o7

Comment thread packages/core/tests/fixtures/basic-react/src/transient-and-async-issues.tsx Outdated
Comment thread packages/core/tests/fixtures/basic-react/src/transient-and-async-issues.tsx Outdated
Comment thread packages/oxlint-plugin-react-doctor/src/plugin/rules/a11y/alt-text.ts Outdated
NisargIO and others added 3 commits May 28, 2026 16:56
Per @rayhanadev's review:
- drop the two `transient-and-async-issues.tsx` block comments around
  `useRouter` / `LoginLink` (the test files document the rule's intent
  on their own)
- drop the duplicate early-return rationale in `alt-text.ts` (the
  shared util's header doc already explains the why)
- inline the metadata-image basename regex into the helper to remove
  the named-constant indirection

Co-authored-by: Cursor <cursoragent@cursor.com>
After merging main, `RuleContext` no longer exposes `getFilename` (PR
\#549 made it `Omit<BaseRuleContext, "getFilename">` and exposed the
absolute path as a property). Switch the alt-text early-return to read
`context.filename` to match the new API.

Co-authored-by: Cursor <cursoragent@cursor.com>
@NisargIO NisargIO merged commit 3a8ef93 into main May 29, 2026
13 checks passed
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.

2 participants