Skip to content

Commit 65711f9

Browse files
jjangga0214sindresorhusfisker
authoredDec 20, 2023
Support ESLint's new config system (#1886)
Co-authored-by: Sindre Sorhus <sindresorhus@gmail.com> Co-authored-by: fisker <lionkay@gmail.com>
1 parent c91c6ad commit 65711f9

10 files changed

+364
-156
lines changed
 

‎.eslint-doc-generatorrc.js

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

33
/** @type {import('eslint-doc-generator').GenerateOptions} */
44
const config = {
5-
ignoreConfig: ['all'],
5+
ignoreConfig: ['all', 'flat/all', 'flat/recommended'],
66
ignoreDeprecatedRules: true,
77
ruleDocTitleFormat: 'desc',
88
ruleListColumns: [

‎configs/all.js

+4-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,6 @@
11
'use strict';
2-
const {rules, ...baseConfigs} = require('./recommended.js');
2+
const recommended = require('./recommended.js');
33

4-
module.exports = {
5-
...baseConfigs,
6-
rules: Object.fromEntries(Object.entries(rules).map(
7-
([ruleId, severity]) => [ruleId, ruleId.startsWith('unicorn/') ? 'error' : severity],
8-
)),
9-
};
4+
module.exports = Object.fromEntries(Object.entries(recommended).map(
5+
([ruleId, severity]) => [ruleId, ruleId.startsWith('unicorn/') ? 'error' : severity],
6+
));

‎configs/flat-config-base.js

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
'use strict';
2+
const eslintrc = require('@eslint/eslintrc');
3+
const legacyConfigBase = require('./legacy-config-base.js');
4+
5+
const {
6+
parserOptions: {
7+
ecmaVersion,
8+
sourceType,
9+
},
10+
} = legacyConfigBase;
11+
12+
const {globals} = eslintrc.Legacy.environments.get('es2024');
13+
14+
module.exports = {
15+
languageOptions: {
16+
ecmaVersion,
17+
sourceType,
18+
globals,
19+
},
20+
};

‎configs/legacy-config-base.js

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
'use strict';
2+
module.exports = {
3+
env: {
4+
es2024: true,
5+
},
6+
parserOptions: {
7+
ecmaVersion: 'latest',
8+
sourceType: 'module',
9+
},
10+
};

‎configs/recommended.js

+114-126
Original file line numberDiff line numberDiff line change
@@ -1,129 +1,117 @@
11
'use strict';
22
module.exports = {
3-
env: {
4-
es2024: true,
5-
},
6-
parserOptions: {
7-
ecmaVersion: 'latest',
8-
sourceType: 'module',
9-
},
10-
plugins: [
11-
'unicorn',
12-
],
13-
rules: {
14-
'unicorn/better-regex': 'error',
15-
'unicorn/catch-error-name': 'error',
16-
'unicorn/consistent-destructuring': 'error',
17-
'unicorn/consistent-function-scoping': 'error',
18-
'unicorn/custom-error-definition': 'off',
19-
'unicorn/empty-brace-spaces': 'error',
20-
'unicorn/error-message': 'error',
21-
'unicorn/escape-case': 'error',
22-
'unicorn/expiring-todo-comments': 'error',
23-
'unicorn/explicit-length-check': 'error',
24-
'unicorn/filename-case': 'error',
25-
'unicorn/import-style': 'error',
26-
'unicorn/new-for-builtins': 'error',
27-
'unicorn/no-abusive-eslint-disable': 'error',
28-
'unicorn/no-array-callback-reference': 'error',
29-
'unicorn/no-array-for-each': 'error',
30-
'unicorn/no-array-method-this-argument': 'error',
31-
'unicorn/no-array-push-push': 'error',
32-
'unicorn/no-array-reduce': 'error',
33-
'unicorn/no-await-expression-member': 'error',
34-
'unicorn/no-console-spaces': 'error',
35-
'unicorn/no-document-cookie': 'error',
36-
'unicorn/no-empty-file': 'error',
37-
'unicorn/no-for-loop': 'error',
38-
'unicorn/no-hex-escape': 'error',
39-
'unicorn/no-instanceof-array': 'error',
40-
'unicorn/no-invalid-remove-event-listener': 'error',
41-
'unicorn/no-keyword-prefix': 'off',
42-
'unicorn/no-lonely-if': 'error',
43-
'no-negated-condition': 'off',
44-
'unicorn/no-negated-condition': 'error',
45-
'no-nested-ternary': 'off',
46-
'unicorn/no-nested-ternary': 'error',
47-
'unicorn/no-new-array': 'error',
48-
'unicorn/no-new-buffer': 'error',
49-
'unicorn/no-null': 'error',
50-
'unicorn/no-object-as-default-parameter': 'error',
51-
'unicorn/no-process-exit': 'error',
52-
'unicorn/no-static-only-class': 'error',
53-
'unicorn/no-thenable': 'error',
54-
'unicorn/no-this-assignment': 'error',
55-
'unicorn/no-typeof-undefined': 'error',
56-
'unicorn/no-unnecessary-await': 'error',
57-
'unicorn/no-unnecessary-polyfills': 'error',
58-
'unicorn/no-unreadable-array-destructuring': 'error',
59-
'unicorn/no-unreadable-iife': 'error',
60-
'unicorn/no-unused-properties': 'off',
61-
'unicorn/no-useless-fallback-in-spread': 'error',
62-
'unicorn/no-useless-length-check': 'error',
63-
'unicorn/no-useless-promise-resolve-reject': 'error',
64-
'unicorn/no-useless-spread': 'error',
65-
'unicorn/no-useless-switch-case': 'error',
66-
'unicorn/no-useless-undefined': 'error',
67-
'unicorn/no-zero-fractions': 'error',
68-
'unicorn/number-literal-case': 'error',
69-
'unicorn/numeric-separators-style': 'error',
70-
'unicorn/prefer-add-event-listener': 'error',
71-
'unicorn/prefer-array-find': 'error',
72-
'unicorn/prefer-array-flat': 'error',
73-
'unicorn/prefer-array-flat-map': 'error',
74-
'unicorn/prefer-array-index-of': 'error',
75-
'unicorn/prefer-array-some': 'error',
76-
'unicorn/prefer-at': 'error',
77-
'unicorn/prefer-blob-reading-methods': 'error',
78-
'unicorn/prefer-code-point': 'error',
79-
'unicorn/prefer-date-now': 'error',
80-
'unicorn/prefer-default-parameters': 'error',
81-
'unicorn/prefer-dom-node-append': 'error',
82-
'unicorn/prefer-dom-node-dataset': 'error',
83-
'unicorn/prefer-dom-node-remove': 'error',
84-
'unicorn/prefer-dom-node-text-content': 'error',
85-
'unicorn/prefer-event-target': 'error',
86-
'unicorn/prefer-export-from': 'error',
87-
'unicorn/prefer-includes': 'error',
88-
'unicorn/prefer-json-parse-buffer': 'off',
89-
'unicorn/prefer-keyboard-event-key': 'error',
90-
'unicorn/prefer-logical-operator-over-ternary': 'error',
91-
'unicorn/prefer-math-trunc': 'error',
92-
'unicorn/prefer-modern-dom-apis': 'error',
93-
'unicorn/prefer-modern-math-apis': 'error',
94-
'unicorn/prefer-module': 'error',
95-
'unicorn/prefer-native-coercion-functions': 'error',
96-
'unicorn/prefer-negative-index': 'error',
97-
'unicorn/prefer-node-protocol': 'error',
98-
'unicorn/prefer-number-properties': 'error',
99-
'unicorn/prefer-object-from-entries': 'error',
100-
'unicorn/prefer-optional-catch-binding': 'error',
101-
'unicorn/prefer-prototype-methods': 'error',
102-
'unicorn/prefer-query-selector': 'error',
103-
'unicorn/prefer-reflect-apply': 'error',
104-
'unicorn/prefer-regexp-test': 'error',
105-
'unicorn/prefer-set-has': 'error',
106-
'unicorn/prefer-set-size': 'error',
107-
'unicorn/prefer-spread': 'error',
108-
'unicorn/prefer-string-replace-all': 'error',
109-
'unicorn/prefer-string-slice': 'error',
110-
'unicorn/prefer-string-starts-ends-with': 'error',
111-
'unicorn/prefer-string-trim-start-end': 'error',
112-
'unicorn/prefer-switch': 'error',
113-
'unicorn/prefer-ternary': 'error',
114-
'unicorn/prefer-top-level-await': 'error',
115-
'unicorn/prefer-type-error': 'error',
116-
'unicorn/prevent-abbreviations': 'error',
117-
'unicorn/relative-url-style': 'error',
118-
'unicorn/require-array-join-separator': 'error',
119-
'unicorn/require-number-to-fixed-digits-argument': 'error',
120-
// Turned off because we can't distinguish `widow.postMessage` and `{Worker,MessagePort,Client,BroadcastChannel}#postMessage()`
121-
// See #1396
122-
'unicorn/require-post-message-target-origin': 'off',
123-
'unicorn/string-content': 'off',
124-
'unicorn/switch-case-braces': 'error',
125-
'unicorn/template-indent': 'error',
126-
'unicorn/text-encoding-identifier-case': 'error',
127-
'unicorn/throw-new-error': 'error',
128-
},
3+
'unicorn/better-regex': 'error',
4+
'unicorn/catch-error-name': 'error',
5+
'unicorn/consistent-destructuring': 'error',
6+
'unicorn/consistent-function-scoping': 'error',
7+
'unicorn/custom-error-definition': 'off',
8+
'unicorn/empty-brace-spaces': 'error',
9+
'unicorn/error-message': 'error',
10+
'unicorn/escape-case': 'error',
11+
'unicorn/expiring-todo-comments': 'error',
12+
'unicorn/explicit-length-check': 'error',
13+
'unicorn/filename-case': 'error',
14+
'unicorn/import-style': 'error',
15+
'unicorn/new-for-builtins': 'error',
16+
'unicorn/no-abusive-eslint-disable': 'error',
17+
'unicorn/no-array-callback-reference': 'error',
18+
'unicorn/no-array-for-each': 'error',
19+
'unicorn/no-array-method-this-argument': 'error',
20+
'unicorn/no-array-push-push': 'error',
21+
'unicorn/no-array-reduce': 'error',
22+
'unicorn/no-await-expression-member': 'error',
23+
'unicorn/no-console-spaces': 'error',
24+
'unicorn/no-document-cookie': 'error',
25+
'unicorn/no-empty-file': 'error',
26+
'unicorn/no-for-loop': 'error',
27+
'unicorn/no-hex-escape': 'error',
28+
'unicorn/no-instanceof-array': 'error',
29+
'unicorn/no-invalid-remove-event-listener': 'error',
30+
'unicorn/no-keyword-prefix': 'off',
31+
'unicorn/no-lonely-if': 'error',
32+
'no-negated-condition': 'off',
33+
'unicorn/no-negated-condition': 'error',
34+
'no-nested-ternary': 'off',
35+
'unicorn/no-nested-ternary': 'error',
36+
'unicorn/no-new-array': 'error',
37+
'unicorn/no-new-buffer': 'error',
38+
'unicorn/no-null': 'error',
39+
'unicorn/no-object-as-default-parameter': 'error',
40+
'unicorn/no-process-exit': 'error',
41+
'unicorn/no-static-only-class': 'error',
42+
'unicorn/no-thenable': 'error',
43+
'unicorn/no-this-assignment': 'error',
44+
'unicorn/no-typeof-undefined': 'error',
45+
'unicorn/no-unnecessary-await': 'error',
46+
'unicorn/no-unnecessary-polyfills': 'error',
47+
'unicorn/no-unreadable-array-destructuring': 'error',
48+
'unicorn/no-unreadable-iife': 'error',
49+
'unicorn/no-unused-properties': 'off',
50+
'unicorn/no-useless-fallback-in-spread': 'error',
51+
'unicorn/no-useless-length-check': 'error',
52+
'unicorn/no-useless-promise-resolve-reject': 'error',
53+
'unicorn/no-useless-spread': 'error',
54+
'unicorn/no-useless-switch-case': 'error',
55+
'unicorn/no-useless-undefined': 'error',
56+
'unicorn/no-zero-fractions': 'error',
57+
'unicorn/number-literal-case': 'error',
58+
'unicorn/numeric-separators-style': 'error',
59+
'unicorn/prefer-add-event-listener': 'error',
60+
'unicorn/prefer-array-find': 'error',
61+
'unicorn/prefer-array-flat': 'error',
62+
'unicorn/prefer-array-flat-map': 'error',
63+
'unicorn/prefer-array-index-of': 'error',
64+
'unicorn/prefer-array-some': 'error',
65+
'unicorn/prefer-at': 'error',
66+
'unicorn/prefer-blob-reading-methods': 'error',
67+
'unicorn/prefer-code-point': 'error',
68+
'unicorn/prefer-date-now': 'error',
69+
'unicorn/prefer-default-parameters': 'error',
70+
'unicorn/prefer-dom-node-append': 'error',
71+
'unicorn/prefer-dom-node-dataset': 'error',
72+
'unicorn/prefer-dom-node-remove': 'error',
73+
'unicorn/prefer-dom-node-text-content': 'error',
74+
'unicorn/prefer-event-target': 'error',
75+
'unicorn/prefer-export-from': 'error',
76+
'unicorn/prefer-includes': 'error',
77+
'unicorn/prefer-json-parse-buffer': 'off',
78+
'unicorn/prefer-keyboard-event-key': 'error',
79+
'unicorn/prefer-logical-operator-over-ternary': 'error',
80+
'unicorn/prefer-math-trunc': 'error',
81+
'unicorn/prefer-modern-dom-apis': 'error',
82+
'unicorn/prefer-modern-math-apis': 'error',
83+
'unicorn/prefer-module': 'error',
84+
'unicorn/prefer-native-coercion-functions': 'error',
85+
'unicorn/prefer-negative-index': 'error',
86+
'unicorn/prefer-node-protocol': 'error',
87+
'unicorn/prefer-number-properties': 'error',
88+
'unicorn/prefer-object-from-entries': 'error',
89+
'unicorn/prefer-optional-catch-binding': 'error',
90+
'unicorn/prefer-prototype-methods': 'error',
91+
'unicorn/prefer-query-selector': 'error',
92+
'unicorn/prefer-reflect-apply': 'error',
93+
'unicorn/prefer-regexp-test': 'error',
94+
'unicorn/prefer-set-has': 'error',
95+
'unicorn/prefer-set-size': 'error',
96+
'unicorn/prefer-spread': 'error',
97+
'unicorn/prefer-string-replace-all': 'error',
98+
'unicorn/prefer-string-slice': 'error',
99+
'unicorn/prefer-string-starts-ends-with': 'error',
100+
'unicorn/prefer-string-trim-start-end': 'error',
101+
'unicorn/prefer-switch': 'error',
102+
'unicorn/prefer-ternary': 'error',
103+
'unicorn/prefer-top-level-await': 'error',
104+
'unicorn/prefer-type-error': 'error',
105+
'unicorn/prevent-abbreviations': 'error',
106+
'unicorn/relative-url-style': 'error',
107+
'unicorn/require-array-join-separator': 'error',
108+
'unicorn/require-number-to-fixed-digits-argument': 'error',
109+
// Turned off because we can't distinguish `widow.postMessage` and `{Worker,MessagePort,Client,BroadcastChannel}#postMessage()`
110+
// See #1396
111+
'unicorn/require-post-message-target-origin': 'off',
112+
'unicorn/string-content': 'off',
113+
'unicorn/switch-case-braces': 'error',
114+
'unicorn/template-indent': 'error',
115+
'unicorn/text-encoding-identifier-case': 'error',
116+
'unicorn/throw-new-error': 'error',
129117
};

‎index.js

+20-7
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
'use strict';
22
const createDeprecatedRules = require('./rules/utils/create-deprecated-rules.js');
33
const {loadRules} = require('./rules/utils/rule.js');
4-
const recommendedConfig = require('./configs/recommended.js');
5-
const allRulesEnabledConfig = require('./configs/all.js');
4+
const legacyConfigBase = require('./configs/legacy-config-base.js');
5+
const flatConfigBase = require('./configs/flat-config-base.js');
6+
const recommendedRules = require('./configs/recommended.js');
7+
const allRules = require('./configs/all.js');
68
const {name, version} = require('./package.json');
79

810
const deprecatedRules = createDeprecatedRules({
@@ -26,7 +28,13 @@ const deprecatedRules = createDeprecatedRules({
2628
'regex-shorthand': 'unicorn/better-regex',
2729
});
2830

29-
module.exports = {
31+
const createConfig = (rules, isLegacyConfig = false) => ({
32+
...(isLegacyConfig ? legacyConfigBase : flatConfigBase),
33+
plugins: isLegacyConfig ? ['unicorn'] : {unicorn},
34+
rules,
35+
});
36+
37+
const unicorn = {
3038
meta: {
3139
name,
3240
version,
@@ -35,8 +43,13 @@ module.exports = {
3543
...loadRules(),
3644
...deprecatedRules,
3745
},
38-
configs: {
39-
recommended: recommendedConfig,
40-
all: allRulesEnabledConfig,
41-
},
4246
};
47+
48+
const configs = {
49+
recommended: createConfig(recommendedRules, /* isLegacyConfig */ true),
50+
all: createConfig(allRules, /* isLegacyConfig */ true),
51+
'flat/recommended': createConfig(recommendedRules),
52+
'flat/all': createConfig(allRules),
53+
};
54+
55+
module.exports = {...unicorn, configs};

‎package.json

+4-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"email": "sindresorhus@gmail.com",
1111
"url": "https://sindresorhus.com"
1212
},
13+
"main": "index.js",
1314
"engines": {
1415
"node": ">=16"
1516
},
@@ -18,12 +19,12 @@
1819
"fix": "run-p --continue-on-error fix:*",
1920
"fix:eslint-docs": "eslint-doc-generator",
2021
"fix:js": "npm run lint:js -- --fix",
21-
"fix:md": "npm run lint:md -- --fix",
22+
"fix:markdown": "npm run lint:markdown -- --fix",
2223
"integration": "node ./test/integration/test.mjs",
2324
"lint": "run-p --continue-on-error lint:*",
2425
"lint:eslint-docs": "npm run fix:eslint-docs -- --check",
2526
"lint:js": "xo",
26-
"lint:md": "markdownlint \"**/*.md\"",
27+
"lint:markdown": "markdownlint \"**/*.md\"",
2728
"lint:package-json": "npmPkgJsonLint .",
2829
"run-rules-on-codebase": "node ./test/run-rules-on-codebase/lint.mjs",
2930
"bundle-lodash": "echo \"export {defaultsDeep, camelCase, kebabCase, snakeCase, upperFirst, lowerFirst} from 'lodash-es';\" | npx esbuild --bundle --outfile=rules/utils/lodash.js --format=cjs",
@@ -49,6 +50,7 @@
4950
"dependencies": {
5051
"@babel/helper-validator-identifier": "^7.22.20",
5152
"@eslint-community/eslint-utils": "^4.4.0",
53+
"@eslint/eslintrc": "^2.1.4",
5254
"ci-info": "^4.0.0",
5355
"clean-regexp": "^1.0.0",
5456
"core-js-compat": "^3.34.0",

‎readme.md

+146-4
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,68 @@ You might want to check out [XO](https://github.com/xojs/xo), which includes thi
1515
npm install --save-dev eslint eslint-plugin-unicorn
1616
```
1717

18-
## Usage
18+
## Usage (`eslint.config.js`)
1919

20-
Use a [preset config](#preset-configs) or configure each rule in `package.json`.
20+
**Requires ESLint `>=8.23.0`.**
21+
22+
Use a [preset config](#preset-configs-eslintconfigjs) or configure each rule in `eslint.config.js`.
23+
24+
If you don't use the preset, ensure you use the same `languageOptions` config as below.
25+
26+
### ES Module (Recommended)
27+
28+
```js
29+
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
30+
import * as eslintrc from '@eslint/eslintrc';
31+
32+
export default [
33+
{
34+
languageOptions: {
35+
ecmaVersion: 'latest',
36+
sourceType: 'module',
37+
globals: eslintrc.Legacy.environments.get('es2024'),
38+
},
39+
plugins: {
40+
unicorn: eslintPluginUnicorn,
41+
},
42+
rules: {
43+
'unicorn/better-regex': 'error',
44+
'unicorn/…': 'error',
45+
},
46+
},
47+
//
48+
];
49+
```
50+
51+
### CommonJS
52+
53+
```js
54+
'use strict';
55+
const eslintPluginUnicorn = require('eslint-plugin-unicorn');
56+
const eslintrc = require('@eslint/eslintrc');
57+
58+
module.exports = [
59+
{
60+
languageOptions: {
61+
ecmaVersion: 'latest',
62+
sourceType: 'module',
63+
globals: eslintrc.Legacy.environments.get('es2024'),
64+
},
65+
plugins: {
66+
unicorn: eslintPluginUnicorn,
67+
},
68+
rules: {
69+
'unicorn/better-regex': 'error',
70+
'unicorn/…': 'error',
71+
},
72+
},
73+
//
74+
];
75+
```
76+
77+
## Usage (legacy: `.eslintrc.*` or `package.json`)
78+
79+
Use a [preset config](#preset-configs-eslintrc-or-packagejson) or configure each rule in `package.json`.
2180

2281
If you don't use the preset, ensure you use the same `env` and `parserOptions` config as below.
2382

@@ -172,7 +231,87 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
172231

173232
See [docs/deprecated-rules.md](docs/deprecated-rules.md)
174233

175-
## Preset configs
234+
## Preset configs (`eslint.config.js`)
235+
236+
See the [ESLint docs](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new) for more information about extending config files.
237+
238+
**Note**: Preset configs will also enable the correct [language options](https://eslint.org/docs/latest/use/configure/configuration-files-new#configuring-language-options).
239+
240+
### Recommended config
241+
242+
This plugin exports a [`recommended` config](configs/recommended.js) that enforces good practices.
243+
244+
#### ES Module (Recommended)
245+
246+
```js
247+
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
248+
249+
export default [
250+
//
251+
eslintPluginUnicorn.config['flat/recommended'],
252+
{
253+
rules: {
254+
'unicorn/better-regex': 'warn',
255+
},
256+
},
257+
];
258+
```
259+
260+
#### CommonJS
261+
262+
```js
263+
'use strict';
264+
const eslintPluginUnicorn = require('eslint-plugin-unicorn');
265+
266+
module.exports = [
267+
//
268+
eslintPluginUnicorn.config['flat/recommended'],
269+
{
270+
rules: {
271+
'unicorn/better-regex': 'warn',
272+
},
273+
},
274+
];
275+
```
276+
277+
### All config
278+
279+
This plugin exports an [`all` config](configs/all.js) that makes use of all rules (except for deprecated ones).
280+
281+
#### ES Module (Recommended)
282+
283+
```js
284+
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
285+
286+
export default [
287+
//
288+
eslintPluginUnicorn.config['flat/all'],
289+
{
290+
rules: {
291+
'unicorn/better-regex': 'warn',
292+
},
293+
},
294+
];
295+
```
296+
297+
#### CommonJS
298+
299+
```js
300+
'use strict';
301+
const eslintPluginUnicorn = require('eslint-plugin-unicorn');
302+
303+
module.exports = [
304+
//
305+
eslintPluginUnicorn.config['flat/all'],
306+
{
307+
rules: {
308+
'unicorn/better-regex': 'warn',
309+
},
310+
},
311+
];
312+
```
313+
314+
## Preset configs (`.eslintrc.*` or `package.json`)
176315

177316
See the [ESLint docs](https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files) for more information about extending config files.
178317

@@ -186,7 +325,10 @@ This plugin exports a [`recommended` config](configs/recommended.js) that enforc
186325
{
187326
"name": "my-awesome-project",
188327
"eslintConfig": {
189-
"extends": "plugin:unicorn/recommended"
328+
"extends": "plugin:unicorn/recommended",
329+
"rules": {
330+
"unicorn/better-regex": "warn"
331+
}
190332
}
191333
}
192334
```

‎scripts/create-rule.mjs

+9-8
Original file line numberDiff line numberDiff line change
@@ -36,21 +36,22 @@ function renderTemplate({source, target, data}) {
3636
}
3737

3838
function updateRecommended(id) {
39-
const RULE_START = '\n\trules: {\n';
40-
const RULE_END = '\n\t}';
41-
const RULE_INDENT = '\t'.repeat(2);
42-
let ruleContent = `${RULE_INDENT}'unicorn/${id}': 'error',`;
43-
39+
const RULE_INDENT = '\t';
4440
const file = path.join(ROOT, 'configs/recommended.js');
4541
const content = fs.readFileSync(file, 'utf8');
46-
const [before, rest] = content.split(RULE_START);
47-
const [rules, after] = rest.split(RULE_END);
42+
const {before, rules, after} = content.match(/(?<before>.*?{\n)(?<rules>.*?)(?<after>\n};\s*)/s)?.groups ?? {};
43+
if (!rules) {
44+
throw new Error('Unexpected content in “configs/recommended.js”.');
45+
}
4846

4947
const lines = rules.split('\n');
48+
5049
if (!lines.every(line => line.startsWith(RULE_INDENT))) {
5150
throw new Error('Unexpected content in “configs/recommended.js”.');
5251
}
5352

53+
let ruleContent = `${RULE_INDENT}'unicorn/${id}': 'error',`;
54+
5455
const unicornRuleLines = lines.filter(line => line.startsWith(`${RULE_INDENT}'unicorn/`));
5556
let insertIndex;
5657
if (ruleContent.localeCompare(unicornRuleLines[0]) === -1) {
@@ -68,7 +69,7 @@ function updateRecommended(id) {
6869

6970
lines.splice(insertIndex, 0, ruleContent);
7071

71-
const updated = `${before}${RULE_START}${lines.join('\n')}${RULE_END}${after}`;
72+
const updated = `${before}${lines.join('\n')}${after}`;
7273
fs.writeFileSync(file, updated);
7374
}
7475

‎test/package.mjs

+36-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import fs, {promises as fsAsync} from 'node:fs';
22
import path from 'node:path';
3+
import process from 'node:process';
34
import test from 'ava';
45
import {ESLint} from 'eslint';
56
import * as eslintrc from '@eslint/eslintrc';
@@ -75,7 +76,7 @@ test('Every rule is defined in index file in alphabetical order', t => {
7576

7677
test('validate configuration', async t => {
7778
const results = await Promise.all(
78-
Object.entries(eslintPluginUnicorn.configs).map(async ([name, config]) => {
79+
Object.entries(eslintPluginUnicorn.configs).filter(([name]) => !name.startsWith('flat/')).map(async ([name, config]) => {
7980
const eslint = new ESLint({
8081
baseConfig: config,
8182
useEslintrc: false,
@@ -215,3 +216,37 @@ test('Plugin should have metadata', t => {
215216
t.is(typeof eslintPluginUnicorn.meta.name, 'string');
216217
t.is(typeof eslintPluginUnicorn.meta.version, 'string');
217218
});
219+
220+
function getCompactConfig(config) {
221+
const compat = new eslintrc.FlatCompat({
222+
baseDirectory: process.cwd(),
223+
resolvePluginsRelativeTo: process.cwd(),
224+
});
225+
226+
const result = {plugins: undefined};
227+
228+
for (const part of compat.config(config)) {
229+
for (const [key, value] of Object.entries(part)) {
230+
if (key === 'languageOptions') {
231+
result[key] = {...result[key], ...value};
232+
} else if (key === 'plugins') {
233+
result[key] = undefined;
234+
} else {
235+
result[key] = value;
236+
}
237+
}
238+
}
239+
240+
return result;
241+
}
242+
243+
test('flat configs', t => {
244+
t.deepEqual(
245+
getCompactConfig(eslintPluginUnicorn.configs.recommended),
246+
{...eslintPluginUnicorn.configs['flat/recommended'], plugins: undefined},
247+
);
248+
t.deepEqual(
249+
getCompactConfig(eslintPluginUnicorn.configs.all),
250+
{...eslintPluginUnicorn.configs['flat/all'], plugins: undefined},
251+
);
252+
});

0 commit comments

Comments
 (0)
Please sign in to comment.