Skip to content

test-noise rules are silently skipped in any path containing tools/, demo/, migrations/, … #1724

Description

@CharlesCalzia

What happens

Every rule tagged test-noise — 332 of the 884 in the registry — is silently disabled on any file whose path carries a directory segment from the built-in "non-application" list. tools, demo, examples, migrations, perf, it and spec are on that list, and in a lot of app codebases those are ordinary product directories: src/components/tools/** is a feature area, not a build script.

Nothing says so. The file is still scanned — untagged rules report on it normally — so the scan looks complete and the affected families simply read as zero. That makes a "this family is at zero repo-wide, promote it to error" ratchet unsound: the zero can mean the rule never ran.

Minimal reproduction

package.json (no doctor.config.ts, no .oxlintrc.json, no ignore rules anywhere):

{
  "name": "reach-repro",
  "private": true,
  "dependencies": { "next": "16.0.0", "react": "19.2.5", "react-dom": "19.2.5" },
  "devDependencies": { "react-doctor": "0.9.12" }
}

Two byte-identical files:

// src/components/chat/widget.tsx  AND  src/components/tools/widget.tsx
export function Widget() {
  return <a href="/about">About</a>;
}
npx react-doctor . --lint --no-dead-code --no-supply-chain --json

Actual: one diagnostic.

src/components/chat/widget.tsx  nextjs-no-a-element  warning

Expected: two — nextjs-no-a-element is tagged test-noise, and src/components/tools/ is application code.

The same holds for no-transition-all (className="transition-all"), no-tiny-text, js-tosorted-immutable and every other test-noise rule. Untagged rules (alt-text, no-array-index-as-key) report on both files, which is what makes the gap easy to miss.

Where it comes from

The rule's create(context) runs for the file and returns the empty-visitor sentinel, so the visitor never fires — the CLI-side diagnostic pipeline never sees anything to drop (shouldAutoSuppress / classifyFileContext both read these paths as "production", correctly). The path list is the 56-entry literal in oxlint-plugin-react-doctor/dist/capability-*.js that starts /test/,/tests/,/testing/,… and includes /scripts/,/cli/,/bin/,/tooling/,/tools/,/codemods/,/migrations/,/generators/,/devtools/.

Bisected on 0.9.12 by planting the same file at a range of paths: tools, scripts, bin, tooling, cli, codemods, generators, examples, demos, playground, fixtures, mocks, __tests__, e2e, cypress, benchmarks, perf, migrations, devtools → silent; tool, toolsx, aitools, config, configs, script, docs, vendor, public → reported.

Why it is hard to work around

ReactDoctorConfig has no application-paths field, so a project cannot opt a directory back in. ignore.overrides only removes rules; there is no inverse. The remaining options are renaming a large feature directory or accepting that half the rule set does not run there, and neither is visible from the scan output.

Suggestions

  1. Make it visible. Report the count somewhere in the JSON and summary — e.g. skippedFileCount per tag, or a suppressions entry with source non-application-path alongside the ones already tracked. A silent skip is indistinguishable from a clean file.
  2. Make it configurable. An applicationPaths / productionPaths field on ReactDoctorConfig, or honouring an explicit include, would let a project say "src/** is all shipped code".
  3. Consider narrowing the default heuristic inside a framework source root. A tools/ or demo/ directory nested under src/, app/ or components/ is product code far more often than it is tooling; the same name at the repo root usually is not.

Version: react-doctor 0.9.12, oxlint-plugin-react-doctor 0.9.12, macOS arm64, Node 24.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions