mirror of
https://github.com/millionco/react-doctor.git
synced 2026-09-14 20:00:24 +08:00
v0.4.2
23 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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
|
||
|
|
f0198e2f2d | refactor: extract @react-doctor/{types,project-info,core} internal packages + cleanup (#249) | ||
|
|
4d622970b5 |
Revert "pub"
This reverts commit
|
||
|
|
4d9d1a5fcf | pub | ||
|
|
8556b31d8e |
feat: address user feedback — reduce false positives, improve scoring transparency, and add config options (#208)
* 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. --------- |
||
|
|
914e5f3aee |
feat: leaderboard in README + dedicated /leaderboard page (#176)
* docs(readme): add leaderboard section with top 10 from benchmarks repo Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * ci(leaderboard): refresh README leaderboard from benchmarks json on schedule Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * feat(website): add /leaderboard page driven by react-doctor-benchmarks Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * chore(scripts): drop bench:scores in favor of leaderboard.json Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * docs(readme): trim leaderboard table to repo + score Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * docs(readme): drop raw-results link from leaderboard section Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * fix(scripts): make update-leaderboard idempotent against formatter Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * refactor(website): extract score thresholds, color, label, doctor face to shared utils 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> |
||
|
|
b3de062cc5 |
chore: upgrade oxlint to 1.63.0 via override (#175)
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> |
||
|
|
6afdc04ba7 | chore(package): add benchmark scores script to package.json | ||
|
|
d71a6bf6eb |
feat(react-doctor): adopt user lint config, ship as ESLint plugin, remove browser surface (#151)
Squashes work that closes #143 plus follow-on cleanup of the browser surface and dead-code. **`adoptExistingLintConfig` (default-on)** — when a project has a JSON-format `.oxlintrc.json` or `.eslintrc.json` at the scanned directory or any ancestor up to the nearest project boundary, that config is folded into the same scan via oxlint's `extends` field. Diagnostics from the user's existing rules count toward the 0–100 health score alongside the curated react-doctor rule set; if oxlint can't load the user config, react-doctor logs the reason on stderr and retries the scan once without `extends`. JS / TS configs are silently skipped (oxlint's `extends` can't evaluate them). Coverage broadened to `.ts` and `.js` files (previously the parser dropped non-JSX). Closes #143. **`react-doctor/eslint-plugin` flat-config export** — the same react-doctor rule set is shipped as an ESLint flat-config plugin so projects that already run ESLint can wire it up without depending on the CLI. Includes presets (`recommended`, `next`, `react-native`, `tanstack-start`, `tanstack-query`, `all`) plus cherry-picking. **Removed browser entrypoints, browser CLI, and `react-doctor-browser` package** — the in-browser diagnostics pipeline (`react-doctor/browser` + `/worker`), the `react-doctor browser …` CLI subcommand (`start` / `stop` / `status` / `snapshot` / `screenshot` / `playwright`), and the standalone `react-doctor-browser` workspace package (Playwright + CDP + cookie extraction) are gone. Nothing in the monorepo consumed them and they pulled in a heavy dep footprint (`playwright`, `libsql`). Source is preserved on the `archive/browser` branch. **`diagnose-core` engine inlined into `index.ts::diagnose`** — the dependency-injection shape was designed for browser sharing. Only one caller now, so collapsed into a flat orchestrator. Score helpers moved from `src/core/` to `src/utils/`; `src/core/` directory removed. **Smaller cleanups** — drop unused `matchGlobPattern` wrapper, `isMemberProperty` import in nextjs rule, `fileContainsPattern` helper, and various dead test locals; ignore `tests/fixtures/**` from workspace lint and add `.oxlintignore` so `lint --fix` against fixture paths can't strip the `debugger;` statements that `adoptExistingLintConfig` tests assert on. Tests: 452 / 452 pass on Node 22 + 24. Build smoke green. |
||
|
|
3f5c031474 |
feat(react-doctor): add browser CLI subcommand and 11 new lint rules (#148)
|
||
|
|
03a94351c7 | Address review-report.md findings + self-review regressions (#140) | ||
|
|
301e121a23 |
chore: update project configuration and dependencies
- Added `.turbo` to `.gitignore`. - Removed obsolete `.oxlintrc.json` configuration file. - Updated `package.json` to use `turbo` for script commands and added new `typecheck` and `check` scripts. - Updated `devDependencies` to include `@voidzero-dev/vite-plus-core` and `turbo`, while removing `oxlint` and `tsdown`. - Adjusted `pnpm-lock.yaml` to reflect new dependencies and overrides for `vite` and `vitest`. - Removed `tsdown.config.ts` and `vitest.config.ts` files from `react-doctor` package. - Refactored interfaces in `src/types.ts`, `src/core`, and `src/plugin` files for consistency. - Cleaned up unused imports and code in various utility files. - Deleted unused `react-doctor-icon.tsx` component from the website package. |
||
|
|
0eb635ac7f |
feat(react-doctor): add TanStack Start rules (#124)
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> |
||
|
|
10bd788323 |
fix: address multiple GitHub issues (#117, #113, #119, #106) (#120)
* fix: detect namespace hook calls (React.useState, React.useEffect) in all rules Fixes #117: isHookCall now handles MemberExpression callee types (e.g. React.useEffect, React.useState) in addition to plain Identifier callee types. This fixes silent misses in no-derived-state-effect, no-fetch-in-effect, no-cascading-set-state, no-effect-event-handler, rerender-functional-setstate, rendering-hydration-no-flicker, and the countSetStateCalls helper. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * fix: remove unsupported jsx-a11y/no-noninteractive-element-interactions rule Fixes #113, #119: This rule does not exist in the bundled oxlint binary, causing 'Failed to parse oxlint configuration file' errors and score suppression on all scans. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * feat: add --no flag to skip prompts and always run full scan Fixes #106: Adds -n/--no flag that skips all interactive prompts (like --yes) but specifically declines the diff-only scan prompt, ensuring a full project scan runs. Useful for CI agents that cannot interact with TTY prompts. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * test: add namespace hook detection tests (React.useEffect, React.useState, etc.) Adds 13 tests verifying that all rules correctly detect namespace-style hook calls (import * as React; React.useEffect, React.useState, etc.). Covers: no-derived-state-effect, no-fetch-in-effect, no-cascading-set-state, no-effect-event-handler, no-derived-useState, rerender-lazy-state-init, rerender-functional-setstate, rerender-dependencies, rendering-hydration-no-flicker, no-usememo-simple-expression, prefer-useReducer, plus a regression guard for direct-import fixtures. Relates to #117. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * test: add edge case tests for project discovery and catalog resolution Adds tests for: - peerDependencies-only with catalog reference resolution - Catalog reference to nonexistent catalog name (fallback to default) - Empty pnpm-workspace.yaml handling - Malformed package.json in workspace subdirectories - Nonexistent root directory for discoverReactSubprojects - File entries mixed with directory entries in project root Relates to #101, #87, #105, #71. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * fix: handle malformed JSON in workspace package.json files gracefully readPackageJson now catches SyntaxError from malformed JSON and returns an empty object instead of crashing. This prevents workspace discovery from aborting when a subdirectory has an invalid package.json. Previously, a broken package.json in any workspace subdirectory would crash the entire scan with an unhandled SyntaxError. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * feat: add customRulesOnly config to disable builtin react/jsx-a11y rules Fixes #109: Teams with existing ESLint setups can now set customRulesOnly: true in react-doctor.config.json to only run react-doctor/* custom rules, skipping the bundled react/, jsx-a11y/, react-perf, and react-compiler rules. This avoids rule duplication for projects that already lint with eslint-plugin-react, eslint-plugin-jsx-a11y, etc. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * feat: inherit config from ancestor directories for monorepo support Fixes #73: loadConfig now walks up the directory tree to find react-doctor.config.json or package.json reactDoctor key from ancestor directories. This means a single config at the monorepo root applies to all workspace packages without needing to duplicate the config in each package. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * feat: add --annotations flag for GitHub Actions annotation output Fixes #81: The --annotations flag outputs diagnostics in GitHub Actions annotation format (::error / ::warning) so they appear as inline annotations on PR diffs. Example: ::error file=src/App.tsx,line=42,title=react-doctor/no-fetch-in-effect::fetch() inside useEffect Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * refactor: remove Ami integration Removes all Ami-related code: - --ami flag, --fix flag, fix subcommand, install-ami subcommand - Ami detection, installation, deeplinks, and open-URL helpers - Fix prompt (maybePromptFix) with Ami banners - Skill installation prompt (maybePromptSkillInstall) - fetchEstimatedScore and EstimatedScoreResult - AMI_WEBSITE_URL, AMI_INSTALL_URL, AMI_RELEASES_URL, OPEN_BASE_URL, ESTIMATE_SCORE_API_URL, ERROR_ESTIMATED_FIX_RATE, WARNING_ESTIMATED_FIX_RATE - skill-prompt.ts utility CLI bundle reduced by ~39KB (324KB → 285KB). Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * chore: remove .ami directory and all remaining Ami references - Delete .ami/ directory (42 skill/config files) - Delete packages/website/src/app/open/ (Ami redirect page) - Remove Ami references from README.md, share page, install-skill route, llms.txt, and install-skill.sh - Zero Ami references remain in the codebase Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * chore: move .ami/skills to .agents/skills Relocates all skill files from the deleted .ami/ directory into .agents/skills/ and removes .agents from .gitignore so they are tracked in version control. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * fix: recognize custom text components in rn-no-raw-text rule Fixes #93: The rn-no-raw-text rule now recognizes a much wider set of text-handling components: 1. Expanded built-in names: Typography, Paragraph, Span, H1-H6 2. Keyword matching instead of suffix-only: any component whose name contains Text, Title, Label, Heading, Caption, Subtitle, Typography, Paragraph, Description, or Body is treated as text-handling (e.g. StyledText, BodyText, CustomLabel, MyTypography) 3. New textComponents config option: users can specify arbitrary component names in react-doctor.config.json that should be treated as text-handling: { "textComponents": ["MyCustomComp", "NativeWindText"] } Diagnostics inside these components are filtered out post-lint. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * chore: upgrade dependencies - oxlint: 1.47.0 → 1.59.0 (fixes SIGABRT crashes on large projects #84/#77) - oxfmt: 0.32.0 → 0.44.0 - typescript: 5.9.3 → 6.0.2 - vitest: 4.0.18 → 4.1.4 - tsdown: 0.20.3 → 0.21.7 - knip: 5.83.1 → 6.3.1 Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * fix: add file count limit per oxlint batch to prevent OOM crashes Mitigates #84/#77: Adds OXLINT_MAX_FILES_PER_BATCH (500) to cap the number of files per oxlint invocation. Combined with the oxlint upgrade from 1.47 to 1.59, this prevents SIGABRT/OOM crashes on large projects with 100+ source files. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * feat: add --staged flag for pre-commit hook scanning Fixes #115: The --staged flag scans only files in the git index (staged with git add). It materializes staged content to a temp directory so oxlint reads the exact index version, not the working tree version. This is critical for pre-commit hooks where the working tree may differ from what's being committed. Usage: npx react-doctor . --staged --fail-on error For lint-staged: "*.{tsx,jsx}": "npx react-doctor . --staged --fail-on error" Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * refactor: code review fixes per AGENTS.md rules - DRY: Extract shared createFileLinesCache in filter-diagnostics.ts - DRY: Extract resolveFailOnLevel in cli.ts (was duplicated in staged and normal paths) - DRY: Reuse exported getCalleeName from helpers.ts instead of duplicating in state-and-effects.ts and performance.ts - Magic number: Move maxBuffer 10MB to GIT_SHOW_MAX_BUFFER_BYTES constant - Variable naming: prevLine → previousLine, tagName → fullTagName, leafName → leafTagName, configFiles → projectConfigFilenames - Remove .pop()! non-null assertion in favor of .at(-1) with fallback Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * fix: prevent shell injection in staged file content reading Replace execSync with string interpolation with spawnSync using an args array. File paths with shell metacharacters (quotes, semicolons, etc.) in their names are now passed safely without shell interpretation. Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> * fix: pass resolved config to staged scan to preserve ancestor inheritance The staged scan materializes files to /tmp, where loadConfig's ancestor traversal would never find a monorepo root's config. Now the CLI resolves config from the real project directory first and passes it via the new configOverride ScanOption, so customRulesOnly, share, ignore rules, and textComponents all apply correctly during --staged scans. 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> |
||
|
|
1aef45a509 |
fix: resolve 7 GitHub issues (catalog resolution, file ignoring, CLI, RN, Next.js, offline, monorepo) (#108)
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com> Co-authored-by: Cursor Agent <cursoragent@cursor.com> |
||
|
|
14106508a6 |
Update package.json and constants for react-doctor
- Added homepage and bugs fields to package.json for better project visibility. - Introduced new constants for React Native deprecated modules and legacy Expo package replacements in constants.ts. - Updated index.ts to include new rules for React Native diagnostics. |
||
|
|
34d1c5b79c | 0.0.13 | ||
|
|
327fcc9b50 |
fix formatting
Generated with [Ami](https://ami.dev) Co-Authored-By: Ami <noreply@ami.dev> |
||
|
|
4b48dce62c |
add packageManager field for CI pnpm setup
Generated with [Ami](https://ami.dev) Co-Authored-By: Ami <noreply@ami.dev> |
||
|
|
43816dbc45 | fix | ||
|
|
c3ba00b7c6 | fix | ||
|
|
a8770b7a10 |
add CLI scaffolding with oxlint integration
Scaffolds the react-doctor CLI with project discovery, oxlint spawning, and categorized diagnostic output. Bundles 36 rules across 5 plugins (react, jsx-a11y, react-perf, import, typescript). Generated with [Ami](https://ami.dev) Co-Authored-By: Ami <noreply@ami.dev> |
||
|
|
f50426b5ad | init |