Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions .changeset/remove-no-multi-comp-alias.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
---
"@react-doctor/core": patch
"oxlint-plugin-react-doctor": patch
---

fix: remove react/no-multi-comp alias to enable strict oxlint version

Removes the alias from `react/no-multi-comp` to `react-doctor/no-multi-comp`. This change allows users who want strict "one component per file" enforcement to access oxlint's `react/no-multi-comp` rule via `.oxlintrc.json`, while preserving the lenient `react-doctor/no-multi-comp` behavior for users who explicitly configure it.

The react-doctor version is intentionally more permissive, with corpus-informed exemptions for:
- Files with ≤2 components (main + helper)
- Feature modules (1-2 exports + private helpers)
- Barrel files (mostly-exported components)

Users who previously configured `react/no-multi-comp` and want to continue using the lenient version should update their config to use `react-doctor/no-multi-comp` explicitly. Users who want the strict oxlint behavior can create an `.oxlintrc.json` file:

```json
{
"plugins": ["react"],
"rules": {"react/no-multi-comp": "error"}
}
```

Closes #1639
1 change: 0 additions & 1 deletion packages/core/src/rule-key-aliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,6 @@ const LEGACY_RULE_KEY_TO_NATIVE_RULE_KEY: Readonly<Record<string, string>> = {
"react/no-direct-mutation-state": "react-doctor/no-direct-mutation-state",
"react/no-find-dom-node": "react-doctor/no-find-dom-node",
"react/no-is-mounted": "react-doctor/no-is-mounted",
"react/no-multi-comp": "react-doctor/no-multi-comp",
"react/no-namespace": "react-doctor/no-namespace",
"react/no-react-children": "react-doctor/no-react-children",
"react/no-redundant-should-component-update": "react-doctor/no-redundant-should-component-update",
Expand Down
18 changes: 18 additions & 0 deletions packages/core/tests/rule-key-aliases.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,22 @@ describe("rule-key-aliases", () => {
expect(getEquivalentRuleKeys("toString")).toEqual(["toString"]);
});
});

describe("removed aliases (issue #1639)", () => {
it("does not alias react/no-multi-comp to react-doctor/no-multi-comp", () => {
expect(isSameRuleKey("react/no-multi-comp", "react-doctor/no-multi-comp")).toBe(false);
});

it("treats react/no-multi-comp as a distinct rule from react-doctor/no-multi-comp", () => {
const reactKeys = getEquivalentRuleKeys("react/no-multi-comp");
expect(reactKeys).toEqual(["react/no-multi-comp"]);
expect(reactKeys).not.toContain("react-doctor/no-multi-comp");
});

it("treats react-doctor/no-multi-comp as its own rule", () => {
const rdKeys = getEquivalentRuleKeys("react-doctor/no-multi-comp");
expect(rdKeys).toEqual(["react-doctor/no-multi-comp"]);
expect(rdKeys).not.toContain("react/no-multi-comp");
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,21 @@ describe("react-builtins/no-multi-comp — regressions", () => {
);
});

// Regression test for issue #1639: A file with 3 components where 1 is
// exported falls under the "feature module" exemption (1-2 exports + private
// helpers). This is intentional — users who want strict "one component per
// file" enforcement should use oxlint's `react/no-multi-comp` via
// `.oxlintrc.json` instead of the lenient react-doctor version.
it("exempts a file with 1 exported component + 2 private helpers (feature module)", () => {
expectPass(
`function Row() { return <li>row</li>; }
function Header() { return <h1>header</h1>; }
export function List() {
return <div><Header /><Row /></div>;
}`,
);
});

it("does not trace an overwritten mutable React HoC alias", () => {
expectFail(
`import { memo } from "react";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,20 @@ import { REACT_HOC_NAMES, REACT_RUNTIME_MODULE_SOURCES } from "../../constants/r
const MESSAGE =
"This file declares several components, so each component is harder to find, test, and change.";

// React Doctor's `no-multi-comp` is intentionally more lenient than oxlint's
// `react/no-multi-comp` to accommodate common real-world React patterns:
//
// - Files with ≤2 components (1 main + 1 helper) are exempt
// - Feature modules (1-2 public exports + private helpers) are exempt
// - Barrel files (mostly-exported components) are exempt
//
// For stricter "one component per file" enforcement matching oxlint's behavior,
// create an `.oxlintrc.json` with:
// {"plugins": ["react"], "rules": {"react/no-multi-comp": "error"}}
//
// This rule was corpus-tuned to avoid false positives in design systems,
// feature modules, and icon barrels. See regression tests for examples.

interface NoMultiCompSettings {
ignoreStateless?: boolean;
}
Expand Down
Loading