eslint-plugin-unicorn 54.0.0 → 56.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/flat-config-base.js +2 -4
- package/index.d.ts +12 -0
- package/package.json +27 -24
- package/readme.md +10 -6
- package/rules/ast/index.js +1 -0
- package/rules/ast/is-negative-one.js +12 -0
- package/rules/better-regex.js +1 -1
- package/rules/consistent-existence-index-check.js +133 -0
- package/rules/no-array-reduce.js +5 -4
- package/rules/no-for-loop.js +5 -19
- package/rules/no-length-as-slice-end.js +53 -0
- package/rules/no-negation-in-equality-check.js +5 -4
- package/rules/no-new-array.js +1 -1
- package/rules/no-single-promise-in-promise-methods.js +22 -9
- package/rules/no-useless-undefined.js +2 -0
- package/rules/prefer-global-this.js +210 -0
- package/rules/prefer-includes.js +1 -2
- package/rules/prefer-math-min-max.js +80 -0
- package/rules/prefer-node-protocol.js +1 -1
- package/rules/prefer-query-selector.js +35 -2
- package/rules/prefer-string-slice.js +0 -8
- package/rules/utils/resolve-variable-name.js +2 -2
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
-
const
|
|
3
|
-
|
|
4
|
-
const {globals} = eslintrc.Legacy.environments.get('es2024');
|
|
2
|
+
const globals = require('globals');
|
|
5
3
|
|
|
6
4
|
module.exports = {
|
|
7
5
|
languageOptions: {
|
|
8
|
-
globals,
|
|
6
|
+
globals: globals.builtin,
|
|
9
7
|
},
|
|
10
8
|
};
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type {ESLint, Linter} from 'eslint';
|
|
2
|
+
|
|
3
|
+
declare const eslintPluginUnicorn: ESLint.Plugin & {
|
|
4
|
+
configs: {
|
|
5
|
+
recommended: Linter.Config;
|
|
6
|
+
all: Linter.Config;
|
|
7
|
+
'flat/all': Linter.FlatConfig;
|
|
8
|
+
'flat/recommended': Linter.FlatConfig;
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export = eslintPluginUnicorn;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-unicorn",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "56.0.0",
|
|
4
4
|
"description": "More than 100 powerful ESLint rules",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "sindresorhus/eslint-plugin-unicorn",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"url": "https://sindresorhus.com"
|
|
12
12
|
},
|
|
13
13
|
"main": "index.js",
|
|
14
|
+
"types": "index.d.ts",
|
|
14
15
|
"sideEffects": false,
|
|
15
16
|
"engines": {
|
|
16
17
|
"node": ">=18.18"
|
|
@@ -28,13 +29,14 @@
|
|
|
28
29
|
"lint:js": "xo",
|
|
29
30
|
"lint:markdown": "markdownlint \"**/*.md\"",
|
|
30
31
|
"lint:package-json": "npmPkgJsonLint .",
|
|
31
|
-
"run-rules-on-codebase": "
|
|
32
|
+
"run-rules-on-codebase": "eslint --config=./eslint.dogfooding.config.mjs",
|
|
32
33
|
"smoke": "eslint-remote-tester --config ./test/smoke/eslint-remote-tester.config.mjs",
|
|
33
34
|
"test": "npm-run-all --continue-on-error lint test:*",
|
|
34
35
|
"test:js": "c8 ava"
|
|
35
36
|
},
|
|
36
37
|
"files": [
|
|
37
38
|
"index.js",
|
|
39
|
+
"index.d.ts",
|
|
38
40
|
"rules",
|
|
39
41
|
"configs"
|
|
40
42
|
],
|
|
@@ -49,13 +51,13 @@
|
|
|
49
51
|
"xo"
|
|
50
52
|
],
|
|
51
53
|
"dependencies": {
|
|
52
|
-
"@babel/helper-validator-identifier": "^7.24.
|
|
54
|
+
"@babel/helper-validator-identifier": "^7.24.7",
|
|
53
55
|
"@eslint-community/eslint-utils": "^4.4.0",
|
|
54
|
-
"@eslint/eslintrc": "^3.0.2",
|
|
55
56
|
"ci-info": "^4.0.0",
|
|
56
57
|
"clean-regexp": "^1.0.0",
|
|
57
|
-
"core-js-compat": "^3.
|
|
58
|
-
"esquery": "^1.
|
|
58
|
+
"core-js-compat": "^3.38.1",
|
|
59
|
+
"esquery": "^1.6.0",
|
|
60
|
+
"globals": "^15.9.0",
|
|
59
61
|
"indent-string": "^4.0.0",
|
|
60
62
|
"is-builtin-module": "^3.2.1",
|
|
61
63
|
"jsesc": "^3.0.2",
|
|
@@ -63,40 +65,41 @@
|
|
|
63
65
|
"read-pkg-up": "^7.0.1",
|
|
64
66
|
"regexp-tree": "^0.1.27",
|
|
65
67
|
"regjsparser": "^0.10.0",
|
|
66
|
-
"semver": "^7.6.
|
|
68
|
+
"semver": "^7.6.3",
|
|
67
69
|
"strip-indent": "^3.0.0"
|
|
68
70
|
},
|
|
69
71
|
"devDependencies": {
|
|
70
|
-
"@babel/code-frame": "^7.24.
|
|
71
|
-
"@babel/core": "^7.
|
|
72
|
-
"@babel/eslint-parser": "^7.
|
|
72
|
+
"@babel/code-frame": "^7.24.7",
|
|
73
|
+
"@babel/core": "^7.25.2",
|
|
74
|
+
"@babel/eslint-parser": "^7.25.1",
|
|
75
|
+
"@eslint/eslintrc": "^3.1.0",
|
|
73
76
|
"@lubien/fixture-beta-package": "^1.0.0-beta.1",
|
|
74
|
-
"@typescript-eslint/parser": "^8.
|
|
77
|
+
"@typescript-eslint/parser": "^8.4.0",
|
|
75
78
|
"ava": "^6.1.3",
|
|
76
|
-
"c8": "^
|
|
79
|
+
"c8": "^10.1.2",
|
|
77
80
|
"chalk": "^5.3.0",
|
|
78
81
|
"enquirer": "^2.4.1",
|
|
79
|
-
"eslint": "^9.
|
|
82
|
+
"eslint": "^9.10.0",
|
|
80
83
|
"eslint-ava-rule-tester": "^5.0.1",
|
|
81
84
|
"eslint-doc-generator": "1.7.0",
|
|
82
|
-
"eslint-plugin-eslint-plugin": "^6.
|
|
85
|
+
"eslint-plugin-eslint-plugin": "^6.2.0",
|
|
83
86
|
"eslint-plugin-internal-rules": "file:./scripts/internal-rules/",
|
|
84
|
-
"eslint-remote-tester": "^4.0.
|
|
87
|
+
"eslint-remote-tester": "^4.0.1",
|
|
85
88
|
"eslint-remote-tester-repositories": "^2.0.0",
|
|
86
|
-
"espree": "^10.0
|
|
89
|
+
"espree": "^10.1.0",
|
|
87
90
|
"execa": "^8.0.1",
|
|
88
91
|
"listr": "^0.14.3",
|
|
89
92
|
"lodash-es": "^4.17.21",
|
|
90
|
-
"markdownlint-cli": "^0.
|
|
93
|
+
"markdownlint-cli": "^0.41.0",
|
|
91
94
|
"memoize": "^10.0.0",
|
|
92
|
-
"npm-package-json-lint": "^
|
|
93
|
-
"npm-run-all2": "^6.
|
|
95
|
+
"npm-package-json-lint": "^8.0.0",
|
|
96
|
+
"npm-run-all2": "^6.2.2",
|
|
94
97
|
"outdent": "^0.8.0",
|
|
95
|
-
"pretty-ms": "^9.
|
|
96
|
-
"typescript": "^5.4
|
|
97
|
-
"vue-eslint-parser": "^9.4.
|
|
98
|
-
"xo": "^0.
|
|
99
|
-
"yaml": "^2.
|
|
98
|
+
"pretty-ms": "^9.1.0",
|
|
99
|
+
"typescript": "^5.5.4",
|
|
100
|
+
"vue-eslint-parser": "^9.4.3",
|
|
101
|
+
"xo": "^0.59.3",
|
|
102
|
+
"yaml": "^2.5.1"
|
|
100
103
|
},
|
|
101
104
|
"peerDependencies": {
|
|
102
105
|
"eslint": ">=8.56.0"
|
package/readme.md
CHANGED
|
@@ -27,12 +27,12 @@ If you don't use the preset, ensure you use the same `languageOptions` config as
|
|
|
27
27
|
|
|
28
28
|
```js
|
|
29
29
|
import eslintPluginUnicorn from 'eslint-plugin-unicorn';
|
|
30
|
-
import
|
|
30
|
+
import globals from 'globals';
|
|
31
31
|
|
|
32
32
|
export default [
|
|
33
33
|
{
|
|
34
34
|
languageOptions: {
|
|
35
|
-
globals:
|
|
35
|
+
globals: globals.builtin,
|
|
36
36
|
},
|
|
37
37
|
plugins: {
|
|
38
38
|
unicorn: eslintPluginUnicorn,
|
|
@@ -51,12 +51,12 @@ export default [
|
|
|
51
51
|
```js
|
|
52
52
|
'use strict';
|
|
53
53
|
const eslintPluginUnicorn = require('eslint-plugin-unicorn');
|
|
54
|
-
const
|
|
54
|
+
const globals = require('globals');
|
|
55
55
|
|
|
56
56
|
module.exports = [
|
|
57
57
|
{
|
|
58
58
|
languageOptions: {
|
|
59
|
-
globals:
|
|
59
|
+
globals: globals.builtin,
|
|
60
60
|
},
|
|
61
61
|
plugins: {
|
|
62
62
|
unicorn: eslintPluginUnicorn,
|
|
@@ -110,10 +110,11 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
|
|
|
110
110
|
|
|
111
111
|
| Name | Description | 💼 | 🔧 | 💡 |
|
|
112
112
|
| :----------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :- | :- | :- |
|
|
113
|
-
| [better-regex](docs/rules/better-regex.md) | Improve regexes by making them shorter, consistent, and safer. |
|
|
113
|
+
| [better-regex](docs/rules/better-regex.md) | Improve regexes by making them shorter, consistent, and safer. | | 🔧 | |
|
|
114
114
|
| [catch-error-name](docs/rules/catch-error-name.md) | Enforce a specific parameter name in catch clauses. | ✅ | 🔧 | |
|
|
115
115
|
| [consistent-destructuring](docs/rules/consistent-destructuring.md) | Use destructured variables over properties. | | 🔧 | 💡 |
|
|
116
116
|
| [consistent-empty-array-spread](docs/rules/consistent-empty-array-spread.md) | Prefer consistent types when spreading a ternary in an array literal. | ✅ | 🔧 | |
|
|
117
|
+
| [consistent-existence-index-check](docs/rules/consistent-existence-index-check.md) | Enforce consistent style for element existence checks with `indexOf()`, `lastIndexOf()`, `findIndex()`, and `findLastIndex()`. | ✅ | 🔧 | |
|
|
117
118
|
| [consistent-function-scoping](docs/rules/consistent-function-scoping.md) | Move function definitions to the highest possible scope. | ✅ | | |
|
|
118
119
|
| [custom-error-definition](docs/rules/custom-error-definition.md) | Enforce correct `Error` subclassing. | | 🔧 | |
|
|
119
120
|
| [empty-brace-spaces](docs/rules/empty-brace-spaces.md) | Enforce no spaces between braces. | ✅ | 🔧 | |
|
|
@@ -142,6 +143,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
|
|
|
142
143
|
| [no-invalid-fetch-options](docs/rules/no-invalid-fetch-options.md) | Disallow invalid options in `fetch()` and `new Request()`. | ✅ | | |
|
|
143
144
|
| [no-invalid-remove-event-listener](docs/rules/no-invalid-remove-event-listener.md) | Prevent calling `EventTarget#removeEventListener()` with the result of an expression. | ✅ | | |
|
|
144
145
|
| [no-keyword-prefix](docs/rules/no-keyword-prefix.md) | Disallow identifiers starting with `new` or `class`. | | | |
|
|
146
|
+
| [no-length-as-slice-end](docs/rules/no-length-as-slice-end.md) | Disallow using `.length` as the `end` argument of `{Array,String,TypedArray}#slice()`. | ✅ | 🔧 | |
|
|
145
147
|
| [no-lonely-if](docs/rules/no-lonely-if.md) | Disallow `if` statements as the only statement in `if` blocks without `else`. | ✅ | 🔧 | |
|
|
146
148
|
| [no-magic-array-flat-depth](docs/rules/no-magic-array-flat-depth.md) | Disallow a magic number as the `depth` argument in `Array#flat(…).` | ✅ | | |
|
|
147
149
|
| [no-negated-condition](docs/rules/no-negated-condition.md) | Disallow negated conditions. | ✅ | 🔧 | |
|
|
@@ -188,10 +190,12 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
|
|
|
188
190
|
| [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | ✅ | | 💡 |
|
|
189
191
|
| [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. | ✅ | | |
|
|
190
192
|
| [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `export…from` when re-exporting. | ✅ | 🔧 | 💡 |
|
|
193
|
+
| [prefer-global-this](docs/rules/prefer-global-this.md) | Prefer `globalThis` over `window`, `self`, and `global`. | ✅ | 🔧 | |
|
|
191
194
|
| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 |
|
|
192
195
|
| [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | 🔧 | |
|
|
193
196
|
| [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | ✅ | 🔧 | |
|
|
194
197
|
| [prefer-logical-operator-over-ternary](docs/rules/prefer-logical-operator-over-ternary.md) | Prefer using a logical operator over a ternary. | ✅ | | 💡 |
|
|
198
|
+
| [prefer-math-min-max](docs/rules/prefer-math-min-max.md) | Prefer `Math.min()` and `Math.max()` over ternaries for simple comparisons. | ✅ | 🔧 | |
|
|
195
199
|
| [prefer-math-trunc](docs/rules/prefer-math-trunc.md) | Enforce the use of `Math.trunc` instead of bitwise operators. | ✅ | 🔧 | 💡 |
|
|
196
200
|
| [prefer-modern-dom-apis](docs/rules/prefer-modern-dom-apis.md) | Prefer `.before()` over `.insertBefore()`, `.replaceWith()` over `.replaceChild()`, prefer one of `.before()`, `.after()`, `.append()` or `.prepend()` over `insertAdjacentText()` and `insertAdjacentElement()`. | ✅ | 🔧 | |
|
|
197
201
|
| [prefer-modern-math-apis](docs/rules/prefer-modern-math-apis.md) | Prefer modern `Math` APIs over legacy patterns. | ✅ | 🔧 | |
|
|
@@ -203,7 +207,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
|
|
|
203
207
|
| [prefer-object-from-entries](docs/rules/prefer-object-from-entries.md) | Prefer using `Object.fromEntries(…)` to transform a list of key-value pairs into an object. | ✅ | 🔧 | |
|
|
204
208
|
| [prefer-optional-catch-binding](docs/rules/prefer-optional-catch-binding.md) | Prefer omitting the `catch` binding parameter. | ✅ | 🔧 | |
|
|
205
209
|
| [prefer-prototype-methods](docs/rules/prefer-prototype-methods.md) | Prefer borrowing methods from the prototype instead of the instance. | ✅ | 🔧 | |
|
|
206
|
-
| [prefer-query-selector](docs/rules/prefer-query-selector.md) | Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`.
|
|
210
|
+
| [prefer-query-selector](docs/rules/prefer-query-selector.md) | Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()` and `.getElementsByName()`. | ✅ | 🔧 | |
|
|
207
211
|
| [prefer-reflect-apply](docs/rules/prefer-reflect-apply.md) | Prefer `Reflect.apply()` over `Function#apply()`. | ✅ | 🔧 | |
|
|
208
212
|
| [prefer-regexp-test](docs/rules/prefer-regexp-test.md) | Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`. | ✅ | 🔧 | 💡 |
|
|
209
213
|
| [prefer-set-has](docs/rules/prefer-set-has.md) | Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 |
|
package/rules/ast/index.js
CHANGED
|
@@ -31,6 +31,7 @@ module.exports = {
|
|
|
31
31
|
isFunction: require('./is-function.js'),
|
|
32
32
|
isMemberExpression: require('./is-member-expression.js'),
|
|
33
33
|
isMethodCall: require('./is-method-call.js'),
|
|
34
|
+
isNegativeOne: require('./is-negative-one.js'),
|
|
34
35
|
isNewExpression,
|
|
35
36
|
isReferenceIdentifier: require('./is-reference-identifier.js'),
|
|
36
37
|
isStaticRequire: require('./is-static-require.js'),
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {isNumberLiteral} = require('./literal.js');
|
|
4
|
+
|
|
5
|
+
function isNegativeOne(node) {
|
|
6
|
+
return node?.type === 'UnaryExpression'
|
|
7
|
+
&& node.operator === '-'
|
|
8
|
+
&& isNumberLiteral(node.argument)
|
|
9
|
+
&& node.argument.value === 1;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
module.exports = isNegativeOne;
|
package/rules/better-regex.js
CHANGED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const toLocation = require('./utils/to-location.js');
|
|
3
|
+
const {isMethodCall, isNegativeOne, isNumberLiteral} = require('./ast/index.js');
|
|
4
|
+
|
|
5
|
+
const MESSAGE_ID = 'consistent-existence-index-check';
|
|
6
|
+
const messages = {
|
|
7
|
+
[MESSAGE_ID]: 'Prefer `{{replacementOperator}} {{replacementValue}}` over `{{originalOperator}} {{originalValue}}` to check {{existenceOrNonExistence}}.',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const isZero = node => isNumberLiteral(node) && node.value === 0;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
@param {parent: import('estree').BinaryExpression} binaryExpression
|
|
14
|
+
@returns {{
|
|
15
|
+
replacementOperator: string,
|
|
16
|
+
replacementValue: string,
|
|
17
|
+
originalOperator: string,
|
|
18
|
+
originalValue: string,
|
|
19
|
+
} | undefined}
|
|
20
|
+
*/
|
|
21
|
+
function getReplacement(binaryExpression) {
|
|
22
|
+
const {operator, right} = binaryExpression;
|
|
23
|
+
|
|
24
|
+
if (operator === '<' && isZero(right)) {
|
|
25
|
+
return {
|
|
26
|
+
replacementOperator: '===',
|
|
27
|
+
replacementValue: '-1',
|
|
28
|
+
originalOperator: operator,
|
|
29
|
+
originalValue: '0',
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (operator === '>' && isNegativeOne(right)) {
|
|
34
|
+
return {
|
|
35
|
+
replacementOperator: '!==',
|
|
36
|
+
replacementValue: '-1',
|
|
37
|
+
originalOperator: operator,
|
|
38
|
+
originalValue: '-1',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (operator === '>=' && isZero(right)) {
|
|
43
|
+
return {
|
|
44
|
+
replacementOperator: '!==',
|
|
45
|
+
replacementValue: '-1',
|
|
46
|
+
originalOperator: operator,
|
|
47
|
+
originalValue: '0',
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
53
|
+
const create = context => ({
|
|
54
|
+
/** @param {import('estree').VariableDeclarator} variableDeclarator */
|
|
55
|
+
* VariableDeclarator(variableDeclarator) {
|
|
56
|
+
if (!(
|
|
57
|
+
variableDeclarator.parent.type === 'VariableDeclaration'
|
|
58
|
+
&& variableDeclarator.parent.kind === 'const'
|
|
59
|
+
&& variableDeclarator.id.type === 'Identifier'
|
|
60
|
+
&& isMethodCall(variableDeclarator.init, {methods: ['indexOf', 'lastIndexOf', 'findIndex', 'findLastIndex']})
|
|
61
|
+
)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const variableIdentifier = variableDeclarator.id;
|
|
66
|
+
const variables = context.sourceCode.getDeclaredVariables(variableDeclarator);
|
|
67
|
+
const [variable] = variables;
|
|
68
|
+
|
|
69
|
+
// Just for safety
|
|
70
|
+
if (
|
|
71
|
+
variables.length !== 1
|
|
72
|
+
|| variable.identifiers.length !== 1
|
|
73
|
+
|| variable.identifiers[0] !== variableIdentifier
|
|
74
|
+
) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
for (const {identifier} of variable.references) {
|
|
79
|
+
/** @type {{parent: import('estree').BinaryExpression}} */
|
|
80
|
+
const binaryExpression = identifier.parent;
|
|
81
|
+
|
|
82
|
+
if (binaryExpression.type !== 'BinaryExpression' || binaryExpression.left !== identifier) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const replacement = getReplacement(binaryExpression);
|
|
87
|
+
|
|
88
|
+
if (!replacement) {
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const {left, operator, right} = binaryExpression;
|
|
93
|
+
const {sourceCode} = context;
|
|
94
|
+
|
|
95
|
+
const operatorToken = sourceCode.getTokenAfter(
|
|
96
|
+
left,
|
|
97
|
+
token => token.type === 'Punctuator' && token.value === operator,
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
yield {
|
|
101
|
+
node: binaryExpression,
|
|
102
|
+
loc: toLocation([operatorToken.range[0], right.range[1]], sourceCode),
|
|
103
|
+
messageId: MESSAGE_ID,
|
|
104
|
+
data: {
|
|
105
|
+
...replacement,
|
|
106
|
+
existenceOrNonExistence: `${replacement.replacementOperator === '===' ? 'non-' : ''}existence`,
|
|
107
|
+
},
|
|
108
|
+
* fix(fixer) {
|
|
109
|
+
yield fixer.replaceText(operatorToken, replacement.replacementOperator);
|
|
110
|
+
|
|
111
|
+
if (replacement.replacementValue !== replacement.originalValue) {
|
|
112
|
+
yield fixer.replaceText(right, replacement.replacementValue);
|
|
113
|
+
}
|
|
114
|
+
},
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
121
|
+
module.exports = {
|
|
122
|
+
create,
|
|
123
|
+
meta: {
|
|
124
|
+
type: 'problem',
|
|
125
|
+
docs: {
|
|
126
|
+
description:
|
|
127
|
+
'Enforce consistent style for element existence checks with `indexOf()`, `lastIndexOf()`, `findIndex()`, and `findLastIndex()`.',
|
|
128
|
+
recommended: true,
|
|
129
|
+
},
|
|
130
|
+
fixable: 'code',
|
|
131
|
+
messages,
|
|
132
|
+
},
|
|
133
|
+
};
|
package/rules/no-array-reduce.js
CHANGED
|
@@ -2,9 +2,11 @@
|
|
|
2
2
|
const {isMethodCall} = require('./ast/index.js');
|
|
3
3
|
const {isNodeValueNotFunction, isArrayPrototypeProperty} = require('./utils/index.js');
|
|
4
4
|
|
|
5
|
-
const
|
|
5
|
+
const MESSAGE_ID_REDUCE = 'reduce';
|
|
6
|
+
const MESSAGE_ID_REDUCE_RIGHT = 'reduceRight';
|
|
6
7
|
const messages = {
|
|
7
|
-
[
|
|
8
|
+
[MESSAGE_ID_REDUCE]: '`Array#reduce()` is not allowed. Prefer other types of loop for readability.',
|
|
9
|
+
[MESSAGE_ID_REDUCE_RIGHT]: '`Array#reduceRight()` is not allowed. Prefer other types of loop for readability. You may want to call `Array#toReversed()` before looping it.',
|
|
8
10
|
};
|
|
9
11
|
|
|
10
12
|
const cases = [
|
|
@@ -104,8 +106,7 @@ const create = context => {
|
|
|
104
106
|
const methodNode = getMethodNode(callExpression);
|
|
105
107
|
yield {
|
|
106
108
|
node: methodNode,
|
|
107
|
-
messageId:
|
|
108
|
-
data: {method: methodNode.name},
|
|
109
|
+
messageId: methodNode.name,
|
|
109
110
|
};
|
|
110
111
|
}
|
|
111
112
|
},
|
package/rules/no-for-loop.js
CHANGED
|
@@ -263,7 +263,7 @@ const getReferencesInChildScopes = (scope, name) =>
|
|
|
263
263
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
264
264
|
const create = context => {
|
|
265
265
|
const {sourceCode} = context;
|
|
266
|
-
const {scopeManager
|
|
266
|
+
const {scopeManager} = sourceCode;
|
|
267
267
|
|
|
268
268
|
return {
|
|
269
269
|
ForStatement(node) {
|
|
@@ -339,12 +339,12 @@ const create = context => {
|
|
|
339
339
|
const elementIdentifierName = elementNode?.id.name;
|
|
340
340
|
const elementVariable = elementIdentifierName && resolveIdentifierName(elementIdentifierName, bodyScope);
|
|
341
341
|
|
|
342
|
-
const shouldFix = !someVariablesLeakOutOfTheLoop(node, [indexVariable, elementVariable].filter(Boolean), forScope)
|
|
342
|
+
const shouldFix = !someVariablesLeakOutOfTheLoop(node, [indexVariable, elementVariable].filter(Boolean), forScope)
|
|
343
|
+
&& !elementNode?.id.typeAnnotation;
|
|
343
344
|
|
|
344
345
|
if (shouldFix) {
|
|
345
346
|
problem.fix = function * (fixer) {
|
|
346
347
|
const shouldGenerateIndex = isIndexVariableUsedElsewhereInTheLoopBody(indexVariable, bodyScope, arrayIdentifierName);
|
|
347
|
-
|
|
348
348
|
const index = indexIdentifierName;
|
|
349
349
|
const element = elementIdentifierName
|
|
350
350
|
|| avoidCapture(singular(arrayIdentifierName) || defaultElementName, getScopes(bodyScope));
|
|
@@ -353,7 +353,6 @@ const create = context => {
|
|
|
353
353
|
let declarationElement = element;
|
|
354
354
|
let declarationType = 'const';
|
|
355
355
|
let removeDeclaration = true;
|
|
356
|
-
let typeAnnotation;
|
|
357
356
|
|
|
358
357
|
if (elementNode) {
|
|
359
358
|
if (elementNode.id.type === 'ObjectPattern' || elementNode.id.type === 'ArrayPattern') {
|
|
@@ -362,26 +361,13 @@ const create = context => {
|
|
|
362
361
|
|
|
363
362
|
if (removeDeclaration) {
|
|
364
363
|
declarationType = element.type === 'VariableDeclarator' ? elementNode.kind : elementNode.parent.kind;
|
|
365
|
-
|
|
366
|
-
declarationElement = sourceCodeText.slice(elementNode.id.range[0], elementNode.id.typeAnnotation.range[0]).trim();
|
|
367
|
-
typeAnnotation = sourceCode.getText(
|
|
368
|
-
elementNode.id.typeAnnotation,
|
|
369
|
-
-1, // Skip leading `:`
|
|
370
|
-
).trim();
|
|
371
|
-
} else {
|
|
372
|
-
declarationElement = sourceCode.getText(elementNode.id);
|
|
373
|
-
}
|
|
364
|
+
declarationElement = sourceCode.getText(elementNode.id);
|
|
374
365
|
}
|
|
375
366
|
}
|
|
376
367
|
|
|
377
368
|
const parts = [declarationType];
|
|
378
369
|
if (shouldGenerateIndex) {
|
|
379
|
-
parts.push(` [${index}, ${declarationElement}]`);
|
|
380
|
-
if (typeAnnotation) {
|
|
381
|
-
parts.push(`: [number, ${typeAnnotation}]`);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
parts.push(` of ${array}.entries()`);
|
|
370
|
+
parts.push(` [${index}, ${declarationElement}] of ${array}.entries()`);
|
|
385
371
|
} else {
|
|
386
372
|
parts.push(` ${declarationElement} of ${array}`);
|
|
387
373
|
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const {isMethodCall, isMemberExpression} = require('./ast/index.js');
|
|
3
|
+
const {removeArgument} = require('./fix/index.js');
|
|
4
|
+
const {isSameReference} = require('./utils/index.js');
|
|
5
|
+
|
|
6
|
+
const MESSAGE_ID = 'no-length-as-slice-end';
|
|
7
|
+
const messages = {
|
|
8
|
+
[MESSAGE_ID]: 'Passing `….length` as the `end` argument is unnecessary.',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
12
|
+
const create = context => {
|
|
13
|
+
context.on('CallExpression', callExpression => {
|
|
14
|
+
if (!isMethodCall(callExpression, {
|
|
15
|
+
method: 'slice',
|
|
16
|
+
argumentsLength: 2,
|
|
17
|
+
optionalCall: false,
|
|
18
|
+
})) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const secondArgument = callExpression.arguments[1];
|
|
23
|
+
const node = secondArgument.type === 'ChainExpression' ? secondArgument.expression : secondArgument;
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
!isMemberExpression(node, {property: 'length', computed: false})
|
|
27
|
+
|| !isSameReference(callExpression.callee.object, node.object)
|
|
28
|
+
) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
node,
|
|
34
|
+
messageId: MESSAGE_ID,
|
|
35
|
+
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
36
|
+
fix: fixer => removeArgument(fixer, secondArgument, context.sourceCode),
|
|
37
|
+
};
|
|
38
|
+
});
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
42
|
+
module.exports = {
|
|
43
|
+
create,
|
|
44
|
+
meta: {
|
|
45
|
+
type: 'suggestion',
|
|
46
|
+
docs: {
|
|
47
|
+
description: 'Disallow using `.length` as the `end` argument of `{Array,String,TypedArray}#slice()`.',
|
|
48
|
+
recommended: true,
|
|
49
|
+
},
|
|
50
|
+
fixable: 'code',
|
|
51
|
+
messages,
|
|
52
|
+
},
|
|
53
|
+
};
|
|
@@ -31,10 +31,11 @@ const create = context => ({
|
|
|
31
31
|
BinaryExpression(binaryExpression) {
|
|
32
32
|
const {operator, left} = binaryExpression;
|
|
33
33
|
|
|
34
|
-
if (
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
34
|
+
if (!(
|
|
35
|
+
isEqualityCheck(binaryExpression)
|
|
36
|
+
&& isNegatedExpression(left)
|
|
37
|
+
&& !isNegatedExpression(left.argument)
|
|
38
|
+
)) {
|
|
38
39
|
return;
|
|
39
40
|
}
|
|
40
41
|
|
package/rules/no-new-array.js
CHANGED
|
@@ -9,7 +9,7 @@ const MESSAGE_ID_LENGTH = 'array-length';
|
|
|
9
9
|
const MESSAGE_ID_ONLY_ELEMENT = 'only-element';
|
|
10
10
|
const MESSAGE_ID_SPREAD = 'spread';
|
|
11
11
|
const messages = {
|
|
12
|
-
[MESSAGE_ID_ERROR]: '
|
|
12
|
+
[MESSAGE_ID_ERROR]: '`new Array()` is unclear in intent; use either `[x]` or `Array.from({length: x})`',
|
|
13
13
|
[MESSAGE_ID_LENGTH]: 'The argument is the length of array.',
|
|
14
14
|
[MESSAGE_ID_ONLY_ELEMENT]: 'The argument is the only element of array.',
|
|
15
15
|
[MESSAGE_ID_SPREAD]: 'Spread the argument.',
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
const {
|
|
3
3
|
isCommaToken,
|
|
4
4
|
} = require('@eslint-community/eslint-utils');
|
|
5
|
-
const {
|
|
5
|
+
const {
|
|
6
|
+
isMethodCall,
|
|
7
|
+
isExpressionStatement,
|
|
8
|
+
} = require('./ast/index.js');
|
|
6
9
|
const {
|
|
7
10
|
getParenthesizedText,
|
|
8
11
|
isParenthesized,
|
|
@@ -77,8 +80,8 @@ const unwrapNonAwaitedCallExpression = (callExpression, sourceCode) => fixer =>
|
|
|
77
80
|
const switchToPromiseResolve = (callExpression, sourceCode) => function * (fixer) {
|
|
78
81
|
/*
|
|
79
82
|
```
|
|
80
|
-
Promise.
|
|
81
|
-
//
|
|
83
|
+
Promise.race([promise,])
|
|
84
|
+
// ^^^^ methodNameNode
|
|
82
85
|
```
|
|
83
86
|
*/
|
|
84
87
|
const methodNameNode = callExpression.callee.property;
|
|
@@ -87,16 +90,16 @@ const switchToPromiseResolve = (callExpression, sourceCode) => function * (fixer
|
|
|
87
90
|
const [arrayExpression] = callExpression.arguments;
|
|
88
91
|
/*
|
|
89
92
|
```
|
|
90
|
-
Promise.
|
|
91
|
-
//
|
|
93
|
+
Promise.race([promise,])
|
|
94
|
+
// ^ openingBracketToken
|
|
92
95
|
```
|
|
93
96
|
*/
|
|
94
97
|
const openingBracketToken = sourceCode.getFirstToken(arrayExpression);
|
|
95
98
|
/*
|
|
96
99
|
```
|
|
97
|
-
Promise.
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
+
Promise.race([promise,])
|
|
101
|
+
// ^ penultimateToken
|
|
102
|
+
// ^ closingBracketToken
|
|
100
103
|
```
|
|
101
104
|
*/
|
|
102
105
|
const [
|
|
@@ -119,11 +122,13 @@ const create = context => ({
|
|
|
119
122
|
return;
|
|
120
123
|
}
|
|
121
124
|
|
|
125
|
+
const methodName = callExpression.callee.property.name;
|
|
126
|
+
|
|
122
127
|
const problem = {
|
|
123
128
|
node: callExpression.arguments[0],
|
|
124
129
|
messageId: MESSAGE_ID_ERROR,
|
|
125
130
|
data: {
|
|
126
|
-
method:
|
|
131
|
+
method: methodName,
|
|
127
132
|
},
|
|
128
133
|
};
|
|
129
134
|
|
|
@@ -132,11 +137,19 @@ const create = context => ({
|
|
|
132
137
|
if (
|
|
133
138
|
callExpression.parent.type === 'AwaitExpression'
|
|
134
139
|
&& callExpression.parent.argument === callExpression
|
|
140
|
+
&& (
|
|
141
|
+
methodName !== 'all'
|
|
142
|
+
|| isExpressionStatement(callExpression.parent.parent)
|
|
143
|
+
)
|
|
135
144
|
) {
|
|
136
145
|
problem.fix = unwrapAwaitedCallExpression(callExpression, sourceCode);
|
|
137
146
|
return problem;
|
|
138
147
|
}
|
|
139
148
|
|
|
149
|
+
if (methodName === 'all') {
|
|
150
|
+
return problem;
|
|
151
|
+
}
|
|
152
|
+
|
|
140
153
|
problem.suggest = [
|
|
141
154
|
{
|
|
142
155
|
messageId: MESSAGE_ID_SUGGESTION_UNWRAP,
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const MESSAGE_ID_ERROR = 'prefer-global-this/error';
|
|
4
|
+
const messages = {
|
|
5
|
+
[MESSAGE_ID_ERROR]: 'Prefer `globalThis` over `{{value}}`.',
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
const globalIdentifier = new Set(['window', 'self', 'global']);
|
|
9
|
+
|
|
10
|
+
const windowSpecificEvents = new Set([
|
|
11
|
+
'resize',
|
|
12
|
+
'blur',
|
|
13
|
+
'focus',
|
|
14
|
+
'load',
|
|
15
|
+
'scroll',
|
|
16
|
+
'scrollend',
|
|
17
|
+
'wheel',
|
|
18
|
+
'beforeunload', // Browsers might have specific behaviors on exactly `window.onbeforeunload =`
|
|
19
|
+
'message',
|
|
20
|
+
'messageerror',
|
|
21
|
+
'pagehide',
|
|
22
|
+
'pagereveal',
|
|
23
|
+
'pageshow',
|
|
24
|
+
'pageswap',
|
|
25
|
+
'unload',
|
|
26
|
+
]);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
Note: What kind of API should be a windows-specific interface?
|
|
30
|
+
|
|
31
|
+
1. It's directly related to window (✅ window.close())
|
|
32
|
+
2. It does NOT work well as globalThis.x or x (✅ window.frames, window.top)
|
|
33
|
+
|
|
34
|
+
Some constructors are occasionally related to window (like Element !== iframe.contentWindow.Element), but they don't need to mention window anyway.
|
|
35
|
+
|
|
36
|
+
Please use these criteria to decide whether an API should be added here. Context: https://github.com/sindresorhus/eslint-plugin-unicorn/pull/2410#discussion_r1695312427
|
|
37
|
+
*/
|
|
38
|
+
const windowSpecificAPIs = new Set([
|
|
39
|
+
// Properties and methods
|
|
40
|
+
// https://html.spec.whatwg.org/multipage/nav-history-apis.html#the-window-object
|
|
41
|
+
'name',
|
|
42
|
+
'locationbar',
|
|
43
|
+
'menubar',
|
|
44
|
+
'personalbar',
|
|
45
|
+
'scrollbars',
|
|
46
|
+
'statusbar',
|
|
47
|
+
'toolbar',
|
|
48
|
+
'status',
|
|
49
|
+
'close',
|
|
50
|
+
'closed',
|
|
51
|
+
'stop',
|
|
52
|
+
'focus',
|
|
53
|
+
'blur',
|
|
54
|
+
'frames',
|
|
55
|
+
'length',
|
|
56
|
+
'top',
|
|
57
|
+
'opener',
|
|
58
|
+
'parent',
|
|
59
|
+
'frameElement',
|
|
60
|
+
'open',
|
|
61
|
+
'originAgentCluster',
|
|
62
|
+
'postMessage',
|
|
63
|
+
|
|
64
|
+
// Events commonly associated with "window"
|
|
65
|
+
...[...windowSpecificEvents].map(event => `on${event}`),
|
|
66
|
+
|
|
67
|
+
// To add/remove/dispatch events that are commonly associated with "window"
|
|
68
|
+
// https://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow
|
|
69
|
+
'addEventListener',
|
|
70
|
+
'removeEventListener',
|
|
71
|
+
'dispatchEvent',
|
|
72
|
+
|
|
73
|
+
// https://dom.spec.whatwg.org/#idl-index
|
|
74
|
+
'event', // Deprecated and quirky, best left untouched
|
|
75
|
+
|
|
76
|
+
// https://drafts.csswg.org/cssom-view/#idl-index
|
|
77
|
+
'screen',
|
|
78
|
+
'visualViewport',
|
|
79
|
+
'moveTo',
|
|
80
|
+
'moveBy',
|
|
81
|
+
'resizeTo',
|
|
82
|
+
'resizeBy',
|
|
83
|
+
'innerWidth',
|
|
84
|
+
'innerHeight',
|
|
85
|
+
'scrollX',
|
|
86
|
+
'pageXOffset',
|
|
87
|
+
'scrollY',
|
|
88
|
+
'pageYOffset',
|
|
89
|
+
'scroll',
|
|
90
|
+
'scrollTo',
|
|
91
|
+
'scrollBy',
|
|
92
|
+
'screenX',
|
|
93
|
+
'screenLeft',
|
|
94
|
+
'screenY',
|
|
95
|
+
'screenTop',
|
|
96
|
+
'screenWidth',
|
|
97
|
+
'screenHeight',
|
|
98
|
+
'devicePixelRatio',
|
|
99
|
+
]);
|
|
100
|
+
|
|
101
|
+
const webWorkerSpecificAPIs = new Set([
|
|
102
|
+
// https://html.spec.whatwg.org/multipage/workers.html#the-workerglobalscope-common-interface
|
|
103
|
+
'addEventListener',
|
|
104
|
+
'removeEventListener',
|
|
105
|
+
'dispatchEvent',
|
|
106
|
+
|
|
107
|
+
'self',
|
|
108
|
+
'location',
|
|
109
|
+
'navigator',
|
|
110
|
+
'onerror',
|
|
111
|
+
'onlanguagechange',
|
|
112
|
+
'onoffline',
|
|
113
|
+
'ononline',
|
|
114
|
+
'onrejectionhandled',
|
|
115
|
+
'onunhandledrejection',
|
|
116
|
+
|
|
117
|
+
// https://html.spec.whatwg.org/multipage/workers.html#dedicated-workers-and-the-dedicatedworkerglobalscope-interface
|
|
118
|
+
'name',
|
|
119
|
+
'postMessage',
|
|
120
|
+
'onconnect',
|
|
121
|
+
]);
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
Check if the node is a window-specific API.
|
|
125
|
+
|
|
126
|
+
@param {import('estree').MemberExpression} node
|
|
127
|
+
@returns {boolean}
|
|
128
|
+
*/
|
|
129
|
+
const isWindowSpecificAPI = node => {
|
|
130
|
+
if (node.type !== 'MemberExpression') {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (node.object.name !== 'window' || node.property.type !== 'Identifier') {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (windowSpecificAPIs.has(node.property.name)) {
|
|
139
|
+
if (['addEventListener', 'removeEventListener', 'dispatchEvent'].includes(node.property.name) && node.parent.type === 'CallExpression' && node.parent.callee === node) {
|
|
140
|
+
const argument = node.parent.arguments[0];
|
|
141
|
+
return argument && argument.type === 'Literal' && windowSpecificEvents.has(argument.value);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return true;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return false;
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
@param {import('estree').Identifier} identifier
|
|
152
|
+
@returns {boolean}
|
|
153
|
+
*/
|
|
154
|
+
function isComputedMemberExpressionObject(identifier) {
|
|
155
|
+
return identifier.parent.type === 'MemberExpression' && identifier.parent.computed && identifier.parent.object === identifier;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
Check if the node is a web worker specific API.
|
|
160
|
+
|
|
161
|
+
@param {import('estree').MemberExpression} node
|
|
162
|
+
@returns {boolean}
|
|
163
|
+
*/
|
|
164
|
+
const isWebWorkerSpecificAPI = node => node.type === 'MemberExpression' && node.object.name === 'self' && node.property.type === 'Identifier' && webWorkerSpecificAPIs.has(node.property.name);
|
|
165
|
+
|
|
166
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
167
|
+
const create = context => ({
|
|
168
|
+
* Program(program) {
|
|
169
|
+
const scope = context.sourceCode.getScope(program);
|
|
170
|
+
|
|
171
|
+
const references = [
|
|
172
|
+
// Variables declared at globals options
|
|
173
|
+
...scope.variables.flatMap(variable => globalIdentifier.has(variable.name) ? variable.references : []),
|
|
174
|
+
// Variables not declared at globals options
|
|
175
|
+
...scope.through.filter(reference => globalIdentifier.has(reference.identifier.name)),
|
|
176
|
+
];
|
|
177
|
+
|
|
178
|
+
for (const {identifier} of references) {
|
|
179
|
+
if (
|
|
180
|
+
isComputedMemberExpressionObject(identifier)
|
|
181
|
+
|| isWindowSpecificAPI(identifier.parent)
|
|
182
|
+
|| isWebWorkerSpecificAPI(identifier.parent)
|
|
183
|
+
) {
|
|
184
|
+
continue;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
yield {
|
|
188
|
+
node: identifier,
|
|
189
|
+
messageId: MESSAGE_ID_ERROR,
|
|
190
|
+
data: {value: identifier.name},
|
|
191
|
+
fix: fixer => fixer.replaceText(identifier, 'globalThis'),
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
198
|
+
module.exports = {
|
|
199
|
+
create,
|
|
200
|
+
meta: {
|
|
201
|
+
type: 'suggestion',
|
|
202
|
+
docs: {
|
|
203
|
+
description: 'Prefer `globalThis` over `window`, `self`, and `global`.',
|
|
204
|
+
recommended: true,
|
|
205
|
+
},
|
|
206
|
+
fixable: 'code',
|
|
207
|
+
hasSuggestions: false,
|
|
208
|
+
messages,
|
|
209
|
+
},
|
|
210
|
+
};
|
package/rules/prefer-includes.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const isMethodNamed = require('./utils/is-method-named.js');
|
|
3
3
|
const simpleArraySearchRule = require('./shared/simple-array-search-rule.js');
|
|
4
|
-
const {isLiteral} = require('./ast/index.js');
|
|
4
|
+
const {isLiteral, isNegativeOne} = require('./ast/index.js');
|
|
5
5
|
|
|
6
6
|
const MESSAGE_ID = 'prefer-includes';
|
|
7
7
|
const messages = {
|
|
@@ -10,7 +10,6 @@ const messages = {
|
|
|
10
10
|
// Ignore `{_,lodash,underscore}.{indexOf,lastIndexOf}`
|
|
11
11
|
const ignoredVariables = new Set(['_', 'lodash', 'underscore']);
|
|
12
12
|
const isIgnoredTarget = node => node.type === 'Identifier' && ignoredVariables.has(node.name);
|
|
13
|
-
const isNegativeOne = node => node.type === 'UnaryExpression' && node.operator === '-' && node.argument && node.argument.type === 'Literal' && node.argument.value === 1;
|
|
14
13
|
const isLiteralZero = node => isLiteral(node, 0);
|
|
15
14
|
const isNegativeResult = node => ['===', '==', '<'].includes(node.operator);
|
|
16
15
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const {fixSpaceAroundKeyword} = require('./fix/index.js');
|
|
3
|
+
|
|
4
|
+
const MESSAGE_ID = 'prefer-math-min-max';
|
|
5
|
+
const messages = {
|
|
6
|
+
[MESSAGE_ID]: 'Prefer `Math.{{method}}()` to simplify ternary expressions.',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
10
|
+
const create = context => ({
|
|
11
|
+
/** @param {import('estree').ConditionalExpression} conditionalExpression */
|
|
12
|
+
ConditionalExpression(conditionalExpression) {
|
|
13
|
+
const {test, consequent, alternate} = conditionalExpression;
|
|
14
|
+
|
|
15
|
+
if (test.type !== 'BinaryExpression') {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const {operator, left, right} = test;
|
|
20
|
+
const [leftText, rightText, alternateText, consequentText] = [left, right, alternate, consequent].map(node => context.sourceCode.getText(node));
|
|
21
|
+
|
|
22
|
+
const isGreaterOrEqual = operator === '>' || operator === '>=';
|
|
23
|
+
const isLessOrEqual = operator === '<' || operator === '<=';
|
|
24
|
+
|
|
25
|
+
let method;
|
|
26
|
+
|
|
27
|
+
// Prefer `Math.min()`
|
|
28
|
+
if (
|
|
29
|
+
// `height > 50 ? 50 : height`
|
|
30
|
+
(isGreaterOrEqual && leftText === alternateText && rightText === consequentText)
|
|
31
|
+
// `height < 50 ? height : 50`
|
|
32
|
+
|| (isLessOrEqual && leftText === consequentText && rightText === alternateText)
|
|
33
|
+
) {
|
|
34
|
+
method = 'min';
|
|
35
|
+
} else if (
|
|
36
|
+
// `height > 50 ? height : 50`
|
|
37
|
+
(isGreaterOrEqual && leftText === consequentText && rightText === alternateText)
|
|
38
|
+
// `height < 50 ? 50 : height`
|
|
39
|
+
|| (isLessOrEqual && leftText === alternateText && rightText === consequentText)
|
|
40
|
+
) {
|
|
41
|
+
method = 'max';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (!method) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
node: conditionalExpression,
|
|
50
|
+
messageId: MESSAGE_ID,
|
|
51
|
+
data: {method},
|
|
52
|
+
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
53
|
+
* fix(fixer) {
|
|
54
|
+
const {sourceCode} = context;
|
|
55
|
+
|
|
56
|
+
yield * fixSpaceAroundKeyword(fixer, conditionalExpression, sourceCode);
|
|
57
|
+
|
|
58
|
+
const argumentsText = [left, right]
|
|
59
|
+
.map(node => node.type === 'SequenceExpression' ? `(${sourceCode.getText(node)})` : sourceCode.getText(node))
|
|
60
|
+
.join(', ');
|
|
61
|
+
|
|
62
|
+
yield fixer.replaceText(conditionalExpression, `Math.${method}(${argumentsText})`);
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
69
|
+
module.exports = {
|
|
70
|
+
create,
|
|
71
|
+
meta: {
|
|
72
|
+
type: 'problem',
|
|
73
|
+
docs: {
|
|
74
|
+
description: 'Prefer `Math.min()` and `Math.max()` over ternaries for simple comparisons.',
|
|
75
|
+
recommended: true,
|
|
76
|
+
},
|
|
77
|
+
fixable: 'code',
|
|
78
|
+
messages,
|
|
79
|
+
},
|
|
80
|
+
};
|
|
@@ -11,10 +11,12 @@ const disallowedIdentifierNames = new Map([
|
|
|
11
11
|
['getElementById', 'querySelector'],
|
|
12
12
|
['getElementsByClassName', 'querySelectorAll'],
|
|
13
13
|
['getElementsByTagName', 'querySelectorAll'],
|
|
14
|
+
['getElementsByName', 'querySelectorAll'],
|
|
14
15
|
]);
|
|
15
16
|
|
|
16
17
|
const getReplacementForId = value => `#${value}`;
|
|
17
18
|
const getReplacementForClass = value => value.match(/\S+/g).map(className => `.${className}`).join('');
|
|
19
|
+
const getReplacementForName = (value, originQuote) => `[name=${wrapQuoted(value, originQuote)}]`;
|
|
18
20
|
|
|
19
21
|
const getQuotedReplacement = (node, value) => {
|
|
20
22
|
const leftQuote = node.raw.charAt(0);
|
|
@@ -22,6 +24,24 @@ const getQuotedReplacement = (node, value) => {
|
|
|
22
24
|
return `${leftQuote}${value}${rightQuote}`;
|
|
23
25
|
};
|
|
24
26
|
|
|
27
|
+
const wrapQuoted = (value, originalQuote) => {
|
|
28
|
+
switch (originalQuote) {
|
|
29
|
+
case '\'': {
|
|
30
|
+
return `"${value}"`;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
case '"': {
|
|
34
|
+
return `'${value}'`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
case '`': {
|
|
38
|
+
return `'${value}'`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// No default
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
25
45
|
function * getLiteralFix(fixer, node, identifierName) {
|
|
26
46
|
let replacement = node.raw;
|
|
27
47
|
if (identifierName === 'getElementById') {
|
|
@@ -32,6 +52,11 @@ function * getLiteralFix(fixer, node, identifierName) {
|
|
|
32
52
|
replacement = getQuotedReplacement(node, getReplacementForClass(node.value));
|
|
33
53
|
}
|
|
34
54
|
|
|
55
|
+
if (identifierName === 'getElementsByName') {
|
|
56
|
+
const quoted = node.raw.charAt(0);
|
|
57
|
+
replacement = getQuotedReplacement(node, getReplacementForName(node.value, quoted));
|
|
58
|
+
}
|
|
59
|
+
|
|
35
60
|
yield fixer.replaceText(node, replacement);
|
|
36
61
|
}
|
|
37
62
|
|
|
@@ -53,6 +78,14 @@ function * getTemplateLiteralFix(fixer, node, identifierName) {
|
|
|
53
78
|
getReplacementForClass(templateElement.value.cooked),
|
|
54
79
|
);
|
|
55
80
|
}
|
|
81
|
+
|
|
82
|
+
if (identifierName === 'getElementsByName') {
|
|
83
|
+
const quoted = node.raw ? node.raw.charAt(0) : '"';
|
|
84
|
+
yield fixer.replaceText(
|
|
85
|
+
templateElement,
|
|
86
|
+
getReplacementForName(templateElement.value.cooked, quoted),
|
|
87
|
+
);
|
|
88
|
+
}
|
|
56
89
|
}
|
|
57
90
|
}
|
|
58
91
|
|
|
@@ -91,7 +124,7 @@ const create = () => ({
|
|
|
91
124
|
CallExpression(node) {
|
|
92
125
|
if (
|
|
93
126
|
!isMethodCall(node, {
|
|
94
|
-
methods: ['getElementById', 'getElementsByClassName', 'getElementsByTagName'],
|
|
127
|
+
methods: ['getElementById', 'getElementsByClassName', 'getElementsByTagName', 'getElementsByName'],
|
|
95
128
|
argumentsLength: 1,
|
|
96
129
|
optionalCall: false,
|
|
97
130
|
optionalMember: false,
|
|
@@ -127,7 +160,7 @@ module.exports = {
|
|
|
127
160
|
meta: {
|
|
128
161
|
type: 'suggestion',
|
|
129
162
|
docs: {
|
|
130
|
-
description: 'Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()`.',
|
|
163
|
+
description: 'Prefer `.querySelector()` over `.getElementById()`, `.querySelectorAll()` over `.getElementsByClassName()` and `.getElementsByTagName()` and `.getElementsByName()`.',
|
|
131
164
|
recommended: true,
|
|
132
165
|
},
|
|
133
166
|
fixable: 'code',
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
const {getStaticValue} = require('@eslint-community/eslint-utils');
|
|
3
3
|
const {getParenthesizedText, getParenthesizedRange} = require('./utils/parentheses.js');
|
|
4
|
-
const isNumber = require('./utils/is-number.js');
|
|
5
4
|
const {replaceArgument} = require('./fix/index.js');
|
|
6
5
|
const {isNumberLiteral, isMethodCall} = require('./ast/index.js');
|
|
7
6
|
|
|
@@ -64,13 +63,6 @@ function * fixSubstrArguments({node, fixer, context, abort}) {
|
|
|
64
63
|
return;
|
|
65
64
|
}
|
|
66
65
|
|
|
67
|
-
if (argumentNodes.every(node => isNumber(node, scope))) {
|
|
68
|
-
const firstArgumentText = getParenthesizedText(firstArgument, sourceCode);
|
|
69
|
-
|
|
70
|
-
yield fixer.insertTextBeforeRange(secondArgumentRange, `${firstArgumentText} + `);
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
66
|
return abort();
|
|
75
67
|
}
|
|
76
68
|
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
Finds a variable named `name` in the scope `scope` (or it's parents).
|
|
5
5
|
|
|
6
6
|
@param {string} name - The variable name to be resolve.
|
|
7
|
-
@param {Scope} scope - The scope to look for the variable in.
|
|
8
|
-
@returns {Variable
|
|
7
|
+
@param {import('eslint').Scope.Scope} scope - The scope to look for the variable in.
|
|
8
|
+
@returns {import('eslint').Scope.Variable | void} - The found variable, if any.
|
|
9
9
|
*/
|
|
10
10
|
module.exports = function resolveVariableName(name, scope) {
|
|
11
11
|
while (scope) {
|