react-doctor 0.1.3 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,17 +41,42 @@ Works with Claude Code, Cursor, Codex, OpenCode, and 50+ other agents.
41
41
 
42
42
  ## GitHub Actions
43
43
 
44
+ A composite action ships with this repository. Drop it into `.github/workflows/react-doctor.yml`:
45
+
44
46
  ```yaml
45
- - uses: actions/checkout@v5
46
- with:
47
- fetch-depth: 0 # required for --diff
48
- - uses: millionco/react-doctor@main
49
- with:
50
- diff: main
51
- github-token: ${{ secrets.GITHUB_TOKEN }}
47
+ name: React Doctor
48
+
49
+ on:
50
+ pull_request:
51
+ push:
52
+ branches: [main]
53
+
54
+ permissions:
55
+ contents: read
56
+ pull-requests: write # required to post PR comments
57
+
58
+ jobs:
59
+ react-doctor:
60
+ runs-on: ubuntu-latest
61
+ steps:
62
+ - uses: actions/checkout@v5
63
+ with:
64
+ fetch-depth: 0 # required for `diff`
65
+ - uses: millionco/react-doctor@main
66
+ with:
67
+ diff: main
68
+ github-token: ${{ secrets.GITHUB_TOKEN }}
52
69
  ```
53
70
 
54
- When `github-token` is set on `pull_request` events, findings are posted as a PR comment. The action also outputs a `score` (0 to 100) you can use in subsequent steps.
71
+ When `github-token` is set on `pull_request` events, findings are posted (and updated) as a PR comment. The action also exposes a `score` output (0100) you can use in subsequent steps.
72
+
73
+ **Inputs:** `directory`, `verbose`, `project`, `diff`, `github-token`, `fail-on` (`error` / `warning` / `none`), `offline`, `node-version`. See [`action.yml`](https://github.com/millionco/react-doctor/blob/main/action.yml) for full descriptions.
74
+
75
+ Prefer not to add a marketplace action? The bare `npx` form works too:
76
+
77
+ ```yaml
78
+ - run: npx -y react-doctor@latest --fail-on warning
79
+ ```
55
80
 
56
81
  ## Configuration
57
82
 
@@ -64,20 +89,39 @@ Create a `react-doctor.config.json` in your project root:
64
89
  "files": ["src/generated/**"],
65
90
  "overrides": [
66
91
  {
67
- "files": ["components/diff/**"],
68
- "rules": ["react-doctor/no-array-index-as-key"]
92
+ "files": ["components/modules/diff/**"],
93
+ "rules": ["react-doctor/no-array-index-as-key", "react-doctor/no-render-in-render"]
94
+ },
95
+ {
96
+ "files": ["components/search/HighlightedSnippet.tsx"],
97
+ "rules": ["react/no-danger"]
69
98
  }
70
99
  ]
71
100
  }
72
101
  }
73
102
  ```
74
103
 
75
- `ignore.rules` silences a rule everywhere. `ignore.files` silences every rule on matched files. `ignore.overrides` silences specific rules in specific directories. You can also use the `"reactDoctor"` key in `package.json`. CLI flags always override config values.
104
+ Three nested keys, three layers of granularity pick the narrowest one that fits:
105
+
106
+ - **`ignore.rules`** silences a rule across the whole codebase.
107
+ - **`ignore.files`** silences **every** rule on the matched files (use sparingly — it loses coverage for unrelated rules).
108
+ - **`ignore.overrides`** silences only the listed rules on the matched files, leaving every other rule active. This is what you want when a single file (or glob) legitimately needs an exemption from one or two rules but should still be scanned for everything else.
109
+
110
+ You can also use the `"reactDoctor"` key in `package.json`. CLI flags always override config values.
76
111
 
77
112
  React Doctor respects `.gitignore`, `.eslintignore`, `.oxlintignore`, `.prettierignore`, and `linguist-vendored` / `linguist-generated` annotations in `.gitattributes`. Inline `// eslint-disable*` and `// oxlint-disable*` comments are honored too.
78
113
 
79
114
  If you have a JSON oxlint or eslint config (`.oxlintrc.json` or `.eslintrc.json`), its rules get merged into the scan automatically and count toward the score. Set `adoptExistingLintConfig: false` to opt out.
80
115
 
116
+ #### Optional companion plugins
117
+
118
+ When the following ESLint plugins are installed in the scanned project (or hoisted in your monorepo), React Doctor folds their rules into the same scan. Both are listed as **optional peer dependencies** — install only what you want.
119
+
120
+ | Plugin | Adds | Namespace |
121
+ | ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
122
+ | [`eslint-plugin-react-hooks`](https://www.npmjs.com/package/eslint-plugin-react-hooks) (v6 or v7) | The React Compiler frontend's correctness rules — fired when a React Compiler is detected in the project. | `react-hooks-js/*` |
123
+ | [`eslint-plugin-react-you-might-not-need-an-effect`](https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect) (v0.10+) | Complementary effects-as-anti-pattern rules (`no-derived-state`, `no-chain-state-updates`, `no-event-handler`, `no-pass-data-to-parent`, …) that run alongside React Doctor's native State & Effects rules. | `effect/*` |
124
+
81
125
  ### Inline suppressions
82
126
 
83
127
  ```tsx
@@ -88,7 +132,24 @@ useEffect(() => {
88
132
  }, [value]);
89
133
  ```
90
134
 
91
- When two rules fire on the same line, comma-separate the rule ids on a single comment. Block comments work inside JSX:
135
+ When two rules fire on the same line, you have two equivalent options. Comma-separate the rule ids on a single comment:
136
+
137
+ ```tsx
138
+ // react-doctor-disable-next-line react-doctor/rerender-state-only-in-handlers, react-doctor/no-derived-useState
139
+ const [localSearch, setLocalSearch] = useState(searchQuery);
140
+ ```
141
+
142
+ Or stack one comment per rule directly above the diagnostic. Stacked comments are honored as long as nothing but other `react-doctor-disable-next-line` comments sits between them and the target line:
143
+
144
+ ```tsx
145
+ // react-doctor-disable-next-line react-doctor/rerender-state-only-in-handlers
146
+ // react-doctor-disable-next-line react-doctor/no-derived-useState
147
+ const [localSearch, setLocalSearch] = useState(searchQuery);
148
+ ```
149
+
150
+ A code line between stacked comments breaks the chain: only the comment immediately above the diagnostic (and any contiguous `react-doctor-disable-next-line` comments stacked on top of it) is honored. If a comment looks adjacent but the rule still fires, run `react-doctor --explain <file:line>` — it reports whether a nearby suppression was found, what rules it covers, and why it didn't apply.
151
+
152
+ Block comments work inside JSX:
92
153
 
93
154
  <!-- prettier-ignore -->
94
155
  ```tsx
@@ -151,10 +212,11 @@ Options:
151
212
  --fail-on <level> exit with error on diagnostics: error, warning, none
152
213
  --annotations output diagnostics as GitHub Actions annotations
153
214
  --explain <file:line> diagnose why a rule fired or why a suppression didn't apply
215
+ --why <file:line> alias for --explain
154
216
  -h, --help display help
155
217
  ```
156
218
 
157
- When a suppression isn't working, `--explain <file:line>` reports what the scanner sees at that location, including why a nearby `react-doctor-disable-next-line` didn't apply. The same hint surfaces inline with `--verbose` and in `--json` output as `diagnostic.suppressionHint`.
219
+ When a suppression isn't working, `--explain <file:line>` (or its alias `--why <file:line>`) reports what the scanner sees at that location, including why a nearby `react-doctor-disable-next-line` didn't apply. The diagnosis distinguishes the common failure modes — adjacent comment for a different rule (use the comma form), a code line between the comment and the diagnostic (the chain is broken), or no nearby suppression at all. The same hint surfaces inline with `--verbose` for every flagged site, and in `--json` output as `diagnostic.suppressionHint`, so a single scan doubles as a suppression audit without a separate flag.
158
220
 
159
221
  `--json` produces a parsable object on stdout with all human-readable output suppressed. Errors still produce a JSON object with `ok: false`, so stdout is always a valid document.
160
222
 
@@ -211,15 +273,15 @@ Top React codebases scanned by React Doctor, ranked by score. Updated automatica
211
273
  | # | Repo | Score |
212
274
  | -- | ---- | ----: |
213
275
  | 1 | [executor](https://github.com/RhysSullivan/executor) | 96 |
214
- | 2 | [nodejs.org](https://github.com/nodejs/nodejs.org) | 87 |
215
- | 3 | [tldraw](https://github.com/tldraw/tldraw) | 76 |
216
- | 4 | [t3code](https://github.com/pingdotgg/t3code) | 75 |
217
- | 5 | [mastra](https://github.com/mastra-ai/mastra) | 70 |
218
- | 6 | [excalidraw](https://github.com/excalidraw/excalidraw) | 69 |
219
- | 7 | [payload](https://github.com/payloadcms/payload) | 69 |
220
- | 8 | [better-auth](https://github.com/better-auth/better-auth) | 69 |
221
- | 9 | [rocket.chat](https://github.com/RocketChat/Rocket.Chat) | 67 |
222
- | 10 | [typebot](https://github.com/baptisteArno/typebot.io) | 66 |
276
+ | 2 | [nodejs.org](https://github.com/nodejs/nodejs.org) | 86 |
277
+ | 3 | [tldraw](https://github.com/tldraw/tldraw) | 70 |
278
+ | 4 | [t3code](https://github.com/pingdotgg/t3code) | 68 |
279
+ | 5 | [better-auth](https://github.com/better-auth/better-auth) | 64 |
280
+ | 6 | [excalidraw](https://github.com/excalidraw/excalidraw) | 63 |
281
+ | 7 | [mastra](https://github.com/mastra-ai/mastra) | 63 |
282
+ | 8 | [payload](https://github.com/payloadcms/payload) | 60 |
283
+ | 9 | [typebot](https://github.com/baptisteArno/typebot.io) | 57 |
284
+ | 10 | [plane](https://github.com/makeplane/plane) | 56 |
223
285
 
224
286
  <!-- LEADERBOARD:END -->
225
287