eslint-plugin-unicorn 49.0.0 → 50.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/configs/all.js CHANGED
@@ -1,9 +1,6 @@
1
1
  'use strict';
2
- const {rules, ...baseConfigs} = require('./recommended.js');
2
+ const recommended = require('./recommended.js');
3
3
 
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
+ ));
@@ -0,0 +1,10 @@
1
+ 'use strict';
2
+ const eslintrc = require('@eslint/eslintrc');
3
+
4
+ const {globals} = eslintrc.Legacy.environments.get('es2024');
5
+
6
+ module.exports = {
7
+ languageOptions: {
8
+ globals,
9
+ },
10
+ };
@@ -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
+ };
@@ -1,128 +1,117 @@
1
1
  'use strict';
2
2
  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-unreadable-array-destructuring': 'error',
58
- 'unicorn/no-unreadable-iife': 'error',
59
- 'unicorn/no-unused-properties': 'off',
60
- 'unicorn/no-useless-fallback-in-spread': 'error',
61
- 'unicorn/no-useless-length-check': 'error',
62
- 'unicorn/no-useless-promise-resolve-reject': 'error',
63
- 'unicorn/no-useless-spread': 'error',
64
- 'unicorn/no-useless-switch-case': 'error',
65
- 'unicorn/no-useless-undefined': 'error',
66
- 'unicorn/no-zero-fractions': 'error',
67
- 'unicorn/number-literal-case': 'error',
68
- 'unicorn/numeric-separators-style': 'error',
69
- 'unicorn/prefer-add-event-listener': 'error',
70
- 'unicorn/prefer-array-find': 'error',
71
- 'unicorn/prefer-array-flat': 'error',
72
- 'unicorn/prefer-array-flat-map': 'error',
73
- 'unicorn/prefer-array-index-of': 'error',
74
- 'unicorn/prefer-array-some': 'error',
75
- 'unicorn/prefer-at': 'error',
76
- 'unicorn/prefer-blob-reading-methods': 'error',
77
- 'unicorn/prefer-code-point': 'error',
78
- 'unicorn/prefer-date-now': 'error',
79
- 'unicorn/prefer-default-parameters': 'error',
80
- 'unicorn/prefer-dom-node-append': 'error',
81
- 'unicorn/prefer-dom-node-dataset': 'error',
82
- 'unicorn/prefer-dom-node-remove': 'error',
83
- 'unicorn/prefer-dom-node-text-content': 'error',
84
- 'unicorn/prefer-event-target': 'error',
85
- 'unicorn/prefer-export-from': 'error',
86
- 'unicorn/prefer-includes': 'error',
87
- 'unicorn/prefer-json-parse-buffer': 'off',
88
- 'unicorn/prefer-keyboard-event-key': 'error',
89
- 'unicorn/prefer-logical-operator-over-ternary': 'error',
90
- 'unicorn/prefer-math-trunc': 'error',
91
- 'unicorn/prefer-modern-dom-apis': 'error',
92
- 'unicorn/prefer-modern-math-apis': 'error',
93
- 'unicorn/prefer-module': 'error',
94
- 'unicorn/prefer-native-coercion-functions': 'error',
95
- 'unicorn/prefer-negative-index': 'error',
96
- 'unicorn/prefer-node-protocol': 'error',
97
- 'unicorn/prefer-number-properties': 'error',
98
- 'unicorn/prefer-object-from-entries': 'error',
99
- 'unicorn/prefer-optional-catch-binding': 'error',
100
- 'unicorn/prefer-prototype-methods': 'error',
101
- 'unicorn/prefer-query-selector': 'error',
102
- 'unicorn/prefer-reflect-apply': 'error',
103
- 'unicorn/prefer-regexp-test': 'error',
104
- 'unicorn/prefer-set-has': 'error',
105
- 'unicorn/prefer-set-size': 'error',
106
- 'unicorn/prefer-spread': 'error',
107
- 'unicorn/prefer-string-replace-all': 'error',
108
- 'unicorn/prefer-string-slice': 'error',
109
- 'unicorn/prefer-string-starts-ends-with': 'error',
110
- 'unicorn/prefer-string-trim-start-end': 'error',
111
- 'unicorn/prefer-switch': 'error',
112
- 'unicorn/prefer-ternary': 'error',
113
- 'unicorn/prefer-top-level-await': 'error',
114
- 'unicorn/prefer-type-error': 'error',
115
- 'unicorn/prevent-abbreviations': 'error',
116
- 'unicorn/relative-url-style': 'error',
117
- 'unicorn/require-array-join-separator': 'error',
118
- 'unicorn/require-number-to-fixed-digits-argument': 'error',
119
- // Turned off because we can't distinguish `widow.postMessage` and `{Worker,MessagePort,Client,BroadcastChannel}#postMessage()`
120
- // See #1396
121
- 'unicorn/require-post-message-target-origin': 'off',
122
- 'unicorn/string-content': 'off',
123
- 'unicorn/switch-case-braces': 'error',
124
- 'unicorn/template-indent': 'error',
125
- 'unicorn/text-encoding-identifier-case': 'error',
126
- 'unicorn/throw-new-error': 'error',
127
- },
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',
128
117
  };
package/index.js CHANGED
@@ -1,8 +1,10 @@
1
1
  'use strict';
2
2
  const createDeprecatedRules = require('./rules/utils/create-deprecated-rules.js');
3
3
  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');
6
8
  const {name, version} = require('./package.json');
7
9
 
8
10
  const deprecatedRules = createDeprecatedRules({
@@ -26,7 +28,13 @@ const deprecatedRules = createDeprecatedRules({
26
28
  'regex-shorthand': 'unicorn/better-regex',
27
29
  });
28
30
 
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 = {
30
38
  meta: {
31
39
  name,
32
40
  version,
@@ -35,8 +43,13 @@ module.exports = {
35
43
  ...loadRules(),
36
44
  ...deprecatedRules,
37
45
  },
38
- configs: {
39
- recommended: recommendedConfig,
40
- all: allRulesEnabledConfig,
41
- },
42
46
  };
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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-unicorn",
3
- "version": "49.0.0",
3
+ "version": "50.0.0",
4
4
  "description": "More than 100 powerful ESLint rules",
5
5
  "license": "MIT",
6
6
  "repository": "sindresorhus/eslint-plugin-unicorn",
@@ -10,6 +10,8 @@
10
10
  "email": "sindresorhus@gmail.com",
11
11
  "url": "https://sindresorhus.com"
12
12
  },
13
+ "main": "index.js",
14
+ "sideEffects": false,
13
15
  "engines": {
14
16
  "node": ">=16"
15
17
  },
@@ -18,12 +20,12 @@
18
20
  "fix": "run-p --continue-on-error fix:*",
19
21
  "fix:eslint-docs": "eslint-doc-generator",
20
22
  "fix:js": "npm run lint:js -- --fix",
21
- "fix:md": "npm run lint:md -- --fix",
23
+ "fix:markdown": "npm run lint:markdown -- --fix",
22
24
  "integration": "node ./test/integration/test.mjs",
23
25
  "lint": "run-p --continue-on-error lint:*",
24
26
  "lint:eslint-docs": "npm run fix:eslint-docs -- --check",
25
27
  "lint:js": "xo",
26
- "lint:md": "markdownlint \"**/*.md\"",
28
+ "lint:markdown": "markdownlint \"**/*.md\"",
27
29
  "lint:package-json": "npmPkgJsonLint .",
28
30
  "run-rules-on-codebase": "node ./test/run-rules-on-codebase/lint.mjs",
29
31
  "bundle-lodash": "echo \"export {defaultsDeep, camelCase, kebabCase, snakeCase, upperFirst, lowerFirst} from 'lodash-es';\" | npx esbuild --bundle --outfile=rules/utils/lodash.js --format=cjs",
@@ -49,8 +51,10 @@
49
51
  "dependencies": {
50
52
  "@babel/helper-validator-identifier": "^7.22.20",
51
53
  "@eslint-community/eslint-utils": "^4.4.0",
52
- "ci-info": "^3.8.0",
54
+ "@eslint/eslintrc": "^2.1.4",
55
+ "ci-info": "^4.0.0",
53
56
  "clean-regexp": "^1.0.0",
57
+ "core-js-compat": "^3.34.0",
54
58
  "esquery": "^1.5.0",
55
59
  "indent-string": "^4.0.0",
56
60
  "is-builtin-module": "^3.2.1",
@@ -63,37 +67,37 @@
63
67
  "strip-indent": "^3.0.0"
64
68
  },
65
69
  "devDependencies": {
66
- "@babel/code-frame": "^7.22.13",
67
- "@babel/core": "^7.23.2",
68
- "@babel/eslint-parser": "^7.22.15",
70
+ "@babel/code-frame": "^7.23.5",
71
+ "@babel/core": "^7.23.6",
72
+ "@babel/eslint-parser": "^7.23.3",
69
73
  "@lubien/fixture-beta-package": "^1.0.0-beta.1",
70
- "@typescript-eslint/parser": "^6.9.0",
71
- "ava": "^3.15.0",
74
+ "@typescript-eslint/parser": "^6.15.0",
75
+ "ava": "^6.0.1",
72
76
  "c8": "^8.0.1",
73
77
  "chalk": "^5.3.0",
74
78
  "enquirer": "^2.4.1",
75
- "eslint": "^8.52.0",
76
- "eslint-ava-rule-tester": "^4.0.0",
77
- "eslint-doc-generator": "^1.5.2",
78
- "eslint-plugin-eslint-plugin": "^5.1.1",
79
+ "eslint": "^8.56.0",
80
+ "eslint-ava-rule-tester": "^4.2.0",
81
+ "eslint-doc-generator": "^1.6.1",
82
+ "eslint-plugin-eslint-plugin": "^5.2.1",
79
83
  "eslint-plugin-internal-rules": "file:./scripts/internal-rules/",
80
84
  "eslint-remote-tester": "^3.0.1",
81
85
  "eslint-remote-tester-repositories": "^1.0.1",
82
86
  "execa": "^8.0.1",
83
87
  "listr": "^0.14.3",
84
88
  "lodash-es": "^4.17.21",
85
- "markdownlint-cli": "^0.37.0",
86
- "mem": "^9.0.2",
87
- "npm-package-json-lint": "^7.0.0",
89
+ "markdownlint-cli": "^0.38.0",
90
+ "memoize": "^10.0.0",
91
+ "npm-package-json-lint": "^7.1.0",
88
92
  "npm-run-all2": "^6.1.1",
89
93
  "outdent": "^0.8.0",
90
- "typescript": "^5.2.2",
94
+ "typescript": "^5.3.3",
91
95
  "vue-eslint-parser": "^9.3.2",
92
96
  "xo": "^0.56.0",
93
- "yaml": "^2.3.3"
97
+ "yaml": "^2.3.4"
94
98
  },
95
99
  "peerDependencies": {
96
- "eslint": ">=8.52.0"
100
+ "eslint": ">=8.56.0"
97
101
  },
98
102
  "ava": {
99
103
  "files": [
package/readme.md CHANGED
@@ -1,7 +1,7 @@
1
1
  # eslint-plugin-unicorn [![Coverage Status](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/main/graph/badge.svg)](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/main) [![npm version](https://img.shields.io/npm/v/eslint-plugin-unicorn.svg?style=flat)](https://npmjs.com/package/eslint-plugin-unicorn)
2
2
 
3
3
  <!-- markdownlint-disable-next-line no-inline-html -->
4
- <img src="https://cloud.githubusercontent.com/assets/170270/18659176/1cc373d0-7f33-11e6-890f-0ba35362ee7e.jpg" width="180" align="right">
4
+ <img src="https://cloud.githubusercontent.com/assets/170270/18659176/1cc373d0-7f33-11e6-890f-0ba35362ee7e.jpg" width="180" align="right" alt="Unicorn">
5
5
 
6
6
  > More than 100 powerful ESLint rules
7
7
 
@@ -15,9 +15,64 @@ You might want to check out [XO](https://github.com/xojs/xo), which includes thi
15
15
  npm install --save-dev eslint eslint-plugin-unicorn
16
16
  ```
17
17
 
18
- ## Usage
18
+ ## Usage (`eslint.config.js`)
19
19
 
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
+ globals: eslintrc.Legacy.environments.get('es2024'),
36
+ },
37
+ plugins: {
38
+ unicorn: eslintPluginUnicorn,
39
+ },
40
+ rules: {
41
+ 'unicorn/better-regex': 'error',
42
+ 'unicorn/…': 'error',
43
+ },
44
+ },
45
+ // …
46
+ ];
47
+ ```
48
+
49
+ ### CommonJS
50
+
51
+ ```js
52
+ 'use strict';
53
+ const eslintPluginUnicorn = require('eslint-plugin-unicorn');
54
+ const eslintrc = require('@eslint/eslintrc');
55
+
56
+ module.exports = [
57
+ {
58
+ languageOptions: {
59
+ globals: eslintrc.Legacy.environments.get('es2024'),
60
+ },
61
+ plugins: {
62
+ unicorn: eslintPluginUnicorn,
63
+ },
64
+ rules: {
65
+ 'unicorn/better-regex': 'error',
66
+ 'unicorn/…': 'error',
67
+ },
68
+ },
69
+ // …
70
+ ];
71
+ ```
72
+
73
+ ## Usage (legacy: `.eslintrc.*` or `package.json`)
74
+
75
+ Use a [preset config](#preset-configs-eslintrc-or-packagejson) or configure each rule in `package.json`.
21
76
 
22
77
  If you don't use the preset, ensure you use the same `env` and `parserOptions` config as below.
23
78
 
@@ -78,7 +133,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
78
133
  | [no-console-spaces](docs/rules/no-console-spaces.md) | Do not use leading/trailing space between `console.log` parameters. | ✅ | 🔧 | |
79
134
  | [no-document-cookie](docs/rules/no-document-cookie.md) | Do not use `document.cookie` directly. | ✅ | | |
80
135
  | [no-empty-file](docs/rules/no-empty-file.md) | Disallow empty files. | ✅ | | |
81
- | [no-for-loop](docs/rules/no-for-loop.md) | Do not use a `for` loop that can be replaced with a `for-of` loop. | ✅ | 🔧 | |
136
+ | [no-for-loop](docs/rules/no-for-loop.md) | Do not use a `for` loop that can be replaced with a `for-of` loop. | ✅ | 🔧 | 💡 |
82
137
  | [no-hex-escape](docs/rules/no-hex-escape.md) | Enforce the use of Unicode escapes instead of hexadecimal escapes. | ✅ | 🔧 | |
83
138
  | [no-instanceof-array](docs/rules/no-instanceof-array.md) | Require `Array.isArray()` instead of `instanceof Array`. | ✅ | 🔧 | |
84
139
  | [no-invalid-remove-event-listener](docs/rules/no-invalid-remove-event-listener.md) | Prevent calling `EventTarget#removeEventListener()` with the result of an expression. | ✅ | | |
@@ -96,6 +151,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
96
151
  | [no-this-assignment](docs/rules/no-this-assignment.md) | Disallow assigning `this` to a variable. | ✅ | | |
97
152
  | [no-typeof-undefined](docs/rules/no-typeof-undefined.md) | Disallow comparing `undefined` using `typeof`. | ✅ | 🔧 | 💡 |
98
153
  | [no-unnecessary-await](docs/rules/no-unnecessary-await.md) | Disallow awaiting non-promise values. | ✅ | 🔧 | |
154
+ | [no-unnecessary-polyfills](docs/rules/no-unnecessary-polyfills.md) | Enforce the use of built-in methods instead of unnecessary polyfills. | ✅ | | |
99
155
  | [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. | ✅ | 🔧 | |
100
156
  | [no-unreadable-iife](docs/rules/no-unreadable-iife.md) | Disallow unreadable IIFEs. | ✅ | | |
101
157
  | [no-unused-properties](docs/rules/no-unused-properties.md) | Disallow unused object properties. | | | |
@@ -171,7 +227,87 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
171
227
 
172
228
  See [docs/deprecated-rules.md](docs/deprecated-rules.md)
173
229
 
174
- ## Preset configs
230
+ ## Preset configs (`eslint.config.js`)
231
+
232
+ See the [ESLint docs](https://eslint.org/docs/latest/user-guide/configuring/configuration-files-new) for more information about extending config files.
233
+
234
+ **Note**: Preset configs will also enable the correct [language options](https://eslint.org/docs/latest/use/configure/configuration-files-new#configuring-language-options).
235
+
236
+ ### Recommended config
237
+
238
+ This plugin exports a [`recommended` config](configs/recommended.js) that enforces good practices.
239
+
240
+ #### ES Module (Recommended)
241
+
242
+ ```js
243
+ import eslintPluginUnicorn from 'eslint-plugin-unicorn';
244
+
245
+ export default [
246
+ // …
247
+ eslintPluginUnicorn.config['flat/recommended'],
248
+ {
249
+ rules: {
250
+ 'unicorn/better-regex': 'warn',
251
+ },
252
+ },
253
+ ];
254
+ ```
255
+
256
+ #### CommonJS
257
+
258
+ ```js
259
+ 'use strict';
260
+ const eslintPluginUnicorn = require('eslint-plugin-unicorn');
261
+
262
+ module.exports = [
263
+ // …
264
+ eslintPluginUnicorn.config['flat/recommended'],
265
+ {
266
+ rules: {
267
+ 'unicorn/better-regex': 'warn',
268
+ },
269
+ },
270
+ ];
271
+ ```
272
+
273
+ ### All config
274
+
275
+ This plugin exports an [`all` config](configs/all.js) that makes use of all rules (except for deprecated ones).
276
+
277
+ #### ES Module (Recommended)
278
+
279
+ ```js
280
+ import eslintPluginUnicorn from 'eslint-plugin-unicorn';
281
+
282
+ export default [
283
+ // …
284
+ eslintPluginUnicorn.config['flat/all'],
285
+ {
286
+ rules: {
287
+ 'unicorn/better-regex': 'warn',
288
+ },
289
+ },
290
+ ];
291
+ ```
292
+
293
+ #### CommonJS
294
+
295
+ ```js
296
+ 'use strict';
297
+ const eslintPluginUnicorn = require('eslint-plugin-unicorn');
298
+
299
+ module.exports = [
300
+ // …
301
+ eslintPluginUnicorn.config['flat/all'],
302
+ {
303
+ rules: {
304
+ 'unicorn/better-regex': 'warn',
305
+ },
306
+ },
307
+ ];
308
+ ```
309
+
310
+ ## Preset configs (`.eslintrc.*` or `package.json`)
175
311
 
176
312
  See the [ESLint docs](https://eslint.org/docs/user-guide/configuring/configuration-files#extending-configuration-files) for more information about extending config files.
177
313
 
@@ -185,7 +321,10 @@ This plugin exports a [`recommended` config](configs/recommended.js) that enforc
185
321
  {
186
322
  "name": "my-awesome-project",
187
323
  "eslintConfig": {
188
- "extends": "plugin:unicorn/recommended"
324
+ "extends": "plugin:unicorn/recommended",
325
+ "rules": {
326
+ "unicorn/better-regex": "warn"
327
+ }
189
328
  }
190
329
  }
191
330
  ```
@@ -120,6 +120,11 @@ function isNotReference(node) {
120
120
  return parent.parameters.includes(node);
121
121
  }
122
122
 
123
+ // `type Foo = { [Identifier in keyof string]: number; };`
124
+ case 'TSTypeParameter': {
125
+ return parent.name === node;
126
+ }
127
+
123
128
  // `type Identifier = Foo`
124
129
  case 'TSTypeAliasDeclaration': {
125
130
  return parent.id === node;
@@ -421,7 +421,7 @@ module.exports = {
421
421
  description: 'Do not use a `for` loop that can be replaced with a `for-of` loop.',
422
422
  },
423
423
  fixable: 'code',
424
+ hasSuggestions: true,
424
425
  messages,
425
- hasSuggestion: true,
426
426
  },
427
427
  };
@@ -0,0 +1,176 @@
1
+ 'use strict';
2
+ const path = require('node:path');
3
+ const readPkgUp = require('read-pkg-up');
4
+ const coreJsCompat = require('core-js-compat');
5
+ const {camelCase} = require('lodash');
6
+ const isStaticRequire = require('./ast/is-static-require.js');
7
+
8
+ const {data: compatData, entries: coreJsEntries} = coreJsCompat;
9
+
10
+ const MESSAGE_ID_POLYFILL = 'unnecessaryPolyfill';
11
+ const MESSAGE_ID_CORE_JS = 'unnecessaryCoreJsModule';
12
+ const messages = {
13
+ [MESSAGE_ID_POLYFILL]: 'Use built-in instead.',
14
+ [MESSAGE_ID_CORE_JS]:
15
+ 'All polyfilled features imported from `{{coreJsModule}}` are available as built-ins. Use the built-ins instead.',
16
+ };
17
+
18
+ const additionalPolyfillPatterns = {
19
+ 'es.promise.finally': '|(p-finally)',
20
+ 'es.object.set-prototype-of': '|(setprototypeof)',
21
+ 'es.string.code-point-at': '|(code-point-at)',
22
+ };
23
+
24
+ const prefixes = '(mdn-polyfills/|polyfill-)';
25
+ const suffixes = '(-polyfill)';
26
+ const delimiter = '(\\.|-|\\.prototype\\.|/)?';
27
+
28
+ const polyfills = Object.keys(compatData).map(feature => {
29
+ let [ecmaVersion, constructorName, methodName = ''] = feature.split('.');
30
+
31
+ if (ecmaVersion === 'es') {
32
+ ecmaVersion = '(es\\d*)';
33
+ }
34
+
35
+ constructorName = `(${constructorName}|${camelCase(constructorName)})`;
36
+ methodName &&= `(${methodName}|${camelCase(methodName)})`;
37
+
38
+ const methodOrConstructor = methodName || constructorName;
39
+
40
+ const patterns = [
41
+ `^((${prefixes}?(`,
42
+ methodName && `(${ecmaVersion}${delimiter}${constructorName}${delimiter}${methodName})|`, // Ex: es6-array-copy-within
43
+ methodName && `(${constructorName}${delimiter}${methodName})|`, // Ex: array-copy-within
44
+ `(${ecmaVersion}${delimiter}${constructorName}))`, // Ex: es6-array
45
+ `${suffixes}?)|`,
46
+ `(${prefixes}${methodOrConstructor}|${methodOrConstructor}${suffixes})`, // Ex: polyfill-copy-within / polyfill-promise
47
+ `${additionalPolyfillPatterns[feature] || ''})$`,
48
+ ];
49
+
50
+ return {
51
+ feature,
52
+ pattern: new RegExp(patterns.join(''), 'i'),
53
+ };
54
+ });
55
+
56
+ function getTargets(options, dirname) {
57
+ if (options?.targets) {
58
+ return options.targets;
59
+ }
60
+
61
+ /** @type {readPkgUp.ReadResult | undefined} */
62
+ let packageResult;
63
+ try {
64
+ // It can fail if, for example, the package.json file has comments.
65
+ packageResult = readPkgUp.sync({normalize: false, cwd: dirname});
66
+ } catch {}
67
+
68
+ if (!packageResult) {
69
+ return;
70
+ }
71
+
72
+ const {browserlist, engines} = packageResult.packageJson;
73
+ return browserlist ?? engines;
74
+ }
75
+
76
+ function create(context) {
77
+ const targets = getTargets(context.options[0], path.dirname(context.filename));
78
+ if (!targets) {
79
+ return {};
80
+ }
81
+
82
+ let unavailableFeatures;
83
+ try {
84
+ unavailableFeatures = coreJsCompat({targets}).list;
85
+ } catch {
86
+ // This can happen if the targets are invalid or use unsupported syntax like `{node:'*'}`.
87
+ return {};
88
+ }
89
+
90
+ const checkFeatures = features => !features.every(feature => unavailableFeatures.includes(feature));
91
+
92
+ return {
93
+ Literal(node) {
94
+ if (
95
+ !(
96
+ (['ImportDeclaration', 'ImportExpression'].includes(node.parent.type) && node.parent.source === node)
97
+ || (isStaticRequire(node.parent) && node.parent.arguments[0] === node)
98
+ )
99
+ ) {
100
+ return;
101
+ }
102
+
103
+ const importedModule = node.value;
104
+ if (typeof importedModule !== 'string' || ['.', '/'].includes(importedModule[0])) {
105
+ return;
106
+ }
107
+
108
+ const coreJsModuleFeatures = coreJsEntries[importedModule.replace('core-js-pure', 'core-js')];
109
+
110
+ if (coreJsModuleFeatures) {
111
+ if (coreJsModuleFeatures.length > 1) {
112
+ if (checkFeatures(coreJsModuleFeatures)) {
113
+ return {
114
+ node,
115
+ messageId: MESSAGE_ID_CORE_JS,
116
+ data: {
117
+ coreJsModule: importedModule,
118
+ },
119
+ };
120
+ }
121
+ } else if (!unavailableFeatures.includes(coreJsModuleFeatures[0])) {
122
+ return {node, messageId: MESSAGE_ID_POLYFILL};
123
+ }
124
+
125
+ return;
126
+ }
127
+
128
+ const polyfill = polyfills.find(({pattern}) => pattern.test(importedModule));
129
+ if (polyfill) {
130
+ const [, namespace, method = ''] = polyfill.feature.split('.');
131
+ const [, features] = Object.entries(coreJsEntries).find(
132
+ entry => entry[0] === `core-js/full/${namespace}${method && '/'}${method}`,
133
+ );
134
+ if (checkFeatures(features)) {
135
+ return {node, messageId: MESSAGE_ID_POLYFILL};
136
+ }
137
+ }
138
+ },
139
+ };
140
+ }
141
+
142
+ const schema = [
143
+ {
144
+ type: 'object',
145
+ additionalProperties: false,
146
+ required: ['targets'],
147
+ properties: {
148
+ targets: {
149
+ oneOf: [
150
+ {
151
+ type: 'string',
152
+ },
153
+ {
154
+ type: 'array',
155
+ },
156
+ {
157
+ type: 'object',
158
+ },
159
+ ],
160
+ },
161
+ },
162
+ },
163
+ ];
164
+
165
+ /** @type {import('eslint').Rule.RuleModule} */
166
+ module.exports = {
167
+ create,
168
+ meta: {
169
+ type: 'suggestion',
170
+ docs: {
171
+ description: 'Enforce the use of built-in methods instead of unnecessary polyfills.',
172
+ },
173
+ schema,
174
+ messages,
175
+ },
176
+ };
@@ -60,6 +60,8 @@ const shouldIgnore = node => {
60
60
 
61
61
  // `React.createContext(undefined)`
62
62
  || name === 'createContext'
63
+ // `setState(undefined)`
64
+ || /^set[A-Z]/.test(name)
63
65
 
64
66
  // https://vuejs.org/api/reactivity-core.html#ref
65
67
  || name === 'ref';
@@ -104,6 +106,7 @@ const create = context => {
104
106
 
105
107
  const options = {
106
108
  checkArguments: true,
109
+ checkArrowFunctionBody: true,
107
110
  ...context.options[0],
108
111
  };
109
112
 
@@ -141,19 +144,21 @@ const create = context => {
141
144
  });
142
145
 
143
146
  // `() => undefined`
144
- context.on('Identifier', node => {
145
- if (
146
- isUndefined(node)
147
- && node.parent.type === 'ArrowFunctionExpression'
148
- && node.parent.body === node
149
- ) {
150
- return getProblem(
151
- node,
152
- fixer => replaceNodeOrTokenAndSpacesBefore(node, ' {}', fixer, sourceCode),
153
- /* CheckFunctionReturnType */ true,
154
- );
155
- }
156
- });
147
+ if (options.checkArrowFunctionBody) {
148
+ context.on('Identifier', node => {
149
+ if (
150
+ isUndefined(node)
151
+ && node.parent.type === 'ArrowFunctionExpression'
152
+ && node.parent.body === node
153
+ ) {
154
+ return getProblem(
155
+ node,
156
+ fixer => replaceNodeOrTokenAndSpacesBefore(node, ' {}', fixer, sourceCode),
157
+ /* CheckFunctionReturnType */ true,
158
+ );
159
+ }
160
+ });
161
+ }
157
162
 
158
163
  // `let foo = undefined` / `var foo = undefined`
159
164
  context.on('Identifier', node => {
@@ -274,6 +279,9 @@ const schema = [
274
279
  checkArguments: {
275
280
  type: 'boolean',
276
281
  },
282
+ checkArrowFunctionBody: {
283
+ type: 'boolean',
284
+ },
277
285
  },
278
286
  },
279
287
  ];
@@ -27,6 +27,13 @@ const methods = new Map([
27
27
  ]),
28
28
  },
29
29
  ],
30
+ [
31
+ 'subarray',
32
+ {
33
+ argumentsIndexes: [0, 1],
34
+ supportObjects: new Set(typedArray),
35
+ },
36
+ ],
30
37
  [
31
38
  'splice',
32
39
  {
@@ -182,6 +182,7 @@ module.exports.defaultReplacements = {
182
182
  res: {
183
183
  response: true,
184
184
  result: true,
185
+ resource: true,
185
186
  },
186
187
  ret: {
187
188
  returnValue: true,
@@ -105,10 +105,13 @@ const create = context => {
105
105
 
106
106
  const fixed = string.replace(regex, suggest);
107
107
  const fix = type === 'Literal'
108
- ? fixer => fixer.replaceText(
109
- node,
110
- escapeString(fixed, raw[0]),
111
- )
108
+ ? fixer => {
109
+ const [quote] = raw;
110
+ return fixer.replaceText(
111
+ node,
112
+ node.parent.type === 'JSXAttribute' ? quote + fixed + quote : escapeString(fixed, quote),
113
+ );
114
+ }
112
115
  : fixer => replaceTemplateElement(
113
116
  fixer,
114
117
  node,