344 Commits

Author SHA1 Message Date
Aiden Bai 28db33f320 Simplify npx usage (#280) v0.3.1 2026-05-16 20:18:03 -07:00
Aiden Bai 08063c0a62 docs: remove stale dead-code claims after Knip removal (#246) (#283)
* docs: remove stale dead-code claims after Knip removal (#246)

PR #246 removed the bundled knip integration, but several user-facing
docs still advertised dead-code coverage and exposed the removed
`--dead-code` / `--no-dead-code` CLI flags:

- packages/react-doctor/README.md ("and dead code" in the categories list)
- skills/react-doctor/SKILL.md (skill description)
- packages/website/public/llms.txt (CLI options block)

Strips those claims and adds a short migration note pointing users to
`npx knip` directly for dead-code analysis.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>

* chore(todos): mark stale dead-code doc cleanup as resolved

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>
2026-05-16 20:16:40 -07:00
Aiden Bai 118741e260 Simplify rule set exports (#277) 2026-05-16 19:58:10 -07:00
Cursor Agent 3bb230f752 Revert "feat(oxlint): add effect/ analyzer primitives for upstream rule port"
This reverts commit 891088c1b0.
2026-05-17 02:08:42 +00:00
Cursor Agent d7efa43fb8 Revert "feat(oxlint): port 8 rules from eslint-plugin-react-you-might-not-need-an-effect"
This reverts commit 86a2a98e7c.
2026-05-17 02:08:42 +00:00
Cursor Agent 86a2a98e7c feat(oxlint): port 8 rules from eslint-plugin-react-you-might-not-need-an-effect
1:1 ports of:
- no-derived-state (avoidDerivedState + avoidSingleSetter branches)
- no-chain-state-updates
- no-event-handler (per-test-identifier reporting, state vs prop variants)
- no-adjust-state-on-prop-change
- no-reset-all-state-on-prop-change (custom-hook skip, full-reset detection)
- no-pass-live-state-to-parent (hook vs component message variants)
- no-pass-data-to-parent (leaf classification, ref/refCurrent exclusions)
- no-initialize-state (mount-only deps detection)

Diagnostic messages match upstream verbatim with template-variable
substitution done in JS. Rules coexist with our existing thematically
related rules (no-derived-state-effect, no-effect-chain,
no-event-trigger-state, no-prop-callback-in-effect); the IDs are
distinct.

Total rule count rises from 179 → 187.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:58:54 +00:00
Cursor Agent 891088c1b0 feat(oxlint): add effect/ analyzer primitives for upstream rule port
Builds the AST-only foundation needed to port the 8 rules from
eslint-plugin-react-you-might-not-need-an-effect (NickvanDyke,
v0.10.1, upstream SHA 4c71faaa7623d2d5feb33983dc2ebcc08206bcc5).

Upstream is ESLint-native and uses context.sourceCode.getScope()
references / resolved.defs[].node.init recursively to chase the
ultimate source of every value. Oxlint plugins have no scope
manager. This commit ports the impedance-bridging analyzer:

- get-effect-deps-array.ts: typed accessor for useEffect(..., deps)
- has-effect-cleanup.ts: matches upstream's loose hasCleanup shape
- is-custom-hook-name.ts: `use[A-Z]` predicate
- is-synchronous.ts: parent-pointer walk bailing on await / void /
  nested function-likes
- get-containing-component-or-hook.ts: classifies the surrounding
  function as component / HOC-wrapped component / custom hook
  (mirrors upstream's pure-HOC short-circuit for memo/forwardRef)
- collect-use-ref-bindings.ts: top-level useRef binding collector
- collect-local-function-bindings.ts: FunctionDeclaration / arrow /
  useCallback bindings with param names + body for indirect-call
  reasoning
- analyze-component-bindings.ts: the cornerstone — classifies every
  component-scope name as state / setter / ref / prop / constant /
  local-function and precomputes localFunctionCalls{StateSetter,
  PropFunction,RefMethod} flags so callees like setX(...) inside an
  intermediateSetter() still register as state-setter calls
- classify-effect-callee-argument.ts: leaf / transitive upstream
  classification used by every rule for argument and deps analysis
- stringify-expression-snippet.ts: bounded AST → text formatter for
  the no-initialize-state "set to ..." message
- create-effect-analyzer-tracker.ts: function-entry / -exit visitor
  pair that maintains the ComponentBindingTable stack

Plus rules/state-and-effects/effect/SOURCE.md with upstream commit
SHA, license attribution, and known divergences.

Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:54:09 +00:00
Cursor Agent 013d8b060b docs(changelog): replace em-dashes with hyphens across all changelog content
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:52:54 +00:00
Cursor Agent 1ece85187e docs(changelog): rewrite react-doctor 0.0.1..0.0.47 from version diffs
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:49:25 +00:00
Cursor Agent 2f4e722074 docs(changelog): rewrite react-doctor 0.1.0..0.1.6 from version diffs
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:44:45 +00:00
Cursor Agent 9add2d4771 docs(changelog): drop @aidenybai attribution per user request
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:41:00 +00:00
Cursor Agent fad39ccd8a docs(changeset): refresh placeholder changeset bodies with real descriptions
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:08:43 +00:00
Cursor Agent 687b52d9af docs(changelog): rewrite react-doctor 0.2.0-beta.2..beta.5 from version diffs
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:07:52 +00:00
Cursor Agent 65553614fc docs(changelog): rewrite @react-doctor/types 0.2.0-beta.2..beta.3
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:06:09 +00:00
Cursor Agent 60ae4f8b3d docs(changelog): rewrite @react-doctor/project-info 0.2.0-beta.2..beta.3
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:05:42 +00:00
Cursor Agent cbe1dacb64 docs(changelog): rewrite @react-doctor/core 0.2.0-beta.2..beta.5
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:04:56 +00:00
Cursor Agent ede38a3864 docs(changelog): rewrite eslint-plugin-react-doctor 0.2.0-beta.2..beta.5
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:03:47 +00:00
Cursor Agent 56a916c8bb docs(changelog): rewrite oxlint-plugin-react-doctor 0.2.0-beta.2..beta.5 from version diffs
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
2026-05-17 01:03:06 +00:00
Aiden Bai 99f6a6ad1c chore: align all package versions to 0.2.0-beta.5
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-16 04:51:57 -07:00
Aiden Bai 4cbf436848 fix(async-parallel): suppress in tests, browser fixtures, and ordered UI flows (#270) 2026-05-16 04:48:23 -07:00
Nisarg Patel b53d873045 fix(nextjs): stop flagging response.headers and local Map/Set/Headers in GET handlers (#206) (#260)
Co-authored-by: Aiden Bai <aidenybai@users.noreply.github.com>
Co-authored-by: Aiden Bai <aiden.bai05@gmail.com>
2026-05-16 04:42:49 -07:00
Aiden Bai 838c7f4174 fix(js-length-check-first): detect length guards in larger && chains (#269) 2026-05-16 04:37:17 -07:00
Aiden Bai 3b7cc7c373 fix(no-prevent-default): make rule framework-aware (#274) 2026-05-16 04:29:51 -07:00
Aiden Bai 7a7ec84fad feat: demote design/Tailwind cleanup from default PR comments via per-surface controls (#271) v0.3.0 2026-05-16 04:23:30 -07:00
Aiden Bai d821ca2a82 fix(oxlint): skip js-combine-iterations on lazy Iterator helper chains (#205) (#272) 2026-05-16 04:23:18 -07:00
Aiden Bai 529015d1d8 Scope React Native rules to RN/Expo packages (#266) 2026-05-16 00:30:39 -07:00
Aiden Bai 18b7033e9e fix: avoid async defer await destructuring false positives (#265) 2026-05-15 21:45:34 -07:00
Aiden Bai 8a3a1bee7e update todos 2026-05-15 17:26:49 -07:00
Aiden Bai 2d90c1c5ae fix: scope client secret diagnostics (#252)
* fix: scope client secret diagnostics

Avoid reporting weak secret-name heuristics in config and tooling files, and render framework-specific public env guidance so non-Next.js projects get actionable output.

* fix: broaden secret false-positive guards

Extend the weak client-secret heuristic boundary to common server and tooling paths while keeping exact secret-shape detection active, and move recommendation helpers into focused utilities.

* fix: tighten rc file secret heuristic

Require rc config filenames to be explicit so normal source files like src.ts still use the weak secret-name heuristic.

* test: cover explicit rc secret heuristic

Add regression coverage that explicit rc config files remain outside the weak client-secret name heuristic while regular src.ts files still report.

* fix: refine secret heuristic boundaries

Keep client API helpers covered while making the server-only heuristic exclusions easier to audit.

* chore: format TODO tracker

Format the inherited TODO tracker so PR merge-commit checks can pass.

* fix: classify client secret exposure

Gate the weak secret-name heuristic on explicit client exposure while keeping high-confidence secret values active everywhere.

* test: cover secret exposure boundaries

Lock down client, server, ambiguous-source, and App Router cases for the client-secret heuristic.

* fix: pass framework to secret classifier

Use detected framework settings to avoid path-only client exposure guesses for app directories and server-owned source roots.

* fix: respect use server in secret classifier

Classify use-server modules as server code so client-path heuristics do not report server action constants.

* fix: harden secret exposure edges

Cover mixed server-only scopes, server-owned source roots, and additional client entry shapes so the weak secret heuristic stays scoped to bundled client code.

* fix: refine secret classifier precedence

Classify secret exposure from project-relative paths and gate Next-only route heuristics so explicit client signals win over broad server path names.

* fix: suppress secret label false positives

Avoid weak secret-name diagnostics for UI copy and storage-key prefixes found during evals while preserving client secret fallback coverage.

* fix: preserve api key secret detection

Keep camel-case apiKey variables eligible for the weak secret heuristic while retaining UI text and storage prefix suppressions.

* chore: format changeset prerelease metadata

Keep the new prerelease metadata from main aligned with the repository formatter so CI can pass on the PR merge commit.
2026-05-16 00:32:54 +03:00
Aiden Bai 29b7229ea1 0.2.0-beta.4 2026-05-15 07:13:11 -07:00
Aiden Bai 10d5de804f 0.2.0-beta.3 2026-05-15 07:11:24 -07:00
Aiden Bai 9783acf525 Fix no-barrel-import index false positives (#253) 2026-05-15 07:07:52 -07:00
Aiden Bai 809e38ceba pub 2026-05-15 07:01:35 -07:00
Aiden Bai 0cd93551a4 fix: narrow effect event handler detection (#256) 2026-05-15 06:47:09 -07:00
Aiden Bai bca5d30fc5 fix(oxlint): collapse duplicate diagnostics + recover diagnostics from large monorepo projects (eval-driven) (#262) 2026-05-15 06:46:36 -07:00
Aiden Bai ffbd20f3d0 fix: suppress local use hook false positives (#257) 2026-05-15 06:22:35 -07:00
Aiden Bai bfaf9c9530 fix: avoid React 19 rule false positives on React 18 (#254) 2026-05-15 05:40:34 -07:00
Aiden Bai 6bc33c8aab fix: treat return guards as render state reads (#255) 2026-05-15 05:40:07 -07:00
Aiden Bai d23137e177 test: add fixed issue regression coverage (#258) 2026-05-15 02:23:03 -07:00
Aiden Bai 92829c7123 chore: format TODOS.md to fix format:check on main 2026-05-15 02:15:22 -07:00
Aiden Bai 0a52ca3d05 fix 2026-05-15 01:47:20 -07:00
Aiden Bai 9902ea671d TODOS.md 2026-05-15 01:45:59 -07:00
Aiden Bai 6e2ee9d474 refactor(cli): reorganize cli/ into commands/utils + port from react-grab (#250) 2026-05-15 00:50:42 -07:00
Aiden Bai f0198e2f2d refactor: extract @react-doctor/{types,project-info,core} internal packages + cleanup (#249) 2026-05-14 22:22:19 -07:00
Aiden Bai 4d622970b5 Revert "pub"
This reverts commit 4d9d1a5fcf.
2026-05-14 22:05:22 -07:00
Aiden Bai 4d9d1a5fcf pub 2026-05-14 22:03:02 -07:00
Aiden Bai 12d9bf75ab chore: drop .fallowrc.json + fallow devDep (one-time sweep, not committing to fallow integration) (#247) 2026-05-14 19:34:29 -07:00
Aiden Bai 6d8ba1b993 refactor: rip out knip / dead-code-detection integration (#246)
react-doctor is dropping dead-code analysis. knip exists as a standalone
tool; users who want it can run `knip` themselves. This removes the
entire integration: subprocess runner, config loading, filter caching,
result merging, CLI flag, types, and tests.

## Source files deleted (7)

- `src/core/run-knip.ts` (224 LOC)
- `src/core/collect-unused-file-paths.ts`
- `src/core/sanitize-knip-config-patterns.ts`
- `src/core/has-knip-config.ts`
- `src/core/is-likely-build-entry.ts` (only consumer was the knip
  auto-suppression branch in merge-and-filter-diagnostics)
- `src/types/knip.ts`
- `src/knip.d.ts`

## Test files deleted (5)

- `tests/run-knip.test.ts`
- `tests/has-knip-config.test.ts`
- `tests/sanitize-knip-config-patterns.test.ts`
- `tests/collect-unused-file-paths.test.ts`
- `tests/extract-failed-plugin-name.test.ts` (was knip-specific)

## Modified files (26)

- `src/core/inspect.ts` — drop `deadCode` option, the entire
  `deadCodePromise` block, `runKnip` import, `didDeadCodeFail`
  tracking, and the "dead code" skip-list entry. `Promise.all` over
  one promise → direct `await`.
- `src/core/combine-diagnostics.ts` — drop `deadCodeDiagnostics`
  parameter
- `src/core/merge-and-filter-diagnostics.ts` — drop the
  `if (plugin === "knip" && rule === "files")` auto-suppression branch
  and its `isLikelyBuildEntry` cache
- `src/types/inspect.ts`, `types/diagnose.ts`, `types/config.ts` —
  drop `deadCode?: boolean` fields and `entryFiles?` (knip-only)
- `src/cli/cli-flags.ts`, `cli/resolve-cli-inspect-options.ts`,
  `cli/index.ts`, `cli/commands/inspect.ts` — drop `--dead-code` /
  `--no-dead-code` flags
- `src/core/validate-config-types.ts` — drop validator entries for
  `deadCode` and `entryFiles`
- `package.json` — drop `"knip"` from `keywords` and from
  `dependencies`
- Various test files — drop knip-related assertions

## Verification

- `pnpm typecheck` — 0 errors
- `pnpm test` — 75 test files / **688 / 688 pass** (was 80/734 — 5
  test files and 46 tests removed with knip)
- `pnpm lint` — 0 warnings, 0 errors
- `pnpm format` — clean
- Diagnostic-snapshot diff vs `origin/main` — byte-identical 262 lines
  (the snapshot only covers `runOxlint` output; knip was a separate
  diagnostic source that didn't contribute on the test fixtures)

## Stats

- **39 files changed, +43 / -1700 net (1657 line reduction)**
- 13 files deleted
- One full dependency dropped (`knip` from package.json)
2026-05-14 19:29:04 -07:00
Aiden Bai 575a245179 chore(dead-code): fallow sweep — drop 2 dead constants, dedupe RuleSeverity, fix test exports (#245)
Ran fallow (https://github.com/fallow-rs/fallow) against the repo for
dead-code analysis. Acted on the genuine findings, skipped false
positives.

## Genuine dead code (deleted)

- **`USE_EFFECT_EVENT_MIN_MAJOR`** (`src/constants.ts`) — only referenced
  in a comment, never in actual code. Real dead.
- **`EM_DASH_CHARACTER`** (`src/plugin/constants/design.ts`) — was used
  by the `no-em-dash-in-jsx-text` rule that #242 deleted as an orphan;
  stale leftover.

## Test helpers (made internal)

- `tests/run-oxlint/_helpers.ts` had `FIXTURES_DIRECTORY` and
  `findDiagnosticsByRule` marked `export const`, but neither is consumed
  outside the file. Stripped the `export` keyword on both — internal use
  still works, dead-export warning gone.

## Duplicate export (renamed)

- `RuleSeverity` was declared in two places with different definitions:
  - `plugin/utils/rule.ts`: `"error" | "warn"` (plugin's defineRule
    type — never "off" because off rules aren't registered)
  - `core/runners/oxlint/types.ts`: `"error" | "warn" | "off"` (oxlint
    config entries — "off" explicitly disables a rule)

  These are related but semantically distinct. Unified by making the
  oxlint version DERIVE from the plugin type: `OxlintRuleSeverity =
  RuleSeverity | "off"`. Renamed all oxlint-layer usages to
  `OxlintRuleSeverity` to make the distinction clear in code. Plugin's
  `RuleSeverity` remains the single source of truth.

## Tool integration

- Added `fallow` as a devDependency
- Added `.fallowrc.json` with this project's entry points
  (`src/cli/index.ts`, `src/index.ts`, `src/plugin/react-doctor-plugin.ts`,
  `src/eslint-plugin.ts`, `scripts/*.mjs`) and `tests/fixtures/**`
  ignored (those are intentional broken-code fixtures)

Run `pnpm exec fallow --summary` from the package to re-audit.

## Not acted on (false positives or out-of-scope)

- 5 `name` field warnings on `Error` subclasses in `src/errors.ts` —
  the `name` property is consumed by the JS runtime when formatting
  errors, not by direct code references. False positive.
- 2 "unlisted dependency" warnings (`undici`, `vite-plus`) — both are
  available transitively. Adding them to `dependencies` would change
  install behavior, which is outside the dead-code-sweep scope.
- 4 "unresolved import" warnings in `tests/fixtures/**` — intentional
  broken-code fixtures used by suppression / resilience tests. Already
  in `.fallowrc.json` ignorePatterns; the next fallow run will skip.

## Verification

- `pnpm typecheck` — 0 errors
- `pnpm test` — 734 / 734 pass
- `pnpm lint` — 0 warnings, 0 errors
- `pnpm format` — clean
- Diagnostic-snapshot diff vs `origin/main` — byte-identical 262 lines

## Stats

- 11 files changed, +115 / -28 net (most of the +/- is the
  `OxlintRuleSeverity` rename touching 5 files)
- Net deletion of 2 dead constants + 2 unnecessary exports
2026-05-14 19:23:56 -07:00
Aiden Bai 7169551251 refactor: tier 1 cleanup — drop dead fields, flatten core/, inline single-use utils (#244)
* refactor: tier 1 cleanup — drop dead fields, flatten core/, inline single-use utils

Five structural simplifications in one PR, all zero-behavior-change
(verified: byte-identical 262-line diagnostic snapshot vs `main`).

## 1. Drop `examples` field on Rule

The `examples: RuleExample[]` slot was authored across all 178 rules in
PR #234 but nothing ever consumed it. Pure storage / maintenance burden.
Removed the field from the interface, deleted `RuleExample` type, and
stripped the field from every rule via codemod.

## 2. Derive `framework` from bucket directory (no per-rule field)

The `framework` field was always 1:1 with the bucket directory:
- `nextjs/*.ts` → `framework: "nextjs"`
- `react-native/*.ts` → `framework: "react-native"`
- `tanstack-start/*.ts` → `framework: "tanstack-start"`
- `tanstack-query/*.ts` → `framework: "tanstack-query"`
- everything else → `framework: "global"`

Pure redundancy. Updated the codegen (`scripts/generate-rule-registry.mjs`)
to compute it from the bucket directory and inject it into the registry
entry. Rule files no longer carry the field at all. 178 lines removed
across rule files; codegen gained the bucket→framework table.

## 3. Default `category` from bucket, preserve overrides

Same shape as #2 but with overrides — most rules in a bucket share a
category, but a few (e.g. `tanstack-start-no-secrets-in-loader`,
`nextjs-no-side-effect-in-get-handler`) override to `"Security"`.

Codegen now provides a bucket→default-category table, and rules only
ship the `category` field when overriding. Codemod stripped ~170 of the
178 `category:` declarations (left 4 overrides intact, verified by
diagnostic snapshot).

## 4. Inline single-use plugin utils

Helpers in `plugin/utils/` and `plugin/rules/<bucket>/utils/` with only
one importer got inlined back into their consumer. Pure file-count
reduction — utilities shared by 2+ rules stay.

- `plugin/utils/`: ~50 → 40 files
- `plugin/rules/*/utils/`: ~30 → 27 files

## 5. Flatten `core/` subdirectories

The 5 subdirectories (`core/config/`, `core/detection/`,
`core/diagnostics/`, `core/runners/`, `core/scoring/`) collapsed into a
flat `core/` directory. Subdirs weren't pulling weight — consumers always
imported by specific file name, never as a group. Now everything's one
level deep.

(Exception: `core/runners/oxlint/` stays nested — those 6 files form a
cohesive oxlint-config subcluster.)

200+ import statements updated across the codebase. `git mv` used so
history is preserved.

## Verification

- `pnpm typecheck` — 0 errors
- `pnpm test` — 80 test files / 734 / 734 pass
- `pnpm lint` — 0 warnings, 0 errors
- `pnpm format` — clean
- Diagnostic-snapshot diff vs `origin/main` — byte-identical 262 lines

## Stats

- 330 files changed, **+650 / -6312 net** (5662 line reduction)
- 18 files deleted (dead utils + the now-pointless RuleExample type)
- 12 renamed (the core/ flatten)
- 54 renamed-with-modifications (core/ flatten + import updates)

After this PR, the largest source-of-truth files are the 178 rule files
themselves; everything else is supporting infrastructure that's now
smaller and flatter.

* fix(codegen): emit multi-line registry entries so prettier doesn't re-wrap them

CI's `format:check` reformatted long single-line registry entries to
multi-line, which would have caused the codegen-overwrite-on-build to
loop forever. Pre-emit the multi-line shape so the file is byte-stable
across `pnpm gen → pnpm format → pnpm gen`.

Verified: MD5 unchanged across gen → format → gen cycle.
2026-05-14 18:50:14 -07:00