@samitouri / QOS-React / commits / 848e0e3a4f

[eprh] Update plugin config to be compatible with flat and legacy (#34762)

This has been incredibly frustrating as [ESLint's own docs](https://eslint.org/docs/latest/extend/plugins#backwards-compatibility-for-legacy-configs) are clearly wrong (see #34679). This PR uses [eslint-plugin-react's setup](https://github.com/jsx-eslint/eslint-plugin-react/blob/master/index.js) as a reference, where the presets are assigned to `configs.flat` (not documented by eslint).

lauren committed Oct 7, 2025 at 16:23 UTC 848e0e3a4f12022d396ddbc2b52fd8fa7ac31fa9
4 files changed +63 -56
fixtures/eslint-v9/eslint.config.ts
+1 -1
@@ -2,6 +2,7 @@ import {defineConfig} from 'eslint/config';
2 import reactHooks from 'eslint-plugin-react-hooks';
3
4 export default defineConfig([
5 + reactHooks.configs.flat['recommended-latest'],
6 {
7 languageOptions: {
8 ecmaVersion: 'latest',
@@ -12,7 +13,6 @@ export default defineConfig([
13 },
14 },
15 },
15 - extends: [reactHooks.configs['recommended-latest']],
16 rules: {
17 'react-hooks/exhaustive-deps': 'error',
18 },
packages/eslint-plugin-react-hooks/package.json
+1 -1
@@ -50,7 +50,7 @@
50 "@babel/preset-typescript": "^7.26.0",
51 "@babel/types": "^7.19.0",
52 "@tsconfig/strictest": "^2.0.5",
53 - "@types/eslint": "^8.56.12",
53 + "@types/eslint": "^9.6.1",
54 "@types/estree": "^1.0.6",
55 "@types/estree-jsx": "^1.0.5",
56 "@types/node": "^20.2.5",
packages/eslint-plugin-react-hooks/src/index.ts
+45 -50
@@ -14,7 +14,6 @@ import {
14 } from './shared/ReactCompiler';
15 import RulesOfHooks from './rules/RulesOfHooks';
16
17 -// All rules
17 const rules = {
18 'exhaustive-deps': ExhaustiveDeps,
19 'rules-of-hooks': RulesOfHooks,
@@ -23,7 +22,6 @@ const rules = {
22 ),
23 } satisfies Record<string, Rule.RuleModule>;
24
26 -// Basic hooks rules (for recommended config)
25 const basicRuleConfigs = {
26 'react-hooks/rules-of-hooks': 'error',
27 'react-hooks/exhaustive-deps': 'warn',
@@ -38,74 +36,71 @@ const compilerRuleConfigs = Object.fromEntries(
36 }),
37 ) as Record<`react-hooks/${string}`, Linter.RuleEntry>;
38
41 -// All rules including compiler rules (for recommended-latest config)
39 const allRuleConfigs: Linter.RulesRecord = {
40 ...basicRuleConfigs,
41 ...compilerRuleConfigs,
42 };
43
47 -type FlatConfig = {
44 +const plugins = ['react-hooks'];
45 +
46 +type ReactHooksFlatConfig = {
47 plugins: Record<string, any>;
48 rules: Linter.RulesRecord;
49 };
50
51 +const configs = {
52 + 'recommended-legacy': {
53 + plugins,
54 + rules: basicRuleConfigs,
55 + },
56 + 'recommended-latest-legacy': {
57 + plugins,
58 + rules: allRuleConfigs,
59 + },
60 + 'flat/recommended': {
61 + plugins,
62 + rules: basicRuleConfigs,
63 + },
64 + 'recommended-latest': {
65 + plugins,
66 + rules: allRuleConfigs,
67 + },
68 + recommended: {
69 + plugins,
70 + rules: basicRuleConfigs,
71 + },
72 + flat: {} as Record<string, ReactHooksFlatConfig>,
73 +};
74 +
75 const plugin = {
76 meta: {
77 name: 'eslint-plugin-react-hooks',
78 },
79 rules,
57 - configs: {} as {
58 - 'recommended-legacy': {
59 - plugins: Array<string>;
60 - rules: Linter.RulesRecord;
61 - };
62 - 'recommended-latest-legacy': {
63 - plugins: Array<string>;
64 - rules: Linter.RulesRecord;
65 - };
66 - 'flat/recommended': Array<FlatConfig>;
67 - 'recommended-latest': Array<FlatConfig>;
68 - recommended: Array<FlatConfig>;
69 - },
80 + configs,
81 };
82
72 -Object.assign(plugin.configs, {
83 +Object.assign(configs.flat, {
84 'recommended-legacy': {
74 - plugins: ['react-hooks'],
75 - rules: basicRuleConfigs,
85 + plugins: {'react-hooks': plugin},
86 + rules: configs['recommended-legacy'].rules,
87 },
77 -
88 'recommended-latest-legacy': {
79 - plugins: ['react-hooks'],
80 - rules: allRuleConfigs,
89 + plugins: {'react-hooks': plugin},
90 + rules: configs['recommended-latest-legacy'].rules,
91 + },
92 + 'flat/recommended': {
93 + plugins: {'react-hooks': plugin},
94 + rules: configs['flat/recommended'].rules,
95 + },
96 + 'recommended-latest': {
97 + plugins: {'react-hooks': plugin},
98 + rules: configs['recommended-latest'].rules,
99 + },
100 + recommended: {
101 + plugins: {'react-hooks': plugin},
102 + rules: configs.recommended.rules,
103 },
82 -
83 - 'flat/recommended': [
84 - {
85 - plugins: {
86 - 'react-hooks': plugin,
87 - },
88 - rules: basicRuleConfigs,
89 - },
90 - ],
91 -
92 - 'recommended-latest': [
93 - {
94 - plugins: {
95 - 'react-hooks': plugin,
96 - },
97 - rules: allRuleConfigs,
98 - },
99 - ],
100 -
101 - recommended: [
102 - {
103 - plugins: {
104 - 'react-hooks': plugin,
105 - },
106 - rules: basicRuleConfigs,
107 - },
108 - ],
104 });
105
106 export default plugin;
yarn.lock
+16 -4
@@ -3907,10 +3907,10 @@
3907 "@types/estree" "*"
3908 "@types/json-schema" "*"
3909
3910 -"@types/eslint@^8.56.12":
3911 - version "8.56.12"
3912 - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.56.12.tgz#1657c814ffeba4d2f84c0d4ba0f44ca7ea1ca53a"
3913 - integrity sha512-03ruubjWyOHlmljCVoxSuNDdmfZDzsrrz0P2LeJsOXr+ZwFQ+0yQIwNCwt/GYhV7Z31fgtXJTAEs+FYlEL851g==
3910 +"@types/eslint@^9.6.1":
3911 + version "9.6.1"
3912 + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-9.6.1.tgz#d5795ad732ce81715f27f75da913004a56751584"
3913 + integrity sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==
3914 dependencies:
3915 "@types/estree" "*"
3916 "@types/json-schema" "*"
@@ -10190,6 +10190,11 @@ hermes-eslint@^0.32.0:
10190 hermes-estree "0.32.0"
10191 hermes-parser "0.32.0"
10192
10193 +hermes-estree@0.25.1:
10194 + version "0.25.1"
10195 + resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.25.1.tgz#6aeec17d1983b4eabf69721f3aa3eb705b17f480"
10196 + integrity sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==
10197 +
10198 hermes-estree@0.29.1:
10199 version "0.29.1"
10200 resolved "https://registry.yarnpkg.com/hermes-estree/-/hermes-estree-0.29.1.tgz#043c7db076e0e8ef8c5f6ed23828d1ba463ebcc5"
@@ -10214,6 +10219,13 @@ hermes-parser@0.32.0, hermes-parser@^0.32.0:
10219 dependencies:
10220 hermes-estree "0.32.0"
10221
10222 +hermes-parser@^0.25.1:
10223 + version "0.25.1"
10224 + resolved "https://registry.yarnpkg.com/hermes-parser/-/hermes-parser-0.25.1.tgz#5be0e487b2090886c62bd8a11724cd766d5f54d1"
10225 + integrity sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==
10226 + dependencies:
10227 + hermes-estree "0.25.1"
10228 +
10229 homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1:
10230 version "1.0.3"
10231 resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.3.tgz#743298cef4e5af3e194161fbadcc2151d3a058e8"