Skip to content

Commit 3aef45b

Browse files
authored
feat(eslint-config-fluid): switch from eslint-plugin-react to @eslint-react/eslint-plugin (#27011)
## Summary - Replace `eslint-plugin-react` (~7.37.5) with `@eslint-react/eslint-plugin` (~2.13.0) in `@fluidframework/eslint-config-fluid` to unblock ESLint 10 adoption - Use the `recommended-typescript` preset which auto-disables rules already handled by TypeScript - Keep `eslint-plugin-react-hooks` separately — it already has ESLint 10 support and provides React Compiler rules - Switch `print-configs` script from `tsx` to `jiti` to fix ESM-only package resolution ### Why `eslint-plugin-react` 7.37.5 is broken with ESLint 10 — it calls `context.getFilename()` which was removed. The upstream fix ([jsx-eslint/eslint-plugin-react#3979](jsx-eslint/eslint-plugin-react#3979)) has been blocked since February 2026. `@eslint-react` v2.13.0 supports ESLint 8/9/10, so this works now and won't block the ESLint 10 upgrade. ### Consumer updates Per-package eslint config and package.json updates (rule renames, removing redundant devDeps) will come in a follow-up PR stacked on this one. Resolves #27009
1 parent 93b6798 commit 3aef45b

File tree

8 files changed

+769
-1518
lines changed

8 files changed

+769
-1518
lines changed

common/build/eslint-config-fluid/CHANGELOG.md

Lines changed: 77 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,78 @@
11
# @fluidframework/eslint-config-fluid Changelog
22

3-
## [9.0.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v9.0_0)
3+
## [10.0.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v10.0.0)
4+
5+
### eslint-plugin-react replaced by @eslint-react/eslint-plugin
6+
7+
The `eslint-plugin-react` dependency has been replaced with
8+
[`@eslint-react/eslint-plugin`](https://eslint-react.xyz/) v2.13.0. This change is required because
9+
`eslint-plugin-react` 7.37.5 is incompatible with ESLint 10 (it calls the removed `context.getFilename()` API), and the
10+
upstream fix has been blocked since February 2026.
11+
12+
`@eslint-react/eslint-plugin` v2.13.0 supports ESLint 8, 9, and 10, so this version works with the current ESLint 9
13+
setup and will not block the future ESLint 10 upgrade.
14+
15+
`eslint-plugin-react-hooks` is unchanged — it already supports ESLint 10 and continues to provide React Compiler rules.
16+
17+
#### Breaking: Rule names changed
18+
19+
All `react/*` rules from `eslint-plugin-react` are replaced by `@eslint-react/*` rules. Any `eslint-disable` comments
20+
or per-package rule overrides referencing `react/*` rules must be updated to the new names.
21+
22+
Key rule mapping:
23+
24+
| Old rule (`eslint-plugin-react`) | New rule (`@eslint-react`) |
25+
| ------------------------------------- | ---------------------------------------------------------------------------------------- |
26+
| `react/jsx-key` | `@eslint-react/no-missing-key` |
27+
| `react/jsx-no-comment-textnodes` | `@eslint-react/jsx-no-comment-textnodes` |
28+
| `react/jsx-no-target-blank` | `@eslint-react/dom/no-unsafe-target-blank` |
29+
| `react/no-children-prop` | `@eslint-react/no-children-prop` |
30+
| `react/no-danger-with-children` | `@eslint-react/dom/no-dangerously-set-innerhtml-with-children` |
31+
| `react/no-deprecated` | Multiple rules (see below) |
32+
| `react/no-direct-mutation-state` | `@eslint-react/no-direct-mutation-state` |
33+
| `react/no-find-dom-node` | `@eslint-react/dom/no-find-dom-node` |
34+
| `react/no-render-return-value` | `@eslint-react/dom/no-render-return-value` |
35+
| `react/no-string-refs` | `@eslint-react/no-string-refs` |
36+
| `react/no-unstable-nested-components` | `@eslint-react/no-nested-component-definitions` |
37+
| `react/jsx-no-useless-fragment` | `@eslint-react/no-useless-fragment` |
38+
| `react/prop-types` | `@eslint-react/no-prop-types` (bans PropTypes usage; TypeScript handles prop validation) |
39+
40+
The `react/no-deprecated` rule is replaced by individual rules for each deprecated API:
41+
`@eslint-react/no-component-will-mount`, `@eslint-react/no-component-will-receive-props`,
42+
`@eslint-react/no-component-will-update`, `@eslint-react/no-create-ref`, `@eslint-react/dom/no-find-dom-node`,
43+
`@eslint-react/dom/no-hydrate`, `@eslint-react/dom/no-render`, `@eslint-react/dom/no-render-return-value`.
44+
45+
#### Breaking: Rules removed (no equivalent)
46+
47+
The following rules have no `@eslint-react` equivalent and are no longer enforced:
48+
49+
- `react/no-unescaped-entities` — Low priority; JSX transpilation handles this correctly.
50+
- `react/no-is-mounted` — Legacy class component pattern; TypeScript discourages this.
51+
- `react/require-render-return` — TypeScript enforces return types.
52+
53+
#### New rules enabled
54+
55+
The `recommended-typescript` preset enables many rules not previously configured. These are set at `warn` severity
56+
unless otherwise noted:
57+
58+
- `@eslint-react/no-nested-component-definitions` (`error`) — Catches component definitions inside render functions.
59+
- `@eslint-react/no-array-index-key` — Warns against using array index as key.
60+
- `@eslint-react/no-clone-element` — Warns against `React.cloneElement`.
61+
- `@eslint-react/no-context-provider` — Warns against deprecated `Context.Provider` (React 19).
62+
- `@eslint-react/no-forward-ref` — Warns against deprecated `React.forwardRef` (React 19).
63+
- `@eslint-react/web-api/no-leaked-event-listener` — Catches leaked event listeners.
64+
- `@eslint-react/web-api/no-leaked-interval` — Catches leaked `setInterval` calls.
65+
- `@eslint-react/web-api/no-leaked-timeout` — Catches leaked `setTimeout` calls.
66+
- `@eslint-react/web-api/no-leaked-resize-observer` — Catches leaked resize observers.
67+
- `@eslint-react/naming-convention/*` — React naming convention rules.
68+
69+
#### Settings namespace changed
70+
71+
The `@eslint-react` plugin uses `react-x` for its settings namespace instead of `react`. If you have custom
72+
`settings.react` configuration for React version detection, it should be changed to `settings["react-x"]`. The
73+
`recommended-typescript` preset configures `react-x.version: "detect"` automatically.
74+
75+
## [9.0.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v9.0.0)
476

577
### Native ESLint 9 Flat Config (No FlatCompat)
678

@@ -125,7 +197,7 @@ The package now uses rules from [eslint-plugin-import-x](https://github.com/un-t
125197
eslint-plugin-import. Integrating this change will require renaming eslint disable comments and overrides, but the
126198
changes are mechanical.
127199

128-
## [7.0.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v7.0_0)
200+
## [7.0.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v7.0.0)
129201

130202
### New Rules
131203

@@ -179,14 +251,14 @@ Enables the following new rules as warnings (they will be promoted to errors in
179251
- [@typescript-eslint/no-unsafe-function-type](https://typescript-eslint.io/rules/no-unsafe-function-type/)
180252
- [@typescript-eslint/no-wrapper-object-types](https://typescript-eslint.io/rules/no-wrapper-object-types/)
181253

182-
## [6.0.1](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v6.0_1)
254+
## [6.0.1](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v6.0.1)
183255

184256
Update dependencies on the following packages:
185257

186258
- `@typescript-eslint/eslint-plugin` (from 7.0.0 to 7.18.0)
187259
- `@typescript-eslint/parser` (from 7.0.0 to 7.18.0)
188260

189-
## [6.0.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v6.0_0)
261+
## [6.0.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v6.0.0)
190262

191263
Adds the following [@typescript-eslint/no-restricted-imports](https://typescript-eslint.io/rules/no-restricted-imports/) rules:
192264

@@ -195,7 +267,7 @@ Adds the following [@typescript-eslint/no-restricted-imports](https://typescript
195267
2. Don't import from parent index file.
196268
- E.g. prefer `import { Foo } from "./Foo.js";` over `import { Foo } from "./index.js";`
197269

198-
## [5.8.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v5.8_0)
270+
## [5.8.0](https://github.com/microsoft/FluidFramework/releases/tag/eslint-config-fluid_v5.8.0)
199271

200272
Promotes the following rules from the `strict` ruleset to the `recommended` ruleset:
201273

common/build/eslint-config-fluid/library/configs/overrides.mts

Lines changed: 31 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,25 @@
1717
* - cjsFileConfig: CommonJS file rule overrides
1818
* - jsNoProject: Disables type-aware parsing for JS and .d.ts files
1919
* - jsTypeAwareDisable: Disables type-aware rules for JS files
20-
* - reactRecommendedOverride: React file overrides for recommended config
20+
* - reactRecommendedOverride: React file overrides for recommended config factory
2121
* - testRecommendedOverride: Test file overrides for recommended config
2222
* - sharedConfigs: Collection of all shared configs in a config array
2323
*/
2424

25+
import eslintReact from "@eslint-react/eslint-plugin";
2526
import dependPlugin from "eslint-plugin-depend";
26-
import reactPlugin from "eslint-plugin-react";
2727
import reactHooksPlugin from "eslint-plugin-react-hooks";
2828
import type { ESLint, Linter } from "eslint";
2929

3030
import { permittedImports, restrictedImportPaths, testFilePatterns } from "../constants.mjs";
3131
import type { FlatConfigArray } from "./base.mjs";
3232

33+
const reactFilePatterns = ["**/*.jsx", "**/*.tsx"] as const;
34+
const reactRecommendedTypeScript = eslintReact.configs[
35+
"recommended-typescript"
36+
] satisfies Linter.Config;
37+
const reactHooksRecommended = reactHooksPlugin.configs.flat.recommended satisfies Linter.Config;
38+
3339
/**
3440
* eslint-plugin-depend configuration.
3541
*/
@@ -127,37 +133,36 @@ export const internalModulesConfig = {
127133
} as const satisfies Linter.Config;
128134

129135
/**
130-
* React rules for ESLint 9 - extends react/recommended and react-hooks/recommended.
136+
* React rules - extends @eslint-react recommended-typescript and react-hooks/recommended.
137+
*
138+
* Uses @eslint-react/eslint-plugin instead of eslint-plugin-react for ESLint 10 compatibility.
139+
* eslint-plugin-react-hooks is kept separately because it provides React Compiler rules
140+
* and already supports ESLint 10.
131141
*/
132142
export const reactConfig = [
133-
// react/flat.recommended
143+
// @eslint-react recommended-typescript preset
134144
{
135-
files: ["**/*.jsx", "**/*.tsx"],
136-
...reactPlugin.configs.flat.recommended,
145+
...reactRecommendedTypeScript,
146+
files: [...reactFilePatterns],
147+
rules: {
148+
...reactRecommendedTypeScript.rules,
149+
"@eslint-react/dom/no-unsafe-target-blank": "error",
150+
"@eslint-react/no-children-prop": "error",
151+
"@eslint-react/no-useless-fragment": "error",
152+
"@eslint-react/jsx-no-comment-textnodes": "error",
153+
},
137154
},
138-
// react-hooks/recommended rules
155+
// react-hooks/recommended rules with custom overrides
139156
{
140-
files: ["**/*.jsx", "**/*.tsx"],
157+
...reactHooksRecommended,
158+
files: [...reactFilePatterns],
141159
plugins: {
160+
...reactHooksRecommended.plugins,
142161
// reactHooksPlugin.configs.flat does not conform. It is not a `ConfigObject`.
143162
"react-hooks": reactHooksPlugin as ESLint.Plugin,
144163
},
145-
rules: reactHooksPlugin.configs.recommended.rules,
146-
settings: {
147-
react: {
148-
version: "detect",
149-
},
150-
},
151-
},
152-
// react/flat["jsx-runtime"]
153-
{
154-
files: ["**/*.jsx", "**/*.tsx"],
155-
...reactPlugin.configs.flat["jsx-runtime"],
156-
},
157-
// Custom overrides for react-hooks rules
158-
{
159-
files: ["**/*.jsx", "**/*.tsx"],
160164
rules: {
165+
...reactHooksRecommended.rules,
161166
"react-hooks/immutability": "warn",
162167
"react-hooks/refs": "warn",
163168
"react-hooks/rules-of-hooks": "warn",
@@ -252,17 +257,17 @@ export const jsTypeAwareDisable = {
252257
} as const satisfies Linter.Config;
253258

254259
/**
255-
* React file overrides for recommended config (from recommended.js).
260+
* React file overrides for recommended config factory.
256261
*/
257262
export const reactRecommendedOverride = {
258-
files: ["**/*.jsx", "**/*.tsx"],
263+
files: [...reactFilePatterns],
259264
rules: {
260265
"unicorn/consistent-function-scoping": "off",
261266
},
262267
} as const satisfies Linter.Config;
263268

264269
/**
265-
* Test file overrides for recommended config (from recommended.js).
270+
* Test file overrides for recommended config factory.
266271
*/
267272
export const testRecommendedOverride = {
268273
// Use of spread operator shouldn't really be needed here. Under VS Code, a

common/build/eslint-config-fluid/package.json

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@fluidframework/eslint-config-fluid",
3-
"version": "9.0.0",
3+
"version": "10.0.0",
44
"description": "Shareable ESLint config for the Fluid Framework",
55
"homepage": "https://fluidframework.com",
66
"repository": {
@@ -18,11 +18,12 @@
1818
"format": "npm run prettier:fix",
1919
"prettier": "prettier --check . --cache --ignore-path ../../../.prettierignore",
2020
"prettier:fix": "prettier --write . --cache --ignore-path ../../../.prettierignore",
21-
"print-configs": "tsx scripts/print-configs.ts printed-configs",
21+
"print-configs": "jiti scripts/print-configs.ts printed-configs",
2222
"test": "echo TODO: add tests"
2323
},
2424
"dependencies": {
2525
"@eslint-community/eslint-plugin-eslint-comments": "~4.5.0",
26+
"@eslint-react/eslint-plugin": "~2.13.0",
2627
"@eslint/eslintrc": "~3.3.3",
2728
"@eslint/js": "~9.39.2",
2829
"@fluid-internal/eslint-plugin-fluid": "^0.4.1",
@@ -36,7 +37,6 @@
3637
"eslint-plugin-import-x": "~4.16.1",
3738
"eslint-plugin-jsdoc": "~61.4.1",
3839
"eslint-plugin-promise": "~7.2.1",
39-
"eslint-plugin-react": "~7.37.5",
4040
"eslint-plugin-react-hooks": "~7.0.1",
4141
"eslint-plugin-tsdoc": "~0.5.0",
4242
"eslint-plugin-unicorn": "~54.0.0",
@@ -55,7 +55,6 @@
5555
"prettier": "~3.6.2",
5656
"rimraf": "^6.1.3",
5757
"sort-json": "^2.0.1",
58-
"tsx": "^4.21.0",
5958
"typescript": "~5.4.5"
6059
},
6160
"packageManager": "pnpm@10.33.0+sha512.10568bb4a6afb58c9eb3630da90cc9516417abebd3fabbe6739f0ae795728da1491e9db5a544c76ad8eb7570f5c4bb3d6c637b2cb41bfdcdb47fa823c8649319",

0 commit comments

Comments
 (0)