* refactor(rules): colocate recommendation text with each defineRule call
Drops the 375-line `RULE_HELP_MAP` table in `run-oxlint.ts` and moves every
entry onto the matching rule's `defineRule({...})` call as a
`recommendation: "..."` field. `run-oxlint.ts` now reads the recommendation
off the plugin registry at lookup time, so the diagnostic-rendering path is
unchanged but each new rule has a single obvious place to put its
recommendation text (next to the rule logic it describes).
178 rule files updated; `run-oxlint.ts` shrinks by 365 net lines. No
behavior or output change — same recommendation strings, same registration
validator (now warning on any rule whose `defineRule` call doesn't ship a
`recommendation`).
* fix(plugin): move plugin object out of plugin/index.ts to avoid barrel-import warning
Renames `plugin/index.ts` → `plugin/react-doctor-plugin.ts` (where it's
actually constructed) and updates the 3 consumers (`eslint-plugin.ts`,
`run-oxlint.ts`, `vite.config.ts`) to import from the direct path. The
file's only export was the plugin object built in-place, so this isn't a
re-export barrel — but the `/index.js` suffix tripped react-doctor's own
`no-barrel-import` rule once a second importer landed in this PR.
Fixes the React Review warning at `run-oxlint.ts:22` introduced by the
previous commit.
* pub
* feat(browser-poc): add AST normalizers for production React analysis
Add 12 AST normalizers that transform minified production code into
patterns the react-doctor plugin rules can analyze. Enables 30+ rules
to fire on production bundles with 0 parse errors and 0 rule failures
across 6 tested sites (vercel, notion, linear, discord, shopify, ami).
Normalizers: SequenceExpression callee unwrap, OXC literal type
normalization, boolean/void recovery, return/expression sequence
splitting, JSX reconstruction (jsx/jsxs/createElement → JSXElement
tree with Fragment, ExpressionContainer, key extraction), setter
binding + reference rename, and component name uppercase recovery.
Also adds parent reference tracking in visitAst, "use client"
directive injection, WASM failure caching for CSP-blocked sites,
truncated source skip, score calculation, and a null-safety fix
for prefer-useReducer.
Co-authored-by: Cursor <cursoragent@cursor.com>
* feat: address user feedback — reduce false positives, improve scoring transparency, and add config options
- Add `offline`, `designRules`, and `entryFiles` config options
- Suppress React 19 deprecation rules on React 18 (migration-hint gate)
- Skip `rn-no-raw-text` for `.web.*` files (RN platform convention)
- Add sleep/delay and paginated-fetch heuristics to `asyncAwaitInLoop`
- Remove `noEmDashInJsxText` rule (em dashes are standard punctuation)
- Add `designRules` toggle to disable opinionated design rules
- Thread `entryFiles` to knip for dead-code false positive reduction
- Export `calculateScoreBreakdown` and show formula in `--verbose`
- Document scoring formula, diff/staged modes, and agent integration
- Switch to `@changesets/changelog-github` for richer changelogs
- Add GitHub Releases workflow
Co-authored-by: Cursor <cursoragent@cursor.com>
* refactor: capabilities-based rule gating system
Replace bespoke flags and filter functions with a unified
capabilities + tags system:
- Add buildCapabilities(project) that derives a flat Set<string>
from ProjectInfo (react:19, nextjs, tanstack-query, etc.)
- Add RULE_METADATA map with requires[] (capability gates) and
tags (static classification like "design", "test-noise")
- Replace filterRulesByReactMajor, filterDesignRules, VERSION_GATED_RULE_IDS,
VersionGateMode, and conditional spreads with one shouldEnableRule predicate
- Replace 9 individual fields on RunOxlintOptions/OxlintConfigOptions
with project: ProjectInfo
- Add ignore.tags to user config (replaces designRules boolean)
- Cherry-pick from cursor/library-aware-deprecation-rules-ec3b:
peerRangeSupportsLegacyReact, isTestFilePath, isLikelyBuildEntry,
parseTailwindMajorMinor, isLikelyStringReceiver (js-set-map-lookups fix)
- Compute effective React version from min(installed, peerRangeFloor)
so library-targeting-legacy is handled by version gating alone
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: library peer-range detection + README designRules cleanup
- Fix Bugbot: peerRangeMinMajor computes the floor major from the peer
range so effective version is min(installed, peerFloor) instead of null
- Fix Bugbot: replace designRules config key with ignore.tags in README
- Add peerRangeMinMajor tests
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: wire isLikelyBuildEntry + isTestFilePath into post-scan suppression
- Add auto-suppression in mergeAndFilterDiagnostics: suppress knip/files
diagnostics when a matching build artifact exists, suppress test-noise
tagged rules in test/fixture files
- Tag deprecation and design rules with "test-noise" in RULE_METADATA
- Fixes Bugbot: isLikelyBuildEntry is no longer dead code
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: remove dead DESIGN_TAGS constant, clear auto-suppression caches
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: remove dead peerRangeSupportsLegacyReact, unused ruleKey param, new Promise false negative
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: forward ignoredTags and entryFiles in diagnose() programmatic API
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: handle destructuring in loop-carried dependency detection
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: apply sleep/dependency heuristics to callback-based iteration
The loopBodyHasOnlySleepLikeAwaits and hasLoopCarriedDependency checks
were only applied in inspectLoopBody (for/while/do-while) but skipped
for callback-based iteration (.forEach, .map, etc.), causing false
positives on patterns like arr.forEach(async item => { await sleep(500) }).
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: guard framework-specific rules against missing RULE_METADATA
Rules from framework-specific maps (NEXTJS_RULES, REACT_NATIVE_RULES,
etc.) without a RULE_METADATA entry were unconditionally enabled for
all projects. Now they are skipped at runtime, and
validateRuleRegistration warns about the gap at dev time.
Co-authored-by: Cursor <cursoragent@cursor.com>
* fix: scope RULE_METADATA validation to framework-specific rules only
Global rules intentionally omit RULE_METADATA entries since they're
unconditionally enabled. Extracted FRAMEWORK_SPECIFIC_RULE_KEYS to
share the set between the runtime guard and the validation check.
---------