mirror of
https://github.com/millionco/react-doctor.git
synced 2026-09-14 20:00:24 +08:00
codex/fix-1724
43 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
6416370836 | feat(rules): add UI-library composition and correctness rules behind capability gates (#1654) | ||
|
|
8c2f03aea9 |
feat: make React cleanup first-class (#1624)
* feat: make React cleanup first-class * refactor: remove editor integrations * fix: harden React cleanup analysis * fix: detect default export duplication roots * fix: unwrap typed duplication roots * feat: add opt-in project analysis rules * fix: canonicalize project analysis paths * fix: harden project analysis precision * fix: recognize cross-platform project entries * fix: eliminate project analysis false positives * fix: harden project analysis reachability * fix: canonicalize project analysis inputs * fix: resolve project analysis review findings * fix: eliminate residual project analysis false positives * fix: ignore commented registry previews * fix: eliminate project analysis false positives * fix: normalize project analysis paths across platforms * fix: normalize Nextra theme path identity * test: canonicalize convention fixture paths * fix: preserve project analysis provenance * fix: harden project analysis precision * fix: honor project analysis boundaries * fix: recognize conditional config plugins * fix: recognize executable project references * fix: recognize Stencil tool contracts * fix: recognize nested tool references * fix: recognize project setup contracts * fix: recognize generated and local package consumers * fix: recognize static template package references * fix: recognize nested package runtime contracts * fix: close project analysis parser gaps * fix: parse project conventions structurally * refactor: replace structural scanners with parsers * fix: recognize functional Next CSS config * fix: close remaining project analysis gaps * fix: apply tag filters to project analysis * fix: preserve embedded source positions * fix: validate static config helper bindings * fix: bound runtime directory discovery * fix: close final dependency analysis gaps * fix: preserve declaration dependency references * chore: refresh generated rule metadata * fix: make project analysis portable and bounded * test: stabilize cleanup scaling guard * refactor: parse project syntax with oxc * fix: normalize native filesystem paths * fix: separate path identity from report paths * fix: match project files by filesystem identity * fix: match build glob files by package identity * fix: use native path keys for file identity * fix: canonicalize Windows file identities * fix: canonicalize package ownership paths * test: inspect Windows path identities * test: trace Windows package ownership * fix: keep Windows path identities consistent * fix: classify test contracts by normalized path * fix: scope test contracts by canonical package path * fix: keep test package graphs conservative * test: keep React complexity advisory |
||
|
|
7b7bfe7c1e | fix: faithfully port upstream React rules (#1642) | ||
|
|
b49f499840 |
fix(rules): port no-multi-comp faithfully (#1641)
* fix(rules): port no-multi-comp faithfully * refactor(rules): preserve component rule ordering |
||
|
|
8dfb013067 | Add comprehensive Three.js and R3F diagnostics (#1629) | ||
|
|
13138a4af5 | refactor: simplify internals across the workspace (#1590) | ||
|
|
44922d6782 |
FPFNFIX82 Fix form and fragment diagnostics (#1561)
* fix(rules): correct form and fragment diagnostics * fix(rules): handle fragment review edge cases * fix: handle document-top fragment links * fix(a11y): parse ids from html tags only * fix(a11y): resolve static fragment aliases * fix(a11y): retain known fragment targets * fix(a11y): tokenize html id attributes * fix: preserve known fragment IDs in mixed spreads * fix: preserve known fragment ID defaults |
||
|
|
2db2a97283 | perf: reduce scan startup and workspace contention (#1533) | ||
|
|
846c2df844 |
feat: add parsed Three.js shader lint rules (#1455)
* feat: add parsed Three.js shader lint rules * fix: tighten shader rule precision * fix: address shader rule review findings * fix: harden parsed shader edge cases * fix: harden shader variant analysis * fix: complete parsed shader edge coverage * fix: close shader control-flow gaps * fix: tighten shader interface analysis * fix: recognize GLSL3 integer samplers * fix: close shader control-flow gaps * fix: close remaining shader rule gaps * fix: close shader review gaps * fix: track shader cache key invalidation * fix: handle shader material review edge cases * fix: close remaining shader review gaps * fix: skip uncertain shader position writes * fix: harden shader position analysis * fix: track divergent shader loop controls * fix: track parameter shader variants * fix: complete shader variant detection * fix: identify and clean up parity sandboxes * fix: tolerate Daytona cleanup races * fix: restore complete parity corpus * fix: tighten Three shader diagnostics * fix: download large eval reports * fix: trace shader variant aliases |
||
|
|
db9d300343 | Make all design rules opt-in by default (#1434) | ||
|
|
1e67af03fe | feat: add deterministic design diagnostics (#1428) | ||
|
|
76263ecc08 |
Add direct Three.js and WebGL diagnostics (#1424)
* add direct Three.js and WebGL rules * fix parity-discovered config cycle * fix Three render construction false positives * fix(rules): preserve JSX-referenced Three components * fix(rules): address Three.js review findings * feat(rules): add Three.js resource cleanup diagnostics |
||
|
|
f5f13b877a | feat(rules): add version-aware React Router diagnostics (#1411) | ||
|
|
5b468f8a92 | feat(rules): add React Three Fiber diagnostics (#1371) | ||
|
|
fc1b3a3984 | feat(rules): add version-gated Ink diagnostics (#1404) | ||
|
|
0b0b5ac523 |
feat(rules): add deterministic UI, accessibility, and animation diagnostics (#1337)
* feat(rules): add 10 motion, accessibility, and design lint rules
Derived from a cross-resource design reference (every principle from the
design knowledge base), deduped against the full existing rule inventory.
Motion: extend no-transition-all to the Tailwind `transition-all` class
(was inline-only) and add no-tailwind-layout-transition for arbitrary
`transition-[<layout-prop>]`.
Accessibility: no-autoplay-without-muted, no-uninformative-aria-label,
no-target-blank-without-rel, and no-low-contrast-inline-style (computes the
real WCAG 2.1 ratio from co-located inline color + backgroundColor).
Design/Tailwind hygiene: no-redundant-display-class, prefer-truncate-shorthand,
no-full-viewport-width, and no-svg-currentcolor-with-fill-class.
All register via codegen (388 rules); 77 co-located tests pass; typecheck,
lint, and format clean.
* fix(rules): correct two false positives found in rule-validate
- no-low-contrast-inline-style: when the inline style has no `fontSize`,
the text may be sized large via a class (`text-5xl`), so fall back to the
lenient 3:1 large-text threshold instead of 4.5:1 to avoid flagging large
text that only needs 3:1. Stricter 4.5:1 applies only when the size is a
visible normal size.
- no-redundant-display-class: `<li>` defaults to `display: list-item`, not
`block`, so `block` on an `<li>` is meaningful — removed `li` from the
block-default set.
Adds regression tests for both.
* fix(rules): resolve Cursor Bugbot findings
- no-low-contrast-inline-style: use a solid `background` shorthand color as
the background (was skipped entirely, missing low-contrast pairs); skip only
gradients/images/ambiguous (both backgroundColor + background). Also treat a
string `fontWeight: "700"` as bold for the large-text threshold.
- no-transition-all: match `transition-all` as a whole Tailwind token (segment
after variant prefixes) so compound classes like `transition-all-custom` are
not falsely flagged.
- no-svg-currentcolor-with-fill-class: exclude stroke-WIDTH utilities
(`stroke-2`, `stroke-[1.5]`) — they set thickness, not color — so they no
longer false-conflict with `stroke="currentColor"`.
Adds regression tests for each.
* feat(rules): add 3 Tailwind-canonicalization rules from ui.sh guidance
- no-deprecated-tailwind-class: flag Tailwind v4 renames (bg-gradient-* →
bg-linear-*, flex-shrink-* → shrink-*, flex-grow-* → grow-*,
overflow-ellipsis → text-ellipsis). Gated on a new `tailwind:4` capability.
- no-arbitrary-px-font-size: `text-[13px]` → rem so text scales with the
user's root font size (px stays fine for border-*/outline-*).
- prefer-dvh-over-vh: `h-screen`/`min-h-screen`/`h-[100vh]` → dvh (tailwind:3.4).
Adds `tailwind:4` capability to @react-doctor/core and extracts a shared
`get-class-name-tokens` util (variant-aware token splitting), reused by
no-transition-all and no-svg-currentcolor-with-fill-class.
* fix(rules): adversarial-review false positives in the new design rules
- no-tailwind-layout-transition: match transition-[…] property names exactly
(comma-split + set) so SVG `stroke-width` / `border-width` — which merely
contain "width" — are no longer flagged as HTML layout thrash.
- no-full-viewport-width: drop `max-w-*` / `maxWidth` (a defensive cap, not the
overflow footgun); keep `w-screen`/`w-[100vw]`/`width`/`minWidth`.
- prefer-dvh-over-vh: drop `max-h-*` / `maxHeight` (a valid height cap, e.g. a
scrollable modal); keep `h-screen`/`min-h-screen`/`height`/`minHeight`.
- no-deprecated-tailwind-class: only rename `bg-gradient-to-*` → `bg-linear-to-*`
(v4 radial/conic are `bg-radial`/`bg-conic`, not `bg-linear-radial`).
Adds regression tests for each false positive.
* fix(rules): resolve thermo-review false positives
- no-svg-currentcolor-with-fill-class: only treat UNPREFIXED `fill-*`/`stroke-*`
color classes as conflicts; `hover:fill-blue-600` / `dark:fill-white` (state-
gated, no static conflict with the base `fill="currentColor"`) are no longer
flagged — a very common icon pattern.
- no-low-contrast-inline-style: reject 4-arg `rgb(0,0,0,0.5)` / slash-form alpha
(was judged opaque), and bail when the style object contains a `{...spread}`
that could override the colors. Tag `test-noise` to match sibling no-tiny-text.
- no-full-viewport-width: drop the redundant `category: "Architecture"` override
(it's the design-bucket default).
- Extract `has-jsx-spread-attribute` util; use it in the two a11y rules.
Adds regression tests for the variant-prefixed svg classes, rgb-alpha, opaque
rgb, and the style-spread bail.
* refactor(rules): deslop the design/motion/a11y rule additions
Consolidate duplication surfaced by an adversarial deslop pass; no behavior
change (the same diagnostics fire for the same inputs):
- no-autoplay-without-muted: fold isStaticallyTrue/isStaticallyFalse into one
tri-state resolveStaticBoolean, mirroring the existing parseJsxValue idiom
- no-svg-currentcolor-with-fill-class: collapse the duplicated fill/stroke
branches into a single loop over the two paint attributes
- no-low-contrast-inline-style: hoist the repeated `properties` fallback
- no-uncontrolled-input: drop the local hasJsxSpreadAttribute copy in favor of
the branch's new shared utils/has-jsx-spread-attribute, completing the extraction
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(rules): no-deprecated-tailwind-class stops flagging overflow-clip
`overflow-clip` is a current Tailwind utility for `overflow: clip` (v3.1+,
still present in v4) — it was never renamed to `text-clip`, which sets the
unrelated `text-overflow: clip`. The stray mapping produced an incorrect
"renamed in v4" suggestion (Cursor Bugbot). Only `overflow-ellipsis ->
text-ellipsis` is a real rename; keep that. Adds a regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(capabilities): require a confirmed Tailwind major for the tailwind:4 gate
`tailwind:4` gates no-deprecated-tailwind-class, which tells users a class was
renamed/removed in v4. The optimistic-on-null policy (assume latest for an
unparseable spec like `workspace:*`) is right for the suggestion rule behind
`tailwind:3.4`, but for a deprecation rule it surfaces confidently-wrong
"renamed in v4" warnings on a v3 project. Require a CONFIRMED parsed major >= 4
for tailwind:4 — favor a false negative over a false positive (Cursor Bugbot).
`tailwind:3.4` stays optimistic. Adds an unparseable-version regression test.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix(rules): resolve Bugbot/Devin review findings on design/a11y rules
- no-full-viewport-width: match variant-prefixed `md:w-screen` / `lg:min-w-screen`, mirroring the sibling `prefer-dvh-over-vh` regex (overflow happens at every breakpoint).
- no-target-blank-without-rel: resolve `rel={'noopener'}` expression-container literals instead of treating them as dynamic, so reverse-tabnabbing isn't silently missed; share one literal resolver with the `target` check.
- Extract the bare `16` root-font-size divisor into `ROOT_FONT_SIZE_PX` per the magic-number convention.
* fix(rules): no-uninformative-aria-label resolves braced string literals
Extracts no-target-blank-without-rel's inline getStringLiteralAttributeValue
into a shared util and uses it in no-uninformative-aria-label, so a static
braced literal like aria-label={'icon'} is judged instead of silently skipped
(getJsxPropStringValue only handles plain string literals). Adds regression
tests for the braced uninformative and braced descriptive cases.
Addresses Bugbot review on #850.
* test(rules): add liveness fixtures after rebase
* fix(rules): address automated review findings
* fix(rules): handle leading-dot stroke widths
* fix(rules): skip dynamic background overrides
* fix(rules): address target blank and transition reviews
* fix(rules): skip svg layout transitions
* fix(rules): preserve font line height suggestion
* fix(rules): match complete transition utilities
* fix(rules): share layout transition properties
* fix(rules): bound deprecated utility prefixes
* fix(rules): clear dynamically overridden colors
* fix(rules): tighten style and svg precision
* fix(rules): preserve competing display utilities
* fix(rules): preserve animation property scopes
* feat(rules): expand deterministic design checks
* feat(rules): add structural design diagnostics
* feat(rules): complete source-auditable design checks
* fix(rules): skip unresolved input types
* fix(rules): tighten layout and boundary detection
* fix(rules): close motion and padding gaps
* fix(rules): tighten surface and line-height proofs
* fix(rules): evaluate opaque hsl contrast
* fix(rules): honor effective static values
* fix(rules): honor final inline style declarations
* fix(rules): respect variants and share surface checks
* fix(rules): traverse fragments and preserve variant scope
* feat(rules): deepen UI quality diagnostics
* feat(rules): add deterministic animation diagnostics
* fix(rules): retain in-house accessibility checks
* feat(rules): expand deterministic UI diagnostics
* feat(rules): detect generated interface patterns
* fix(rules): respect dynamic style overrides
* feat(rules): expand deterministic design diagnostics
* fix(rules): close design precision gaps
* feat(rules): expand deterministic design and accessibility checks
* feat(rules): detect unsafe Motion API usage
* test(fuzz): remove duplicate module snippets
* feat(cli): add focused design command
* fix(design): include the complete rule bucket
* fix(core): align opt-in rule gates
* feat(rules): expand deterministic motion and interaction checks
* feat(rules): harden deterministic design diagnostics
* fix(rules): harden design expansion diagnostics
* fix(rules): apply large-corpus precision review
* fix(rules): suppress large-corpus accessibility noise
* fix(core): detect compiler in rstack configs
* fix(a11y): avoid responsive navigation false positives
* fix(rules): reduce design corpus false positives
* fix(rules): restore will-change build after rebase
* fix(rules): harden all-rules corpus precision
---------
Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
|
||
|
|
cd9ca68faa |
NEW_RULES: Add rule analysis foundation and 56 rules (#1402)
* NEW_RULES: Reapply rule analysis foundation Reapply the complete reviewed NEW_RULES stack on top of current main after the rollback. * fix(rules): harden new diagnostics against false positives * fix(rules): preserve dynamic context providers * fix(core): resolve styled-components workspace versions * fix(rules): harden new diagnostics after deep audit |
||
|
|
61bf03eedc | feat(rules): add MobX diagnostics foundation (#1405) | ||
|
|
a4eaeddefe | feat(rules): detect Valtio proxy reads during render (#1396) | ||
|
|
88114a2332 |
Revert "NEW_RULES: Add rule analysis foundation" (#1401)
Reverts #1338 in full. |
||
|
|
3018538b7c |
NEW_RULES: Add rule analysis foundation (#1338)
Replace aggregate PR #1000 with the reviewed NEW_RULES stack, including workspace capability aggregation and stable performance regression coverage. |
||
|
|
b97a92f611 |
refactor(capabilities): unify framework gating into one capability vocabulary (#976) (#978)
Co-authored-by: Aiden Bai <aiden.bai05@gmail.com> |
||
|
|
0053a0283e | Revert "feat(rules): add 10 motion, accessibility, and design lint rules (#825)" (#849) | ||
|
|
09dde181c7 |
feat(rules): add 10 motion, accessibility, and design lint rules (#825)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com> |
||
|
|
451beeb284 |
feat(rules): add 3 mined platform/correctness rules (batch 2) (#828)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com> |
||
|
|
8908f98d02 |
feat(rules): add 8 mined correctness, performance, a11y, and security rules (#826)
* feat(rules): add 8 mined correctness, performance, a11y, and security rules
Mined from a 12-cluster pass over the design/engineering brain and
synthesized into docs/rule-candidates-backlog.md. Ships the highest-signal,
lowest-noise batch, each with adversarial + FP-regression tests:
- no-call-component-as-function (Bugs)
- no-create-ref-in-function-component (Bugs)
- no-async-effect-callback (Bugs)
- no-json-parse-stringify-clone (Performance)
- img-missing-dimensions (Performance)
- no-img-lazy-with-high-fetchpriority (Performance)
- dialog-has-accessible-name (Accessibility)
- auth-token-in-web-storage (Security)
Adds the shared has-jsx-spread-attribute util and registers the three
in-house ported-bucket rules in RULES_NOT_PORTED_FROM_EXTERNAL.
* fix(rules): drop img-missing-dimensions; make component rules shadow-safe
- Drop `img-missing-dimensions`: width/height attributes are only one of
several valid ways to reserve space (CSS aspect-ratio, container sizing),
and the authoritative signal lives in stylesheets a static linter can't
see. This is a runtime check (Lighthouse `unsized-images`), so a syntax
rule would false-positive on any CSS-sized image.
- `no-call-component-as-function`: resolve the callee through its scope
symbol instead of by name, so a parameter/local shadowing a component
name is no longer flagged. The component signal is the binding's
declaration (same-file component returning JSX) or an imported binding
also rendered as JSX.
- `no-create-ref-in-function-component`: skip a shadowing local `createRef`
(only flag React's import / `React.createRef`).
- Adds shadow-safety regression tests for both.
* fix(rules): tag no-call-component-as-function test-noise (eval finding)
An OSS eval sweep (react-use, radix primitives, excalidraw, mantine;
~2.8k diagnostics) showed every real-world hit of this rule was a test
render-helper (`render(Wrapper({...}))` in `.test.tsx`/`.story.tsx`) —
harmless for hookless wrappers and the dominant firing surface. Tag it
`test-noise` so it skips test/story/playground files and only nags
shipped code. The sweep also confirmed dialog-has-accessible-name (2 real
a11y bugs in excalidraw) and no-create-ref-in-function-component (1 real
bug in a mantine story) as true positives with zero false positives.
|
||
|
|
eacdcf2e65 |
Add project-level security scan rules (#744)
Co-authored-by: Aiden Bai <aiden.bai05@gmail.com> Co-authored-by: Rayhan Noufal Arayilakath <me@rayhanadev.com> |
||
|
|
93d4eecdb8 |
feat: run React Doctor on TypeScript repos without React (#756)
* feat(core): gate React-specific rules behind a `react` capability Add a `react` capability (set when React or Preact is detected) and use it to gate every React-runtime rule family so they stay off on a project without React. The codegen injects the requirement for the React-runtime buckets (hooks, JSX, a11y, render performance, React state), and `shouldEnableRule` treats the `react-jsx-only` tag as implying it. Two bare-name migration rules that matched class-member names without a React guard (`no-legacy-class-lifecycles`, `no-legacy-context-api`) now require it explicitly. Without this, hook/component-name heuristics would false-fire on ordinary TypeScript once non-React scanning is enabled. Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com> * feat: scan TypeScript projects that don't depend on React A project is now analyzable when it has source files, not only when React or Preact is present, so a plain TypeScript/JavaScript codebase runs the framework-agnostic rules instead of hard-failing with 'No React project found'. `discoverProject` tolerates a directory with no package.json (a monorepo's `packages/` subfolder, a loose tree of files) by synthesizing a ProjectInfo, inheriting dependency/framework detection from the enclosing workspace root so a React monorepo subdirectory still gets its React capabilities. Only a directory with neither source files nor an enclosing project still reports nothing-to-scan. Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com> * refactor(core): clarify discoverProject's no-package.json branches Split the synthesized ProjectInfo into its two real cases — a workspace subfolder (spread the enclosing root's detection, scope the directory- specific fields) and a standalone source tree (explicit all-null, framework-agnostic) — replacing the noisy `inherited?.x ?? null` chain repeated across every field. Behavior is unchanged. Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Nisarg Patel <NisargIO@users.noreply.github.com> |
||
|
|
cbdff6203d |
feat(cli): "Add to CI" handoff option + default CI setup in install (#658)
Co-authored-by: Aiden Bai <aiden.bai05@gmail.com> Co-authored-by: Nisarg Patel <awesomenisarg@gmail.com> |
||
|
|
87440abac8 | Fix React Native rule false positives (#662) | ||
|
|
b3c3aa9401 | chore: codebase audit cleanup (dead code, dedup, CI/config fixes) (#619) | ||
|
|
6e59f10ef8 | feat: new onboarding (#596) | ||
|
|
b65b15794f | feat(zod): add Zod v4 migration rules (#578) | ||
|
|
3a8ef93931 |
chore: triage-driven rule severity and scope adjustments (#536)
* fix(a11y): skip alt-text on Next.js metadata image route files 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> * chore(architecture): demote only-export-components to warn 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> * chore(architecture): unregister react-compiler-destructure-method 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> * fix(state-and-effects): promote no-adjust-state-on-prop-change to error 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> * chore: address review nits — drop explanatory comments, inline regex 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> * fix(a11y): use context.filename instead of removed getFilename 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> --------- Co-authored-by: Cursor <cursoragent@cursor.com> |
||
|
|
7fe3e41131 | feat: add Preact framework support and rules (#519) | ||
|
|
849b25dd91 |
feat(jotai): add three Jotai rules + register jotai bucket (#531)
|
||
|
|
ae7752ba86 |
refactor(oxlint-plugin): collapse duplicate framework/category/severity from rule-registry
Every generated rule-registry entry stored framework, category, and
severity at BOTH the outer entry level AND inside the inner rule:
{...} object. Consumers read framework/category/severity from
whichever side they happened to import — rules.ts read the outer,
react-doctor-plugin.ts and core/runners/oxlint/config.ts read the
inner.
Stop emitting the outer copies. Keep them only on the inner Rule
object (where they're set by the codegen spread). The outer entry
now ships:
{ key, id, source, originallyExternal, rule: { ...sourceRule,
framework, category, tags? } }
Consumers in rules.ts now read severity/framework via entry.rule.*
through a small toKeyedSeverity helper.
rule-registry.ts: 4324 -> 3469 lines (-855 / -20%).
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
|
||
|
|
47772b7da4 |
feat(oxlint-plugin): native TypeScript ports of OXC react/* + jsx-a11y/* + react-hooks + you-might-not-need-an-effect (108+ rules) (#273)
* feat(oxlint-plugin): port OXC react/* + jsx-a11y/* (100 rules) and effect-rules (8 rules) onto main
Squashed-rebase of cursor/port-oxc-react-rules-1778917290 onto current main.
Covers all 30 commits previously on the branch:
- Native port of every `oxc_linter::rules::react` (44),
`react_perf` (4), and `jsx_a11y` (52) rule into
`oxlint-plugin-react-doctor` (`react-builtins/` and `a11y/`
buckets), driven by an oxc-parser harness running OXC's own
fixture vec.
* 5411 / 5574 fixture cases pass (97.1%).
* 163 documented divergences in `__fixtures__/oxc-divergences.ts`
(per-rule).
- Semantic infrastructure: `scope-analysis.ts`,
`control-flow-graph.ts`, `closure-captures.ts`, plus
`wrap-with-semantic-context.ts` lazy injection.
- 8 ported `react-doctor/*` effect rules from
`eslint-plugin-react-you-might-not-need-an-effect` (PR #278), with
the eslint-scope analyzer + 1:1 ports of upstream's
`util/{ast,react}.js`.
- Drop OXC's `react` + `jsx-a11y` plugins from oxlintrc;
`BUILTIN_REACT_RULES` / `BUILTIN_A11Y_RULES` /
`YOU_MIGHT_NOT_NEED_EFFECT_RULES` are now empty maps preserved
for back-compat with consumers that import them.
- Drop `eslint-plugin-react-you-might-not-need-an-effect` peer dep
from `@react-doctor/core` and `react-doctor`.
- Drop `resolveYouMightNotNeedEffectPlugin` from plugin-resolution.ts.
Adopts main's structural changes since branch creation: PR #277
(rule re-exports moved into oxlint-plugin-react-doctor), PR #284
(picomatch glob compiler), PR #281 (annotations input on action.yml),
PR #282 (PR-blocking docs), PR #283 (knip removal docs).
Verification:
* pnpm typecheck — 10/10 packages clean
* pnpm lint — 0 warnings, 0 errors (951 files)
* pnpm test — 1350 passed | 4 skipped (oxlint-plugin: 5411 / 5574,
+149 fixtures vs the fresh squash baseline)
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
* feat(oxlint-plugin): upstream-parity test suite for rules-of-hooks + exhaustive-deps
Drives every `valid:` / `invalid:` case from the React team's
`eslint-plugin-react-hooks` v7 test fixtures
(`__tests__/ESLintRulesOfHooks-test.js` +
`__tests__/ESLintRuleExhaustiveDeps-test.js`) through our native
ports via the `runRule` harness.
Captured fixtures (committed JSON in
`src/plugin/rules/react-builtins/__upstream-fixtures__/`):
- rules-of-hooks: 58 valid + 77 invalid = 135 cases
- exhaustive-deps: 122 valid + 191 invalid = 313 cases
Total: 448 upstream cases.
Result:
- 241 / 448 upstream cases pass on our native port (53.8%).
- 207 documented divergences in `divergences.ts`. The largest gap is
in exhaustive-deps (135 invalid + 31 valid skipped) — upstream's
port has decade-old refined heuristics for useState-setter /
useRef stable-identity detection, useEffectEvent hoisting, deep
TS-aware unwrapping (typeof + as casts + satisfies), useMemo /
useCallback dep-array suggestion text, and React 19 `use()`
semantics inside dep arrays — none of which are replicated yet.
rules-of-hooks gaps: Flow `component` / `hook` syntax, classes
with hooks detection, useEffectEvent placement rules, deep
conditional/loop patterns from upstream's hermes-eslint scope walker.
Note on the "port all eslint-plugin-react-hooks rules" ask:
- `exhaustive-deps` and `rules-of-hooks`: native ports landed via
the OXC port; this commit adds upstream-fixture-driven parity
tests.
- The 16 React Compiler rules (`set-state-in-render`, `immutability`,
`refs`, `purity`, `hooks`, `set-state-in-effect`, `globals`,
`error-boundaries`, `preserve-manual-memoization`,
`unsupported-syntax`, `static-components`, `use-memo`,
`void-use-memo`, `incompatible-library`, `todo`,
`component-hook-factories`) are NOT individual ESLint rules — they
are dispatcher rules that run `babel-plugin-react-compiler`
internally and report its diagnostics filtered by category. These
CANNOT be ported natively without bundling the React Compiler.
React Doctor already loads them as external `react-hooks-js/*`
via `eslint-plugin-react-hooks` when React Compiler is in scope.
Test totals: 5411 → 5652 passing (+241 from the new parity suite),
163 → 370 skipped.
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
* fix(oxlint-plugin): rules-of-hooks gains broad upstream parity (24 → 19 divergences, 100% on valid cases)
After deep review of every divergent upstream test case:
`isHookCall` now matches upstream's stance:
- Bare `use*` callees are hooks unless they are parameter / catch-clause
bindings (the React-import filter is reserved for the React 19 `use`
hook only — that one is too general to flag without resolving to React).
- Member-expression hook calls fire on PascalCase namespaces or
call-expression chains (`Hook.useState`, `This.useHook`,
`FooStore.useFeatureFlag`, `someCall().useFoo`). Lowercase
namespaces (`jest.useFakeTimers`, `this.useHook`, `super.useHook`)
and non-hook-named members are NOT flagged.
`isInsideClassComponent` now correctly walks past class-method boundaries:
class-member function expressions don't terminate the walk, so
`class C { m() { useState() } }` is flagged.
Anonymous-function fallback now walks OUT to the enclosing context
instead of skipping unconditionally — when the outer function is a
component / hook, the inner anonymous callback's hook call is flagged
(catches `useEffect(() => { useHookInsideCallback() })` patterns).
`inferFunctionName` traverses transparent wrapper nodes
(AssignmentPattern for destructure defaults, TS as / satisfies /
non-null, ChainExpression) so cases like
`const {j = () => useState()}` correctly resolve to "j".
ExportDefaultDeclaration anonymous functions return null name
(treat as truly anonymous) — matches upstream's deliberate
non-enforcement on `export default () => {}`.
`use()` inside try/catch now flagged separately from the
conditional/loop checks (the React 19 `use` hook is allowed in
conditionals but NOT in try/catch).
CFG `computeUnconditionalSet` now treats:
- Dead-code blocks (statements after an unconditional return) as
vacuously unconditional — they're never reached so the rule
doesn't apply.
- ThrowStatement-to-exit edges as type "throw", excluded from the
reachability BFS — `if (x) throw; useState();` correctly evaluates
as unconditional because the throw branch isn't a normal completion.
OXC fixture pass[10] (`Sinon.useFakeTimers`) skipped: OXC's
pass-stance conflicts with upstream's flag-stance for
PascalCase-namespaced use-prefixed calls. We match upstream.
Upstream parity: 94 → 116 passing of 135 (was 70%, now 86%). All
valid cases pass. 19 remaining invalid divergences are useEffectEvent
placement (16) — separate rule layer not yet implemented — and
Flow-syntax (3) which require hermes-eslint.
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
* fix(oxlint-plugin): exhaustive-deps gains broad upstream parity (+22 fixtures)
Deep review of every divergent upstream test case:
`symbolHasStableHookOrigin` extended to cover the full upstream
stable-hook-origin set:
- useEffectEvent return values (RFC stable callback)
- Primitive literal initializers (number / string / boolean / null /
no-substitution template) on `const` bindings — `let`/`var`
bindings remain treated as mutable.
`isOutsideAllFunctions` walks the scope chain looking for ANY enclosing
function, so block-at-module-level constants (`{ const x = {}; useEffect... }`)
are correctly classified as stable. Imports / module-scope values are
NOT added to `stableCapturedNames` so the unnecessary-dep check still
fires for redundant imports listed in deps.
`unwrapExpression` strips TS `as` / `satisfies` / non-null / type-assertion
wrappers as well as `(...)` parens and ChainExpression — applied
both to the deps-array argument itself (so `[deps] as const` is
seen as an array) and to individual elements (so `[(props.x as Foo)]`
canonicalizes to `props.x`).
`computeDepKey` walks through ChainExpression wrappers when
collecting the outermost member-chain, so `props.foo?.bar` and
`props.foo.bar` both produce the same canonical key.
`stringifyMemberChain` standalone helper handles ThisExpression and
optional / TS-wrapped member chains.
New diagnostic surface:
- `buildMissingCallbackMessage`: `useEffect()` etc. with no callback.
- `buildMissingDepArrayMessage`: useMemo / useCallback / useImperativeHandle
without a deps array.
- `buildNonArrayDepsMessage`: a non-array second argument
(`useEffect(fn, dependencies)`).
- `buildLiteralDepMessage`: deps-array contains a non-string literal
(`[42, false, null]`). String literals are deliberately skipped —
upstream emits the missing-dep hint for those instead.
- `buildDuplicateDepMessage`: same dep listed twice (`[local, local]`).
- `buildRefCurrentDepMessage`: `<ref>.current` listed in deps where
`<ref>` resolves to a useRef binding — upstream's "depend on the
ref itself, not its mutable .current" warning.
null / undefined deps argument now treated as "no deps" for
useEffect-style hooks (silently OK) and as "missing deps array" for
useMemo / useCallback / useImperativeHandle.
`stableCapturedNames` set tracks bindings that the callback DID
capture but that we filtered out of the dep-keys for stability. The
unnecessary-dep check uses this set to suppress reports on
legal-but-redundant deps (e.g. `[local1]` where `local1 = 42` is
literal-stable).
CFG `computeUnconditionalSet` now treats:
- Dead-code blocks (statements after an unconditional return) as
vacuously unconditional.
- ThrowStatement-to-exit edges as a separate `throw` edge kind,
excluded from the reachability BFS — matches upstream's
"if (x) throw; useState();" → unconditional semantics.
Ported divergence count: 31 valid + 135 invalid = 166 fixed →
19 valid + 125 invalid = 144 documented divergences (-22 net).
Test totals: 5673 → 5695 passing (+22).
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
* refactor(oxlint-plugin): tighten code quality on hooks rules per AGENTS.md
- Hoist inline type imports (`import("...").Foo`) to top-level imports
- Replace ASCII char-code magic numbers (65/90) with named constants
- Drop unused `HookContext.hookExpression` field
- Lift HOC-name set, transparent-wrapper-type set, and required-deps
hook sets to module-level `ReadonlySet` constants
- Rename short variables to descriptive names (decl→declarator,
init→initializer, obj→objectName, out→indices, etc.)
- Replace `A ? true : false` with `Boolean(A)`
- Inline trivial intermediate variables; tighten control flow
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
* fix: address bugbot findings on PR #273
- Restore the `customRulesOnly` gate on rules ported from OXC's
`react/*` and `jsx-a11y/*` plugins. The generated registry now
carries an `originallyExternal: true` flag for each rule in the
`react-builtins` and `a11y` buckets; `createOxlintConfig` filters
those out when the user opts into `customRulesOnly`. Without this,
users who set `customRulesOnly: true` would have started receiving
~26 OXC-equivalent rules they explicitly opted out of.
- Drop the dead `BUILTIN_REACT_RULES` / `BUILTIN_A11Y_RULES` imports +
spreads from `createOxlintConfig`. Both maps are permanently empty
now that the rules are natively ported, so the
`customRulesOnly ? {} : MAP` ternaries always resolve to `{}`.
- Rename `isValidAriaProperty` in `dom-aria-properties.ts` →
`isValidDomAriaProperty` to disambiguate from the spec-strict
case-sensitive variant in `aria-properties.ts`. The DOM variant
remains case-insensitive (HTML attributes are case-insensitive) and
is the right helper for `no-unknown-property`; the spec-strict one
stays in `aria-props` for exact-match validation.
- Switch `wrap-with-semantic-context`'s `fallbackCfg` to return
`false` from `isUnconditionalFromEntry` / `dominatesExit`. The
fallback is unreachable in practice (the wrapper captures the
Program root before any visitor reads `context.cfg`), but if it
ever fires, `false` errs toward flagging a potential violation
instead of silently passing every hook call as unconditional.
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
* fix: address bugbot findings on PR #273 (round 2)
- extract-oxc-fixtures.mjs: `upstreamRelative` was computed as
`path.relative(path.dirname(oxcFilePath), oxcFilePath)`, which always
yielded just the filename. The header comment in each generated
fixture file therefore claimed `crates/oxc_linter/src/rules/<file>.rs`
— missing the bucket subdirectory. Compute the relative path against
`<oxcRoot>/crates/oxc_linter/src/rules` instead so the comment shows
`react/no_array_index_key.rs`, etc.
- aria-roles.ts: `"row"` was simultaneously listed in
`INTERACTIVE_ROLES` and `NON_INTERACTIVE_ROLES`. The is-interactive /
is-non-interactive checks would both return `true`, breaking
classification logic in a11y rules. Upstream `eslint-plugin-jsx-a11y`
classifies `row` as interactive (user-navigable inside a grid /
treegrid) and `rowgroup` as non-interactive — drop `row` from
`NON_INTERACTIVE_ROLES`.
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
* feat(oxlint-plugin): pin rules-of-hooks + exhaustive-deps to upstream eslint-plugin-react-hooks@7.1.1
Re-extracts upstream's RuleTester fixtures from
`facebook/react@eslint-plugin-react-hooks@7.1.1` and replays them
through our native TypeScript ports of the two pure-JS rules in the
plugin (`rules-of-hooks`, `exhaustive-deps`). The 16 React Compiler
rules the same package ships are runtime wrappers over
`babel-plugin-react-compiler`'s HIR analyzer (~30k LoC of bundled
compiler output) and aren't portable to a visitor-only TypeScript
plugin — those continue to ship via the optional peer dep on the npm
package, see plugin-resolution.ts.
Changes:
- scripts/extract-react-hooks-tests.mjs: NEW. Re-creates the upstream
fixture extractor that was lost in the squash-rebase. Reads any of
upstream's `allTests` / `tests` / `testsFlow` / `testsTypescript`
/ `testsTypescriptEslintParserV4` globals, dedupes by
`kind:code:JSON.stringify(options)`, and writes JSON. Sets
`process.env.CI=1` so upstream's not-in-CI filter (which deletes
`skip` flags from cases) is bypassed — we want every case the
upstream test suite asserts.
- exhaustive-deps.ts: `flattenReferenceRootName` now accepts
`JSXIdentifier` references, not just `Identifier`. This unlocks the
v7.1.1-added test case `<Component />` JSX usage inside an effect's
callback being detected as a missing dep. Verified locally:
invalid #191 (the new `function Foo({ Component }) { useEffect(() =>
console.log(<Component />), []) }`) now reports the correct missing
dep.
- rules-of-hooks.ts: `buildNonComponentMessage` now mirrors v7.1.1's
expanded diagnostic — appends 'React component names must start with
an uppercase letter. React Hook names must start with the word
"use".' This matches the changelog-noted message expansion in the
release.
- __upstream-fixtures__/README.md: NEW. Documents the v7.1.1 source
pin, the regeneration command, and a summary of why the
Compiler-backed rules aren't in scope for native porting.
- package.json: NEW `gen:react-hooks-fixtures` script.
- exhaustive-deps.json: regenerated; +1 case (the JSX-Component case)
and minor formatting changes.
Verification:
- pnpm format/lint/typecheck/test all green.
- Upstream parity scoreboard unchanged net of the new case (now
passing): 286 passed / 163 skipped (449 total upstream cases).
- Skip lists in divergences.ts unchanged — the skipped categories
(Flow `component`/`hook`, useEffectEvent placement, deep TS
type-aware unwrapping, mutation tracking, composite error counts)
remain documented as fundamental visitor-only limitations.
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
* fix(oxlint-plugin): scope-analysis records JSX-member-chain object as a reference
After exposing JSXIdentifier captures to the exhaustive-deps rule
(commit
|
||
|
|
dafe231e24 |
feat: add category-level rule controls (severity overrides + family tags) (#285)
* feat(core): add severityOverrides config with per-rule / per-category / per-tag channels Introduces a unified severity-override resolver that lets users promote, demote, or fully disable rules across every diagnostic surface (CLI, PR comment, score, --fail-on) without editing source. - New `severityOverrides` field on `ReactDoctorConfig` with `rules`, `categories`, and `tags` channels. Values: `error` | `warn` | `off`. Precedence: `rules` > `categories` > `tags`; within the tag channel the most permissive override wins (`off` > `warn` > `error`). - `resolveRuleSeverityOverride` is a pure resolver shared between the oxlint config builder (next commit) and the post-lint diagnostic filter, so external-plugin rules (`react/*`, `jsx-a11y/*`) honor `off` too even though their severities live outside our control. - `applySeverityOverrides` drops `off` diagnostics, re-stamps `warn` / `error` severities, and short-circuits to the input array when no overrides are configured so the common path stays allocation-free. - `validateConfigTypes` coerces stringy mistakes and warns on invalid severities / channel shapes; `mergeAndFilterDiagnostics` runs the override before the existing ignore pipeline. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * feat(core): apply severityOverrides at oxlint config time Threads `severityOverrides` from the user config through `runOxlint` into `createOxlintConfig`, where it short-circuits `off` rules before registration (so they never run, never emit, and never reach any surface) and re-stamps the registered severity for `warn` / `error` overrides. The retry-without-extends fallback honors the same overrides so a crashed adopt-extends path doesn't quietly reactivate a silenced rule family. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * feat(plugin): bucket-derived auto-tags and migration-hint family Cross-cutting controls (`severityOverrides.tags`, `surfaces.*.excludeTags`, `config.ignore.tags`) need a way to target whole rule families without each rule file repeating the same tag. The codegen now merges bucket-derived tags into every entry: - `react-native/` bucket rules get `"react-native"` - `server/` bucket rules get `"server-action"` Tags authored on the rule (e.g. `test-noise` on `no-react19-deprecated-apis`) are preserved and deduped at registry build time. The four migration-prompt rules now carry an explicit `migration-hint` tag too: - `no-react19-deprecated-apis` - `no-react-dom-deprecated-apis` - `no-legacy-class-lifecycles` - `no-legacy-context-api` `formatAutoTagsLine` mirrors prettier's wrap-at-100-cols decision at codegen time so the regen-equals-formatted contract enforced by `gen:check` stays stable. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * test: cover severityOverrides resolution, post-lint apply, and tag registration - `resolve-rule-severity-override.test.ts` checks precedence, empty-overrides short-circuit, the multi-tag most-permissive-wins rule, and category / tag fallback. - `apply-severity-overrides.test.ts` verifies `off` drops both react-doctor and external-plugin diagnostics; `warn` / `error` re-stamps the severity; and the no-overrides path returns the input array by identity. - `rule-tag-registration.test.ts` guards the bucket-derived auto-tags (`react-native` on every RN rule, `server-action` on every server rule) and the explicit `migration-hint` tags on the four migration rules, plus that authored tags survive the bucket merge. - `validate-config-types.test.ts` extends the existing surfaces coverage with severityOverrides happy / invalid / partial-channel paths. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * docs: document severityOverrides and the five rule-family tags Adds a README section right after the surface controls explaining when to use `severityOverrides` (single value applied across every channel) vs `surfaces` (visibility-only, per channel), with a copy-pasteable JSON example covering the five tag families called out in the original TODO — `design`, `test-noise`, `react-native`, `server-action`, `migration-hint`. Updates the TODOs entry to landed status and adds a minor-bump changeset. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * refactor: tighten severity-override surface for elegance Net -58 lines while making each path read more directly: - New `get-diagnostic-rule-identity.ts` projects a diagnostic onto the three axes every rule-targeted control reasons about (`ruleKey`, `category`, `tags`). `filter-for-surface` and `apply-severity-overrides` both consume it instead of repeating the plugin-aware tag lookup inline. - `resolve-rule-severity-override` drops the two-level `pickMostPermissive` / `collectTagOverride` helpers in favor of a single linear scan with a small `isMorePermissive` predicate; reads top-to-bottom in rule > category > tags precedence order. - `apply-severity-overrides` factors a `restampSeverity` helper driven by a `SEVERITY_FOR_OVERRIDE` map, keeping the main loop to three lines of decision logic. - `createOxlintConfig` collapses the override-then-fallback into one `?? rule.severity` expression so the "\"off\" skips registration" check is the only branch left. - Codegen drops the manual prettier-width math entirely — the shorter `[...new Set(...)]` form always fits under 100 cols, so `gen:check` stays idempotent without the wrap-aware fork. - `validate-config-types` extracts `applyFieldValidator`, `isPlainObject`, and `formatType` so the four "if undefined skip; validate; store or delete" blocks at the bottom collapse into one expression each, and the three "must be plain object" guards stop duplicating their type check. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * test: dogfood js-combine-iterations in rule-tag-registration test React Review flagged two .filter().map() chains in the test file as `js-combine-iterations` warnings (we eat our own dogfood). Replaces both chains with a shared `collectRuleIdsMatching` helper that walks `Object.entries(plugin.rules)` once and pushes ids into a single result array — same behavior, single pass. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * fix(core): warn on unknown severityOverrides channel keys Bugbot caught an asymmetry: `validateSurfacesField` iterates the user-provided keys via `Object.entries` and warns on unknown surface names, but `validateSeverityOverridesField` only iterated the allowlist — so a typo like `"rule"` instead of `"rules"` or `"category"` instead of `"categories"` silently dropped the override with no signal to the user. Switches the iteration to walk the user's keys (matching surfaces) and emits `config field "severityOverrides.<key>" is not a known channel (expected one of: rules, categories, tags); ignoring.` when the key isn't recognized. Adds a regression test for the typo path. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * refactor: rename severityOverrides config to severity for ESLint/oxlint idiomacy The wrapper key `severityOverrides` was the only non-idiomatic part of the design — ESLint exposes severity under top-level `rules`, oxlint exposes it under top-level `rules` + `categories`, and Biome under `linter.rules`. None wrap it in a multi-word `*Overrides` namespace, and our own project's existing wrapper namespaces (`ignore`, `surfaces`) are single-word. Renames: - Config field `severityOverrides` → `severity`. The triple `ignore` / `surfaces` / `severity` reads as a coherent set of single-word namespaces parallel to the rest of the project. - Type `SeverityOverrideControls` → `RuleSeverityControls`. The value type stays `RuleSeverityOverride` so it doesn't collide with the plugin's existing `RuleSeverity = "error" | "warn"` (the registrable subset). - File `apply-severity-overrides.ts` → `apply-severity-controls.ts`, function `applySeverityOverrides` → `applySeverityControls`. Matches the config field's noun form. - Validation error messages updated from `config field "severityOverrides.<x>"` to `config field "severity.<x>"`. - README, changeset, and TODOs.md updated to draw the explicit parallel to ESLint's `rules: { ... }` and oxlint's `rules: { ... } + categories: { ... }`, plus the extra `tags` channel for behavioral families. All 1158 tests pass. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * refactor: promote severity controls to top-level rules / categories / tags (ESLint shape) The wrapper namespace was the last non-idiomatic piece. ESLint exposes severity under top-level `rules` (its hallmark field name and shape). oxlint exposes `rules` + `categories` at the top level. Promoting our three channels to the config root gives users the exact field they already know from ESLint, plus oxlint-shaped `categories` and a `tags` extension with the same Record-of-severity shape. Before: { "severity": { "rules": { "react-doctor/no-array-index-as-key": "error" }, "categories": { "React Native": "warn" }, "tags": { "design": "off" } } } After (matches ESLint's flat-config / .eslintrc shape exactly, plus oxlint's `categories`): { "rules": { "react-doctor/no-array-index-as-key": "error" }, "categories": { "React Native": "warn" }, "tags": { "design": "off" } } Changes: - `ReactDoctorConfig` drops `severity` and gains three top-level fields `rules`, `categories`, `tags`. Each is a `Record<string, RuleSeverityOverride>`. - New `buildRuleSeverityControls(config)` adapter assembles the internal `RuleSeverityControls` shape from the three top-level fields so the resolver and the oxlint config builder don't need to know about the public API shape. - `applySeverityControls` and `runOxlint` switch to the adapter; `resolveRuleSeverityOverride` is unchanged (still takes `RuleSeverityControls` directly). - Validation: drops the `severity` wrapper validator; adds a shared `validateSeverityMap(fieldName, value)` invoked once per top-level field. Error messages now read `config field "rules"` / `config field "tags.design"` — matching what the user typed. - Tests, README, changeset, and TODOs.md updated. README now opens the section with "The exact ESLint / oxlint shape" so the parity is the first thing a reader sees. All 1161 tests pass (gained 3 from build-rule-severity-controls.test.ts). Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * ci: retrigger workflow after path filter miss Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * ci: nudge actions queue Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * chore: drop changeset for category-level rule controls Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * docs(readme): tighten severity-controls section Previous version was 32 lines of intro + 5-bullet behavior reference + full tag/category enumeration. Cuts to one intro sentence, a compact JSON example, and a single paragraph covering precedence and where the value applies. The full tag/category list is implicit from the example; the precedence and most-permissive-wins rule are now one sentence each. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * refactor: drop user-facing severity-by-tag (tags are internal metadata) Per-tag severity at the top level exposed an internal grouping concept that doesn't exist in ESLint or oxlint. Tags are behavioral labels rule authors invent; surfacing them as a first-class user config field made `react-doctor.config.json` depend on internal labeling decisions. Drops the top-level `tags` field. The severity surface is now strictly ESLint + oxlint shaped: - `rules` — ESLint's exact field - `categories` — oxlint's exact field Recovers the dropped functionality via existing fields: - whole-family suppression: `ignore.tags: ["design", ...]` (existing field, unchanged) - per-channel visibility: `surfaces.*.{include,exclude}Tags` (existing field, unchanged) - per-rule severity: list rules explicitly in `rules` Code falls out: - `RuleSeverityControls.tags` removed - `resolveRuleSeverityOverride` collapses to a two-line lookup (`rules` -> `categories`); drops the most-permissive-wins machinery and the `SEVERITY_RANK` map entirely - `buildRuleSeverityControls` drops the tag merge - `validate-config-types` SEVERITY_FIELD_NAMES = ["rules", "categories"] - `applySeverityControls` destructures only the two channels it needs from `getDiagnosticRuleIdentity` (which keeps returning tags for surface filters / ignore.tags consumers) Tests, README, and TODOs updated to reflect the smaller surface. 1169 tests still passing. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> |
||
|
|
118741e260 | Simplify rule set exports (#277) | ||
|
|
f0198e2f2d | refactor: extract @react-doctor/{types,project-info,core} internal packages + cleanup (#249) | ||
|
|
4d622970b5 |
Revert "pub"
This reverts commit
|
||
|
|
4d9d1a5fcf | pub |