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 +4 -7
- package/configs/flat-config-base.js +10 -0
- package/configs/legacy-config-base.js +10 -0
- package/configs/recommended.js +114 -125
- package/index.js +20 -7
- package/package.json +23 -19
- package/readme.md +145 -6
- package/rules/ast/is-reference-identifier.js +5 -0
- package/rules/no-for-loop.js +1 -1
- package/rules/no-unnecessary-polyfills.js +176 -0
- package/rules/no-useless-undefined.js +21 -13
- package/rules/prefer-negative-index.js +7 -0
- package/rules/shared/abbreviations.js +1 -0
- package/rules/string-content.js +7 -4
package/configs/all.js
CHANGED
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
const
|
|
2
|
+
const recommended = require('./recommended.js');
|
|
3
3
|
|
|
4
|
-
module.exports =
|
|
5
|
-
|
|
6
|
-
|
|
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
|
+
));
|
package/configs/recommended.js
CHANGED
|
@@ -1,128 +1,117 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
module.exports = {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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
|
|
5
|
-
const
|
|
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
|
-
|
|
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": "
|
|
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:
|
|
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:
|
|
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
|
-
"
|
|
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.
|
|
67
|
-
"@babel/core": "^7.23.
|
|
68
|
-
"@babel/eslint-parser": "^7.
|
|
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.
|
|
71
|
-
"ava": "^
|
|
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.
|
|
76
|
-
"eslint-ava-rule-tester": "^4.
|
|
77
|
-
"eslint-doc-generator": "^1.
|
|
78
|
-
"eslint-plugin-eslint-plugin": "^5.
|
|
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.
|
|
86
|
-
"
|
|
87
|
-
"npm-package-json-lint": "^7.
|
|
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.
|
|
94
|
+
"typescript": "^5.3.3",
|
|
91
95
|
"vue-eslint-parser": "^9.3.2",
|
|
92
96
|
"xo": "^0.56.0",
|
|
93
|
-
"yaml": "^2.3.
|
|
97
|
+
"yaml": "^2.3.4"
|
|
94
98
|
},
|
|
95
99
|
"peerDependencies": {
|
|
96
|
-
"eslint": ">=8.
|
|
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 [](https://codecov.io/gh/sindresorhus/eslint-plugin-unicorn/branch/main) [](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
|
-
|
|
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;
|
package/rules/no-for-loop.js
CHANGED
|
@@ -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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
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
|
];
|
package/rules/string-content.js
CHANGED
|
@@ -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 =>
|
|
109
|
-
|
|
110
|
-
|
|
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,
|