react-doctor 0.0.47 → 0.1.0

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
@@ -7,22 +7,13 @@
7
7
  [![version](https://img.shields.io/npm/v/react-doctor?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/react-doctor)
8
8
  [![downloads](https://img.shields.io/npm/dt/react-doctor.svg?style=flat&colorA=000000&colorB=000000)](https://npmjs.com/package/react-doctor)
9
9
 
10
- Let coding agents diagnose and fix your React code.
10
+ Your agent writes bad React, this catches it.
11
11
 
12
- One command scans your codebase for security, performance, correctness, and architecture issues, then outputs a **0100 score** with actionable diagnostics.
12
+ One command scans your codebase and outputs a **0 to 100 health score** with actionable diagnostics.
13
13
 
14
- ### [See it in action →](https://react.doctor)
15
-
16
- https://github.com/user-attachments/assets/07cc88d9-9589-44c3-aa73-5d603cb1c570
17
-
18
- ## How it works
19
-
20
- React Doctor detects your framework (Next.js, Vite, Remix, etc.), React version, and compiler setup, then runs two analysis passes **in parallel**:
21
-
22
- 1. **Lint**: Checks 60+ rules across state & effects, performance, architecture, bundle size, security, correctness, accessibility, and framework-specific categories (Next.js, React Native). Rules are toggled automatically based on your project setup.
23
- 2. **Dead code**: Detects unused files, exports, types, and duplicates.
14
+ Works with Next.js, Vite, and React Native.
24
15
 
25
- Diagnostics are filtered through your config, then scored by severity (errors weigh more than warnings) to produce a **0–100 health score** (75+ Great, 50–74 Needs work, <50 Critical).
16
+ ### [See it in action →](https://react.doctor)
26
17
 
27
18
  ## Install
28
19
 
@@ -32,23 +23,21 @@ Run this at your project root:
32
23
  npx -y react-doctor@latest .
33
24
  ```
34
25
 
35
- Use `--verbose` to see affected files and line numbers:
26
+ You'll get a score (75+ Great, 50 to 74 Needs work, under 50 Critical) and a list of issues across state & effects, performance, architecture, security, accessibility, and dead code. Rules toggle automatically based on your framework and React version.
36
27
 
37
- ```bash
38
- npx -y react-doctor@latest . --verbose
39
- ```
28
+ https://github.com/user-attachments/assets/07cc88d9-9589-44c3-aa73-5d603cb1c570
40
29
 
41
30
  ## Install for your coding agent
42
31
 
43
- Teach your coding agent React best practices. Run this at your project root:
32
+ Teach your coding agent React best practices so it stops writing the bad code in the first place.
44
33
 
45
34
  ```bash
46
35
  npx -y react-doctor@latest install
47
36
  ```
48
37
 
49
- You'll be prompted to pick which detected agents to install for. Pass `--yes` to skip prompts and install for every detected agent.
38
+ You'll be prompted to pick which detected agents to install for. Pass `--yes` to skip prompts.
50
39
 
51
- Supports 50+ coding agents via [`agent-install`](https://www.npmjs.com/package/agent-install), including Claude Code, Codex, Cursor, Factory Droid, Gemini CLI, GitHub Copilot, Goose, OpenCode, Pi, Windsurf, Roo Code, Cline, Kilo Code, Warp, Replit, OpenHands, Continue, and many more. Detection is the union of CLI binaries on `$PATH` and config dirs in `$HOME` (`~/.claude`, `~/.cursor`, `~/.codex`, `~/.factory`, `~/.pi`, etc.).
40
+ Works with Claude Code, Cursor, Codex, OpenCode, and 50+ other agents.
52
41
 
53
42
  ## GitHub Actions
54
43
 
@@ -62,289 +51,168 @@ Supports 50+ coding agents via [`agent-install`](https://www.npmjs.com/package/a
62
51
  github-token: ${{ secrets.GITHUB_TOKEN }}
63
52
  ```
64
53
 
65
- | Input | Default | Description |
66
- | -------------- | ------- | ----------------------------------------------------------------- |
67
- | `directory` | `.` | Project directory to scan |
68
- | `verbose` | `true` | Show file details per rule |
69
- | `project` | | Workspace project(s) to scan (comma-separated) |
70
- | `diff` | | Base branch for diff mode. Only changed files are scanned |
71
- | `github-token` | | When set on `pull_request` events, posts findings as a PR comment |
72
- | `fail-on` | `error` | Exit with error code on diagnostics: `error`, `warning`, `none` |
73
- | `offline` | `false` | Skip sending diagnostics to the react.doctor API |
74
- | `node-version` | `22` | Node.js version to use |
75
-
76
- The action outputs a `score` (0–100) you can use in subsequent steps.
77
-
78
- ## Options
79
-
80
- ```
81
- Usage: react-doctor [directory] [options]
82
-
83
- Options:
84
- -v, --version display the version number
85
- --no-lint skip linting
86
- --no-dead-code skip dead code detection
87
- --verbose show file details per rule
88
- --score output only the score
89
- --json output a single structured JSON report (suppresses other output)
90
- -y, --yes skip prompts, scan all workspace projects
91
- --full skip prompts, always run a full scan (decline diff-only)
92
- --project <name> select workspace project (comma-separated for multiple)
93
- --diff [base] scan only files changed vs base branch
94
- --offline skip telemetry (anonymous, not stored, only used to calculate score)
95
- --staged scan only staged (git index) files for pre-commit hooks
96
- --fail-on <level> exit with error code on diagnostics: error, warning, none
97
- --annotations output diagnostics as GitHub Actions annotations
98
- -h, --help display help for command
99
- ```
100
-
101
- ## JSON output
102
-
103
- Pass `--json` to get a single, parsable JSON object on stdout. All human-readable output, prompts, and the share link are suppressed; pipe straight into `jq`, `node`, or any other tool:
104
-
105
- ```bash
106
- npx -y react-doctor@latest . --json | jq '.summary'
107
- ```
108
-
109
- Exit code is `0` on success and `1` if the scan throws or `--fail-on` is triggered. Errors still produce a JSON object with `ok: false`, so the stdout is always a valid document.
110
-
111
- ### Schema
112
-
113
- ```ts
114
- interface JsonReport {
115
- schemaVersion: 1;
116
- version: string; // react-doctor version
117
- ok: boolean; // false when an error was thrown
118
- directory: string; // resolved root passed to the CLI
119
- mode: "full" | "diff" | "staged";
120
- diff: {
121
- baseBranch: string;
122
- currentBranch: string;
123
- changedFileCount: number;
124
- isCurrentChanges: boolean;
125
- } | null;
126
- projects: Array<{
127
- directory: string;
128
- project: ProjectInfo;
129
- diagnostics: Diagnostic[];
130
- score: { score: number; label: string } | null;
131
- skippedChecks: string[];
132
- elapsedMilliseconds: number;
133
- }>;
134
- diagnostics: Diagnostic[]; // flattened across all scanned projects
135
- summary: {
136
- errorCount: number;
137
- warningCount: number;
138
- affectedFileCount: number;
139
- totalDiagnosticCount: number;
140
- score: number | null; // worst project score, when available
141
- scoreLabel: string | null;
142
- };
143
- elapsedMilliseconds: number; // total wall time across all projects
144
- error: {
145
- message: string;
146
- name: string;
147
- chain: string[]; // outer error message first, every `error.cause`
148
- // unwrapped after; chain[0] always equals `message`
149
- } | null; // null on success, populated when ok=false
150
- }
151
- ```
152
-
153
- ## Browser API
154
-
155
- Import `react-doctor/browser` to run the same **diagnostics merge, config-based filtering, timing, and scoring pipeline** as `react-doctor/api`’s `diagnose`, but with **caller-supplied** inputs: `project` metadata, a virtual `projectFiles` map (contents keyed by paths relative to `rootDirectory`) for ignore/suppression resolution, and a `runOxlint` callback that performs linting in your environment (for example a Web Worker with oxlint).
156
-
157
- Git history, real filesystem discovery, knip, the CLI, staged-file detection, and interactive prompts are **not** available in the browser bundle; treat those as Node-only or supply equivalents yourself. `react-doctor/worker` re-exports the same browser-facing modules for worker targets.
158
-
159
- If you call **`diagnoseCore`** yourself in the browser, pass **`calculateDiagnosticsScore`** from this package (re-exported as **`calculateScore`** on `react-doctor/browser`) so the bundle never pulls in Node-only proxy code.
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.
160
55
 
161
56
  ## Configuration
162
57
 
163
- Create a `react-doctor.config.json` in your project root to customize behavior:
58
+ Create a `react-doctor.config.json` in your project root:
164
59
 
165
60
  ```json
166
61
  {
167
62
  "ignore": {
168
- "rules": ["react/no-danger", "jsx-a11y/no-autofocus", "knip/exports"],
169
- "files": ["src/generated/**"]
63
+ "rules": ["react/no-danger", "jsx-a11y/no-autofocus"],
64
+ "files": ["src/generated/**"],
65
+ "overrides": [
66
+ {
67
+ "files": ["components/diff/**"],
68
+ "rules": ["react-doctor/no-array-index-as-key"]
69
+ }
70
+ ]
170
71
  }
171
72
  }
172
73
  ```
173
74
 
174
- You can also use the `"reactDoctor"` key in your `package.json` instead:
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.
175
76
 
176
- ```json
177
- {
178
- "reactDoctor": {
179
- "ignore": {
180
- "rules": ["react/no-danger"]
181
- }
182
- }
183
- }
184
- ```
77
+ 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.
185
78
 
186
- If both exist, `react-doctor.config.json` takes precedence.
79
+ 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.
187
80
 
188
81
  ### Inline suppressions
189
82
 
190
- Suppress a rule on a specific line with `// react-doctor-disable-line` or the next line with `// react-doctor-disable-next-line`:
191
-
192
83
  ```tsx
193
84
  // react-doctor-disable-next-line react-doctor/no-cascading-set-state
194
85
  useEffect(() => {
195
86
  setA(value);
196
87
  setB(value);
197
- setC(value);
198
88
  }, [value]);
199
-
200
- const value = expensiveComputation(); // react-doctor-disable-line react-doctor/no-usememo-simple-expression
201
89
  ```
202
90
 
203
- Comma- or space-separate multiple rule ids on the same comment. With no rule id, the comment suppresses every diagnostic on that line.
91
+ When two rules fire on the same line, comma-separate the rule ids on a single comment. Block comments work inside JSX:
204
92
 
205
- ### Respecting your existing project ignores
93
+ <!-- prettier-ignore -->
94
+ ```tsx
95
+ {/* react-doctor-disable-next-line react/no-danger */}
96
+ <div dangerouslySetInnerHTML={{ __html }} />
97
+ ```
98
+
99
+ For multi-line JSX, putting the comment immediately above the opening tag covers the entire attribute list (matching ESLint convention).
206
100
 
207
- By default, React Doctor honors all of the ignore-style files your project already has, so you don't need to maintain a separate "what should react-doctor skip" list:
101
+ ## Lint plugin (standalone)
208
102
 
209
- | File | What gets skipped |
210
- | ------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
211
- | `.gitignore` | files git ignores (oxlint default) |
212
- | `.eslintignore` | files eslint skips (oxlint default) |
213
- | `.oxlintignore` | files oxlint skips (added via `--ignore-pattern` so `.eslintignore` still applies) |
214
- | `.prettierignore` | files prettier skips — typically vendored code, generated builds, and lockfiles |
215
- | `.gitattributes` (`linguist-vendored`, `linguist-generated`) | paths GitHub's linguist library hides from language stats; if it's not "your" code by GitHub's reckoning, it shouldn't be audited as your code by react-doctor either |
103
+ The same rule set ships as both an oxlint plugin and an ESLint plugin, so you can wire it into whichever lint engine your project already runs.
216
104
 
217
- React Doctor also respects inline lint suppressions in source files:
105
+ **oxlint** in `.oxlintrc.json`:
218
106
 
219
- - `// oxlint-disable`, `// oxlint-disable-line`, `// oxlint-disable-next-line` — with or without rule ids.
220
- - `// eslint-disable`, `// eslint-disable-line`, `// eslint-disable-next-line` — oxlint reads both prefixes interchangeably.
107
+ ```jsonc
108
+ {
109
+ "jsPlugins": [{ "name": "react-doctor", "specifier": "react-doctor/oxlint-plugin" }],
110
+ "rules": {
111
+ "react-doctor/no-fetch-in-effect": "warn",
112
+ "react-doctor/no-derived-state-effect": "warn",
113
+ },
114
+ }
115
+ ```
221
116
 
222
- > Note: `.editorconfig` is intentionally NOT consulted. It describes editor settings (indent size, charset, end-of-line) and has no concept of "files to skip" — there's nothing in it that would change what react-doctor lints.
117
+ **ESLint** flat config:
223
118
 
224
- If you want React Doctor to ignore those inline suppressions and audit your codebase for everything (useful for one-off "what does my project actually score?" runs), set:
119
+ ```js
120
+ import reactDoctor from "react-doctor/eslint-plugin";
225
121
 
226
- ```jsonc
227
- { "respectInlineDisables": false }
122
+ export default [
123
+ reactDoctor.configs.recommended,
124
+ reactDoctor.configs.next,
125
+ reactDoctor.configs["react-native"],
126
+ reactDoctor.configs["tanstack-start"],
127
+ reactDoctor.configs["tanstack-query"],
128
+ ];
228
129
  ```
229
130
 
230
- This only neutralizes the inline `// eslint-disable*` / `// oxlint-disable*` comments — the file-level ignore lists above are always honored, even in audit mode, because they typically point at vendored or generated code that genuinely shouldn't be linted.
131
+ The full rule list lives in [`oxlint-config.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/oxlint-config.ts).
231
132
 
232
- ### Config options
133
+ ## CLI reference
233
134
 
234
- | Key | Type | Default | Description |
235
- | ----------------------- | -------------------------------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
236
- | `ignore.rules` | `string[]` | `[]` | Rules to suppress, using the `plugin/rule` format shown in diagnostic output (e.g. `react/no-danger`, `knip/exports`, `knip/types`) |
237
- | `ignore.files` | `string[]` | `[]` | File paths to exclude, supports glob patterns (`src/generated/**`, `**/*.test.tsx`) |
238
- | `lint` | `boolean` | `true` | Enable/disable lint checks (same as `--no-lint`) |
239
- | `deadCode` | `boolean` | `true` | Enable/disable dead code detection (same as `--no-dead-code`) |
240
- | `verbose` | `boolean` | `false` | Show file details per rule (same as `--verbose`) |
241
- | `diff` | `boolean \| string` | — | Force diff mode (`true`) or pin a base branch (`"main"`). Set to `false` to disable auto-detection. |
242
- | `failOn` | `"error" \| "warning" \| "none"` | `"none"` | Exit with error code on diagnostics of the given severity or above |
243
- | `customRulesOnly` | `boolean` | `false` | Disable built-in react/jsx-a11y/compiler rules, keeping only `react-doctor/*` plugin rules |
244
- | `share` | `boolean` | `true` | Show the share-your-results URL after scanning |
245
- | `textComponents` | `string[]` | `[]` | React Native only. Component names whose children should not trigger `rn-no-raw-text` (e.g. `["MyText", "Label.Bold"]`) |
246
- | `respectInlineDisables` | `boolean` | `true` | Respect inline `// eslint-disable*` / `// oxlint-disable*` comments. Set `false` for audit mode. File-level ignores (`.gitignore`, `.eslintignore`, `.oxlintignore`, `.prettierignore`, `.gitattributes` linguist annotations) are always respected. |
135
+ ```
136
+ Usage: react-doctor [directory] [options]
247
137
 
248
- CLI flags always override config values.
138
+ Options:
139
+ -v, --version display the version number
140
+ --no-lint skip linting
141
+ --no-dead-code skip dead code detection
142
+ --verbose show every rule and per-file details (default shows top 3 rules)
143
+ --score output only the score
144
+ --json output a single structured JSON report
145
+ -y, --yes skip prompts, scan all workspace projects
146
+ --full skip prompts, always run a full scan
147
+ --project <name> select workspace project (comma-separated for multiple)
148
+ --diff [base] scan only files changed vs base branch
149
+ --staged scan only staged files (for pre-commit hooks)
150
+ --offline skip telemetry
151
+ --fail-on <level> exit with error on diagnostics: error, warning, none
152
+ --annotations output diagnostics as GitHub Actions annotations
153
+ --explain <file:line> diagnose why a rule fired or why a suppression didn't apply
154
+ -h, --help display help
155
+ ```
156
+
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`.
158
+
159
+ `--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
+
161
+ ### Config keys
162
+
163
+ | Key | Type | Default |
164
+ | ------------------------- | -------------------------------- | -------- |
165
+ | `ignore.rules` | `string[]` | `[]` |
166
+ | `ignore.files` | `string[]` | `[]` |
167
+ | `ignore.overrides` | `{ files, rules? }[]` | `[]` |
168
+ | `lint` | `boolean` | `true` |
169
+ | `deadCode` | `boolean` | `true` |
170
+ | `verbose` | `boolean` | `false` |
171
+ | `diff` | `boolean \| string` | |
172
+ | `failOn` | `"error" \| "warning" \| "none"` | `"none"` |
173
+ | `customRulesOnly` | `boolean` | `false` |
174
+ | `share` | `boolean` | `true` |
175
+ | `textComponents` | `string[]` | `[]` |
176
+ | `respectInlineDisables` | `boolean` | `true` |
177
+ | `adoptExistingLintConfig` | `boolean` | `true` |
249
178
 
250
179
  ## Node.js API
251
180
 
252
- You can also use React Doctor programmatically:
253
-
254
181
  ```js
255
- import { diagnose } from "react-doctor/api";
182
+ import { diagnose, toJsonReport, summarizeDiagnostics } from "react-doctor/api";
256
183
 
257
184
  const result = await diagnose("./path/to/your/react-project");
258
185
 
259
186
  console.log(result.score); // { score: 82, label: "Great" } or null
260
- console.log(result.diagnostics); // Array of Diagnostic objects
261
- console.log(result.project); // Detected framework, React version, etc.
262
- ```
263
-
264
- The `diagnose` function accepts an optional second argument:
265
-
266
- ```js
267
- const result = await diagnose(".", {
268
- lint: true, // run lint checks (default: true)
269
- deadCode: true, // run dead code detection (default: true)
270
- });
187
+ console.log(result.diagnostics); // Diagnostic[]
188
+ console.log(result.project); // detected framework, React version, etc.
271
189
  ```
272
190
 
273
- Each diagnostic has the following shape:
274
-
275
- ```ts
276
- interface Diagnostic {
277
- filePath: string;
278
- plugin: string;
279
- rule: string;
280
- severity: "error" | "warning";
281
- message: string;
282
- help: string;
283
- line: number;
284
- column: number;
285
- category: string;
286
- }
287
- ```
288
-
289
- To produce the same structured output the `--json` CLI flag emits, use `toJsonReport`:
191
+ `diagnose` accepts a second argument: `{ lint?: boolean, deadCode?: boolean }`.
290
192
 
291
193
  ```js
292
- import { diagnose, toJsonReport, summarizeDiagnostics } from "react-doctor/api";
293
-
294
- const result = await diagnose(".");
295
-
296
194
  const report = toJsonReport(result, { version: "1.0.0" });
297
- console.log(JSON.stringify(report, null, 2));
298
-
299
195
  const counts = summarizeDiagnostics(result.diagnostics);
300
- console.log(`${counts.errorCount} errors, ${counts.warningCount} warnings`);
301
196
  ```
302
197
 
303
- `react-doctor/api` also re-exports the `JsonReport`, `JsonReportSummary`, `JsonReportProjectEntry`, and `JsonReportMode` types, plus the lower-level `buildJsonReport` and `buildJsonReportError` builders if you need to assemble reports from multiple `diagnose()` calls.
198
+ `react-doctor/api` re-exports `JsonReport`, `JsonReportSummary`, `JsonReportProjectEntry`, `JsonReportMode`, plus the lower-level `buildJsonReport` and `buildJsonReportError` builders. See [`packages/react-doctor/src/api.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/api.ts) for the full types.
304
199
 
305
- ## Use the oxlint plugin standalone
200
+ ## Resources & Contributing Back
306
201
 
307
- If you already use oxlint and just want React Doctor's rule set, register the plugin directly in your `.oxlintrc.json`:
202
+ Want to try it out? Check out [the demo](https://react.doctor).
308
203
 
309
- ```jsonc
310
- {
311
- "jsPlugins": [
312
- {
313
- "name": "react-doctor",
314
- "specifier": "react-doctor/oxlint-plugin",
315
- },
316
- ],
317
- "rules": {
318
- "react-doctor/no-fetch-in-effect": "warn",
319
- "react-doctor/no-derived-state-effect": "warn",
320
- // ...pick the rules you want
321
- },
322
- }
323
- ```
324
-
325
- The full rule list is in [`oxlint-config.ts`](https://github.com/millionco/react-doctor/blob/main/packages/react-doctor/src/oxlint-config.ts).
326
-
327
- ## Scores for popular open-source projects
328
-
329
- See the live leaderboard at [react.doctor/leaderboard](https://react.doctor/leaderboard) for current scores across React projects.
330
-
331
- ## Contributing
332
-
333
- Want to contribute? Check out the codebase and submit a PR.
204
+ Looking to contribute back? Clone the repo, install, build, and submit a PR.
334
205
 
335
206
  ```bash
336
207
  git clone https://github.com/millionco/react-doctor
337
208
  cd react-doctor
338
209
  pnpm install
339
210
  pnpm build
340
- ```
341
-
342
- Run locally:
343
-
344
- ```bash
345
211
  node packages/react-doctor/bin/react-doctor.js /path/to/your/react-project
346
212
  ```
347
213
 
214
+ Find a bug? Head to the [issue tracker](https://github.com/millionco/react-doctor/issues).
215
+
348
216
  ### License
349
217
 
350
218
  React Doctor is MIT-licensed open-source software.