Skip to content

Commit 26b177b

Browse files
authored
[eprh] Fix recommended config for flat config compatibility (#34700)
Previously, the `recommended` config used the legacy ESLint format (plugins as an array of strings). This causes errors when used with ESLint v9's `defineConfig()` helper. This was following [eslint's own docs](https://eslint.org/docs/latest/extend/plugins#backwards-compatibility-for-legacy-configs): > With this approach, both configuration systems recognize "recommended". The old config system uses the recommended key while the current config system uses the flat/recommended key. The defineConfig() helper first looks at the recommended key, and if that is not in the correct format, it looks for the flat/recommended key. This allows you an upgrade path if you’d later like to rename flat/recommended to recommended when you no longer need to support the old config system. However, [`isLegacyConfig()`](https://github.com/eslint/rewrite/blob/main/packages/config-helpers/src/define-config.js#L73-L81) (also see [`eslintrcKeys`](https://github.com/eslint/rewrite/blob/main/packages/config-helpers/src/define-config.js#L24-L35)) function doesn't check for the `plugins` key, so our config was incorrectly treated as flat config despite being in legacy format. This PR fixes the issue, along with a few other fixes combined: 1. Convert `recommended` to flat config format 2. Separate basic rules (exhaustive-deps, rules-of-hooks) from compiler rules 3. Add `recommended-latest-legacy` config for non-flat config users who want all recommended rules (including compiler rules) 4. Adding more types for the exported config Our shipped presets in 6.x.x will essentially be: - `recommended-legacy`: legacy (non-flat), with basic rules only - `recommended-latest-legacy`: legacy (non-flat), all rules (basic + compiler) - `flat/recommended`: flat, basic rules only (now the same as recommended, but to avoid making a breaking change we'll just keep it around in 6.x.x) - `recommended-latest`: flat, all rules (basic + compiler) - `recommended`: flat, basic rules only In the next breaking release 7.x.x, we will collapse down the presets into three: - `recommended-legacy`: all recommended rules - `recommended`: all recommended rules - `recommended-experimental`: all recommended rules + new bleeding edge experimental rules Closes #34679 --- [//]: # (BEGIN SAPLING FOOTER) Stack created with [Sapling](https://sapling-scm.com). Best reviewed with [ReviewStack](https://reviewstack.dev/facebook/react/pull/34700). * #34703 * __->__ #34700
1 parent 056a586 commit 26b177b

File tree

10 files changed

+504
-34
lines changed

10 files changed

+504
-34
lines changed

fixtures/eslint-v6/.eslintrc.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
{
22
"root": true,
3-
"extends": ["plugin:react-hooks/recommended-legacy"],
3+
"extends": ["plugin:react-hooks/recommended-latest-legacy"],
44
"parserOptions": {
55
"ecmaVersion": 2020,
66
"sourceType": "module",
77
"ecmaFeatures": {
88
"jsx": true
99
}
10-
},
11-
"rules": {
12-
"react-hooks/exhaustive-deps": "error"
1310
}
1411
}

fixtures/eslint-v6/index.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,27 @@ function useHookInLoops() {
141141
useHook4();
142142
}
143143
}
144+
145+
/**
146+
* Compiler Rules
147+
*/
148+
// Invalid: component factory
149+
function InvalidComponentFactory() {
150+
const DynamicComponent = () => <div>Hello</div>;
151+
// eslint-disable-next-line react-hooks/static-components
152+
return <DynamicComponent />;
153+
}
154+
155+
// Invalid: mutating globals
156+
function InvalidGlobals() {
157+
// eslint-disable-next-line react-hooks/immutability
158+
window.myGlobal = 42;
159+
return <div>Done</div>;
160+
}
161+
162+
// Invalid: useMemo with wrong deps - triggers preserve-manual-memoization
163+
function InvalidUseMemo({items}) {
164+
// eslint-disable-next-line react-hooks/preserve-manual-memoization, react-hooks/exhaustive-deps
165+
const sorted = useMemo(() => [...items].sort(), []);
166+
return <div>{sorted.length}</div>;
167+
}

fixtures/eslint-v7/.eslintrc.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
{
22
"root": true,
3-
"extends": ["plugin:react-hooks/recommended-legacy"],
3+
"extends": ["plugin:react-hooks/recommended-latest-legacy"],
44
"parserOptions": {
55
"ecmaVersion": 2020,
66
"sourceType": "module",
77
"ecmaFeatures": {
88
"jsx": true
99
}
10-
},
11-
"rules": {
12-
"react-hooks/exhaustive-deps": "error"
1310
}
1411
}

fixtures/eslint-v7/index.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,27 @@ function useHookInLoops() {
141141
useHook4();
142142
}
143143
}
144+
145+
/**
146+
* Compiler Rules
147+
*/
148+
// Invalid: component factory
149+
function InvalidComponentFactory() {
150+
const DynamicComponent = () => <div>Hello</div>;
151+
// eslint-disable-next-line react-hooks/static-components
152+
return <DynamicComponent />;
153+
}
154+
155+
// Invalid: mutating globals
156+
function InvalidGlobals() {
157+
// eslint-disable-next-line react-hooks/immutability
158+
window.myGlobal = 42;
159+
return <div>Done</div>;
160+
}
161+
162+
// Invalid: useMemo with wrong deps - triggers preserve-manual-memoization
163+
function InvalidUseMemo({items}) {
164+
// eslint-disable-next-line react-hooks/preserve-manual-memoization, react-hooks/exhaustive-deps
165+
const sorted = useMemo(() => [...items].sort(), []);
166+
return <div>{sorted.length}</div>;
167+
}

fixtures/eslint-v8/.eslintrc.json

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
{
22
"root": true,
3-
"extends": ["plugin:react-hooks/recommended-legacy"],
3+
"extends": ["plugin:react-hooks/recommended-latest-legacy"],
44
"parserOptions": {
55
"ecmaVersion": 2020,
66
"sourceType": "module",
77
"ecmaFeatures": {
88
"jsx": true
99
}
10-
},
11-
"rules": {
12-
"react-hooks/exhaustive-deps": "error"
1310
}
1411
}

fixtures/eslint-v8/index.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,27 @@ function useHookInLoops() {
141141
useHook4();
142142
}
143143
}
144+
145+
/**
146+
* Compiler Rules
147+
*/
148+
// Invalid: component factory
149+
function InvalidComponentFactory() {
150+
const DynamicComponent = () => <div>Hello</div>;
151+
// eslint-disable-next-line react-hooks/static-components
152+
return <DynamicComponent />;
153+
}
154+
155+
// Invalid: mutating globals
156+
function InvalidGlobals() {
157+
// eslint-disable-next-line react-hooks/immutability
158+
window.myGlobal = 42;
159+
return <div>Done</div>;
160+
}
161+
162+
// Invalid: useMemo with wrong deps - triggers preserve-manual-memoization
163+
function InvalidUseMemo({items}) {
164+
// eslint-disable-next-line react-hooks/preserve-manual-memoization, react-hooks/exhaustive-deps
165+
const sorted = useMemo(() => [...items].sort(), []);
166+
return <div>{sorted.length}</div>;
167+
}

fixtures/eslint-v9/eslint.config.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import {defineConfig} from 'eslint/config';
22
import reactHooks from 'eslint-plugin-react-hooks';
33

4-
console.log(reactHooks.configs['recommended-latest']);
5-
64
export default defineConfig([
75
{
86
languageOptions: {

fixtures/eslint-v9/index.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,27 @@ function useHookInLoops() {
141141
useHook4();
142142
}
143143
}
144+
145+
/**
146+
* Compiler Rules
147+
*/
148+
// Invalid: component factory
149+
function InvalidComponentFactory() {
150+
const DynamicComponent = () => <div>Hello</div>;
151+
// eslint-disable-next-line react-hooks/static-components
152+
return <DynamicComponent />;
153+
}
154+
155+
// Invalid: mutating globals
156+
function InvalidGlobals() {
157+
// eslint-disable-next-line react-hooks/immutability
158+
window.myGlobal = 42;
159+
return <div>Done</div>;
160+
}
161+
162+
// Invalid: useMemo with wrong deps - triggers preserve-manual-memoization
163+
function InvalidUseMemo({items}) {
164+
// eslint-disable-next-line react-hooks/preserve-manual-memoization, react-hooks/exhaustive-deps
165+
const sorted = useMemo(() => [...items].sort(), []);
166+
return <div>{sorted.length}</div>;
167+
}

0 commit comments

Comments
 (0)