eslint-plugin-unicorn 55.0.0 → 56.0.1
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/package.json +26 -24
- package/readme.md +17 -14
- 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/expiring-todo-comments.js +10 -5
- package/rules/no-array-reduce.js +5 -4
- package/rules/no-for-loop.js +5 -19
- package/rules/no-negation-in-equality-check.js +1 -1
- package/rules/no-new-array.js +1 -1
- 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-query-selector.js +35 -2
- package/rules/prefer-string-slice.js +0 -8
- package/rules/utils/resolve-variable-name.js +2 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-unicorn",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "56.0.1",
|
|
4
4
|
"description": "More than 100 powerful ESLint rules",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "sindresorhus/eslint-plugin-unicorn",
|
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
"fix:eslint-docs": "eslint-doc-generator",
|
|
24
24
|
"fix:js": "npm run lint:js -- --fix",
|
|
25
25
|
"fix:markdown": "npm run lint:markdown -- --fix",
|
|
26
|
+
"fix:snapshots": "ava --update-snapshots",
|
|
26
27
|
"integration": "node ./test/integration/test.mjs",
|
|
27
28
|
"lint": "run-p --continue-on-error lint:*",
|
|
28
29
|
"lint:eslint-docs": "npm run fix:eslint-docs -- --check",
|
|
@@ -51,13 +52,13 @@
|
|
|
51
52
|
"xo"
|
|
52
53
|
],
|
|
53
54
|
"dependencies": {
|
|
54
|
-
"@babel/helper-validator-identifier": "^7.24.
|
|
55
|
+
"@babel/helper-validator-identifier": "^7.24.7",
|
|
55
56
|
"@eslint-community/eslint-utils": "^4.4.0",
|
|
56
57
|
"ci-info": "^4.0.0",
|
|
57
58
|
"clean-regexp": "^1.0.0",
|
|
58
|
-
"core-js-compat": "^3.
|
|
59
|
-
"esquery": "^1.
|
|
60
|
-
"globals": "^15.
|
|
59
|
+
"core-js-compat": "^3.38.1",
|
|
60
|
+
"esquery": "^1.6.0",
|
|
61
|
+
"globals": "^15.9.0",
|
|
61
62
|
"indent-string": "^4.0.0",
|
|
62
63
|
"is-builtin-module": "^3.2.1",
|
|
63
64
|
"jsesc": "^3.0.2",
|
|
@@ -65,41 +66,41 @@
|
|
|
65
66
|
"read-pkg-up": "^7.0.1",
|
|
66
67
|
"regexp-tree": "^0.1.27",
|
|
67
68
|
"regjsparser": "^0.10.0",
|
|
68
|
-
"semver": "^7.6.
|
|
69
|
+
"semver": "^7.6.3",
|
|
69
70
|
"strip-indent": "^3.0.0"
|
|
70
71
|
},
|
|
71
72
|
"devDependencies": {
|
|
72
|
-
"@babel/code-frame": "^7.24.
|
|
73
|
-
"@babel/core": "^7.
|
|
74
|
-
"@babel/eslint-parser": "^7.
|
|
73
|
+
"@babel/code-frame": "^7.24.7",
|
|
74
|
+
"@babel/core": "^7.25.2",
|
|
75
|
+
"@babel/eslint-parser": "^7.25.1",
|
|
75
76
|
"@eslint/eslintrc": "^3.1.0",
|
|
76
77
|
"@lubien/fixture-beta-package": "^1.0.0-beta.1",
|
|
77
|
-
"@typescript-eslint/parser": "^8.
|
|
78
|
+
"@typescript-eslint/parser": "^8.4.0",
|
|
78
79
|
"ava": "^6.1.3",
|
|
79
|
-
"c8": "^
|
|
80
|
+
"c8": "^10.1.2",
|
|
80
81
|
"chalk": "^5.3.0",
|
|
81
82
|
"enquirer": "^2.4.1",
|
|
82
|
-
"eslint": "^9.
|
|
83
|
+
"eslint": "^9.10.0",
|
|
83
84
|
"eslint-ava-rule-tester": "^5.0.1",
|
|
84
85
|
"eslint-doc-generator": "1.7.0",
|
|
85
|
-
"eslint-plugin-eslint-plugin": "^6.
|
|
86
|
+
"eslint-plugin-eslint-plugin": "^6.2.0",
|
|
86
87
|
"eslint-plugin-internal-rules": "file:./scripts/internal-rules/",
|
|
87
|
-
"eslint-remote-tester": "^4.0.
|
|
88
|
+
"eslint-remote-tester": "^4.0.1",
|
|
88
89
|
"eslint-remote-tester-repositories": "^2.0.0",
|
|
89
|
-
"espree": "^10.0
|
|
90
|
+
"espree": "^10.1.0",
|
|
90
91
|
"execa": "^8.0.1",
|
|
91
92
|
"listr": "^0.14.3",
|
|
92
93
|
"lodash-es": "^4.17.21",
|
|
93
|
-
"markdownlint-cli": "^0.
|
|
94
|
+
"markdownlint-cli": "^0.41.0",
|
|
94
95
|
"memoize": "^10.0.0",
|
|
95
|
-
"npm-package-json-lint": "^
|
|
96
|
-
"npm-run-all2": "^6.
|
|
96
|
+
"npm-package-json-lint": "^8.0.0",
|
|
97
|
+
"npm-run-all2": "^6.2.2",
|
|
97
98
|
"outdent": "^0.8.0",
|
|
98
|
-
"pretty-ms": "^9.
|
|
99
|
-
"typescript": "^5.4
|
|
100
|
-
"vue-eslint-parser": "^9.4.
|
|
101
|
-
"xo": "^0.
|
|
102
|
-
"yaml": "^2.
|
|
99
|
+
"pretty-ms": "^9.1.0",
|
|
100
|
+
"typescript": "^5.5.4",
|
|
101
|
+
"vue-eslint-parser": "^9.4.3",
|
|
102
|
+
"xo": "^0.59.3",
|
|
103
|
+
"yaml": "^2.5.1"
|
|
103
104
|
},
|
|
104
105
|
"peerDependencies": {
|
|
105
106
|
"eslint": ">=8.56.0"
|
|
@@ -175,7 +176,8 @@
|
|
|
175
176
|
],
|
|
176
177
|
"eslint-plugin/require-meta-docs-url": "off",
|
|
177
178
|
"eslint-plugin/require-meta-has-suggestions": "off",
|
|
178
|
-
"eslint-plugin/require-meta-schema": "off"
|
|
179
|
+
"eslint-plugin/require-meta-schema": "off",
|
|
180
|
+
"eslint-plugin/require-meta-schema-description": "off"
|
|
179
181
|
}
|
|
180
182
|
}
|
|
181
183
|
]
|
package/readme.md
CHANGED
|
@@ -72,7 +72,7 @@ module.exports = [
|
|
|
72
72
|
|
|
73
73
|
## Usage (legacy: `.eslintrc.*` or `package.json`)
|
|
74
74
|
|
|
75
|
-
Use a [preset config](#preset-configs-eslintrc-or-packagejson) or configure each rule in `package.json`.
|
|
75
|
+
Use a [preset config](#legacy-preset-configs-eslintrc-or-packagejson) or configure each rule in `package.json`.
|
|
76
76
|
|
|
77
77
|
If you don't use the preset, ensure you use the same `env` and `parserOptions` config as below.
|
|
78
78
|
|
|
@@ -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. | ✅ | 🔧 | |
|
|
@@ -189,10 +190,12 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
|
|
|
189
190
|
| [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | ✅ | | 💡 |
|
|
190
191
|
| [prefer-event-target](docs/rules/prefer-event-target.md) | Prefer `EventTarget` over `EventEmitter`. | ✅ | | |
|
|
191
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`. | ✅ | 🔧 | |
|
|
192
194
|
| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()`, `.lastIndexOf()`, and `Array#some()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 |
|
|
193
195
|
| [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | 🔧 | |
|
|
194
196
|
| [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | ✅ | 🔧 | |
|
|
195
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. | ✅ | 🔧 | |
|
|
196
199
|
| [prefer-math-trunc](docs/rules/prefer-math-trunc.md) | Enforce the use of `Math.trunc` instead of bitwise operators. | ✅ | 🔧 | 💡 |
|
|
197
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()`. | ✅ | 🔧 | |
|
|
198
201
|
| [prefer-modern-math-apis](docs/rules/prefer-modern-math-apis.md) | Prefer modern `Math` APIs over legacy patterns. | ✅ | 🔧 | |
|
|
@@ -204,7 +207,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c
|
|
|
204
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. | ✅ | 🔧 | |
|
|
205
208
|
| [prefer-optional-catch-binding](docs/rules/prefer-optional-catch-binding.md) | Prefer omitting the `catch` binding parameter. | ✅ | 🔧 | |
|
|
206
209
|
| [prefer-prototype-methods](docs/rules/prefer-prototype-methods.md) | Prefer borrowing methods from the prototype instead of the instance. | ✅ | 🔧 | |
|
|
207
|
-
| [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()`. | ✅ | 🔧 | |
|
|
208
211
|
| [prefer-reflect-apply](docs/rules/prefer-reflect-apply.md) | Prefer `Reflect.apply()` over `Function#apply()`. | ✅ | 🔧 | |
|
|
209
212
|
| [prefer-regexp-test](docs/rules/prefer-regexp-test.md) | Prefer `RegExp#test()` over `String#match()` and `RegExp#exec()`. | ✅ | 🔧 | 💡 |
|
|
210
213
|
| [prefer-set-has](docs/rules/prefer-set-has.md) | Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. | ✅ | 🔧 | 💡 |
|
|
@@ -239,13 +242,13 @@ See [docs/deprecated-rules.md](docs/deprecated-rules.md)
|
|
|
239
242
|
|
|
240
243
|
## Preset configs (`eslint.config.js`)
|
|
241
244
|
|
|
242
|
-
See the [ESLint docs](https://eslint.org/docs/latest/
|
|
245
|
+
See the [ESLint docs](https://eslint.org/docs/latest/use/configure/configuration-files) for more information about extending config files.
|
|
243
246
|
|
|
244
|
-
**Note**: Preset configs will also enable the correct [language options](https://eslint.org/docs/latest/use/configure/
|
|
247
|
+
**Note**: Preset configs will also enable the correct [language options](https://eslint.org/docs/latest/use/configure/language-options).
|
|
245
248
|
|
|
246
249
|
### Recommended config
|
|
247
250
|
|
|
248
|
-
This plugin exports a
|
|
251
|
+
This plugin exports a `recommended` config that enforces good practices.
|
|
249
252
|
|
|
250
253
|
#### ES Module (Recommended)
|
|
251
254
|
|
|
@@ -282,7 +285,7 @@ module.exports = [
|
|
|
282
285
|
|
|
283
286
|
### All config
|
|
284
287
|
|
|
285
|
-
This plugin exports an
|
|
288
|
+
This plugin exports an `all` that makes use of all rules (except for deprecated ones).
|
|
286
289
|
|
|
287
290
|
#### ES Module (Recommended)
|
|
288
291
|
|
|
@@ -317,15 +320,15 @@ module.exports = [
|
|
|
317
320
|
];
|
|
318
321
|
```
|
|
319
322
|
|
|
320
|
-
##
|
|
323
|
+
## Legacy preset configs (`.eslintrc.*` or `package.json`)
|
|
321
324
|
|
|
322
|
-
See the [ESLint docs](https://eslint.org/docs/
|
|
325
|
+
See the [ESLint docs](https://eslint.org/docs/latest/use/configure/configuration-files-deprecated) for more information about extending deprecated legacy config files.
|
|
323
326
|
|
|
324
|
-
**Note**: Preset configs will also enable the correct [parser options](https://eslint.org/docs/
|
|
327
|
+
**Note**: Preset configs will also enable the correct [parser options](https://eslint.org/docs/latest/use/configure/parser-deprecated) and [environment](https://eslint.org/docs/latest/use/configure/language-options-deprecated).
|
|
325
328
|
|
|
326
|
-
### Recommended config
|
|
329
|
+
### Recommended legacy config
|
|
327
330
|
|
|
328
|
-
This plugin exports a
|
|
331
|
+
This plugin exports a `recommended` legacy config that enforces good practices.
|
|
329
332
|
|
|
330
333
|
```json
|
|
331
334
|
{
|
|
@@ -339,9 +342,9 @@ This plugin exports a [`recommended` config](configs/recommended.js) that enforc
|
|
|
339
342
|
}
|
|
340
343
|
```
|
|
341
344
|
|
|
342
|
-
### All config
|
|
345
|
+
### All legacy config
|
|
343
346
|
|
|
344
|
-
This plugin exports an
|
|
347
|
+
This plugin exports an `all` legacy config that makes use of all rules (except for deprecated ones).
|
|
345
348
|
|
|
346
349
|
```json
|
|
347
350
|
{
|
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
|
+
};
|
|
@@ -271,13 +271,17 @@ function semverComparisonForOperator(operator) {
|
|
|
271
271
|
}[operator];
|
|
272
272
|
}
|
|
273
273
|
|
|
274
|
+
const DEFAULT_OPTIONS = {
|
|
275
|
+
terms: ['todo', 'fixme', 'xxx'],
|
|
276
|
+
ignore: [],
|
|
277
|
+
ignoreDatesOnPullRequests: true,
|
|
278
|
+
allowWarningComments: true,
|
|
279
|
+
};
|
|
280
|
+
|
|
274
281
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
275
282
|
const create = context => {
|
|
276
283
|
const options = {
|
|
277
|
-
|
|
278
|
-
ignore: [],
|
|
279
|
-
ignoreDatesOnPullRequests: true,
|
|
280
|
-
allowWarningComments: true,
|
|
284
|
+
...DEFAULT_OPTIONS,
|
|
281
285
|
date: new Date().toISOString().slice(0, 10),
|
|
282
286
|
...context.options[0],
|
|
283
287
|
};
|
|
@@ -564,7 +568,7 @@ const schema = [
|
|
|
564
568
|
},
|
|
565
569
|
allowWarningComments: {
|
|
566
570
|
type: 'boolean',
|
|
567
|
-
default:
|
|
571
|
+
default: true,
|
|
568
572
|
},
|
|
569
573
|
date: {
|
|
570
574
|
type: 'string',
|
|
@@ -584,6 +588,7 @@ module.exports = {
|
|
|
584
588
|
recommended: true,
|
|
585
589
|
},
|
|
586
590
|
schema,
|
|
591
|
+
defaultOptions: [{...DEFAULT_OPTIONS}],
|
|
587
592
|
messages,
|
|
588
593
|
},
|
|
589
594
|
};
|
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
|
}
|
|
@@ -12,7 +12,7 @@ const {
|
|
|
12
12
|
const MESSAGE_ID_ERROR = 'no-negation-in-equality-check/error';
|
|
13
13
|
const MESSAGE_ID_SUGGESTION = 'no-negation-in-equality-check/suggestion';
|
|
14
14
|
const messages = {
|
|
15
|
-
[MESSAGE_ID_ERROR]: 'Negated expression
|
|
15
|
+
[MESSAGE_ID_ERROR]: 'Negated expression is not allowed in equality check.',
|
|
16
16
|
[MESSAGE_ID_SUGGESTION]: 'Switch to \'{{operator}}\' check.',
|
|
17
17
|
};
|
|
18
18
|
|
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.',
|
|
@@ -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) {
|