Skip to content

Commit e64e260

Browse files
bradzachermdjermanovic
authored andcommitted
[New] export flat configs from plugin root and fix flat config crash
Co-authored-by: Brad Zacher <[email protected]> Co-authored-by: Milos Djermanovic <[email protected]>
1 parent 7a7b756 commit e64e260

25 files changed

+386
-120
lines changed

.eslint-doc-generatorrc.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ const config = {
44
['jsx-runtime', '🏃'],
55
['recommended', '☑️'],
66
],
7-
ignoreConfig: ['all'],
7+
ignoreConfig: ['all', 'flat'],
88
urlConfigs: 'https://github.com/jsx-eslint/eslint-plugin-react/#shareable-configs',
99
};
1010

.eslintrc

+1
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
"ignorePatterns": [
1717
"coverage/",
1818
".nyc_output/",
19+
"tests/fixtures/flat-config/"
1920
],
2021
"rules": {
2122
"comma-dangle": [2, "always-multiline"],

CHANGELOG.md

+7-2
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,17 @@ This change log adheres to standards from [Keep a CHANGELOG](https://keepachange
55

66
## Unreleased
77

8+
### Added
9+
* export flat configs from plugin root and fix flat config crash ([#3694][] @bradzacher @mdjermanovic)
10+
11+
[#3694]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3694
12+
813
## [7.34.4] - 2024.07.13
914

1015
### Fixed
1116

12-
* [`prop-types`]: fix `className` missing in prop validation false negative ([#3749] @akulsr0)
13-
* [`sort-prop-types`]: Check for undefined before accessing `node.typeAnnotation.typeAnnotation` ([#3779] @tylerlaprade)
17+
* [`prop-types`]: fix `className` missing in prop validation false negative ([#3749][] @akulsr0)
18+
* [`sort-prop-types`]: Check for undefined before accessing `node.typeAnnotation.typeAnnotation` ([#3779][] @tylerlaprade)
1419

1520
[7.34.4]: https://github.com/jsx-eslint/eslint-plugin-react/compare/v7.34.3...v7.34.4
1621
[#3779]: https://github.com/jsx-eslint/eslint-plugin-react/pull/3779

README.md

+13-18
Original file line numberDiff line numberDiff line change
@@ -203,27 +203,22 @@ Refer to the [official docs](https://eslint.org/docs/latest/user-guide/configuri
203203
The schema of the `settings.react` object would be identical to that of what's already described above in the legacy config section.
204204

205205
<!-- markdownlint-disable-next-line no-duplicate-heading -->
206-
### Shareable configs
207-
208-
There're also 3 shareable configs.
209-
210-
- `eslint-plugin-react/configs/all`
211-
- `eslint-plugin-react/configs/recommended`
212-
- `eslint-plugin-react/configs/jsx-runtime`
206+
### Flat Configs
213207

214-
If your eslint.config.js is ESM, include the `.js` extension (e.g. `eslint-plugin-react/recommended.js`). Note that the next semver-major will require omitting the extension for these imports.
208+
This plugin exports 3 flat configs:
215209

216-
**Note**: These configurations will import `eslint-plugin-react` and enable JSX in [`languageOptions.parserOptions`](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new#configuration-objects).
210+
- `flat.all`
211+
- `flat.recommended`
212+
- `flat['jsx-runtime']`
217213

218-
In the new config system, `plugin:` protocol(e.g. `plugin:react/recommended`) is no longer valid.
219-
As eslint does not automatically import the preset config (shareable config), you explicitly do it by yourself.
214+
The flat configs are available via the root plugin import. They will configure the plugin under the `react/` namespace and enable JSX in [`languageOptions.parserOptions`](https://eslint.org/docs/latest/use/configure/language-options#specifying-parser-options).
220215

221216
```js
222-
const reactRecommended = require('eslint-plugin-react/configs/recommended');
217+
const reactPlugin = require('eslint-plugin-react');
223218

224219
module.exports = [
225220
226-
reactRecommended, // This is not a plugin object, but a shareable config object
221+
reactPlugin.configs.flat.recommended, // This is not a plugin object, but a shareable config object
227222
228223
];
229224
```
@@ -234,16 +229,16 @@ You can of course add/override some properties.
234229
For most of the cases, you probably want to configure some properties by yourself.
235230

236231
```js
237-
const reactRecommended = require('eslint-plugin-react/configs/recommended');
232+
const reactPlugin = require('eslint-plugin-react');
238233
const globals = require('globals');
239234

240235
module.exports = [
241236
242237
{
243238
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
244-
...reactRecommended,
239+
...reactPlugin.configs.flat.recommended,
245240
languageOptions: {
246-
...reactRecommended.languageOptions,
241+
...reactPlugin.configs.flat.recommended.languageOptions,
247242
globals: {
248243
...globals.serviceworker,
249244
...globals.browser,
@@ -257,14 +252,14 @@ module.exports = [
257252
The above example is same as the example below, as the new config system is based on chaining.
258253

259254
```js
260-
const reactRecommended = require('eslint-plugin-react/configs/recommended');
255+
const reactPlugin = require('eslint-plugin-react');
261256
const globals = require('globals');
262257

263258
module.exports = [
264259
265260
{
266261
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],
267-
...reactRecommended,
262+
...reactPlugin.configs.flat.recommended,
268263
},
269264
{
270265
files: ['**/*.{js,mjs,cjs,jsx,mjsx,ts,tsx,mtsx}'],

configs/all.js

+5-41
Original file line numberDiff line numberDiff line change
@@ -1,49 +1,13 @@
11
'use strict';
22

3-
const fromEntries = require('object.fromentries');
4-
const entries = require('object.entries');
3+
const plugin = require('..');
54

6-
const allRules = require('../lib/rules');
7-
8-
function filterRules(rules, predicate) {
9-
return fromEntries(entries(rules).filter((entry) => predicate(entry[1])));
10-
}
11-
12-
/**
13-
* @param {object} rules - rules object mapping rule name to rule module
14-
* @returns {Record<string, 2>}
15-
*/
16-
function configureAsError(rules) {
17-
return fromEntries(Object.keys(rules).map((key) => [`react/${key}`, 2]));
18-
}
19-
20-
const activeRules = filterRules(allRules, (rule) => !rule.meta.deprecated);
21-
const activeRulesConfig = configureAsError(activeRules);
22-
23-
const deprecatedRules = filterRules(allRules, (rule) => rule.meta.deprecated);
5+
const legacyConfig = plugin.configs.all;
246

257
module.exports = {
26-
plugins: {
27-
/**
28-
* @type {{
29-
* deprecatedRules: Record<string, import('eslint').Rule.RuleModule>,
30-
* rules: Record<string, import('eslint').Rule.RuleModule>,
31-
* }}
32-
*/
33-
react: {
34-
deprecatedRules,
35-
rules: allRules,
36-
},
37-
},
38-
rules: activeRulesConfig,
39-
languageOptions: {
40-
parserOptions: {
41-
ecmaFeatures: {
42-
jsx: true,
43-
},
44-
},
45-
},
8+
plugins: { react: plugin },
9+
rules: legacyConfig.rules,
10+
languageOptions: { parserOptions: legacyConfig.parserOptions },
4611
};
4712

48-
// this is so the `languageOptions` property won't be warned in the new config system
4913
Object.defineProperty(module.exports, 'languageOptions', { enumerable: false });

configs/jsx-runtime.js

+8-13
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,13 @@
11
'use strict';
22

3-
const all = require('./all');
3+
const plugin = require('..');
44

5-
module.exports = Object.assign({}, all, {
6-
languageOptions: Object.assign({}, all.languageOptions, {
7-
parserOptions: Object.assign({}, all.languageOptions.parserOptions, {
8-
jsxPragma: null, // for @typescript/eslint-parser
9-
}),
10-
}),
11-
rules: {
12-
'react/react-in-jsx-scope': 0,
13-
'react/jsx-uses-react': 0,
14-
},
15-
});
5+
const legacyConfig = plugin.configs['jsx-runtime'];
6+
7+
module.exports = {
8+
plugins: { react: plugin },
9+
rules: legacyConfig.rules,
10+
languageOptions: { parserOptions: legacyConfig.parserOptions },
11+
};
1612

17-
// this is so the `languageOptions` property won't be warned in the new config system
1813
Object.defineProperty(module.exports, 'languageOptions', { enumerable: false });

configs/recommended.js

+8-29
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,13 @@
11
'use strict';
22

3-
const all = require('./all');
3+
const plugin = require('..');
44

5-
module.exports = Object.assign({}, all, {
6-
languageOptions: all.languageOptions,
7-
rules: {
8-
'react/display-name': 2,
9-
'react/jsx-key': 2,
10-
'react/jsx-no-comment-textnodes': 2,
11-
'react/jsx-no-duplicate-props': 2,
12-
'react/jsx-no-target-blank': 2,
13-
'react/jsx-no-undef': 2,
14-
'react/jsx-uses-react': 2,
15-
'react/jsx-uses-vars': 2,
16-
'react/no-children-prop': 2,
17-
'react/no-danger-with-children': 2,
18-
'react/no-deprecated': 2,
19-
'react/no-direct-mutation-state': 2,
20-
'react/no-find-dom-node': 2,
21-
'react/no-is-mounted': 2,
22-
'react/no-render-return-value': 2,
23-
'react/no-string-refs': 2,
24-
'react/no-unescaped-entities': 2,
25-
'react/no-unknown-property': 2,
26-
'react/no-unsafe': 0,
27-
'react/prop-types': 2,
28-
'react/react-in-jsx-scope': 2,
29-
'react/require-render-return': 2,
30-
},
31-
});
5+
const legacyConfig = plugin.configs.recommended;
6+
7+
module.exports = {
8+
plugins: { react: plugin },
9+
rules: legacyConfig.rules,
10+
languageOptions: { parserOptions: legacyConfig.parserOptions },
11+
};
3212

33-
// this is so the `languageOptions` property won't be warned in the new config system
3413
Object.defineProperty(module.exports, 'languageOptions', { enumerable: false });

index.js

+92-14
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,109 @@
11
'use strict';
22

3-
const configAll = require('./configs/all');
4-
const configRecommended = require('./configs/recommended');
5-
const configRuntime = require('./configs/jsx-runtime');
3+
const fromEntries = require('object.fromentries');
4+
const entries = require('object.entries');
65

76
const allRules = require('./lib/rules');
87

8+
function filterRules(rules, predicate) {
9+
return fromEntries(entries(rules).filter((entry) => predicate(entry[1])));
10+
}
11+
12+
/**
13+
* @param {object} rules - rules object mapping rule name to rule module
14+
* @returns {Record<string, 2>}
15+
*/
16+
function configureAsError(rules) {
17+
return fromEntries(Object.keys(rules).map((key) => [`react/${key}`, 2]));
18+
}
19+
20+
const activeRules = filterRules(allRules, (rule) => !rule.meta.deprecated);
21+
const activeRulesConfig = configureAsError(activeRules);
22+
23+
const deprecatedRules = filterRules(allRules, (rule) => rule.meta.deprecated);
24+
925
// for legacy config system
1026
const plugins = [
1127
'react',
1228
];
1329

14-
module.exports = {
15-
deprecatedRules: configAll.plugins.react.deprecatedRules,
30+
const plugin = {
31+
deprecatedRules,
1632
rules: allRules,
1733
configs: {
18-
recommended: Object.assign({}, configRecommended, {
19-
parserOptions: configRecommended.languageOptions.parserOptions,
34+
recommended: {
2035
plugins,
21-
}),
22-
all: Object.assign({}, configAll, {
23-
parserOptions: configAll.languageOptions.parserOptions,
36+
parserOptions: {
37+
ecmaFeatures: {
38+
jsx: true,
39+
},
40+
},
41+
rules: {
42+
'react/display-name': 2,
43+
'react/jsx-key': 2,
44+
'react/jsx-no-comment-textnodes': 2,
45+
'react/jsx-no-duplicate-props': 2,
46+
'react/jsx-no-target-blank': 2,
47+
'react/jsx-no-undef': 2,
48+
'react/jsx-uses-react': 2,
49+
'react/jsx-uses-vars': 2,
50+
'react/no-children-prop': 2,
51+
'react/no-danger-with-children': 2,
52+
'react/no-deprecated': 2,
53+
'react/no-direct-mutation-state': 2,
54+
'react/no-find-dom-node': 2,
55+
'react/no-is-mounted': 2,
56+
'react/no-render-return-value': 2,
57+
'react/no-string-refs': 2,
58+
'react/no-unescaped-entities': 2,
59+
'react/no-unknown-property': 2,
60+
'react/no-unsafe': 0,
61+
'react/prop-types': 2,
62+
'react/react-in-jsx-scope': 2,
63+
'react/require-render-return': 2,
64+
},
65+
},
66+
all: {
2467
plugins,
25-
}),
26-
'jsx-runtime': Object.assign({}, configRuntime, {
27-
parserOptions: configRuntime.languageOptions.parserOptions,
68+
parserOptions: {
69+
ecmaFeatures: {
70+
jsx: true,
71+
},
72+
},
73+
rules: activeRulesConfig,
74+
},
75+
'jsx-runtime': {
2876
plugins,
29-
}),
77+
parserOptions: {
78+
ecmaFeatures: {
79+
jsx: true,
80+
},
81+
jsxPragma: null, // for @typescript/eslint-parser
82+
},
83+
rules: {
84+
'react/react-in-jsx-scope': 0,
85+
'react/jsx-uses-react': 0,
86+
},
87+
},
88+
},
89+
};
90+
91+
plugin.configs.flat = {
92+
recommended: {
93+
plugins: { react: plugin },
94+
rules: plugin.configs.recommended.rules,
95+
languageOptions: { parserOptions: plugin.configs.recommended.parserOptions },
96+
},
97+
all: {
98+
plugins: { react: plugin },
99+
rules: plugin.configs.all.rules,
100+
languageOptions: { parserOptions: plugin.configs.all.parserOptions },
101+
},
102+
'jsx-runtime': {
103+
plugins: { react: plugin },
104+
rules: plugin.configs['jsx-runtime'].rules,
105+
languageOptions: { parserOptions: plugin.configs['jsx-runtime'].parserOptions },
30106
},
31107
};
108+
109+
module.exports = plugin;

lib/rules/index.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
/* eslint global-require: 0 */
44

5-
/** @type {Record<string, import('eslint').Rule.RuleModule>} */
5+
/** @satisfies {Record<string, import('eslint').Rule.RuleModule>} */
66
module.exports = {
77
'boolean-prop-naming': require('./boolean-prop-naming'),
88
'button-has-type': require('./button-has-type'),

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"test": "npm run unit-test",
1616
"posttest": "aud --production",
1717
"type-check": "tsc",
18-
"unit-test": "istanbul cover node_modules/mocha/bin/_mocha tests/lib/**/*.js tests/util/**/*.js tests/index.js",
18+
"unit-test": "istanbul cover node_modules/mocha/bin/_mocha tests/lib/**/*.js tests/util/**/*.js tests/index.js tests/flat-config.js",
1919
"update:eslint-docs": "eslint-doc-generator"
2020
},
2121
"repository": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
'use strict';
2+
3+
const reactAll = require('../../../../configs/all');
4+
5+
module.exports = [{
6+
files: ['**/*.jsx'],
7+
...reactAll,
8+
languageOptions: {
9+
...reactAll.languageOptions
10+
}
11+
}];
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
'use strict';
2+
3+
const reactPlugin = require('../../../..');
4+
5+
module.exports = [{
6+
files: ['**/*.jsx'],
7+
...reactPlugin.configs.flat.all
8+
}];

0 commit comments

Comments
 (0)