mirror of
https://github.com/millionco/react-doctor.git
synced 2026-09-14 20:00:24 +08:00
90 lines
3.9 KiB
Markdown
90 lines
3.9 KiB
Markdown
# FN mining
|
|
|
|
A repeatable false-negative mining harness for `oxlint-plugin-react-doctor`
|
|
rules. Each case is a **syntactic variant** of a rule's documented bad
|
|
pattern — same underlying problem, different surface shape (arithmetic on
|
|
the index, a template literal, an aliased import, indirection through a
|
|
variable, a different loop kind). The runner executes every variant against
|
|
its rule and reports which ones did NOT fire. Those silent variants are
|
|
**false-negative candidates** for human triage.
|
|
|
|
## Run
|
|
|
|
```bash
|
|
pnpm fn-mining
|
|
```
|
|
|
|
The runner always exits 0 — it is a mining/report tool, not a CI gate. A
|
|
silent variant is not automatically a bug: many rules deliberately trade
|
|
recall for precision (documented carve-outs, name gates, same-file-proof
|
|
requirements). Every silent variant needs a human to read the rule source
|
|
and decide: real FN worth fixing, or deliberate scoping.
|
|
|
|
The script runs through `tsx` (provided transitively by `vite-plus`)
|
|
rather than `node --experimental-strip-types` like the other root scripts,
|
|
because it imports the plugin's TypeScript sources, whose internal
|
|
`./x.js`-suffixed imports Node's type stripping cannot resolve.
|
|
|
|
## Adding cases
|
|
|
|
1. Pick a rule where detection breadth matters (pattern-matching rules, not
|
|
simple attribute checks). Read its source under
|
|
`packages/oxlint-plugin-react-doctor/src/plugin/rules/` to understand
|
|
exactly what it matches — the goal is probing edge shapes it might miss,
|
|
not re-testing its canonical example.
|
|
2. Create `cases/<rule-id>.ts` exporting an array of `FnMiningCase`:
|
|
|
|
```ts
|
|
import type { FnMiningCase } from "../fn-mining-case.js";
|
|
|
|
export const myRuleCases: FnMiningCase[] = [
|
|
{
|
|
ruleId: "my-rule", // the rule's public id (ruleRegistry key)
|
|
description: "index offset by arithmetic: key={index + 1}",
|
|
filePath: "src/list.tsx", // drives parser lang + file-based gates
|
|
code: `...`,
|
|
shouldFire: true,
|
|
},
|
|
];
|
|
```
|
|
|
|
3. Register the array in `cases/index.ts`.
|
|
When triage concludes a silent variant is deliberate rule scoping, keep
|
|
the case but set `shouldFire: false` and explain the gate in
|
|
`carveOutReason` — the runner then prints it as `[carved]` instead of
|
|
re-flagging it as an FN candidate, and warns if it ever starts firing
|
|
(a precision regression).
|
|
4. Include one or two canonical "must fire" variants as a baseline — if
|
|
those go silent, the case itself is broken (wrong file path hitting a
|
|
testlike-filename gate, a missing import a rule's import check needs,
|
|
etc.), not the rule.
|
|
|
|
Case-writing notes:
|
|
|
|
- `filePath` matters. Rules tagged `test-noise` skip testlike filenames
|
|
(`*.test.tsx`, `*.stories.tsx`); scan rules skip non-production paths.
|
|
Use a production-looking path unless the file-based gate is itself what
|
|
you are probing.
|
|
- AST rules run through `runRule` (the same pure-TS oxc-parser host the
|
|
unit tests use); rules with a `scan` function run through `runScanRule`
|
|
against an in-memory file. The runner picks automatically based on the
|
|
rule shape in the registry.
|
|
- Framework gating (`requires: ["react-native"]` etc.) is a registry-level
|
|
concern and does not apply here — the rule logic runs directly.
|
|
|
|
## Interpreting output
|
|
|
|
Per rule, each variant prints as `[fired]` or `[SILENT]`, followed by a
|
|
summary and the aggregated FN-candidate list. Triage each silent variant
|
|
by reading the rule source:
|
|
|
|
- **Real FN candidate** — the variant is the same user-visible problem and
|
|
the miss is an implementation gap (e.g. the key-expression walker not
|
|
handling `key={i + 1}` while handling `key={\`item-${i}\`}`).
|
|
- **Deliberate scoping** — the rule author chose precision over recall and
|
|
the source says so (e.g. spread props may carry the missing attribute,
|
|
same-file memo proof required, name-gated index parameters).
|
|
|
|
Report real FNs with a pointer into the rule source (file:line of the
|
|
check that bails) so the fix conversation starts concrete.
|