react-doctor 0.2.0 → 0.2.2
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 +66 -13
- package/dist/chunk-q7NCDQ7-.js +26 -0
- package/dist/cli.js +7785 -1527
- package/dist/dist-2B-kn9PW.js +17940 -0
- package/dist/dist-BPzE37C6.js +17940 -0
- package/dist/index.d.ts +29 -3
- package/dist/index.js +508 -230
- package/package.json +10 -16
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ jobs:
|
|
|
70
70
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
71
71
|
```
|
|
72
72
|
|
|
73
|
-
When `github-token` is set on `pull_request` events, findings are posted (and updated) as a sticky PR comment. The action also exposes a `score` output (0–100) you can
|
|
73
|
+
When `github-token` is set on `pull_request` events, findings are posted (and updated) as a sticky PR comment. The action also exposes a `score` output (0–100) you can read in subsequent steps — see [PR blocking and exit codes](#pr-blocking-and-exit-codes) for a score-floor recipe.
|
|
74
74
|
|
|
75
75
|
**Inputs:** `directory`, `verbose`, `project`, `diff`, `github-token`, `fail-on` (`error` / `warning` / `none`), `offline`, `annotations`, `node-version`. See [`action.yml`](https://github.com/millionco/react-doctor/blob/main/action.yml) for full descriptions.
|
|
76
76
|
|
|
@@ -143,7 +143,13 @@ Combine `--fail-on` with `--diff <base>` to scope the gate to the PR's changed f
|
|
|
143
143
|
SCORE: ${{ steps.doctor.outputs.score }}
|
|
144
144
|
FLOOR: "80"
|
|
145
145
|
run: |
|
|
146
|
-
|
|
146
|
+
# `score` is best-effort and may be empty (e.g. when offline is on).
|
|
147
|
+
# Skip the floor when it's empty so unrelated PRs aren't blocked.
|
|
148
|
+
if [ -z "$SCORE" ]; then
|
|
149
|
+
echo "::notice::React Doctor score unavailable — skipping floor check"
|
|
150
|
+
exit 0
|
|
151
|
+
fi
|
|
152
|
+
if [ "$SCORE" -lt "$FLOOR" ]; then
|
|
147
153
|
echo "::error::React Doctor score $SCORE is below floor $FLOOR"
|
|
148
154
|
exit 1
|
|
149
155
|
fi
|
|
@@ -158,7 +164,7 @@ Create a `react-doctor.config.json` in your project root:
|
|
|
158
164
|
```json
|
|
159
165
|
{
|
|
160
166
|
"ignore": {
|
|
161
|
-
"rules": ["react/no-danger", "
|
|
167
|
+
"rules": ["react-doctor/no-danger", "react-doctor/no-autofocus"],
|
|
162
168
|
"files": ["src/generated/**"],
|
|
163
169
|
"overrides": [
|
|
164
170
|
{
|
|
@@ -167,7 +173,7 @@ Create a `react-doctor.config.json` in your project root:
|
|
|
167
173
|
},
|
|
168
174
|
{
|
|
169
175
|
"files": ["components/search/HighlightedSnippet.tsx"],
|
|
170
|
-
"rules": ["react/no-danger"]
|
|
176
|
+
"rules": ["react-doctor/no-danger"]
|
|
171
177
|
}
|
|
172
178
|
]
|
|
173
179
|
}
|
|
@@ -220,12 +226,13 @@ Per-rule wins over per-category. `"off"` short-circuits before the rule runs; `"
|
|
|
220
226
|
|
|
221
227
|
#### Optional companion plugins
|
|
222
228
|
|
|
223
|
-
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.
|
|
229
|
+
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. Listed as **optional peer dependencies** — install only what you want.
|
|
230
|
+
|
|
231
|
+
| Plugin | Adds | Namespace |
|
|
232
|
+
| ------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ------------------ |
|
|
233
|
+
| [`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/*` |
|
|
224
234
|
|
|
225
|
-
|
|
226
|
-
| ----------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
|
227
|
-
| [`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/*` |
|
|
228
|
-
| [`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/*` |
|
|
235
|
+
The 8 rules from [`eslint-plugin-react-you-might-not-need-an-effect`](https://github.com/nickjvandyke/eslint-plugin-react-you-might-not-need-an-effect) (NickvanDyke, MIT) are now ported natively into React Doctor — they fire as `react-doctor/no-derived-state`, `react-doctor/no-chain-state-updates`, `react-doctor/no-event-handler`, `react-doctor/no-adjust-state-on-prop-change`, `react-doctor/no-reset-all-state-on-prop-change`, `react-doctor/no-pass-live-state-to-parent`, `react-doctor/no-pass-data-to-parent`, and `react-doctor/no-initialize-state`. No peer dependency required.
|
|
229
236
|
|
|
230
237
|
### Inline suppressions
|
|
231
238
|
|
|
@@ -258,7 +265,7 @@ Block comments work inside JSX:
|
|
|
258
265
|
|
|
259
266
|
<!-- prettier-ignore -->
|
|
260
267
|
```tsx
|
|
261
|
-
{/* react-doctor-disable-next-line react/no-danger */}
|
|
268
|
+
{/* react-doctor-disable-next-line react-doctor/no-danger */}
|
|
262
269
|
<div dangerouslySetInnerHTML={{ __html }} />
|
|
263
270
|
```
|
|
264
271
|
|
|
@@ -355,7 +362,7 @@ When a suppression isn't working, `--explain <file:line>` (or its alias `--why <
|
|
|
355
362
|
|
|
356
363
|
`ignore.tags` suppresses entire categories of rules by tag. For example, `"tags": ["design"]` disables all opinionated design rules (gradient text, pure black backgrounds, side tab borders, default Tailwind palettes). Available tags: `"design"`.
|
|
357
364
|
|
|
358
|
-
`offline` skips the score API entirely — no score is shown and no share URL is generated.
|
|
365
|
+
`offline` skips the score API entirely — no score is shown and no share URL is generated. CI runs (GitHub Actions, GitLab CI, CircleCI) are not offline by default; only the share URL is suppressed. Set `offline: true` (or `--offline`) explicitly when you want zero network.
|
|
359
366
|
|
|
360
367
|
### React Native rules in mixed monorepos
|
|
361
368
|
|
|
@@ -383,7 +390,7 @@ The walker stops at function and `Program` boundaries — JSX defined inside a c
|
|
|
383
390
|
|
|
384
391
|
The health score formula: `100 - (unique_error_rules x 1.5) - (unique_warning_rules x 0.75)`.
|
|
385
392
|
|
|
386
|
-
Scoring runs on react.doctor's API and is **network-dependent**: without a successful API round-trip (or under `--offline`) the score is omitted and the rest of the report still renders normally. Key details:
|
|
393
|
+
Scoring runs on react.doctor's API and is **network-dependent**: without a successful API round-trip (or under `--offline`) the score is omitted and the rest of the report still renders normally. Score-based automation must treat an empty value as a no-op (see the strict-threshold example above). Key details:
|
|
387
394
|
|
|
388
395
|
- The score counts **unique rules triggered**, not total instances. Fixing 49 of 50 `no-barrel-import` violations does not change the score; fixing all 50 removes the 0.75 penalty for that rule.
|
|
389
396
|
- Error-severity rules cost 1.5 points each. Warning-severity rules cost 0.75 points each.
|
|
@@ -405,6 +412,52 @@ When on a feature branch without explicit flags, you'll be prompted: "Only scan
|
|
|
405
412
|
|
|
406
413
|
`--staged` and `--diff` cannot be combined.
|
|
407
414
|
|
|
415
|
+
### Pre-commit hooks with Husky + lint-staged
|
|
416
|
+
|
|
417
|
+
The most common setup is [Husky](https://typicode.github.io/husky/) for the git hook and [lint-staged](https://github.com/lint-staged/lint-staged) to filter which files run through each tool. React Doctor's `--staged` mode is built for this: it reads file contents from the git **index** (not the working tree) and materializes them into a temp directory, so partially-staged files are scanned exactly as they will be committed.
|
|
418
|
+
|
|
419
|
+
Install both, then wire them up:
|
|
420
|
+
|
|
421
|
+
```bash
|
|
422
|
+
npx ni -D husky lint-staged
|
|
423
|
+
npx husky init
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
`husky init` creates `.husky/pre-commit`. Replace its contents with:
|
|
427
|
+
|
|
428
|
+
```bash
|
|
429
|
+
npx lint-staged
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Then add a `lint-staged` block to your `package.json`. Because React Doctor already filters to the staged set via `--staged`, **do not pass the lint-staged-injected file list** — invoke it with a single command and let it discover the index itself:
|
|
433
|
+
|
|
434
|
+
```json
|
|
435
|
+
{
|
|
436
|
+
"lint-staged": {
|
|
437
|
+
"*.{ts,tsx,js,jsx}": "react-doctor --staged --fail-on warning"
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
```
|
|
441
|
+
|
|
442
|
+
A few notes that bite people:
|
|
443
|
+
|
|
444
|
+
- **Don't append `{staged-files}`** — lint-staged would otherwise pass the matched paths as positional arguments and you'd get the union (path filter + index scan) instead of the intent.
|
|
445
|
+
- **Use the function form when you only want the hook to run if any matching file is staged** but still want a single project-wide scan:
|
|
446
|
+
|
|
447
|
+
```js
|
|
448
|
+
// lint-staged.config.js
|
|
449
|
+
export default {
|
|
450
|
+
"*.{ts,tsx,js,jsx}": () => "react-doctor --staged --fail-on warning",
|
|
451
|
+
};
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
- **`--fail-on warning`** blocks the commit on any diagnostic. Use `--fail-on error` for a softer gate, or `--fail-on none` to lint advisory-only.
|
|
455
|
+
- **Index vs. working tree:** `--staged` reflects `git diff --cached`, not your editor buffer. If you `git add` half a file and keep typing, only the added half is scanned — the unstaged tail is ignored.
|
|
456
|
+
- **Skip in CI:** lint-staged is a pre-commit concern. In CI, use the GitHub Action (above) or `react-doctor --diff <base>` directly; running both does duplicate work.
|
|
457
|
+
- **Other hook managers:** the same `react-doctor --staged --fail-on warning` command works under [Lefthook](https://lefthook.dev/), [pre-commit](https://pre-commit.com/), or a hand-written `.git/hooks/pre-commit` — `--staged` is hook-manager-agnostic.
|
|
458
|
+
|
|
459
|
+
To bypass the hook for a one-off commit, use `git commit --no-verify`.
|
|
460
|
+
|
|
408
461
|
## Agent and CI integration
|
|
409
462
|
|
|
410
463
|
React Doctor detects 50+ coding agents (Claude Code, Cursor, Codex, OpenCode, Windsurf, and more) and adapts its behavior automatically:
|
|
@@ -416,7 +469,7 @@ React Doctor detects 50+ coding agents (Claude Code, Cursor, Codex, OpenCode, Wi
|
|
|
416
469
|
- **Exit codes**: `--fail-on error` (default) exits non-zero when error-severity diagnostics are found. Use `--fail-on warning` or `--fail-on none` to tune CI gating. See [PR blocking and exit codes](#pr-blocking-and-exit-codes) for the full model — including how to fail only on new regressions vs. fail on the baseline score.
|
|
417
470
|
- **Programmatic API**: `import { diagnose } from "react-doctor/api"` for direct integration in scripts and automation.
|
|
418
471
|
|
|
419
|
-
In CI environments, prompts are automatically skipped
|
|
472
|
+
In CI environments, prompts are automatically skipped. Pass `--offline` explicitly when you need zero network.
|
|
420
473
|
|
|
421
474
|
## Node.js API
|
|
422
475
|
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
//#region \0rolldown/runtime.js
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") for (var keys = __getOwnPropNames(from), i = 0, n = keys.length, key; i < n; i++) {
|
|
12
|
+
key = keys[i];
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, {
|
|
14
|
+
get: ((k) => from[k]).bind(null, key),
|
|
15
|
+
enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", {
|
|
21
|
+
value: mod,
|
|
22
|
+
enumerable: true
|
|
23
|
+
}) : target, mod));
|
|
24
|
+
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
25
|
+
//#endregion
|
|
26
|
+
export { __require as n, __toESM as r, __commonJSMin as t };
|