eslint-plugin-unicorn 41.0.1 β 42.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/recommended.js +6 -2
- package/package.json +4 -7
- package/readme.md +10 -6
- package/rules/ast/index.js +4 -0
- package/rules/ast/is-empty-node.js +20 -0
- package/rules/no-array-for-each.js +59 -28
- package/rules/no-await-expression-member.js +1 -1
- package/rules/no-empty-file.js +4 -8
- package/rules/no-static-only-class.js +1 -1
- package/rules/no-unreadable-iife.js +48 -0
- package/rules/no-useless-fallback-in-spread.js +1 -1
- package/rules/no-useless-switch-case.js +71 -0
- package/rules/no-useless-undefined.js +13 -0
- package/rules/prefer-modern-dom-apis.js +2 -2
- package/rules/prefer-modern-math-apis.js +138 -0
- package/rules/prefer-native-coercion-functions.js +182 -0
- package/rules/prefer-object-from-entries.js +0 -10
- package/rules/prefer-query-selector.js +2 -2
- package/rules/prefer-string-trim-start-end.js +1 -0
- package/rules/prefer-ternary.js +53 -49
- package/rules/shared/abbreviations.js +8 -0
- package/rules/text-encoding-identifier-case.js +33 -9
- package/rules/utils/get-switch-case-head-location.js +21 -0
package/configs/recommended.js
CHANGED
|
@@ -52,12 +52,14 @@ module.exports = {
|
|
|
52
52
|
'unicorn/no-thenable': 'error',
|
|
53
53
|
'unicorn/no-this-assignment': 'error',
|
|
54
54
|
'unicorn/no-unreadable-array-destructuring': 'error',
|
|
55
|
+
'unicorn/no-unreadable-iife': 'error',
|
|
55
56
|
'unicorn/no-unsafe-regex': 'off',
|
|
56
57
|
'unicorn/no-unused-properties': 'off',
|
|
57
58
|
'unicorn/no-useless-fallback-in-spread': 'error',
|
|
58
59
|
'unicorn/no-useless-length-check': 'error',
|
|
59
60
|
'unicorn/no-useless-promise-resolve-reject': 'error',
|
|
60
61
|
'unicorn/no-useless-spread': 'error',
|
|
62
|
+
'unicorn/no-useless-switch-case': 'error',
|
|
61
63
|
'unicorn/no-useless-undefined': 'error',
|
|
62
64
|
'unicorn/no-zero-fractions': 'error',
|
|
63
65
|
'unicorn/number-literal-case': 'error',
|
|
@@ -79,11 +81,13 @@ module.exports = {
|
|
|
79
81
|
'unicorn/prefer-dom-node-text-content': 'error',
|
|
80
82
|
'unicorn/prefer-export-from': 'error',
|
|
81
83
|
'unicorn/prefer-includes': 'error',
|
|
82
|
-
'unicorn/prefer-json-parse-buffer': '
|
|
84
|
+
'unicorn/prefer-json-parse-buffer': 'off',
|
|
83
85
|
'unicorn/prefer-keyboard-event-key': 'error',
|
|
84
86
|
'unicorn/prefer-math-trunc': 'error',
|
|
85
87
|
'unicorn/prefer-modern-dom-apis': 'error',
|
|
88
|
+
'unicorn/prefer-modern-math-apis': 'error',
|
|
86
89
|
'unicorn/prefer-module': 'error',
|
|
90
|
+
'unicorn/prefer-native-coercion-functions': 'error',
|
|
87
91
|
'unicorn/prefer-negative-index': 'error',
|
|
88
92
|
'unicorn/prefer-node-protocol': 'error',
|
|
89
93
|
'unicorn/prefer-number-properties': 'error',
|
|
@@ -113,7 +117,7 @@ module.exports = {
|
|
|
113
117
|
// See #1396
|
|
114
118
|
'unicorn/require-post-message-target-origin': 'off',
|
|
115
119
|
'unicorn/string-content': 'off',
|
|
116
|
-
'unicorn/template-indent': '
|
|
120
|
+
'unicorn/template-indent': 'error',
|
|
117
121
|
'unicorn/text-encoding-identifier-case': 'error',
|
|
118
122
|
'unicorn/throw-new-error': 'error',
|
|
119
123
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-unicorn",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "42.0.0",
|
|
4
4
|
"description": "Various awesome ESLint rules",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": "sindresorhus/eslint-plugin-unicorn",
|
|
@@ -161,12 +161,9 @@
|
|
|
161
161
|
"files": [
|
|
162
162
|
"rules/**/*.js"
|
|
163
163
|
],
|
|
164
|
-
"
|
|
165
|
-
"internal-rules"
|
|
166
|
-
]
|
|
167
|
-
"rules": {
|
|
168
|
-
"internal-rules/prefer-negative-boolean-attribute": "error"
|
|
169
|
-
}
|
|
164
|
+
"extends": [
|
|
165
|
+
"plugin:internal-rules/all"
|
|
166
|
+
]
|
|
170
167
|
}
|
|
171
168
|
]
|
|
172
169
|
},
|
package/readme.md
CHANGED
|
@@ -69,11 +69,11 @@ Each rule has emojis denoting:
|
|
|
69
69
|
| [new-for-builtins](docs/rules/new-for-builtins.md) | Enforce the use of `new` for all builtins, except `String`, `Number`, `Boolean`, `Symbol` and `BigInt`. | β
| π§ | |
|
|
70
70
|
| [no-abusive-eslint-disable](docs/rules/no-abusive-eslint-disable.md) | Enforce specifying rules to disable in `eslint-disable` comments. | β
| | |
|
|
71
71
|
| [no-array-callback-reference](docs/rules/no-array-callback-reference.md) | Prevent passing a function reference directly to iterator methods. | β
| | π‘ |
|
|
72
|
-
| [no-array-for-each](docs/rules/no-array-for-each.md) | Prefer `forβ¦of` over `Array#forEach(β¦)`. | β
| π§ |
|
|
72
|
+
| [no-array-for-each](docs/rules/no-array-for-each.md) | Prefer `forβ¦of` over `Array#forEach(β¦)`. | β
| π§ | π‘ |
|
|
73
73
|
| [no-array-method-this-argument](docs/rules/no-array-method-this-argument.md) | Disallow using the `this` argument in array methods. | β
| π§ | π‘ |
|
|
74
74
|
| [no-array-push-push](docs/rules/no-array-push-push.md) | Enforce combining multiple `Array#push()` into one call. | β
| π§ | π‘ |
|
|
75
75
|
| [no-array-reduce](docs/rules/no-array-reduce.md) | Disallow `Array#reduce()` and `Array#reduceRight()`. | β
| | |
|
|
76
|
-
| [no-await-expression-member](docs/rules/no-await-expression-member.md) |
|
|
76
|
+
| [no-await-expression-member](docs/rules/no-await-expression-member.md) | Disallow member access from await expression. | β
| π§ | |
|
|
77
77
|
| [no-console-spaces](docs/rules/no-console-spaces.md) | Do not use leading/trailing space between `console.log` parameters. | β
| π§ | |
|
|
78
78
|
| [no-document-cookie](docs/rules/no-document-cookie.md) | Do not use `document.cookie` directly. | β
| | |
|
|
79
79
|
| [no-empty-file](docs/rules/no-empty-file.md) | Disallow empty files. | β
| | |
|
|
@@ -89,16 +89,18 @@ Each rule has emojis denoting:
|
|
|
89
89
|
| [no-null](docs/rules/no-null.md) | Disallow the use of the `null` literal. | β
| π§ | π‘ |
|
|
90
90
|
| [no-object-as-default-parameter](docs/rules/no-object-as-default-parameter.md) | Disallow the use of objects as default parameters. | β
| | |
|
|
91
91
|
| [no-process-exit](docs/rules/no-process-exit.md) | Disallow `process.exit()`. | β
| | |
|
|
92
|
-
| [no-static-only-class](docs/rules/no-static-only-class.md) |
|
|
92
|
+
| [no-static-only-class](docs/rules/no-static-only-class.md) | Disallow classes that only have static members. | β
| π§ | |
|
|
93
93
|
| [no-thenable](docs/rules/no-thenable.md) | Disallow `then` property. | β
| | |
|
|
94
94
|
| [no-this-assignment](docs/rules/no-this-assignment.md) | Disallow assigning `this` to a variable. | β
| | |
|
|
95
95
|
| [no-unreadable-array-destructuring](docs/rules/no-unreadable-array-destructuring.md) | Disallow unreadable array destructuring. | β
| π§ | |
|
|
96
|
+
| [no-unreadable-iife](docs/rules/no-unreadable-iife.md) | Disallow unreadable IIFEs. | β
| | |
|
|
96
97
|
| [no-unsafe-regex](docs/rules/no-unsafe-regex.md) | Disallow unsafe regular expressions. | | | |
|
|
97
98
|
| [no-unused-properties](docs/rules/no-unused-properties.md) | Disallow unused object properties. | | | |
|
|
98
|
-
| [no-useless-fallback-in-spread](docs/rules/no-useless-fallback-in-spread.md) |
|
|
99
|
+
| [no-useless-fallback-in-spread](docs/rules/no-useless-fallback-in-spread.md) | Disallow useless fallback when spreading in object literals. | β
| π§ | |
|
|
99
100
|
| [no-useless-length-check](docs/rules/no-useless-length-check.md) | Disallow useless array length check. | β
| π§ | |
|
|
100
101
|
| [no-useless-promise-resolve-reject](docs/rules/no-useless-promise-resolve-reject.md) | Disallow returning/yielding `Promise.resolve/reject()` in async functions or promise callbacks | β
| π§ | |
|
|
101
102
|
| [no-useless-spread](docs/rules/no-useless-spread.md) | Disallow unnecessary spread. | β
| π§ | |
|
|
103
|
+
| [no-useless-switch-case](docs/rules/no-useless-switch-case.md) | Disallow useless case in switch statements. | β
| | π‘ |
|
|
102
104
|
| [no-useless-undefined](docs/rules/no-useless-undefined.md) | Disallow useless `undefined`. | β
| π§ | |
|
|
103
105
|
| [no-zero-fractions](docs/rules/no-zero-fractions.md) | Disallow number literals with zero fractions or dangling dots. | β
| π§ | |
|
|
104
106
|
| [number-literal-case](docs/rules/number-literal-case.md) | Enforce proper case for numeric literals. | β
| π§ | |
|
|
@@ -119,11 +121,13 @@ Each rule has emojis denoting:
|
|
|
119
121
|
| [prefer-dom-node-text-content](docs/rules/prefer-dom-node-text-content.md) | Prefer `.textContent` over `.innerText`. | β
| | π‘ |
|
|
120
122
|
| [prefer-export-from](docs/rules/prefer-export-from.md) | Prefer `exportβ¦from` when re-exporting. | β
| π§ | π‘ |
|
|
121
123
|
| [prefer-includes](docs/rules/prefer-includes.md) | Prefer `.includes()` over `.indexOf()` and `Array#some()` when checking for existence or non-existence. | β
| π§ | π‘ |
|
|
122
|
-
| [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. |
|
|
124
|
+
| [prefer-json-parse-buffer](docs/rules/prefer-json-parse-buffer.md) | Prefer reading a JSON file as a buffer. | | π§ | |
|
|
123
125
|
| [prefer-keyboard-event-key](docs/rules/prefer-keyboard-event-key.md) | Prefer `KeyboardEvent#key` over `KeyboardEvent#keyCode`. | β
| π§ | |
|
|
124
126
|
| [prefer-math-trunc](docs/rules/prefer-math-trunc.md) | Enforce the use of `Math.trunc` instead of bitwise operators. | β
| π§ | π‘ |
|
|
125
127
|
| [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()`. | β
| π§ | |
|
|
128
|
+
| [prefer-modern-math-apis](docs/rules/prefer-modern-math-apis.md) | Prefer modern `Math` APIs over legacy patterns. | β
| π§ | |
|
|
126
129
|
| [prefer-module](docs/rules/prefer-module.md) | Prefer JavaScript modules (ESM) over CommonJS. | β
| π§ | π‘ |
|
|
130
|
+
| [prefer-native-coercion-functions](docs/rules/prefer-native-coercion-functions.md) | Prefer using `String`, `Number`, `BigInt`, `Boolean`, and `Symbol` directly. | β
| π§ | |
|
|
127
131
|
| [prefer-negative-index](docs/rules/prefer-negative-index.md) | Prefer negative index over `.length - index` for `{String,Array,TypedArray}#slice()`, `Array#splice()` and `Array#at()`. | β
| π§ | |
|
|
128
132
|
| [prefer-node-protocol](docs/rules/prefer-node-protocol.md) | Prefer using the `node:` protocol when importing Node.js builtin modules. | β
| π§ | |
|
|
129
133
|
| [prefer-number-properties](docs/rules/prefer-number-properties.md) | Prefer `Number` static properties over global ones. | β
| π§ | π‘ |
|
|
@@ -150,7 +154,7 @@ Each rule has emojis denoting:
|
|
|
150
154
|
| [require-post-message-target-origin](docs/rules/require-post-message-target-origin.md) | Enforce using the `targetOrigin` argument with `window.postMessage()`. | | | π‘ |
|
|
151
155
|
| [string-content](docs/rules/string-content.md) | Enforce better string content. | | π§ | π‘ |
|
|
152
156
|
| [template-indent](docs/rules/template-indent.md) | Fix whitespace-insensitive template indentation. | β
| π§ | |
|
|
153
|
-
| [text-encoding-identifier-case](docs/rules/text-encoding-identifier-case.md) | Enforce consistent case for text encoding identifiers. | β
|
|
|
157
|
+
| [text-encoding-identifier-case](docs/rules/text-encoding-identifier-case.md) | Enforce consistent case for text encoding identifiers. | β
| π§ | π‘ |
|
|
154
158
|
| [throw-new-error](docs/rules/throw-new-error.md) | Require `new` when throwing an error. | β
| π§ | |
|
|
155
159
|
<!-- /RULES_TABLE -->
|
|
156
160
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
function isEmptyNode(node, additionalEmpty) {
|
|
3
|
+
const {type} = node;
|
|
4
|
+
|
|
5
|
+
if (type === 'BlockStatement') {
|
|
6
|
+
return node.body.every(currentNode => isEmptyNode(currentNode, additionalEmpty));
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
if (type === 'EmptyStatement') {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
if (additionalEmpty && additionalEmpty(node)) {
|
|
14
|
+
return true;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
module.exports = isEmptyNode;
|
|
@@ -6,20 +6,24 @@ const {
|
|
|
6
6
|
isSemicolonToken,
|
|
7
7
|
isClosingParenToken,
|
|
8
8
|
findVariable,
|
|
9
|
+
hasSideEffect,
|
|
9
10
|
} = require('eslint-utils');
|
|
10
11
|
const {methodCallSelector, referenceIdentifierSelector} = require('./selectors/index.js');
|
|
11
12
|
const {extendFixRange} = require('./fix/index.js');
|
|
12
13
|
const needsSemicolon = require('./utils/needs-semicolon.js');
|
|
13
14
|
const shouldAddParenthesesToExpressionStatementExpression = require('./utils/should-add-parentheses-to-expression-statement-expression.js');
|
|
15
|
+
const shouldAddParenthesesToMemberExpressionObject = require('./utils/should-add-parentheses-to-member-expression-object.js');
|
|
14
16
|
const {getParentheses} = require('./utils/parentheses.js');
|
|
15
17
|
const isFunctionSelfUsedInside = require('./utils/is-function-self-used-inside.js');
|
|
16
18
|
const {isNodeMatches} = require('./utils/is-node-matches.js');
|
|
17
19
|
const assertToken = require('./utils/assert-token.js');
|
|
18
|
-
const {fixSpaceAroundKeyword} = require('./fix/index.js');
|
|
20
|
+
const {fixSpaceAroundKeyword, removeParentheses} = require('./fix/index.js');
|
|
19
21
|
|
|
20
|
-
const
|
|
22
|
+
const MESSAGE_ID_ERROR = 'no-array-for-each/error';
|
|
23
|
+
const MESSAGE_ID_SUGGESTION = 'no-array-for-each/suggestion';
|
|
21
24
|
const messages = {
|
|
22
|
-
[
|
|
25
|
+
[MESSAGE_ID_ERROR]: 'Use `forβ¦of` instead of `Array#forEach(β¦)`.',
|
|
26
|
+
[MESSAGE_ID_SUGGESTION]: 'Switch to `forβ¦of`.',
|
|
23
27
|
};
|
|
24
28
|
|
|
25
29
|
const arrayForEachCallSelector = methodCallSelector({
|
|
@@ -36,6 +40,11 @@ const continueAbleNodeTypes = new Set([
|
|
|
36
40
|
'ForInStatement',
|
|
37
41
|
]);
|
|
38
42
|
|
|
43
|
+
const stripChainExpression = node =>
|
|
44
|
+
(node.parent.type === 'ChainExpression' && node.parent.expression === node)
|
|
45
|
+
? node.parent
|
|
46
|
+
: node;
|
|
47
|
+
|
|
39
48
|
function isReturnStatementInContinueAbleNodes(returnStatement, callbackFunction) {
|
|
40
49
|
for (let node = returnStatement; node && node !== callbackFunction; node = node.parent) {
|
|
41
50
|
if (continueAbleNodeTypes.has(node.type)) {
|
|
@@ -73,6 +82,9 @@ function getFixFunction(callExpression, functionInfo, context) {
|
|
|
73
82
|
const parameters = callback.params;
|
|
74
83
|
const array = callExpression.callee.object;
|
|
75
84
|
const {returnStatements} = functionInfo.get(callback);
|
|
85
|
+
const isOptionalArray = callExpression.callee.optional;
|
|
86
|
+
const expressionStatement = stripChainExpression(callExpression).parent;
|
|
87
|
+
const arrayText = sourceCode.getText(array);
|
|
76
88
|
|
|
77
89
|
const getForOfLoopHeadText = () => {
|
|
78
90
|
const [elementText, indexText] = parameters.map(parameter => sourceCode.getText(parameter));
|
|
@@ -84,12 +96,16 @@ function getFixFunction(callExpression, functionInfo, context) {
|
|
|
84
96
|
text += useEntries ? `[${indexText}, ${elementText}]` : elementText;
|
|
85
97
|
text += ' of ';
|
|
86
98
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
99
|
+
const shouldAddParenthesesToArray
|
|
100
|
+
= isParenthesized(array, sourceCode)
|
|
101
|
+
|| (
|
|
102
|
+
// `1?.forEach()` -> `(1).entries()`
|
|
103
|
+
isOptionalArray
|
|
104
|
+
&& useEntries
|
|
105
|
+
&& shouldAddParenthesesToMemberExpressionObject(array, sourceCode)
|
|
106
|
+
);
|
|
91
107
|
|
|
92
|
-
text += arrayText;
|
|
108
|
+
text += shouldAddParenthesesToArray ? `(${arrayText})` : arrayText;
|
|
93
109
|
|
|
94
110
|
if (useEntries) {
|
|
95
111
|
text += '.entries()';
|
|
@@ -193,6 +209,9 @@ function getFixFunction(callExpression, functionInfo, context) {
|
|
|
193
209
|
}
|
|
194
210
|
|
|
195
211
|
return function * (fixer) {
|
|
212
|
+
// `(( foo.forEach(bar => bar) ))`
|
|
213
|
+
yield * removeParentheses(callExpression, fixer, sourceCode);
|
|
214
|
+
|
|
196
215
|
// Replace these with `for (const β¦ of β¦) `
|
|
197
216
|
// foo.forEach(bar => bar)
|
|
198
217
|
// ^^^^^^^^^^^^^^^^^^ (space after `=>` didn't included)
|
|
@@ -228,7 +247,7 @@ function getFixFunction(callExpression, functionInfo, context) {
|
|
|
228
247
|
yield * replaceReturnStatement(returnStatement, fixer);
|
|
229
248
|
}
|
|
230
249
|
|
|
231
|
-
const expressionStatementLastToken = sourceCode.getLastToken(
|
|
250
|
+
const expressionStatementLastToken = sourceCode.getLastToken(expressionStatement);
|
|
232
251
|
// Remove semicolon if it's not needed anymore
|
|
233
252
|
// foo.forEach(bar => {});
|
|
234
253
|
// ^
|
|
@@ -238,6 +257,10 @@ function getFixFunction(callExpression, functionInfo, context) {
|
|
|
238
257
|
|
|
239
258
|
yield * fixSpaceAroundKeyword(fixer, callExpression.parent, sourceCode);
|
|
240
259
|
|
|
260
|
+
if (isOptionalArray) {
|
|
261
|
+
yield fixer.insertTextBefore(callExpression, `if (${arrayText}) `);
|
|
262
|
+
}
|
|
263
|
+
|
|
241
264
|
// Prevent possible variable conflicts
|
|
242
265
|
yield * extendFixRange(fixer, callExpression.parent.range);
|
|
243
266
|
};
|
|
@@ -303,25 +326,13 @@ function isFunctionParameterVariableReassigned(callbackFunction, context) {
|
|
|
303
326
|
}
|
|
304
327
|
|
|
305
328
|
function isFixable(callExpression, {scope, functionInfo, allIdentifiers, context}) {
|
|
306
|
-
const sourceCode = context.getSourceCode();
|
|
307
329
|
// Check `CallExpression`
|
|
308
|
-
if (
|
|
309
|
-
callExpression.optional
|
|
310
|
-
|| isParenthesized(callExpression, sourceCode)
|
|
311
|
-
|| callExpression.arguments.length !== 1
|
|
312
|
-
) {
|
|
330
|
+
if (callExpression.optional || callExpression.arguments.length !== 1) {
|
|
313
331
|
return false;
|
|
314
332
|
}
|
|
315
333
|
|
|
316
|
-
// Check `
|
|
317
|
-
if (callExpression.parent.type !== 'ExpressionStatement') {
|
|
318
|
-
return false;
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
// Check `CallExpression.callee`
|
|
322
|
-
// Because of `ChainExpression` wrapper, `foo?.forEach()` is already failed on previous check keep this just for safety
|
|
323
|
-
/* c8 ignore next 3 */
|
|
324
|
-
if (callExpression.callee.optional) {
|
|
334
|
+
// Check ancestors, we only fix `ExpressionStatement`
|
|
335
|
+
if (stripChainExpression(callExpression).parent.type !== 'ExpressionStatement') {
|
|
325
336
|
return false;
|
|
326
337
|
}
|
|
327
338
|
|
|
@@ -373,6 +384,7 @@ const create = context => {
|
|
|
373
384
|
const callExpressions = [];
|
|
374
385
|
const allIdentifiers = [];
|
|
375
386
|
const functionInfo = new Map();
|
|
387
|
+
const sourceCode = context.getSourceCode();
|
|
376
388
|
|
|
377
389
|
return {
|
|
378
390
|
':function'(node) {
|
|
@@ -405,13 +417,31 @@ const create = context => {
|
|
|
405
417
|
},
|
|
406
418
|
* 'Program:exit'() {
|
|
407
419
|
for (const {node, scope} of callExpressions) {
|
|
420
|
+
// TODO: Rename this to iteratable
|
|
421
|
+
const array = node.callee;
|
|
422
|
+
|
|
408
423
|
const problem = {
|
|
409
|
-
node:
|
|
410
|
-
messageId:
|
|
424
|
+
node: array.property,
|
|
425
|
+
messageId: MESSAGE_ID_ERROR,
|
|
411
426
|
};
|
|
412
427
|
|
|
413
|
-
if (isFixable(node, {scope, allIdentifiers, functionInfo, context})) {
|
|
414
|
-
problem
|
|
428
|
+
if (!isFixable(node, {scope, allIdentifiers, functionInfo, context})) {
|
|
429
|
+
yield problem;
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const shouldUseSuggestion = array.optional && hasSideEffect(array, sourceCode);
|
|
434
|
+
const fix = getFixFunction(node, functionInfo, context);
|
|
435
|
+
|
|
436
|
+
if (shouldUseSuggestion) {
|
|
437
|
+
problem.suggest = [
|
|
438
|
+
{
|
|
439
|
+
messageId: MESSAGE_ID_SUGGESTION,
|
|
440
|
+
fix,
|
|
441
|
+
},
|
|
442
|
+
];
|
|
443
|
+
} else {
|
|
444
|
+
problem.fix = fix;
|
|
415
445
|
}
|
|
416
446
|
|
|
417
447
|
yield problem;
|
|
@@ -429,6 +459,7 @@ module.exports = {
|
|
|
429
459
|
description: 'Prefer `forβ¦of` over `Array#forEach(β¦)`.',
|
|
430
460
|
},
|
|
431
461
|
fixable: 'code',
|
|
462
|
+
hasSuggestions: true,
|
|
432
463
|
messages,
|
|
433
464
|
},
|
|
434
465
|
};
|
package/rules/no-empty-file.js
CHANGED
|
@@ -1,17 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
const {isEmptyNode} = require('./ast/index.js');
|
|
2
3
|
|
|
3
4
|
const MESSAGE_ID = 'no-empty-file';
|
|
4
5
|
const messages = {
|
|
5
6
|
[MESSAGE_ID]: 'Empty files are not allowed.',
|
|
6
7
|
};
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
|
|
10
|
-
(node.type === 'Program' || node.type === 'BlockStatement')
|
|
11
|
-
&& node.body.every(currentNode => isEmpty(currentNode))
|
|
12
|
-
)
|
|
13
|
-
|| node.type === 'EmptyStatement'
|
|
14
|
-
|| (node.type === 'ExpressionStatement' && 'directive' in node);
|
|
9
|
+
const isDirective = node => node.type === 'ExpressionStatement' && 'directive' in node;
|
|
10
|
+
const isEmpty = node => isEmptyNode(node, isDirective);
|
|
15
11
|
|
|
16
12
|
const isTripleSlashDirective = node =>
|
|
17
13
|
node.type === 'Line' && node.value.startsWith('/');
|
|
@@ -29,7 +25,7 @@ const create = context => {
|
|
|
29
25
|
|
|
30
26
|
return {
|
|
31
27
|
Program(node) {
|
|
32
|
-
if (!isEmpty(node)) {
|
|
28
|
+
if (node.body.some(node => !isEmpty(node))) {
|
|
33
29
|
return;
|
|
34
30
|
}
|
|
35
31
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const {
|
|
3
|
+
isParenthesized,
|
|
4
|
+
getParenthesizedRange,
|
|
5
|
+
} = require('./utils/parentheses.js');
|
|
6
|
+
const toLocation = require('./utils/to-location.js');
|
|
7
|
+
|
|
8
|
+
const MESSAGE_ID_ERROR = 'no-unreadable-iife';
|
|
9
|
+
const messages = {
|
|
10
|
+
[MESSAGE_ID_ERROR]: 'IIFE with parenthesized arrow function body is considered unreadable.',
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const selector = [
|
|
14
|
+
'CallExpression',
|
|
15
|
+
' > ',
|
|
16
|
+
'ArrowFunctionExpression.callee',
|
|
17
|
+
' > ',
|
|
18
|
+
':not(BlockStatement).body',
|
|
19
|
+
].join('');
|
|
20
|
+
|
|
21
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
22
|
+
const create = context => ({
|
|
23
|
+
[selector](node) {
|
|
24
|
+
const sourceCode = context.getSourceCode();
|
|
25
|
+
if (!isParenthesized(node, sourceCode)) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return {
|
|
30
|
+
node,
|
|
31
|
+
loc: toLocation(getParenthesizedRange(node, sourceCode), sourceCode),
|
|
32
|
+
messageId: MESSAGE_ID_ERROR,
|
|
33
|
+
};
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
38
|
+
module.exports = {
|
|
39
|
+
create,
|
|
40
|
+
meta: {
|
|
41
|
+
type: 'suggestion',
|
|
42
|
+
docs: {
|
|
43
|
+
description: 'Disallow unreadable IIFEs.',
|
|
44
|
+
},
|
|
45
|
+
hasSuggestions: false,
|
|
46
|
+
messages,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -62,7 +62,7 @@ module.exports = {
|
|
|
62
62
|
meta: {
|
|
63
63
|
type: 'suggestion',
|
|
64
64
|
docs: {
|
|
65
|
-
description: '
|
|
65
|
+
description: 'Disallow useless fallback when spreading in object literals.',
|
|
66
66
|
},
|
|
67
67
|
fixable: 'code',
|
|
68
68
|
messages,
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const {isEmptyNode} = require('./ast/index.js');
|
|
3
|
+
const getSwitchCaseHeadLocation = require('./utils/get-switch-case-head-location.js');
|
|
4
|
+
|
|
5
|
+
const MESSAGE_ID_ERROR = 'no-useless-switch-case/error';
|
|
6
|
+
const MESSAGE_ID_SUGGESTION = 'no-useless-switch-case/suggestion';
|
|
7
|
+
const messages = {
|
|
8
|
+
[MESSAGE_ID_ERROR]: 'Useless case in switch statement.',
|
|
9
|
+
[MESSAGE_ID_SUGGESTION]: 'Remove this case.',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const isEmptySwitchCase = node => node.consequent.every(node => isEmptyNode(node));
|
|
13
|
+
|
|
14
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
15
|
+
const create = context => ({
|
|
16
|
+
* 'SwitchStatement[cases.length>1]'(switchStatement) {
|
|
17
|
+
const {cases} = switchStatement;
|
|
18
|
+
|
|
19
|
+
// TypeScript allows multiple `default` cases
|
|
20
|
+
const defaultCases = cases.filter(switchCase => switchCase.test === null);
|
|
21
|
+
if (defaultCases.length !== 1) {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const [defaultCase] = defaultCases;
|
|
26
|
+
|
|
27
|
+
// We only check cases where the last case is the `default` case
|
|
28
|
+
if (defaultCase !== cases[cases.length - 1]) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const uselessCases = [];
|
|
33
|
+
|
|
34
|
+
for (let index = cases.length - 2; index >= 0; index--) {
|
|
35
|
+
const node = cases[index];
|
|
36
|
+
if (isEmptySwitchCase(node)) {
|
|
37
|
+
uselessCases.unshift(node);
|
|
38
|
+
} else {
|
|
39
|
+
break;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const node of uselessCases) {
|
|
44
|
+
yield {
|
|
45
|
+
node,
|
|
46
|
+
loc: getSwitchCaseHeadLocation(node, context.getSourceCode()),
|
|
47
|
+
messageId: MESSAGE_ID_ERROR,
|
|
48
|
+
suggest: [
|
|
49
|
+
{
|
|
50
|
+
messageId: MESSAGE_ID_SUGGESTION,
|
|
51
|
+
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
52
|
+
fix: fixer => fixer.remove(node),
|
|
53
|
+
},
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
61
|
+
module.exports = {
|
|
62
|
+
create,
|
|
63
|
+
meta: {
|
|
64
|
+
type: 'suggestion',
|
|
65
|
+
docs: {
|
|
66
|
+
description: 'Disallow useless case in switch statements.',
|
|
67
|
+
},
|
|
68
|
+
hasSuggestions: true,
|
|
69
|
+
messages,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
@@ -91,6 +91,13 @@ const getFunction = scope => {
|
|
|
91
91
|
}
|
|
92
92
|
};
|
|
93
93
|
|
|
94
|
+
const isFunctionBindCall = node =>
|
|
95
|
+
!node.optional
|
|
96
|
+
&& node.callee.type === 'MemberExpression'
|
|
97
|
+
&& !node.callee.computed
|
|
98
|
+
&& node.callee.property.type === 'Identifier'
|
|
99
|
+
&& node.callee.property.name === 'bind';
|
|
100
|
+
|
|
94
101
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
95
102
|
const create = context => {
|
|
96
103
|
const listener = (fix, checkFunctionReturnType) => node => {
|
|
@@ -142,6 +149,12 @@ const create = context => {
|
|
|
142
149
|
}
|
|
143
150
|
|
|
144
151
|
const argumentNodes = node.arguments;
|
|
152
|
+
|
|
153
|
+
// Ignore arguments in `Function#bind()`, but not `this` argument
|
|
154
|
+
if (isFunctionBindCall(node) && argumentNodes.length !== 1) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
145
158
|
const undefinedArguments = [];
|
|
146
159
|
for (let index = argumentNodes.length - 1; index >= 0; index--) {
|
|
147
160
|
const node = argumentNodes[index];
|
|
@@ -23,7 +23,7 @@ const replaceChildOrInsertBeforeSelector = [
|
|
|
23
23
|
'[callee.object.type="Identifier"]',
|
|
24
24
|
].join('');
|
|
25
25
|
|
|
26
|
-
const
|
|
26
|
+
const disallowedMethods = new Map([
|
|
27
27
|
['replaceChild', 'replaceWith'],
|
|
28
28
|
['insertBefore', 'before'],
|
|
29
29
|
]);
|
|
@@ -32,7 +32,7 @@ const checkForReplaceChildOrInsertBefore = (context, node) => {
|
|
|
32
32
|
const method = node.callee.property.name;
|
|
33
33
|
const parentNode = node.callee.object.name;
|
|
34
34
|
const [newChildNode, oldChildNode] = node.arguments.map(({name}) => name);
|
|
35
|
-
const preferredMethod =
|
|
35
|
+
const preferredMethod = disallowedMethods.get(method);
|
|
36
36
|
|
|
37
37
|
const fix = isValueNotUsable(node)
|
|
38
38
|
? fixer => fixer.replaceText(
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const {getParenthesizedText} = require('./utils/parentheses.js');
|
|
3
|
+
|
|
4
|
+
const MESSAGE_ID = 'prefer-modern-math-apis';
|
|
5
|
+
const messages = {
|
|
6
|
+
[MESSAGE_ID]: 'Prefer `{{replacement}}` over `{{description}}`.',
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const isMathProperty = (node, property) =>
|
|
10
|
+
node.type === 'MemberExpression'
|
|
11
|
+
&& !node.optional
|
|
12
|
+
&& !node.computed
|
|
13
|
+
&& node.object.type === 'Identifier'
|
|
14
|
+
&& node.object.name === 'Math'
|
|
15
|
+
&& node.property.type === 'Identifier'
|
|
16
|
+
&& node.property.name === property;
|
|
17
|
+
|
|
18
|
+
const isMathMethodCall = (node, method) =>
|
|
19
|
+
node.type === 'CallExpression'
|
|
20
|
+
&& !node.optional
|
|
21
|
+
&& isMathProperty(node.callee, method)
|
|
22
|
+
&& node.arguments.length === 1
|
|
23
|
+
&& node.arguments[0].type !== 'SpreadElement';
|
|
24
|
+
|
|
25
|
+
// `Math.log(x) * Math.LOG10E` -> `Math.log10(x)`
|
|
26
|
+
// `Math.LOG10E * Math.log(x)` -> `Math.log10(x)`
|
|
27
|
+
// `Math.log(x) * Math.LOG2E` -> `Math.log2(x)`
|
|
28
|
+
// `Math.LOG2E * Math.log(x)` -> `Math.log2(x)`
|
|
29
|
+
function createLogCallTimesConstantCheck({constantName, replacementMethod}) {
|
|
30
|
+
const replacement = `Math.${replacementMethod}(β¦)`;
|
|
31
|
+
|
|
32
|
+
return function (node, context) {
|
|
33
|
+
if (!(node.type === 'BinaryExpression' && node.operator === '*')) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
let mathLogCall;
|
|
38
|
+
let description;
|
|
39
|
+
if (isMathMethodCall(node.left, 'log') && isMathProperty(node.right, constantName)) {
|
|
40
|
+
mathLogCall = node.left;
|
|
41
|
+
description = `Math.log(β¦) * Math.${constantName}`;
|
|
42
|
+
} else if (isMathMethodCall(node.right, 'log') && isMathProperty(node.left, constantName)) {
|
|
43
|
+
mathLogCall = node.right;
|
|
44
|
+
description = `Math.${constantName} * Math.log(β¦)`;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!mathLogCall) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const [valueNode] = mathLogCall.arguments;
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
node,
|
|
55
|
+
messageId: MESSAGE_ID,
|
|
56
|
+
data: {
|
|
57
|
+
replacement,
|
|
58
|
+
description,
|
|
59
|
+
},
|
|
60
|
+
fix: fixer => fixer.replaceText(node, `Math.${replacementMethod}(${getParenthesizedText(valueNode, context.getSourceCode())})`),
|
|
61
|
+
};
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// `Math.log(x) / Math.LN10` -> `Math.log10(x)`
|
|
66
|
+
// `Math.log(x) / Math.LN2` -> `Math.log2(x)`
|
|
67
|
+
function createLogCallDivideConstantCheck({constantName, replacementMethod}) {
|
|
68
|
+
const message = {
|
|
69
|
+
messageId: MESSAGE_ID,
|
|
70
|
+
data: {
|
|
71
|
+
replacement: `Math.${replacementMethod}(β¦)`,
|
|
72
|
+
description: `Math.log(β¦) / Math.${constantName}`,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return function (node, context) {
|
|
77
|
+
if (
|
|
78
|
+
!(
|
|
79
|
+
node.type === 'BinaryExpression'
|
|
80
|
+
&& node.operator === '/'
|
|
81
|
+
&& isMathMethodCall(node.left, 'log')
|
|
82
|
+
&& isMathProperty(node.right, constantName)
|
|
83
|
+
)
|
|
84
|
+
) {
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const [valueNode] = node.left.arguments;
|
|
89
|
+
|
|
90
|
+
return {
|
|
91
|
+
...message,
|
|
92
|
+
node,
|
|
93
|
+
fix: fixer => fixer.replaceText(node, `Math.${replacementMethod}(${getParenthesizedText(valueNode, context.getSourceCode())})`),
|
|
94
|
+
};
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const checkFunctions = [
|
|
99
|
+
createLogCallTimesConstantCheck({constantName: 'LOG10E', replacementMethod: 'log10'}),
|
|
100
|
+
createLogCallTimesConstantCheck({constantName: 'LOG2E', replacementMethod: 'log2'}),
|
|
101
|
+
createLogCallDivideConstantCheck({constantName: 'LN10', replacementMethod: 'log10'}),
|
|
102
|
+
createLogCallDivideConstantCheck({constantName: 'LN2', replacementMethod: 'log2'}),
|
|
103
|
+
];
|
|
104
|
+
|
|
105
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
106
|
+
const create = context => {
|
|
107
|
+
const nodes = [];
|
|
108
|
+
|
|
109
|
+
return {
|
|
110
|
+
BinaryExpression(node) {
|
|
111
|
+
nodes.push(node);
|
|
112
|
+
},
|
|
113
|
+
* 'Program:exit'() {
|
|
114
|
+
for (const node of nodes) {
|
|
115
|
+
for (const getProblem of checkFunctions) {
|
|
116
|
+
const problem = getProblem(node, context);
|
|
117
|
+
|
|
118
|
+
if (problem) {
|
|
119
|
+
yield problem;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
128
|
+
module.exports = {
|
|
129
|
+
create,
|
|
130
|
+
meta: {
|
|
131
|
+
type: 'suggestion',
|
|
132
|
+
docs: {
|
|
133
|
+
description: 'Prefer modern `Math` APIs over legacy patterns.',
|
|
134
|
+
},
|
|
135
|
+
fixable: 'code',
|
|
136
|
+
messages,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const {getFunctionHeadLocation, getFunctionNameWithKind} = require('eslint-utils');
|
|
3
|
+
const {not} = require('./selectors/index.js');
|
|
4
|
+
|
|
5
|
+
const MESSAGE_ID = 'prefer-native-coercion-functions';
|
|
6
|
+
const messages = {
|
|
7
|
+
[MESSAGE_ID]: '{{functionNameWithKind}} is equivalent to `{{replacementFunction}}`. Use `{{replacementFunction}}` directly.',
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const nativeCoercionFunctionNames = new Set(['String', 'Number', 'BigInt', 'Boolean', 'Symbol']);
|
|
11
|
+
const arrayMethodsWithBooleanCallback = new Set(['every', 'filter', 'find', 'findIndex', 'some']);
|
|
12
|
+
|
|
13
|
+
const isNativeCoercionFunctionCall = (node, firstArgumentName) =>
|
|
14
|
+
node
|
|
15
|
+
&& node.type === 'CallExpression'
|
|
16
|
+
&& !node.optional
|
|
17
|
+
&& node.callee.type === 'Identifier'
|
|
18
|
+
&& nativeCoercionFunctionNames.has(node.callee.name)
|
|
19
|
+
&& node.arguments[0]
|
|
20
|
+
&& node.arguments[0].type === 'Identifier'
|
|
21
|
+
&& node.arguments[0].name === firstArgumentName;
|
|
22
|
+
|
|
23
|
+
const isIdentityFunction = node =>
|
|
24
|
+
(
|
|
25
|
+
// `v => v`
|
|
26
|
+
node.type === 'ArrowFunctionExpression'
|
|
27
|
+
&& node.body.type === 'Identifier'
|
|
28
|
+
&& node.body.name === node.params[0].name
|
|
29
|
+
)
|
|
30
|
+
|| (
|
|
31
|
+
// `(v) => {return v;}`
|
|
32
|
+
// `function (v) {return v;}`
|
|
33
|
+
node.body.type === 'BlockStatement'
|
|
34
|
+
&& node.body.body.length === 1
|
|
35
|
+
&& node.body.body[0].type === 'ReturnStatement'
|
|
36
|
+
&& node.body.body[0].argument
|
|
37
|
+
&& node.body.body[0].argument.type === 'Identifier'
|
|
38
|
+
&& node.body.body[0].argument.name === node.params[0].name
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
const isArrayIdentityCallback = node =>
|
|
42
|
+
isIdentityFunction(node)
|
|
43
|
+
&& node.parent.type === 'CallExpression'
|
|
44
|
+
&& !node.parent.optional
|
|
45
|
+
&& node.parent.arguments[0] === node
|
|
46
|
+
&& node.parent.callee.type === 'MemberExpression'
|
|
47
|
+
&& !node.parent.callee.computed
|
|
48
|
+
&& !node.parent.callee.optional
|
|
49
|
+
&& node.parent.callee.property.type === 'Identifier'
|
|
50
|
+
&& arrayMethodsWithBooleanCallback.has(node.parent.callee.property.name);
|
|
51
|
+
|
|
52
|
+
function getCallExpression(node) {
|
|
53
|
+
const firstParameterName = node.params[0].name;
|
|
54
|
+
|
|
55
|
+
// `(v) => String(v)`
|
|
56
|
+
if (
|
|
57
|
+
node.type === 'ArrowFunctionExpression'
|
|
58
|
+
&& isNativeCoercionFunctionCall(node.body, firstParameterName)
|
|
59
|
+
) {
|
|
60
|
+
return node.body;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// `(v) => {return String(v);}`
|
|
64
|
+
// `function (v) {return String(v);}`
|
|
65
|
+
if (
|
|
66
|
+
node.body.type === 'BlockStatement'
|
|
67
|
+
&& node.body.body.length === 1
|
|
68
|
+
&& node.body.body[0].type === 'ReturnStatement'
|
|
69
|
+
&& isNativeCoercionFunctionCall(node.body.body[0].argument, firstParameterName)
|
|
70
|
+
) {
|
|
71
|
+
return node.body.body[0].argument;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const functionsSelector = [
|
|
76
|
+
':function',
|
|
77
|
+
'[async!=true]',
|
|
78
|
+
'[generator!=true]',
|
|
79
|
+
'[params.length>0]',
|
|
80
|
+
'[params.0.type="Identifier"]',
|
|
81
|
+
not([
|
|
82
|
+
'MethodDefinition[kind="constructor"] > .value',
|
|
83
|
+
'MethodDefinition[kind="set"] > .value',
|
|
84
|
+
'Property[kind="set"] > .value',
|
|
85
|
+
]),
|
|
86
|
+
].join('');
|
|
87
|
+
|
|
88
|
+
function getArrayCallbackProblem(node) {
|
|
89
|
+
if (!isArrayIdentityCallback(node)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return {
|
|
94
|
+
replacementFunction: 'Boolean',
|
|
95
|
+
fix: fixer => fixer.replaceText(node, 'Boolean'),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function getCoercionFunctionProblem(node) {
|
|
100
|
+
const callExpression = getCallExpression(node);
|
|
101
|
+
|
|
102
|
+
if (!callExpression) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const {name} = callExpression.callee;
|
|
107
|
+
|
|
108
|
+
const problem = {replacementFunction: name};
|
|
109
|
+
|
|
110
|
+
if (node.type === 'FunctionDeclaration' || callExpression.arguments.length !== 1) {
|
|
111
|
+
return problem;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
115
|
+
problem.fix = fixer => {
|
|
116
|
+
let text = name;
|
|
117
|
+
|
|
118
|
+
if (
|
|
119
|
+
node.parent.type === 'Property'
|
|
120
|
+
&& node.parent.method
|
|
121
|
+
&& node.parent.value === node
|
|
122
|
+
) {
|
|
123
|
+
text = `: ${text}`;
|
|
124
|
+
} else if (node.parent.type === 'MethodDefinition') {
|
|
125
|
+
text = ` = ${text};`;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return fixer.replaceText(node, text);
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
return problem;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
135
|
+
const create = context => ({
|
|
136
|
+
[functionsSelector](node) {
|
|
137
|
+
let problem = getArrayCallbackProblem(node) || getCoercionFunctionProblem(node);
|
|
138
|
+
|
|
139
|
+
if (!problem) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const sourceCode = context.getSourceCode();
|
|
144
|
+
const {replacementFunction, fix} = problem;
|
|
145
|
+
|
|
146
|
+
problem = {
|
|
147
|
+
node,
|
|
148
|
+
loc: getFunctionHeadLocation(node, sourceCode),
|
|
149
|
+
messageId: MESSAGE_ID,
|
|
150
|
+
data: {
|
|
151
|
+
functionNameWithKind: getFunctionNameWithKind(node, sourceCode),
|
|
152
|
+
replacementFunction,
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/*
|
|
157
|
+
We do not fix if there are:
|
|
158
|
+
- Comments: No proper place to put them.
|
|
159
|
+
- Extra parameters: Removing them may break types.
|
|
160
|
+
*/
|
|
161
|
+
if (!fix || node.params.length !== 1 || sourceCode.getCommentsInside(node).length > 0) {
|
|
162
|
+
return problem;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
problem.fix = fix;
|
|
166
|
+
|
|
167
|
+
return problem;
|
|
168
|
+
},
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
172
|
+
module.exports = {
|
|
173
|
+
create,
|
|
174
|
+
meta: {
|
|
175
|
+
type: 'suggestion',
|
|
176
|
+
docs: {
|
|
177
|
+
description: 'Prefer using `String`, `Number`, `BigInt`, `Boolean`, and `Symbol` directly.',
|
|
178
|
+
},
|
|
179
|
+
fixable: 'code',
|
|
180
|
+
messages,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
@@ -183,10 +183,6 @@ function create(context) {
|
|
|
183
183
|
|
|
184
184
|
for (const {selector, test, getProperty} of fixableArrayReduceCases) {
|
|
185
185
|
listeners[selector] = node => {
|
|
186
|
-
// If this listener exits without adding a fix, the `arrayReduceWithEmptyObject` listener
|
|
187
|
-
// should still add it into the `arrayReduce` map. To be safer, add it here too.
|
|
188
|
-
arrayReduce.set(node, undefined);
|
|
189
|
-
|
|
190
186
|
const [callbackFunction] = node.arguments;
|
|
191
187
|
if (!test(callbackFunction)) {
|
|
192
188
|
return;
|
|
@@ -211,12 +207,6 @@ function create(context) {
|
|
|
211
207
|
};
|
|
212
208
|
}
|
|
213
209
|
|
|
214
|
-
listeners[arrayReduceWithEmptyObject] = node => {
|
|
215
|
-
if (!arrayReduce.has(node)) {
|
|
216
|
-
arrayReduce.set(node, undefined);
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
|
|
220
210
|
listeners['Program:exit'] = () => {
|
|
221
211
|
for (const [node, fix] of arrayReduce.entries()) {
|
|
222
212
|
context.report({
|
|
@@ -14,7 +14,7 @@ const selector = [
|
|
|
14
14
|
notDomNodeSelector('callee.object'),
|
|
15
15
|
].join('');
|
|
16
16
|
|
|
17
|
-
const
|
|
17
|
+
const disallowedIdentifierNames = new Map([
|
|
18
18
|
['getElementById', 'querySelector'],
|
|
19
19
|
['getElementsByClassName', 'querySelectorAll'],
|
|
20
20
|
['getElementsByTagName', 'querySelectorAll'],
|
|
@@ -103,7 +103,7 @@ const fix = (node, identifierName, preferredSelector) => {
|
|
|
103
103
|
const create = () => ({
|
|
104
104
|
[selector](node) {
|
|
105
105
|
const method = node.callee.property.name;
|
|
106
|
-
const preferredSelector =
|
|
106
|
+
const preferredSelector = disallowedIdentifierNames.get(method);
|
|
107
107
|
|
|
108
108
|
const problem = {
|
|
109
109
|
node: node.callee.property,
|
package/rules/prefer-ternary.js
CHANGED
|
@@ -194,61 +194,65 @@ const create = context => {
|
|
|
194
194
|
return;
|
|
195
195
|
}
|
|
196
196
|
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
return {
|
|
200
|
-
node,
|
|
201
|
-
messageId,
|
|
202
|
-
* fix(fixer) {
|
|
203
|
-
const testText = getText(node.test);
|
|
204
|
-
const consequentText = typeof result.consequent === 'string'
|
|
205
|
-
? result.consequent
|
|
206
|
-
: getText(result.consequent);
|
|
207
|
-
const alternateText = typeof result.alternate === 'string'
|
|
208
|
-
? result.alternate
|
|
209
|
-
: getText(result.alternate);
|
|
210
|
-
|
|
211
|
-
let {type, before, after} = result;
|
|
212
|
-
|
|
213
|
-
let generateNewVariables = false;
|
|
214
|
-
if (type === 'ThrowStatement') {
|
|
215
|
-
const scopes = getScopes(scope);
|
|
216
|
-
const errorName = avoidCapture('error', scopes, isSafeName);
|
|
217
|
-
|
|
218
|
-
for (const scope of scopes) {
|
|
219
|
-
if (!scopeToNamesGeneratedByFixer.has(scope)) {
|
|
220
|
-
scopeToNamesGeneratedByFixer.set(scope, new Set());
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
|
|
224
|
-
generatedNames.add(errorName);
|
|
225
|
-
}
|
|
197
|
+
const problem = {node, messageId};
|
|
226
198
|
|
|
227
|
-
|
|
199
|
+
// Don't fix if there are comments
|
|
200
|
+
if (sourceCode.getCommentsInside(node).length > 0) {
|
|
201
|
+
return problem;
|
|
202
|
+
}
|
|
228
203
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
204
|
+
const scope = context.getScope();
|
|
205
|
+
problem.fix = function * (fixer) {
|
|
206
|
+
const testText = getText(node.test);
|
|
207
|
+
const consequentText = typeof result.consequent === 'string'
|
|
208
|
+
? result.consequent
|
|
209
|
+
: getText(result.consequent);
|
|
210
|
+
const alternateText = typeof result.alternate === 'string'
|
|
211
|
+
? result.alternate
|
|
212
|
+
: getText(result.alternate);
|
|
213
|
+
|
|
214
|
+
let {type, before, after} = result;
|
|
215
|
+
|
|
216
|
+
let generateNewVariables = false;
|
|
217
|
+
if (type === 'ThrowStatement') {
|
|
218
|
+
const scopes = getScopes(scope);
|
|
219
|
+
const errorName = avoidCapture('error', scopes, isSafeName);
|
|
220
|
+
|
|
221
|
+
for (const scope of scopes) {
|
|
222
|
+
if (!scopeToNamesGeneratedByFixer.has(scope)) {
|
|
223
|
+
scopeToNamesGeneratedByFixer.set(scope, new Set());
|
|
224
|
+
}
|
|
237
225
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
const shouldAddSemicolonBefore = needsSemicolon(tokenBefore, sourceCode, fixed);
|
|
241
|
-
if (shouldAddSemicolonBefore) {
|
|
242
|
-
fixed = `;${fixed}`;
|
|
226
|
+
const generatedNames = scopeToNamesGeneratedByFixer.get(scope);
|
|
227
|
+
generatedNames.add(errorName);
|
|
243
228
|
}
|
|
244
229
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
230
|
+
const indentString = getIndentString(node, sourceCode);
|
|
231
|
+
|
|
232
|
+
after = after
|
|
233
|
+
.replace('{{INDENT_STRING}}', indentString)
|
|
234
|
+
.replace('{{ERROR_NAME}}', errorName);
|
|
235
|
+
before = before
|
|
236
|
+
.replace('{{INDENT_STRING}}', indentString)
|
|
237
|
+
.replace('{{ERROR_NAME}}', errorName);
|
|
238
|
+
generateNewVariables = true;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
let fixed = `${before}${testText} ? ${consequentText} : ${alternateText}${after}`;
|
|
242
|
+
const tokenBefore = sourceCode.getTokenBefore(node);
|
|
243
|
+
const shouldAddSemicolonBefore = needsSemicolon(tokenBefore, sourceCode, fixed);
|
|
244
|
+
if (shouldAddSemicolonBefore) {
|
|
245
|
+
fixed = `;${fixed}`;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
yield fixer.replaceText(node, fixed);
|
|
249
|
+
|
|
250
|
+
if (generateNewVariables) {
|
|
251
|
+
yield * extendFixRange(fixer, sourceCode.ast.range);
|
|
252
|
+
}
|
|
251
253
|
};
|
|
254
|
+
|
|
255
|
+
return problem;
|
|
252
256
|
},
|
|
253
257
|
};
|
|
254
258
|
};
|
|
@@ -59,6 +59,9 @@ module.exports.defaultReplacements = {
|
|
|
59
59
|
dirs: {
|
|
60
60
|
directories: true,
|
|
61
61
|
},
|
|
62
|
+
dist: {
|
|
63
|
+
distribution: true,
|
|
64
|
+
},
|
|
62
65
|
doc: {
|
|
63
66
|
document: true,
|
|
64
67
|
},
|
|
@@ -66,6 +69,11 @@ module.exports.defaultReplacements = {
|
|
|
66
69
|
documentation: true,
|
|
67
70
|
documents: true,
|
|
68
71
|
},
|
|
72
|
+
dst: {
|
|
73
|
+
daylightSavingTime: true,
|
|
74
|
+
destination: true,
|
|
75
|
+
distribution: true,
|
|
76
|
+
},
|
|
69
77
|
e: {
|
|
70
78
|
error: true,
|
|
71
79
|
event: true,
|
|
@@ -19,6 +19,19 @@ const getReplacement = encoding => {
|
|
|
19
19
|
}
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
+
// `fs.{readFile,readFileSync}()`
|
|
23
|
+
const isFsReadFileEncoding = node =>
|
|
24
|
+
node.parent.type === 'CallExpression'
|
|
25
|
+
&& !node.parent.optional
|
|
26
|
+
&& node.parent.arguments[1] === node
|
|
27
|
+
&& node.parent.arguments[0]
|
|
28
|
+
&& node.parent.arguments[0].type !== 'SpreadElement'
|
|
29
|
+
&& node.parent.callee.type === 'MemberExpression'
|
|
30
|
+
&& !node.parent.callee.optional
|
|
31
|
+
&& !node.parent.callee.computed
|
|
32
|
+
&& node.parent.callee.property.type === 'Identifier'
|
|
33
|
+
&& (node.parent.callee.property.name === 'readFile' || node.parent.callee.property.name === 'readFileSync');
|
|
34
|
+
|
|
22
35
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
23
36
|
const create = () => ({
|
|
24
37
|
Literal(node) {
|
|
@@ -39,19 +52,29 @@ const create = () => ({
|
|
|
39
52
|
replacement,
|
|
40
53
|
};
|
|
41
54
|
|
|
42
|
-
|
|
55
|
+
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
56
|
+
const fix = fixer => replaceStringLiteral(fixer, node, replacement);
|
|
57
|
+
|
|
58
|
+
const problem = {
|
|
43
59
|
node,
|
|
44
60
|
messageId: MESSAGE_ID_ERROR,
|
|
45
61
|
data: messageData,
|
|
46
|
-
suggest: [
|
|
47
|
-
{
|
|
48
|
-
messageId: MESSAGE_ID_SUGGESTION,
|
|
49
|
-
data: messageData,
|
|
50
|
-
/** @param {import('eslint').Rule.RuleFixer} fixer */
|
|
51
|
-
fix: fixer => replaceStringLiteral(fixer, node, replacement),
|
|
52
|
-
},
|
|
53
|
-
],
|
|
54
62
|
};
|
|
63
|
+
|
|
64
|
+
if (isFsReadFileEncoding(node)) {
|
|
65
|
+
problem.fix = fix;
|
|
66
|
+
return problem;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
problem.suggest = [
|
|
70
|
+
{
|
|
71
|
+
messageId: MESSAGE_ID_SUGGESTION,
|
|
72
|
+
data: messageData,
|
|
73
|
+
fix: fixer => replaceStringLiteral(fixer, node, replacement),
|
|
74
|
+
},
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
return problem;
|
|
55
78
|
},
|
|
56
79
|
});
|
|
57
80
|
|
|
@@ -63,6 +86,7 @@ module.exports = {
|
|
|
63
86
|
docs: {
|
|
64
87
|
description: 'Enforce consistent case for text encoding identifiers.',
|
|
65
88
|
},
|
|
89
|
+
fixable: 'code',
|
|
66
90
|
hasSuggestions: true,
|
|
67
91
|
messages,
|
|
68
92
|
},
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {isColonToken} = require('eslint-utils');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
@typedef {line: number, column: number} Position
|
|
7
|
+
|
|
8
|
+
Get the location of the given `SwitchCase` node for reporting.
|
|
9
|
+
|
|
10
|
+
@param {Node} node - The `SwitchCase` node to get.
|
|
11
|
+
@param {SourceCode} sourceCode - The source code object to get tokens from.
|
|
12
|
+
@returns {{start: Position, end: Position}} The location of the class node for reporting.
|
|
13
|
+
*/
|
|
14
|
+
function getSwitchCaseHeadLocation(node, sourceCode) {
|
|
15
|
+
const startToken = node.test || sourceCode.getFirstToken(node);
|
|
16
|
+
const colonToken = sourceCode.getTokenAfter(startToken, isColonToken);
|
|
17
|
+
|
|
18
|
+
return {start: node.loc.start, end: colonToken.loc.end};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = getSwitchCaseHeadLocation;
|