react-doctor 0.0.42 → 0.0.44
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 +169 -41
- package/bin/react-doctor.js +13 -0
- package/dist/{process-browser-diagnostics-BHiLPUJT.js → browser-BOxs7MrK.js} +35 -21
- package/dist/{browser-DFbjNpPb.d.ts → browser-Dcq3yn-p.d.ts} +18 -3
- package/dist/browser.d.ts +1 -1
- package/dist/browser.js +1 -1
- package/dist/cli.d.ts +0 -1
- package/dist/cli.js +1391 -426
- package/dist/index.d.ts +119 -12
- package/dist/index.js +1136 -327
- package/dist/react-doctor-plugin.js +2335 -127
- package/dist/worker.d.ts +2 -2
- package/dist/worker.js +2 -2
- package/package.json +35 -13
- package/dist/browser-DFbjNpPb.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/process-browser-diagnostics-BHiLPUJT.js.map +0 -1
- package/dist/react-doctor-plugin.d.ts.map +0 -1
- package/dist/react-doctor-plugin.js.map +0 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ npx -y react-doctor@latest . --verbose
|
|
|
40
40
|
|
|
41
41
|
## Install for your coding agent
|
|
42
42
|
|
|
43
|
-
Teach your coding agent
|
|
43
|
+
Teach your coding agent React best practices. Run this at your project root:
|
|
44
44
|
|
|
45
45
|
```bash
|
|
46
46
|
npx -y react-doctor@latest install
|
|
@@ -71,7 +71,7 @@ Supports Claude Code, Codex, GitHub Copilot, Gemini CLI, Cursor, OpenCode, Facto
|
|
|
71
71
|
| `github-token` | | When set on `pull_request` events, posts findings as a PR comment |
|
|
72
72
|
| `fail-on` | `error` | Exit with error code on diagnostics: `error`, `warning`, `none` |
|
|
73
73
|
| `offline` | `false` | Skip sending diagnostics to the react.doctor API |
|
|
74
|
-
| `node-version` | `
|
|
74
|
+
| `node-version` | `22` | Node.js version to use |
|
|
75
75
|
|
|
76
76
|
The action outputs a `score` (0–100) you can use in subsequent steps.
|
|
77
77
|
|
|
@@ -81,15 +81,73 @@ The action outputs a `score` (0–100) you can use in subsequent steps.
|
|
|
81
81
|
Usage: react-doctor [directory] [options]
|
|
82
82
|
|
|
83
83
|
Options:
|
|
84
|
-
-v, --version
|
|
85
|
-
--no-lint
|
|
86
|
-
--no-dead-code
|
|
87
|
-
--verbose
|
|
88
|
-
--score
|
|
89
|
-
|
|
90
|
-
--
|
|
91
|
-
--
|
|
92
|
-
|
|
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
|
+
}
|
|
93
151
|
```
|
|
94
152
|
|
|
95
153
|
## Browser API
|
|
@@ -127,20 +185,65 @@ You can also use the `"reactDoctor"` key in your `package.json` instead:
|
|
|
127
185
|
|
|
128
186
|
If both exist, `react-doctor.config.json` takes precedence.
|
|
129
187
|
|
|
188
|
+
### Inline suppressions
|
|
189
|
+
|
|
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
|
+
```tsx
|
|
193
|
+
// react-doctor-disable-next-line react-doctor/no-cascading-set-state
|
|
194
|
+
useEffect(() => {
|
|
195
|
+
setA(value);
|
|
196
|
+
setB(value);
|
|
197
|
+
setC(value);
|
|
198
|
+
}, [value]);
|
|
199
|
+
|
|
200
|
+
const value = expensiveComputation(); // react-doctor-disable-line react-doctor/no-usememo-simple-expression
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Comma- or space-separate multiple rule ids on the same comment. With no rule id, the comment suppresses every diagnostic on that line.
|
|
204
|
+
|
|
205
|
+
### Respecting your existing project ignores
|
|
206
|
+
|
|
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:
|
|
208
|
+
|
|
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 |
|
|
216
|
+
|
|
217
|
+
React Doctor also respects inline lint suppressions in source files:
|
|
218
|
+
|
|
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.
|
|
221
|
+
|
|
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.
|
|
223
|
+
|
|
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:
|
|
225
|
+
|
|
226
|
+
```jsonc
|
|
227
|
+
{ "respectInlineDisables": false }
|
|
228
|
+
```
|
|
229
|
+
|
|
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.
|
|
231
|
+
|
|
130
232
|
### Config options
|
|
131
233
|
|
|
132
|
-
| Key
|
|
133
|
-
|
|
|
134
|
-
| `ignore.rules`
|
|
135
|
-
| `ignore.files`
|
|
136
|
-
| `lint`
|
|
137
|
-
| `deadCode`
|
|
138
|
-
| `verbose`
|
|
139
|
-
| `diff`
|
|
140
|
-
| `failOn`
|
|
141
|
-
| `customRulesOnly`
|
|
142
|
-
| `share`
|
|
143
|
-
| `textComponents`
|
|
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. |
|
|
144
247
|
|
|
145
248
|
CLI flags always override config values.
|
|
146
249
|
|
|
@@ -153,7 +256,7 @@ import { diagnose } from "react-doctor/api";
|
|
|
153
256
|
|
|
154
257
|
const result = await diagnose("./path/to/your/react-project");
|
|
155
258
|
|
|
156
|
-
console.log(result.score); // { score: 82, label: "
|
|
259
|
+
console.log(result.score); // { score: 82, label: "Great" } or null
|
|
157
260
|
console.log(result.diagnostics); // Array of Diagnostic objects
|
|
158
261
|
console.log(result.project); // Detected framework, React version, etc.
|
|
159
262
|
```
|
|
@@ -183,22 +286,47 @@ interface Diagnostic {
|
|
|
183
286
|
}
|
|
184
287
|
```
|
|
185
288
|
|
|
186
|
-
|
|
289
|
+
To produce the same structured output the `--json` CLI flag emits, use `toJsonReport`:
|
|
290
|
+
|
|
291
|
+
```js
|
|
292
|
+
import { diagnose, toJsonReport, summarizeDiagnostics } from "react-doctor/api";
|
|
293
|
+
|
|
294
|
+
const result = await diagnose(".");
|
|
295
|
+
|
|
296
|
+
const report = toJsonReport(result, { version: "1.0.0" });
|
|
297
|
+
console.log(JSON.stringify(report, null, 2));
|
|
298
|
+
|
|
299
|
+
const counts = summarizeDiagnostics(result.diagnostics);
|
|
300
|
+
console.log(`${counts.errorCount} errors, ${counts.warningCount} warnings`);
|
|
301
|
+
```
|
|
302
|
+
|
|
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.
|
|
304
|
+
|
|
305
|
+
## Use the oxlint plugin standalone
|
|
306
|
+
|
|
307
|
+
If you already use oxlint and just want React Doctor's rule set, register the plugin directly in your `.oxlintrc.json`:
|
|
308
|
+
|
|
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
|
|
187
328
|
|
|
188
|
-
|
|
189
|
-
| ------------------------------------------------------ | ------ | --------------------------------------------------------------------------------------- |
|
|
190
|
-
| [tldraw](https://github.com/tldraw/tldraw) | **84** | [view](https://www.react.doctor/share?p=tldraw&s=84&e=98&w=139&f=40) |
|
|
191
|
-
| [excalidraw](https://github.com/excalidraw/excalidraw) | **84** | [view](https://www.react.doctor/share?p=%40excalidraw%2Fexcalidraw&s=84&e=2&w=196&f=80) |
|
|
192
|
-
| [twenty](https://github.com/twentyhq/twenty) | **78** | [view](https://www.react.doctor/share?p=twenty-front&s=78&e=99&w=293&f=268) |
|
|
193
|
-
| [plane](https://github.com/makeplane/plane) | **78** | [view](https://www.react.doctor/share?p=web&s=78&e=7&w=525&f=292) |
|
|
194
|
-
| [formbricks](https://github.com/formbricks/formbricks) | **75** | [view](https://www.react.doctor/share?p=%40formbricks%2Fweb&s=75&e=15&w=389&f=242) |
|
|
195
|
-
| [posthog](https://github.com/PostHog/posthog) | **72** | [view](https://www.react.doctor/share?p=%40posthog%2Ffrontend&s=72&e=82&w=1177&f=585) |
|
|
196
|
-
| [supabase](https://github.com/supabase/supabase) | **69** | [view](https://www.react.doctor/share?p=studio&s=69&e=74&w=1087&f=566) |
|
|
197
|
-
| [onlook](https://github.com/onlook-dev/onlook) | **69** | [view](https://www.react.doctor/share?p=%40onlook%2Fweb-client&s=69&e=64&w=418&f=178) |
|
|
198
|
-
| [payload](https://github.com/payloadcms/payload) | **68** | [view](https://www.react.doctor/share?p=%40payloadcms%2Fui&s=68&e=139&w=408&f=298) |
|
|
199
|
-
| [sentry](https://github.com/getsentry/sentry) | **64** | [view](https://www.react.doctor/share?p=sentry&s=64&e=94&w=1345&f=818) |
|
|
200
|
-
| [cal.com](https://github.com/calcom/cal.com) | **63** | [view](https://www.react.doctor/share?p=%40calcom%2Fweb&s=63&e=31&w=558&f=311) |
|
|
201
|
-
| [dub](https://github.com/dubinc/dub) | **62** | [view](https://www.react.doctor/share?p=web&s=62&e=52&w=966&f=457) |
|
|
329
|
+
See the live leaderboard at [react.doctor/leaderboard](https://react.doctor/leaderboard) for current scores across React projects.
|
|
202
330
|
|
|
203
331
|
## Contributing
|
|
204
332
|
|
|
@@ -208,13 +336,13 @@ Want to contribute? Check out the codebase and submit a PR.
|
|
|
208
336
|
git clone https://github.com/millionco/react-doctor
|
|
209
337
|
cd react-doctor
|
|
210
338
|
pnpm install
|
|
211
|
-
pnpm
|
|
339
|
+
pnpm build
|
|
212
340
|
```
|
|
213
341
|
|
|
214
342
|
Run locally:
|
|
215
343
|
|
|
216
344
|
```bash
|
|
217
|
-
node packages/react-doctor/
|
|
345
|
+
node packages/react-doctor/bin/react-doctor.js /path/to/your/react-project
|
|
218
346
|
```
|
|
219
347
|
|
|
220
348
|
### License
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import module from "node:module";
|
|
4
|
+
|
|
5
|
+
if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
|
|
6
|
+
try {
|
|
7
|
+
module.enableCompileCache();
|
|
8
|
+
} catch {
|
|
9
|
+
// Ignore compile-cache errors.
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
await import("../dist/cli.js");
|
|
@@ -4,6 +4,7 @@ const SCORE_API_URL = "https://www.react.doctor/api/score";
|
|
|
4
4
|
const FETCH_TIMEOUT_MS = 1e4;
|
|
5
5
|
const ERROR_RULE_PENALTY = 1.5;
|
|
6
6
|
const WARNING_RULE_PENALTY = .75;
|
|
7
|
+
const buildNoReactDependencyError = (directory) => `No React dependency found in ${directory}/package.json. Add "react" to dependencies (or peerDependencies) and re-run.`;
|
|
7
8
|
//#endregion
|
|
8
9
|
//#region src/core/calculate-score-locally.ts
|
|
9
10
|
const getScoreLabel = (score) => {
|
|
@@ -49,19 +50,31 @@ const parseScoreResult = (value) => {
|
|
|
49
50
|
label: labelValue
|
|
50
51
|
};
|
|
51
52
|
};
|
|
53
|
+
const stripFilePaths = (diagnostics) => diagnostics.map(({ filePath: _filePath, ...rest }) => rest);
|
|
54
|
+
const isAbortError = (error) => error instanceof Error && (error.name === "AbortError" || error.name === "TimeoutError");
|
|
55
|
+
const describeFailure = (error) => {
|
|
56
|
+
if (isAbortError(error)) return `timed out after ${FETCH_TIMEOUT_MS / 1e3}s`;
|
|
57
|
+
if (error instanceof Error && error.message) return error.message;
|
|
58
|
+
return String(error);
|
|
59
|
+
};
|
|
52
60
|
const tryScoreFromApi = async (diagnostics, fetchImplementation) => {
|
|
61
|
+
if (typeof fetchImplementation !== "function") return null;
|
|
53
62
|
const controller = new AbortController();
|
|
54
63
|
const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);
|
|
55
64
|
try {
|
|
56
65
|
const response = await fetchImplementation(SCORE_API_URL, {
|
|
57
66
|
method: "POST",
|
|
58
67
|
headers: { "Content-Type": "application/json" },
|
|
59
|
-
body: JSON.stringify({ diagnostics }),
|
|
68
|
+
body: JSON.stringify({ diagnostics: stripFilePaths(diagnostics) }),
|
|
60
69
|
signal: controller.signal
|
|
61
70
|
});
|
|
62
|
-
if (!response.ok)
|
|
71
|
+
if (!response.ok) {
|
|
72
|
+
console.warn(`[react-doctor] Score API returned ${response.status} ${response.statusText} — using local scoring`);
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
63
75
|
return parseScoreResult(await response.json());
|
|
64
|
-
} catch {
|
|
76
|
+
} catch (error) {
|
|
77
|
+
console.warn(`[react-doctor] Score API unreachable (${describeFailure(error)}) — using local scoring`);
|
|
65
78
|
return null;
|
|
66
79
|
} finally {
|
|
67
80
|
clearTimeout(timeoutId);
|
|
@@ -69,7 +82,8 @@ const tryScoreFromApi = async (diagnostics, fetchImplementation) => {
|
|
|
69
82
|
};
|
|
70
83
|
//#endregion
|
|
71
84
|
//#region src/utils/calculate-score-browser.ts
|
|
72
|
-
const
|
|
85
|
+
const getGlobalFetch = () => typeof fetch === "function" ? fetch : void 0;
|
|
86
|
+
const calculateScore = async (diagnostics, fetchImplementation = getGlobalFetch()) => await tryScoreFromApi(diagnostics, fetchImplementation) ?? calculateScoreLocally(diagnostics);
|
|
73
87
|
//#endregion
|
|
74
88
|
//#region src/utils/match-glob-pattern.ts
|
|
75
89
|
const REGEX_SPECIAL_CHARACTERS = /[.+^${}()|[\]\\]/g;
|
|
@@ -105,7 +119,11 @@ const toRelativePath = (filePath, rootDirectory) => {
|
|
|
105
119
|
if (normalizedFilePath.startsWith(normalizedRoot)) return normalizedFilePath.slice(normalizedRoot.length);
|
|
106
120
|
return normalizedFilePath.replace(/^\.\//, "");
|
|
107
121
|
};
|
|
108
|
-
const compileIgnoredFilePatterns = (userConfig) =>
|
|
122
|
+
const compileIgnoredFilePatterns = (userConfig) => {
|
|
123
|
+
const files = userConfig?.ignore?.files;
|
|
124
|
+
if (!Array.isArray(files)) return [];
|
|
125
|
+
return files.filter((entry) => typeof entry === "string").map(compileGlobPattern);
|
|
126
|
+
};
|
|
109
127
|
const isFileIgnoredByPatterns = (filePath, rootDirectory, patterns) => {
|
|
110
128
|
if (patterns.length === 0) return false;
|
|
111
129
|
const relativePath = toRelativePath(filePath, rootDirectory);
|
|
@@ -146,9 +164,9 @@ const isRuleSuppressed = (commentRules, ruleId) => {
|
|
|
146
164
|
return commentRules.split(/[,\s]+/).some((rule) => rule.trim() === ruleId);
|
|
147
165
|
};
|
|
148
166
|
const filterIgnoredDiagnostics = (diagnostics, config, rootDirectory, readFileLinesSync) => {
|
|
149
|
-
const ignoredRules = new Set(Array.isArray(config.ignore?.rules) ? config.ignore.rules : []);
|
|
167
|
+
const ignoredRules = new Set(Array.isArray(config.ignore?.rules) ? config.ignore.rules.filter((rule) => typeof rule === "string") : []);
|
|
150
168
|
const ignoredFilePatterns = compileIgnoredFilePatterns(config);
|
|
151
|
-
const textComponentNames = new Set(Array.isArray(config.textComponents) ? config.textComponents : []);
|
|
169
|
+
const textComponentNames = new Set(Array.isArray(config.textComponents) ? config.textComponents.filter((name) => typeof name === "string") : []);
|
|
152
170
|
const hasTextComponents = textComponentNames.size > 0;
|
|
153
171
|
const getFileLines = createFileLinesCache(rootDirectory, readFileLinesSync);
|
|
154
172
|
return diagnostics.filter((diagnostic) => {
|
|
@@ -218,7 +236,7 @@ const createBrowserReadFileLinesSync = (rootDirectory, projectFiles) => {
|
|
|
218
236
|
//#endregion
|
|
219
237
|
//#region src/adapters/browser/diagnose.ts
|
|
220
238
|
const diagnose = async (input) => {
|
|
221
|
-
if (!input.project.reactVersion) throw new Error(
|
|
239
|
+
if (!input.project.reactVersion) throw new Error(buildNoReactDependencyError(input.rootDirectory));
|
|
222
240
|
const readFileLinesSync = createBrowserReadFileLinesSync(input.rootDirectory, input.projectFiles);
|
|
223
241
|
const userConfig = input.userConfig ?? null;
|
|
224
242
|
const deadCodeDiagnostics = input.deadCodeDiagnostics ?? [];
|
|
@@ -241,14 +259,6 @@ const diagnose = async (input) => {
|
|
|
241
259
|
};
|
|
242
260
|
};
|
|
243
261
|
//#endregion
|
|
244
|
-
//#region src/core/build-diagnose-result.ts
|
|
245
|
-
const buildDiagnoseResult = (params) => ({
|
|
246
|
-
diagnostics: params.diagnostics,
|
|
247
|
-
score: params.score,
|
|
248
|
-
project: params.project,
|
|
249
|
-
elapsedMilliseconds: params.elapsedMilliseconds
|
|
250
|
-
});
|
|
251
|
-
//#endregion
|
|
252
262
|
//#region src/utils/jsx-include-paths.ts
|
|
253
263
|
const computeJsxIncludePaths = (includePaths) => includePaths.length > 0 ? includePaths.filter((filePath) => JSX_FILE_PATTERN.test(filePath)) : void 0;
|
|
254
264
|
//#endregion
|
|
@@ -262,7 +272,7 @@ const diagnoseCore = async (deps, options = {}) => {
|
|
|
262
272
|
const userConfig = deps.loadUserConfig();
|
|
263
273
|
const effectiveLint = options.lint ?? userConfig?.lint ?? true;
|
|
264
274
|
const effectiveDeadCode = options.deadCode ?? userConfig?.deadCode ?? true;
|
|
265
|
-
if (!projectInfo.reactVersion) throw new Error(
|
|
275
|
+
if (!projectInfo.reactVersion) throw new Error(buildNoReactDependencyError(deps.rootDirectory));
|
|
266
276
|
const lintIncludePaths = options.lintIncludePaths !== void 0 ? options.lintIncludePaths : computeJsxIncludePaths(includePaths);
|
|
267
277
|
const { runLint, runDeadCode } = deps.createRunners({
|
|
268
278
|
resolvedDirectory,
|
|
@@ -280,7 +290,11 @@ const diagnoseCore = async (deps, options = {}) => {
|
|
|
280
290
|
console.error("Dead code analysis failed:", error);
|
|
281
291
|
return emptyDiagnostics;
|
|
282
292
|
}) : Promise.resolve(emptyDiagnostics);
|
|
283
|
-
const [
|
|
293
|
+
const [lintSettled, deadCodeSettled] = await Promise.allSettled([lintPromise, deadCodePromise]);
|
|
294
|
+
const lintDiagnostics = lintSettled.status === "fulfilled" ? lintSettled.value : emptyDiagnostics;
|
|
295
|
+
const deadCodeDiagnostics = deadCodeSettled.status === "fulfilled" ? deadCodeSettled.value : emptyDiagnostics;
|
|
296
|
+
if (lintSettled.status === "rejected") console.error("Lint rejected:", lintSettled.reason);
|
|
297
|
+
if (deadCodeSettled.status === "rejected") console.error("Dead code rejected:", deadCodeSettled.reason);
|
|
284
298
|
const environmentDiagnostics = deps.getExtraDiagnostics?.() ?? [];
|
|
285
299
|
const timed = await buildDiagnoseTimedResult({
|
|
286
300
|
mergedDiagnostics: [
|
|
@@ -294,12 +308,12 @@ const diagnoseCore = async (deps, options = {}) => {
|
|
|
294
308
|
startTime,
|
|
295
309
|
calculateDiagnosticsScore: deps.calculateDiagnosticsScore
|
|
296
310
|
});
|
|
297
|
-
return
|
|
311
|
+
return {
|
|
298
312
|
diagnostics: timed.diagnostics,
|
|
299
313
|
score: timed.score,
|
|
300
314
|
project: projectInfo,
|
|
301
315
|
elapsedMilliseconds: timed.elapsedMilliseconds
|
|
302
|
-
}
|
|
316
|
+
};
|
|
303
317
|
};
|
|
304
318
|
//#endregion
|
|
305
319
|
//#region src/adapters/browser/diagnose-browser.ts
|
|
@@ -342,4 +356,4 @@ const processBrowserDiagnostics = async (input) => {
|
|
|
342
356
|
//#endregion
|
|
343
357
|
export { calculateScore as a, diagnose as i, diagnoseBrowser as n, calculateScoreLocally as o, diagnoseCore as r, processBrowserDiagnostics as t };
|
|
344
358
|
|
|
345
|
-
//# sourceMappingURL=
|
|
359
|
+
//# sourceMappingURL=browser-BOxs7MrK.js.map
|
|
@@ -8,6 +8,7 @@ interface ProjectInfo {
|
|
|
8
8
|
framework: Framework;
|
|
9
9
|
hasTypeScript: boolean;
|
|
10
10
|
hasReactCompiler: boolean;
|
|
11
|
+
hasTanStackQuery: boolean;
|
|
11
12
|
sourceFileCount: number;
|
|
12
13
|
}
|
|
13
14
|
interface Diagnostic {
|
|
@@ -20,7 +21,6 @@ interface Diagnostic {
|
|
|
20
21
|
line: number;
|
|
21
22
|
column: number;
|
|
22
23
|
category: string;
|
|
23
|
-
weight?: number;
|
|
24
24
|
}
|
|
25
25
|
interface ScoreResult {
|
|
26
26
|
score: number;
|
|
@@ -40,13 +40,28 @@ interface ReactDoctorConfig {
|
|
|
40
40
|
customRulesOnly?: boolean;
|
|
41
41
|
share?: boolean;
|
|
42
42
|
textComponents?: string[];
|
|
43
|
+
/**
|
|
44
|
+
* Whether to respect inline `// eslint-disable*` / `// oxlint-disable*`
|
|
45
|
+
* comments in source files. Default: `true`.
|
|
46
|
+
*
|
|
47
|
+
* File-level ignores (`.gitignore`, `.eslintignore`, `.oxlintignore`,
|
|
48
|
+
* `.prettierignore`, `.gitattributes` `linguist-vendored` /
|
|
49
|
+
* `linguist-generated`) are ALWAYS honored regardless of this option
|
|
50
|
+
* — they typically point at vendored or generated code that
|
|
51
|
+
* genuinely shouldn't be linted at all.
|
|
52
|
+
*
|
|
53
|
+
* Set to `false` for "audit mode": every inline suppression is
|
|
54
|
+
* neutralized so react-doctor reports every diagnostic regardless
|
|
55
|
+
* of historical hide-comments.
|
|
56
|
+
*/
|
|
57
|
+
respectInlineDisables?: boolean;
|
|
43
58
|
}
|
|
44
59
|
//#endregion
|
|
45
60
|
//#region src/core/calculate-score-locally.d.ts
|
|
46
61
|
declare const calculateScoreLocally: (diagnostics: Diagnostic[]) => ScoreResult;
|
|
47
62
|
//#endregion
|
|
48
63
|
//#region src/utils/calculate-score-browser.d.ts
|
|
49
|
-
declare const calculateScore: (diagnostics: Diagnostic[]) => Promise<ScoreResult | null>;
|
|
64
|
+
declare const calculateScore: (diagnostics: Diagnostic[], fetchImplementation?: typeof fetch | undefined) => Promise<ScoreResult | null>;
|
|
50
65
|
//#endregion
|
|
51
66
|
//#region src/adapters/browser/diagnose.d.ts
|
|
52
67
|
interface BrowserDiagnoseInput {
|
|
@@ -128,4 +143,4 @@ interface ProcessBrowserDiagnosticsResult {
|
|
|
128
143
|
declare const processBrowserDiagnostics: (input: ProcessBrowserDiagnosticsInput) => Promise<ProcessBrowserDiagnosticsResult>;
|
|
129
144
|
//#endregion
|
|
130
145
|
export { ScoreResult as _, diagnoseBrowser as a, diagnoseCore as c, diagnose as d, calculateScore as f, ReactDoctorConfig as g, ProjectInfo as h, DiagnoseBrowserInput as i, BrowserDiagnoseInput as l, Diagnostic as m, ProcessBrowserDiagnosticsResult as n, DiagnoseCoreOptions as o, calculateScoreLocally as p, processBrowserDiagnostics as r, DiagnoseCoreResult as s, ProcessBrowserDiagnosticsInput as t, BrowserDiagnoseResult as u };
|
|
131
|
-
//# sourceMappingURL=browser-
|
|
146
|
+
//# sourceMappingURL=browser-Dcq3yn-p.d.ts.map
|
package/dist/browser.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { _ as ScoreResult, a as diagnoseBrowser, c as diagnoseCore, d as diagnose, f as calculateScore, g as ReactDoctorConfig, h as ProjectInfo, i as DiagnoseBrowserInput, l as BrowserDiagnoseInput, m as Diagnostic, n as ProcessBrowserDiagnosticsResult, o as DiagnoseCoreOptions, p as calculateScoreLocally, r as processBrowserDiagnostics, s as DiagnoseCoreResult, t as ProcessBrowserDiagnosticsInput, u as BrowserDiagnoseResult } from "./browser-
|
|
1
|
+
import { _ as ScoreResult, a as diagnoseBrowser, c as diagnoseCore, d as diagnose, f as calculateScore, g as ReactDoctorConfig, h as ProjectInfo, i as DiagnoseBrowserInput, l as BrowserDiagnoseInput, m as Diagnostic, n as ProcessBrowserDiagnosticsResult, o as DiagnoseCoreOptions, p as calculateScoreLocally, r as processBrowserDiagnostics, s as DiagnoseCoreResult, t as ProcessBrowserDiagnosticsInput, u as BrowserDiagnoseResult } from "./browser-Dcq3yn-p.js";
|
|
2
2
|
export { BrowserDiagnoseInput, BrowserDiagnoseResult, DiagnoseBrowserInput, DiagnoseCoreOptions, DiagnoseCoreResult, Diagnostic, ProcessBrowserDiagnosticsInput, ProcessBrowserDiagnosticsResult, ProjectInfo, ReactDoctorConfig, ScoreResult, calculateScore, calculateScoreLocally, diagnose, diagnoseBrowser, diagnoseCore, processBrowserDiagnostics };
|
package/dist/browser.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as calculateScore, i as diagnose, n as diagnoseBrowser, o as calculateScoreLocally, r as diagnoseCore, t as processBrowserDiagnostics } from "./
|
|
1
|
+
import { a as calculateScore, i as diagnose, n as diagnoseBrowser, o as calculateScoreLocally, r as diagnoseCore, t as processBrowserDiagnostics } from "./browser-BOxs7MrK.js";
|
|
2
2
|
export { calculateScore, calculateScoreLocally, diagnose, diagnoseBrowser, diagnoseCore, processBrowserDiagnostics };
|
package/dist/cli.d.ts
CHANGED