refactor(plugin): split rules into per-file modules with TSESTree-narrowed type layer#218
Merged
Merged
Conversation
…TSESTree-narrowed isNodeOfType
Replaces the monolithic plugin/types.ts and plugin/helpers.ts (~600 lines combined)
with 50 single-purpose files under plugin/utils/, and wires up TSESTree as the
source of truth for AST node shapes so isNodeOfType<T> narrows to the real
typescript-eslint node interfaces (e.g. MemberExpression.computed: boolean,
CallExpression.arguments.length: number) instead of `any`.
The old plugin/types.ts and plugin/helpers.ts now re-export from plugin/utils
so no other file needs to change to import them. Adds a defineRule helper
in plugin/utils/define-rule.ts ready for the per-rule split in a follow-up.
- adds @typescript-eslint/types as a devDependency for type narrowing
- EsTreeNode keeps its loose `[key: string]: any` escape hatch so existing
rule bodies continue to compile unchanged
- EsTreeNodeOfType<T> resolves to Extract<TSESTree.Node, { type: T }> with
a relaxed `parent: EsTreeNode | null` (our walker assigns parent freely)
- 2 narrow null-tolerance fixes in plugin/rules/react-native.ts (parent param)
Tests: 734 passing, typecheck/lint/format clean.
…rapper
Splits the 16 monolithic plugin/rules/<bucket>.ts files (10,368 LOC,
248 rules) into 179 per-rule files under plugin/rules/<bucket>/<rule>.ts.
Each rule now lives in its own file, wraps its definition with
defineRule<Rule>({...}) from plugin/utils, and only imports the helpers
+ constants it actually uses. The bucket files become thin re-export
barrels (12-24 lines each) so plugin/index.ts and external consumers
keep working unchanged.
Private helpers (consts, functions, interfaces, type aliases) that were
only used by a single rule are inlined into that rule's file; helpers
shared across multiple rules are duplicated into each consumer (matches
the v2 plan: keep the file self-contained, lift to plugin/utils later
if it grows beyond a handful of copies).
Generated by a one-shot codemod (parsed each bucket with oxc-parser,
computed each rule's transitive helper closure, emitted per-rule files
with only the imports they need, replaced the bucket with a barrel).
- 16 bucket files: 10,368 LOC -> 179 LOC (barrels)
- 179 new per-rule files under plugin/rules/<bucket>/
- Each rule's `recommendation`/`examples` metadata is now ready to be
added per-file in a follow-up
- 3 react-native split files: re-apply parent param null-tolerance
(these helpers got copied into each consumer file by the codemod)
Tests: 734 passing, typecheck/lint/format clean.
|
🔴 React Review — 0/100 (unchanged) · Copy prompt for agentReviewed by react-review for commit 6002723. Configure here. |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5f689e7. Configure here.
Addresses two reviewer comments on the per-rule split:
1. Bugbot found that the codemod inlined some helpers up to 13x across files
in the same category (e.g. getInlineStyleExpression x13 in design/,
resolveJsxElementName x8 in react-native/, collectUseStateBindings x6 in
state-and-effects/). A bug fix in any one copy risks missing the others.
2. React Review fired the package's own no-barrel-import rule 192 times
on the new per-rule files, which all imported from "../../utils/index.js".
Fixes:
- Per-rule files now import directly from individual util source files
(e.g. ../../utils/define-rule.js) instead of the utils/index.js barrel.
- Lifted 33 multi-use helpers from per-rule files into shared per-category
utils dirs at plugin/rules/<category>/utils/<helper>.ts:
architecture/utils/ 2 helpers (deprecated-react-import factory + options)
design/utils/ 8 helpers (style/color extraction)
react-native/utils/ 2 helpers (resolveJsxElementName, SCROLLVIEW_NAMES)
react-ui/utils/ 5 helpers (className/jsx parsing)
state-and-effects/utils/ 12 helpers (subscribe-shape, cleanup, dep-graph)
tanstack-start/utils/ 4 helpers (route options, server fn chain walker)
- Each lifted helper picks up only the imports it actually needs from
constants and plugin/utils.
- Pruned 53 unused-import warnings in rule files that no longer reference
utilities used only by the lifted helpers.
Tests: 734 passing, typecheck/lint/format clean, zero duplicates remaining.
The DefineRule interface in plugin/utils/define-rule.ts is only needed to type the defineRule const inside the same file -- no external file imports it. Removing `export` from the interface keeps the file's public surface minimal (just defineRule itself). Found via dead-export audit before merge: this was the only truly dead export in the new plugin/utils/ + plugin/rules/<bucket>/utils/ layout. The other low-reference helpers (DeprecatedReactImportRuleOptions, ServerFnChainInfo, isReleaseLikeCall, containsReleaseLikeCall) are all legitimate transitive dependencies within their per-category utils dir.
3 tasks
aidenybai
added a commit
that referenced
this pull request
May 13, 2026
Same shape as the plugin/ split (#218) but for the CLI tier: carves the 743-line src/cli.ts orchestrator and its 6 CLI-only utility files out of src/utils/ and into a dedicated src/cli/ directory. Files moved (via `git mv` so history is preserved): src/cli.ts -> src/cli/index.ts src/utils/annotation-encoding.ts -> src/cli/annotation-encoding.ts src/utils/find-owning-project.ts -> src/cli/find-owning-project.ts src/utils/get-staged-files.ts -> src/cli/get-staged-files.ts src/utils/handle-error.ts -> src/cli/handle-error.ts src/utils/parse-file-line-argument.ts -> src/cli/parse-file-line-argument.ts src/utils/select-projects.ts -> src/cli/select-projects.ts Picked these specifically because they are the only utils with cli.ts as their sole non-test importer (per a classifier audit). Utils still used by scan.ts, index.ts (public API), eslint-plugin.ts, or install-skill.ts stay in src/utils/ since they aren't CLI-only. Import path rewrites: - Inside the moved files: ./<other-cli-util>.js stays (same dir now), ./<shared-util>.js becomes ../utils/<util>.js - Inside cli/index.ts: ./utils/<x>.js -> ../utils/<x>.js for shared utils, -> ./<x>.js for the 6 moved CLI utils; ./scan.js, ./install-skill.js, ./constants.js, ./types.js -> ../<file>.js - 4 test files updated to import the moved utils from src/cli/ instead of src/utils/ - vite.config.ts: cli entry "./src/cli.ts" -> "./src/cli/index.ts" No behavior change. 734 tests pass, lint clean, typecheck clean, build produces identical dist/cli.js output.
This was referenced May 13, 2026
aidenybai
added a commit
that referenced
this pull request
May 13, 2026
Audit after the refactor wave (#218 plugin/, #220 cli/, #221 scan/, #222 drop browser-poc) found 16 zero-reference exports across src/. 15 of them were genuinely dead; 1 was kept (clearCaches is public API). Removals: src/utils/is-rule-suppressed-at.ts ENTIRELY ORPHANED: no file imports it (it just wraps evaluateSuppression) src/constants.ts: REACT_19_DEPRECATION_MIN_MAJOR Defined for rule version-gating that inlined the numbers in oxlint-config.ts instead. REACT_DOM_LEGACY_API_MIN_MAJOR Same -- never wired up TAILWIND_SIZE_SHORTHAND_MIN_MAJOR Same TAILWIND_SIZE_SHORTHAND_MIN_MINOR Same (plus the orphaned HACK comment that described them) Un-exported (interface/helpers only used inside own file): src/cli/parse-file-line-argument.ts ParsedFileLineArgument src/oxlint-config.ts RuleMetadataEntry buildCapabilities shouldEnableRule src/utils/apply-ignore-overrides.ts CompiledIgnoreOverride src/utils/build-category-breakdown.ts CategoryBreakdownEntry src/utils/calculate-score-locally.ts ScoreBreakdown src/utils/evaluate-suppression.ts SuppressionEvaluation src/utils/load-config.ts LoadedReactDoctorConfig src/utils/parse-tailwind-major-minor.ts TailwindMajorMinor Kept: src/index.ts clearCaches Public-API entry, intentionally exposed even if no internal caller uses it. 10 files changed, +10 / -36. typecheck/lint/format/test all clean, 734/734 tests still pass.
4 tasks
aidenybai
added a commit
that referenced
this pull request
May 13, 2026
The utils->core import-path codemod's regex matched `from '...'`
substrings inside string literals -- specifically the EXAMPLE code
shown to users in oxlint diagnostic help text. Three messages in
HELP_TEXT_MAP had their example paths mangled into the codemod's
target form (`../../utils/...`) even though they were supposed to
illustrate paths in the USER'S code, not paths internal to this
package.
Restored to the original example paths:
no-barrel-import:
`import { Button } from '../../utils/components/Button.js'`
-> `import { Button } from './components/Button'`
no-dynamic-import-path:
`import('../../utils/feature/heavy.js')`
-> `import('./feature/heavy.js')`
nextjs-no-css-link:
`import styles from '../../utils/Button.module.css.js'`
-> `import styles from './Button.module.css'`
Verified no other string literals were corrupted (grepped for
`["'`]../../(utils|core|cli)/` across src/ -- all remaining hits are
legitimate plugin/utils imports from the per-rule split in #218, which
is its own unrelated `utils/` directory).
Closes the Bugbot finding on PR #224.
aidenybai
added a commit
that referenced
this pull request
May 13, 2026
…s,scoring,runners}/ + src/cli/ (#224) * refactor: split src/utils/ (68 flat files) into src/core/{config,detection,diagnostics,scoring,runners}/ + src/cli/ Final big organizational refactor in the series (#218 plugin/, #220 cli/, #221 scan/, #222 drop browser-poc, #223 dead-code). Carves the 68-file src/utils/ dumping ground into purpose-built directories. New layout: src/cli/ presentation (26 files) + prompts.ts, highlighter.ts, spinner.ts, logger.ts + colorize-by-score.ts, format-error-chain.ts + indent-multiline-text.ts, wrap-indented-text.ts + build-hidden-diagnostics-summary.ts + should-auto-select-current-choice.ts, should-select-all-choices.ts + detect-agents.ts, to-display-name.ts + (existing cli/ files: index.ts, render-*.ts, etc.) src/core/ core logic (7 files at root + 5 subdirs) build-json-report.ts shared JSON reporting (CLI + public API) build-json-report-error.ts shared JSON error envelope group-by.ts, is-file.ts generic primitives is-plain-object.ts, to-relative-path.ts read-file-lines-node.ts config/ (11) config loading, root-dir resolution, ignore patterns, glob matching, gitattributes parsing, validation detection/ (6) project discovery, react/tailwind major-version parsing, package.json reading, monorepo root detection diagnostics/ (12) combine/filter/suppress/merge, jsx-opener-span lookup, disable-directive walks scoring/ (6) local + remote score calc, score breakdown, reduced-motion check, proxy fetch runners/ (13) oxlint + knip + their config builders, include-path computation, Node-version compat, diff-files src/plugin/ (unchanged from #218) scan.ts, index.ts, errors.ts, types.ts, constants.ts, oxlint-config.ts, eslint-plugin.ts, install-skill.ts top-level Driven by a one-shot codemod that performed the moves via `git mv` (so all 68 file renames preserve blame/log history) and rewrote every relative import in the package + tests (98 importer files touched) to the new paths. One non-mechanical fix needed after the move: src/core/runners/run-oxlint.ts resolved its built plugin via `../../dist/react-doctor-plugin.js` -- with 1 extra level of nesting that needs to be `../../../dist/react-doctor-plugin.js`. One test fixed: tests/regressions/scan-resilience.test.ts read a source file by hardcoded path (`src/utils/calculate-score-locally.ts`) to assert no `fetch(` reference -- updated to the new location (`src/core/scoring/calculate-score-locally.ts`). Verification: - 128 files changed, +237 / -222 (almost entirely import-path edits) - typecheck/lint/format clean - 734/734 tests pass - build produces identical dist/cli.js, dist/index.js, etc. - No public API change - src/utils/ no longer exists * refactor: move orchestrators into tiers + rename scan -> inspect (#225) * refactor: move install-skill, scan, oxlint-config out of src/ root into their tiers Top-level src/ was still holding three orchestrator-grade files that logically belong inside the existing tier directories. Move them so the src/ root only contains public-API entry points and cross-cutting types. Moves (via git mv, history preserved): src/install-skill.ts -> src/cli/install-skill.ts CLI subcommand (already imported 6 things from cli/) src/scan.ts -> src/core/scan.ts Orchestrator (lives with the logic it calls) src/oxlint-config.ts -> src/core/runners/oxlint-config.ts Sibling of run-oxlint.ts (config + runner) After: top-level src/ contains only the entry points and shared types: src/ ├── cli/ presentation ├── core/ logic ├── plugin/ rules ├── constants.ts ├── errors.ts ├── eslint-plugin.ts public-API entry ├── index.ts public-API entry ├── knip.d.ts type declaration └── types.ts Imports rewritten via a small codemod (11 files): - 3 moved files' internal imports recomputed for new depth - 4 src/ consumers of the moved files updated - 4 test files updated Verification: - 11 files changed, +39/-39 (all import path edits) - typecheck/lint/format clean - 734/734 tests pass - build produces identical dist/* output - run-oxlint.ts's relative path to dist/react-doctor-plugin.js (../../../) is unchanged -- run-oxlint.ts itself didn't move Stacks on top of #224 (utils->core reorg). Once #224 merges, this PR's base auto-rebases to main. * refactor: rename scan -> inspect (function, file, types, test) "scan" was a misnomer -- the function does much more than scan files: it runs lint via oxlint, runs dead-code via knip, calculates a score, merges/filters diagnostics, and renders CLI output. "inspect" describes the role accurately and matches v2's chosen name. Renames: src/core/scan.ts -> src/core/inspect.ts (git mv) tests/scan.test.ts -> tests/inspect.test.ts (git mv) scan() function -> inspect() function runScan() -> runInspect() mergeScanOptions() -> mergeInspectOptions() ResolvedScanOptions -> ResolvedInspectOptions ScanResult -> InspectResult (src/types.ts) ScanOptions -> InspectOptions (src/types.ts) describe("scan", ...) -> describe("inspect", ...) (test) Importers updated: src/cli/index.ts 4 sites src/core/build-json-report.ts 3 sites tests/inspect.test.ts 5 sites tests/regressions/cli-and-output.test.ts 4 sites tests/build-json-report.test.ts 2 sites Kept as English prose (not identifiers): - "full scan", "scan output", "scan only files", "scan banner" in comments, CLI option descriptions, and prompt text -- these describe the action of scanning the codebase, not references to the function. - constants.ts JSX_OPENER_SCAN_MAX_LINES (about lexical JSX scanning, unrelated to inspect()). Verification: - 7 files changed, +54/-54 (all renames) - typecheck/lint/format clean - 734/734 tests pass - No public API change (scan/ScanResult/ScanOptions weren't exported from src/index.ts -- the public API still calls the underlying function diagnose() which is unchanged) Stacks on #225 (which moved scan.ts to src/core/). Once #225 merges, this PR's base auto-rebases to main. * fix: restore help-text example paths corrupted by the import codemod The utils->core import-path codemod's regex matched `from '...'` substrings inside string literals -- specifically the EXAMPLE code shown to users in oxlint diagnostic help text. Three messages in HELP_TEXT_MAP had their example paths mangled into the codemod's target form (`../../utils/...`) even though they were supposed to illustrate paths in the USER'S code, not paths internal to this package. Restored to the original example paths: no-barrel-import: `import { Button } from '../../utils/components/Button.js'` -> `import { Button } from './components/Button'` no-dynamic-import-path: `import('../../utils/feature/heavy.js')` -> `import('./feature/heavy.js')` nextjs-no-css-link: `import styles from '../../utils/Button.module.css.js'` -> `import styles from './Button.module.css'` Verified no other string literals were corrupted (grepped for `["'`]../../(utils|core|cli)/` across src/ -- all remaining hits are legitimate plugin/utils imports from the per-rule split in #218, which is its own unrelated `utils/` directory). Closes the Bugbot finding on PR #224. * refactor: move format-error-chain / logger / highlighter to core/ (fix layering) Addresses Bugbot's "Core modules depend backwards on CLI layer" finding on PR #224. Several core/ files were importing from cli/, undermining the layered structure the refactor was introducing: core/build-json-report-error.ts -> cli/format-error-chain core/runners/extract-failed-plugin-name -> cli/format-error-chain core/config/load-config -> cli/logger core/config/read-ignore-file -> cli/logger core/config/resolve-config-root-dir -> cli/logger The 3 utilities in question are all pure abstractions (no CLI-specific behavior beyond using console.* for output), so they belong in core/ where both core AND cli consumers can import them without inversion. Moves (git mv, history preserved): cli/format-error-chain.ts -> core/format-error-chain.ts (Error.cause walker) cli/logger.ts -> core/logger.ts (silent-mode logger) cli/highlighter.ts -> core/highlighter.ts (picocolors wrapper) 22 files changed, +29/-29 -- all relative-import path rewrites driven by a small codemod with a strict `^import ... from "..."` regex (line-anchored to avoid the help-text-literal corruption bug from the previous codemod, which triggered the bigger Bugbot follow-up). After this: - core/ no longer imports from cli/ for utilities -- only core/inspect.ts still does (legitimately: it's the CLI orchestrator that calls render-*.ts + resolve-oxlint-node prompt + spinner) - cli/ files import the moved utilities via `../core/<name>.js` Verification: - typecheck/lint/format clean - 734/734 tests pass - `rg "from .+cli/" src/core` shows only inspect.ts's legitimate imports of render-*, spinner, resolve-oxlint-node (all of which are CLI presentation/interaction, not utilities)
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>
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>
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>
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>
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>
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>
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.

Refactor-only port of the code-organization ideas from #217 to
main. Zero behavior drift, no rules added/removed, no scoring or CLI changes, no v2 surface.Summary
Two stacked commits, each independently testable:
refactor(plugin): split helpers/types into single-purpose files with TSESTree-narrowed isNodeOfType(06e9687)plugin/helpers.ts(~580 LOC) andplugin/types.ts(~30 LOC) with 50 single-purpose files underplugin/utils/@typescript-eslint/typesas a devDependency and wires it throughEsTreeNodeOfType<T> = Extract<TSESTree.Node, { type: T }>soisNodeOfType(node, "MemberExpression")narrows to the real estree shape (node.computed: boolean,node.arguments.length: number, etc.) instead ofanyplugin/helpers.tsandplugin/types.tsbecome 8- and 25-line re-export shims so nothing else needs to change to import themdefineRulehelper inplugin/utils/define-rule.ts(identity function, used by PR 2)EsTreeNode | null | undefinedwidenings inreact-native.tslocal helpers (forced by PR 1's tighterparenttype)refactor(plugin): split each rule into its own file with defineRule wrapper(5f689e7)plugin/rules/<bucket>.tsfiles (10,368 LOC) into 179 per-rule files underplugin/rules/<bucket>/<rule>.tsdefineRule<Rule>({...})and only imports the helpers it usesplugin/index.tsand external consumers keep working unchangedrecommendation/examplesmetadata fields are now ready to be added per-file in a follow-upWhat this is NOT
Of the v2 PR (#217), only the code-organization ideas came across. None of these are in this branch:
--unstaged,--changed, annotations behavior changes)createReactDoctor/inspectReactProjectsurfacebrowser-poc,knip,bippy, wasm parsereslint-plugin-react-you-might-not-need-an-effectVerification
A whitespace + type-only-widening normalized byte-comparison of every rule body confirms:
EsTreeNode | null | undefinedwidening)plugin/index.tsrule registry: byte-identical (same rule IDs, same identifiers, same order)plugin/constants.ts: byte-identicalBranch totals vs main: 251 files changed, +13,241 / −11,058. 734 tests pass, lint clean, format clean, typecheck clean. No new runtime deps.
Test plan
pnpm typecheckclean on both commitspnpm test— 734/734 tests pass on both commitspnpm lint— 0 warnings, 0 errorspnpm format— cleanmainshows zero behavior driftNote
Medium Risk
Large-scale refactor that moves/rewires many rule implementations and helper imports; risk is mainly in missed exports/path resolution or subtle type-narrowing fallout despite intended no behavior change.
Overview
Refactors
react-doctor’s ESLint-style plugin code by extracting the previously monolithichelpers.tsand several rule “bucket” modules into many single-purpose files undersrc/plugin/utils/andsrc/plugin/rules/<bucket>/, while keeping the public entrypoints as thin re-export shims.Adds a shared
createDeprecatedReactImportRuleutility for React 19 deprecation checks and wraps individual rule modules withdefineRule, plus a devDependency on@typescript-eslint/typesto support a tighter, TSESTree-narrowed node/type layer.Reviewed by Cursor Bugbot for commit 6002723. Bugbot is set up for automated code reviews on this repo. Configure here.