eslint-plugin-unicorn 69.0.0 → 70.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/package.json +1 -1
- package/readme.md +10 -0
- package/rules/consistent-boolean-name.js +637 -36
- package/rules/expiring-todo-comments.js +23 -0
- package/rules/fix/{add-parenthesizes-to-return-or-throw-expression.js → add-parentheses-to-return-or-throw-expression.js} +1 -1
- package/rules/fix/index.js +1 -1
- package/rules/fix/switch-new-expression-to-call-expression.js +2 -2
- package/rules/index.js +10 -0
- package/rules/isolated-functions.js +156 -12
- package/rules/no-async-promise-finally.js +111 -0
- package/rules/no-collection-bracket-access.js +27 -4
- package/rules/no-manually-wrapped-comments.js +5 -1
- package/rules/no-negated-array-predicate.js +2 -2
- package/rules/no-negated-comparison.js +24 -61
- package/rules/no-negated-condition.js +2 -2
- package/rules/no-negation-in-equality-check.js +2 -2
- package/rules/no-non-function-verb-prefix.js +25 -2
- package/rules/no-return-array-push.js +9 -2
- package/rules/no-typeof-undefined.js +2 -2
- package/rules/no-unnecessary-array-flat-map.js +307 -0
- package/rules/no-unnecessary-await.js +2 -2
- package/rules/no-unnecessary-fetch-options.js +624 -0
- package/rules/no-unsafe-promise-all-settled-values.js +865 -0
- package/rules/no-useless-else.js +32 -1
- package/rules/no-useless-iterator-to-array.js +42 -16
- package/rules/no-useless-spread.js +2 -2
- package/rules/prefer-abort-signal-any.js +1391 -0
- package/rules/prefer-array-flat-map.js +6 -11
- package/rules/prefer-array-from-range.js +98 -0
- package/rules/prefer-block-statement-over-iife.js +159 -0
- package/rules/prefer-continue.js +6 -2
- package/rules/prefer-early-return.js +3 -1
- package/rules/prefer-group-by.js +576 -0
- package/rules/prefer-iterator-helpers.js +196 -0
- package/rules/prefer-number-coercion.js +1 -1
- package/rules/prefer-object-iterable-methods.js +57 -14
- package/rules/prefer-simplified-conditions.js +584 -0
- package/rules/prefer-spread.js +3 -0
- package/rules/prefer-string-raw.js +2 -2
- package/rules/shared/array-range.js +66 -0
- package/rules/shared/iterator-helpers.js +43 -1
- package/rules/utils/comparison.js +65 -0
- package/rules/utils/is-boolean.js +21 -6
- package/rules/utils/track-branch-exits.js +8 -7
|
@@ -1,13 +1,21 @@
|
|
|
1
1
|
import {
|
|
2
2
|
removeParentheses,
|
|
3
3
|
fixSpaceAroundKeyword,
|
|
4
|
-
|
|
4
|
+
addParenthesesToReturnOrThrowExpression,
|
|
5
5
|
} from './fix/index.js';
|
|
6
6
|
import {
|
|
7
7
|
isParenthesized,
|
|
8
8
|
isOnSameLine,
|
|
9
9
|
needsSemicolon,
|
|
10
10
|
} from './utils/index.js';
|
|
11
|
+
import {
|
|
12
|
+
getBinaryExpressionWithReplacedOperatorText,
|
|
13
|
+
getPunctuatorBinaryExpressionOperatorToken,
|
|
14
|
+
hasLowerLogicalOperatorPrecedence,
|
|
15
|
+
negatedComparisonOperators,
|
|
16
|
+
negatedEqualityOperators,
|
|
17
|
+
negatedLogicalOperators,
|
|
18
|
+
} from './utils/comparison.js';
|
|
11
19
|
|
|
12
20
|
const MESSAGE_ID_ERROR = 'no-negated-comparison/error';
|
|
13
21
|
const MESSAGE_ID_LOGICAL_ERROR = 'no-negated-comparison/logical-error';
|
|
@@ -20,29 +28,6 @@ const messages = {
|
|
|
20
28
|
[MESSAGE_ID_LOGICAL_SUGGESTION]: 'Switch to opposite comparisons.',
|
|
21
29
|
};
|
|
22
30
|
|
|
23
|
-
const operatorReplacements = new Map([
|
|
24
|
-
['===', '!=='],
|
|
25
|
-
['!==', '==='],
|
|
26
|
-
['==', '!='],
|
|
27
|
-
['!=', '=='],
|
|
28
|
-
['>', '<='],
|
|
29
|
-
['>=', '<'],
|
|
30
|
-
['<', '>='],
|
|
31
|
-
['<=', '>'],
|
|
32
|
-
]);
|
|
33
|
-
|
|
34
|
-
const safelyFixableOperators = new Set([
|
|
35
|
-
'===',
|
|
36
|
-
'!==',
|
|
37
|
-
'==',
|
|
38
|
-
'!=',
|
|
39
|
-
]);
|
|
40
|
-
|
|
41
|
-
const logicalOperatorReplacements = new Map([
|
|
42
|
-
['&&', '||'],
|
|
43
|
-
['||', '&&'],
|
|
44
|
-
]);
|
|
45
|
-
|
|
46
31
|
const defaultOptions = {
|
|
47
32
|
checkLogicalExpressions: false,
|
|
48
33
|
};
|
|
@@ -72,20 +57,20 @@ const isNegation = node =>
|
|
|
72
57
|
|
|
73
58
|
const isComparison = node =>
|
|
74
59
|
node.type === 'BinaryExpression'
|
|
75
|
-
&&
|
|
60
|
+
&& negatedComparisonOperators.has(node.operator);
|
|
76
61
|
|
|
77
62
|
const isLogicalExpressionWithOnlyComparisons = node =>
|
|
78
63
|
isComparison(node)
|
|
79
64
|
|| (
|
|
80
65
|
node.type === 'LogicalExpression'
|
|
81
|
-
&&
|
|
66
|
+
&& negatedLogicalOperators.has(node.operator)
|
|
82
67
|
&& isLogicalExpressionWithOnlyComparisons(node.left)
|
|
83
68
|
&& isLogicalExpressionWithOnlyComparisons(node.right)
|
|
84
69
|
);
|
|
85
70
|
|
|
86
71
|
const isSafelyFixableLogicalExpression = node =>
|
|
87
72
|
isComparison(node)
|
|
88
|
-
?
|
|
73
|
+
? negatedEqualityOperators.has(node.operator)
|
|
89
74
|
: isSafelyFixableLogicalExpression(node.left) && isSafelyFixableLogicalExpression(node.right);
|
|
90
75
|
|
|
91
76
|
const parentNeedsGroupedComparison = parent => [
|
|
@@ -111,10 +96,7 @@ function * fix({
|
|
|
111
96
|
const bangToken = sourceCode.getFirstToken(unaryExpression);
|
|
112
97
|
const tokenAfterBang = sourceCode.getTokenAfter(bangToken);
|
|
113
98
|
const tokenAfterBangIncludingComments = sourceCode.getTokenAfter(bangToken, {includeComments: true});
|
|
114
|
-
const operatorToken =
|
|
115
|
-
comparison.left,
|
|
116
|
-
token => token.type === 'Punctuator' && token.value === comparison.operator,
|
|
117
|
-
);
|
|
99
|
+
const operatorToken = getPunctuatorBinaryExpressionOperatorToken(comparison, context);
|
|
118
100
|
const {parent} = unaryExpression;
|
|
119
101
|
const needsReturnOrThrowParentheses = (
|
|
120
102
|
(parent.type === 'ReturnStatement' || parent.type === 'ThrowStatement')
|
|
@@ -145,7 +127,7 @@ function * fix({
|
|
|
145
127
|
yield fixer.replaceText(operatorToken, replacementOperator);
|
|
146
128
|
|
|
147
129
|
if (needsReturnOrThrowParentheses) {
|
|
148
|
-
yield
|
|
130
|
+
yield addParenthesesToReturnOrThrowExpression(fixer, parent, context);
|
|
149
131
|
return;
|
|
150
132
|
}
|
|
151
133
|
|
|
@@ -156,37 +138,18 @@ function * fix({
|
|
|
156
138
|
}
|
|
157
139
|
}
|
|
158
140
|
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
const getOperatorToken = (comparison, sourceCode) => sourceCode.getTokenAfter(
|
|
165
|
-
comparison.left,
|
|
166
|
-
token => token.type === 'Punctuator' && token.value === comparison.operator,
|
|
141
|
+
const getFixedComparisonText = (comparison, context) => getBinaryExpressionWithReplacedOperatorText(
|
|
142
|
+
comparison,
|
|
143
|
+
context,
|
|
144
|
+
negatedComparisonOperators.get(comparison.operator),
|
|
167
145
|
);
|
|
168
146
|
|
|
169
|
-
const getFixedComparisonText = (comparison, sourceCode) => {
|
|
170
|
-
const operatorToken = getOperatorToken(comparison, sourceCode);
|
|
171
|
-
const [comparisonStart] = sourceCode.getRange(comparison);
|
|
172
|
-
const [operatorStart, operatorEnd] = sourceCode.getRange(operatorToken);
|
|
173
|
-
const comparisonText = sourceCode.getText(comparison);
|
|
174
|
-
|
|
175
|
-
return [
|
|
176
|
-
comparisonText.slice(0, operatorStart - comparisonStart),
|
|
177
|
-
operatorReplacements.get(comparison.operator),
|
|
178
|
-
comparisonText.slice(operatorEnd - comparisonStart),
|
|
179
|
-
].join('');
|
|
180
|
-
};
|
|
181
|
-
|
|
182
147
|
const getFixedLogicalExpressionText = (node, context, parentOperator) => {
|
|
183
|
-
const {sourceCode} = context;
|
|
184
|
-
|
|
185
148
|
if (isComparison(node)) {
|
|
186
|
-
return getFixedComparisonText(node,
|
|
149
|
+
return getFixedComparisonText(node, context);
|
|
187
150
|
}
|
|
188
151
|
|
|
189
|
-
const operator =
|
|
152
|
+
const operator = negatedLogicalOperators.get(node.operator);
|
|
190
153
|
const text = [
|
|
191
154
|
getFixedLogicalExpressionText(node.left, context, operator),
|
|
192
155
|
operator,
|
|
@@ -197,7 +160,7 @@ const getFixedLogicalExpressionText = (node, context, parentOperator) => {
|
|
|
197
160
|
operator !== parentOperator
|
|
198
161
|
&& isParenthesized(node, context)
|
|
199
162
|
)
|
|
200
|
-
||
|
|
163
|
+
|| hasLowerLogicalOperatorPrecedence(operator, parentOperator)
|
|
201
164
|
);
|
|
202
165
|
|
|
203
166
|
return needsParentheses
|
|
@@ -230,7 +193,7 @@ function * fixLogical({
|
|
|
230
193
|
yield fixer.replaceText(logicalExpression, getFixedLogicalExpressionText(logicalExpression, context));
|
|
231
194
|
|
|
232
195
|
if (needsReturnOrThrowParentheses) {
|
|
233
|
-
yield
|
|
196
|
+
yield addParenthesesToReturnOrThrowExpression(fixer, parent, context);
|
|
234
197
|
return;
|
|
235
198
|
}
|
|
236
199
|
|
|
@@ -253,7 +216,7 @@ const create = context => {
|
|
|
253
216
|
|
|
254
217
|
if (isComparison(argument)) {
|
|
255
218
|
const comparison = argument;
|
|
256
|
-
const replacementOperator =
|
|
219
|
+
const replacementOperator = negatedComparisonOperators.get(comparison.operator);
|
|
257
220
|
const problem = {
|
|
258
221
|
node: unaryExpression,
|
|
259
222
|
messageId: MESSAGE_ID_ERROR,
|
|
@@ -267,7 +230,7 @@ const create = context => {
|
|
|
267
230
|
replacementOperator,
|
|
268
231
|
});
|
|
269
232
|
|
|
270
|
-
if (
|
|
233
|
+
if (negatedEqualityOperators.has(comparison.operator)) {
|
|
271
234
|
problem.fix = fixFunction;
|
|
272
235
|
} else {
|
|
273
236
|
problem.suggest = [
|
|
@@ -5,7 +5,7 @@ https://github.com/eslint/eslint/blob/5c39425fc55ecc0b97bbd07ac22654c0eb4f789c/l
|
|
|
5
5
|
import {
|
|
6
6
|
removeParentheses,
|
|
7
7
|
fixSpaceAroundKeyword,
|
|
8
|
-
|
|
8
|
+
addParenthesesToReturnOrThrowExpression,
|
|
9
9
|
} from './fix/index.js';
|
|
10
10
|
import {
|
|
11
11
|
getParenthesizedRange,
|
|
@@ -118,7 +118,7 @@ const create = context => {
|
|
|
118
118
|
&& !isParenthesized(node, context)
|
|
119
119
|
&& !isParenthesized(test, context)
|
|
120
120
|
) {
|
|
121
|
-
yield
|
|
121
|
+
yield addParenthesesToReturnOrThrowExpression(fixer, parent, context);
|
|
122
122
|
return;
|
|
123
123
|
}
|
|
124
124
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {fixSpaceAroundKeyword,
|
|
1
|
+
import {fixSpaceAroundKeyword, addParenthesesToReturnOrThrowExpression} from './fix/index.js';
|
|
2
2
|
import {needsSemicolon, isParenthesized, isOnSameLine} from './utils/index.js';
|
|
3
3
|
|
|
4
4
|
const MESSAGE_ID_ERROR = 'no-negation-in-equality-check/error';
|
|
@@ -58,7 +58,7 @@ const create = context => {
|
|
|
58
58
|
) {
|
|
59
59
|
const returnToken = sourceCode.getFirstToken(parent);
|
|
60
60
|
if (!isOnSameLine(returnToken, tokenAfterBang, context)) {
|
|
61
|
-
yield
|
|
61
|
+
yield addParenthesesToReturnOrThrowExpression(fixer, parent, context);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {isRegExp} from 'node:util/types';
|
|
1
2
|
import {
|
|
2
3
|
isNullishType,
|
|
3
4
|
isTypeScriptFile,
|
|
@@ -31,8 +32,12 @@ const messages = {
|
|
|
31
32
|
|
|
32
33
|
const isUppercaseAscii = character => character >= 'A' && character <= 'Z';
|
|
33
34
|
|
|
34
|
-
const prepareOptions = ({
|
|
35
|
+
const prepareOptions = ({
|
|
36
|
+
verbs,
|
|
37
|
+
ignore = [],
|
|
38
|
+
}) => ({
|
|
35
39
|
verbs: verbs.toSorted((first, second) => second.length - first.length),
|
|
40
|
+
ignore: ignore.map(pattern => isRegExp(pattern) ? pattern : new RegExp(pattern, 'u')),
|
|
36
41
|
});
|
|
37
42
|
|
|
38
43
|
function getVerbPrefix(name, verbs) {
|
|
@@ -129,6 +134,15 @@ const getIntersectionTypeCallability = (type, checker, program, visitedTypes) =>
|
|
|
129
134
|
const isInDeclaredModule = (node, sourceCode) =>
|
|
130
135
|
sourceCode.getAncestors(node).some(ancestor => ancestor.type === 'TSModuleDeclaration' && ancestor.declare);
|
|
131
136
|
|
|
137
|
+
function isIgnoredName(name, ignore) {
|
|
138
|
+
return ignore.some(regexp => {
|
|
139
|
+
regexp.lastIndex = 0;
|
|
140
|
+
const isIgnored = regexp.test(name);
|
|
141
|
+
regexp.lastIndex = 0;
|
|
142
|
+
return isIgnored;
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
132
146
|
function getTypeCallability(type, checker, program, visitedTypes = new Set()) {
|
|
133
147
|
if (isIgnoredType(type)) {
|
|
134
148
|
return unknown;
|
|
@@ -184,6 +198,10 @@ function getProblem(identifier, context, options, typeNode = identifier) {
|
|
|
184
198
|
return;
|
|
185
199
|
}
|
|
186
200
|
|
|
201
|
+
if (isIgnoredName(identifier.name, options.ignore)) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
|
|
187
205
|
const {parserServices} = context.sourceCode;
|
|
188
206
|
let type;
|
|
189
207
|
try {
|
|
@@ -298,6 +316,11 @@ const schema = [
|
|
|
298
316
|
uniqueItems: true,
|
|
299
317
|
description: 'Function-style verb prefixes to check.',
|
|
300
318
|
},
|
|
319
|
+
ignore: {
|
|
320
|
+
type: 'array',
|
|
321
|
+
uniqueItems: true,
|
|
322
|
+
description: 'Patterns to ignore.',
|
|
323
|
+
},
|
|
301
324
|
},
|
|
302
325
|
},
|
|
303
326
|
];
|
|
@@ -313,7 +336,7 @@ const config = {
|
|
|
313
336
|
requiresTypeChecking: true,
|
|
314
337
|
},
|
|
315
338
|
schema,
|
|
316
|
-
defaultOptions: [{verbs: defaultVerbs}],
|
|
339
|
+
defaultOptions: [{verbs: defaultVerbs, ignore: []}],
|
|
317
340
|
messages,
|
|
318
341
|
languages: [
|
|
319
342
|
'js/js',
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import {isMethodCall} from './ast/index.js';
|
|
2
|
-
import {isKnownNonArray, needsSemicolon} from './utils/index.js';
|
|
2
|
+
import {isArray, isKnownNonArray, needsSemicolon} from './utils/index.js';
|
|
3
3
|
|
|
4
4
|
const MESSAGE_ID_ERROR = 'no-return-array-push/error';
|
|
5
5
|
const MESSAGE_ID_SUGGESTION = 'no-return-array-push/suggestion';
|
|
@@ -10,7 +10,10 @@ const messages = {
|
|
|
10
10
|
|
|
11
11
|
const ignoredCallees = [
|
|
12
12
|
'stream.push',
|
|
13
|
+
'router.push',
|
|
13
14
|
'this.push',
|
|
15
|
+
'this.router.push',
|
|
16
|
+
'this.$router.push',
|
|
14
17
|
'this.stream.push',
|
|
15
18
|
'process.stdin.push',
|
|
16
19
|
'process.stdout.push',
|
|
@@ -53,6 +56,10 @@ function isStaticMemberPath(node, path) {
|
|
|
53
56
|
|
|
54
57
|
const isIgnoredCallee = callee => ignoredCallees.some(ignoredCallee => isStaticMemberPath(callee, ignoredCallee));
|
|
55
58
|
|
|
59
|
+
const isIgnoredPushCallee = (callExpression, context) =>
|
|
60
|
+
isIgnoredCallee(callExpression.callee)
|
|
61
|
+
&& !isArray(callExpression.callee.object, context);
|
|
62
|
+
|
|
56
63
|
function getCallExpressionResultNode(callExpression) {
|
|
57
64
|
let node = callExpression;
|
|
58
65
|
|
|
@@ -133,7 +140,7 @@ const create = context => {
|
|
|
133
140
|
const {name: method} = property;
|
|
134
141
|
|
|
135
142
|
if (
|
|
136
|
-
(method === 'push' &&
|
|
143
|
+
(method === 'push' && isIgnoredPushCallee(callExpression, context))
|
|
137
144
|
|| isReturnValueDiscarded(callExpression)
|
|
138
145
|
|| isResultMemberAccessed(callExpression)
|
|
139
146
|
|| isKnownNonArray(callExpression.callee.object, context)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {isLiteral} from './ast/index.js';
|
|
2
2
|
import {
|
|
3
|
-
|
|
3
|
+
addParenthesesToReturnOrThrowExpression,
|
|
4
4
|
removeSpacesAfter,
|
|
5
5
|
} from './fix/index.js';
|
|
6
6
|
import {
|
|
@@ -74,7 +74,7 @@ const create = context => {
|
|
|
74
74
|
&& !isParenthesized(binaryExpression, context)
|
|
75
75
|
&& !isParenthesized(typeofNode, context)
|
|
76
76
|
) {
|
|
77
|
-
yield
|
|
77
|
+
yield addParenthesesToReturnOrThrowExpression(fixer, parent, context);
|
|
78
78
|
return;
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
import {hasSideEffect} from '@eslint-community/eslint-utils';
|
|
2
|
+
import {isMethodCall} from './ast/index.js';
|
|
3
|
+
import {
|
|
4
|
+
getParenthesizedText,
|
|
5
|
+
isKnownNonArray,
|
|
6
|
+
isParenthesized,
|
|
7
|
+
isSameIdentifier,
|
|
8
|
+
shouldAddParenthesesToMemberExpressionObject,
|
|
9
|
+
wouldRemoveComments,
|
|
10
|
+
} from './utils/index.js';
|
|
11
|
+
|
|
12
|
+
const MESSAGE_ID = 'no-unnecessary-array-flat-map';
|
|
13
|
+
const SUGGESTION_ID_FILTER_MAP = 'no-unnecessary-array-flat-map/filter-map-suggestion';
|
|
14
|
+
|
|
15
|
+
const messages = {
|
|
16
|
+
[MESSAGE_ID]: 'Prefer `.{{method}}(…)` over `.flatMap(…)` for this single-item array callback.',
|
|
17
|
+
[SUGGESTION_ID_FILTER_MAP]: 'Replace `.flatMap(…)` with `.filter(…).map(…)`.',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const arrowBodyNeedsParenthesesTypes = new Set([
|
|
21
|
+
'ObjectExpression',
|
|
22
|
+
'SequenceExpression',
|
|
23
|
+
]);
|
|
24
|
+
|
|
25
|
+
const typescriptExpressionWrapperTypes = new Set([
|
|
26
|
+
'TSAsExpression',
|
|
27
|
+
'TSNonNullExpression',
|
|
28
|
+
'TSSatisfiesExpression',
|
|
29
|
+
'TSTypeAssertion',
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
const hasTypeArguments = node => node.typeArguments || node.typeParameters;
|
|
33
|
+
|
|
34
|
+
const isFilterCallExpression = node => isMethodCall(node, {
|
|
35
|
+
method: 'filter',
|
|
36
|
+
argumentsLength: 1,
|
|
37
|
+
optionalCall: false,
|
|
38
|
+
optionalMember: false,
|
|
39
|
+
computed: false,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const isSimpleSingleParameterArrowCallback = node =>
|
|
43
|
+
node.type === 'ArrowFunctionExpression'
|
|
44
|
+
&& !node.async
|
|
45
|
+
&& !node.returnType
|
|
46
|
+
&& !node.typeParameters
|
|
47
|
+
&& node.params.length === 1
|
|
48
|
+
&& node.params[0].type === 'Identifier'
|
|
49
|
+
&& !node.params[0].optional
|
|
50
|
+
&& node.body.type !== 'BlockStatement';
|
|
51
|
+
|
|
52
|
+
const isEmptyArrayExpression = node =>
|
|
53
|
+
node.type === 'ArrayExpression'
|
|
54
|
+
&& node.elements.length === 0;
|
|
55
|
+
|
|
56
|
+
const getSingleArrayElement = node => {
|
|
57
|
+
if (
|
|
58
|
+
node.type !== 'ArrayExpression'
|
|
59
|
+
|| node.elements.length !== 1
|
|
60
|
+
|| !node.elements[0]
|
|
61
|
+
|| node.elements[0].type === 'SpreadElement'
|
|
62
|
+
) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return node.elements[0];
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
function shouldParenthesizeArrowBody(node, context) {
|
|
70
|
+
if (isParenthesized(node, context)) {
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (arrowBodyNeedsParenthesesTypes.has(node.type)) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return typescriptExpressionWrapperTypes.has(node.type) && shouldParenthesizeArrowBody(node.expression, context);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function getCallbackResult(callback) {
|
|
82
|
+
const directElement = getSingleArrayElement(callback.body);
|
|
83
|
+
if (directElement) {
|
|
84
|
+
return {
|
|
85
|
+
type: 'map',
|
|
86
|
+
element: directElement,
|
|
87
|
+
arrayExpression: callback.body,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
if (callback.body.type !== 'ConditionalExpression') {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const {test, consequent, alternate} = callback.body;
|
|
96
|
+
const element = getSingleArrayElement(consequent);
|
|
97
|
+
if (!element || !isEmptyArrayExpression(alternate)) {
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return {
|
|
102
|
+
type: 'conditional',
|
|
103
|
+
test,
|
|
104
|
+
element,
|
|
105
|
+
arrayExpression: consequent,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getArrowBodyText(node, context) {
|
|
110
|
+
if (isParenthesized(node, context)) {
|
|
111
|
+
return getParenthesizedText(node, context);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const text = context.sourceCode.getText(node);
|
|
115
|
+
return shouldParenthesizeArrowBody(node, context)
|
|
116
|
+
? `(${text})`
|
|
117
|
+
: text;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function getMemberExpressionObjectText(node, context) {
|
|
121
|
+
if (node.type === 'Super') {
|
|
122
|
+
return 'super';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (isParenthesized(node, context)) {
|
|
126
|
+
return getParenthesizedText(node, context);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const text = context.sourceCode.getText(node);
|
|
130
|
+
return shouldAddParenthesesToMemberExpressionObject(node, context) ? `(${text})` : text;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function getArrowParameterText(callback, context) {
|
|
134
|
+
const parameterText = context.sourceCode.getText(callback.params[0]);
|
|
135
|
+
return callback.params[0].typeAnnotation ? `(${parameterText})` : parameterText;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function getFilterMapSuggestion(flatMapCallExpression, callback, callbackResult, context) {
|
|
139
|
+
if (
|
|
140
|
+
hasTypeArguments(flatMapCallExpression)
|
|
141
|
+
|| callback.params[0].typeAnnotation
|
|
142
|
+
|| wouldRemoveComments(context, flatMapCallExpression, [
|
|
143
|
+
flatMapCallExpression.callee.object,
|
|
144
|
+
callbackResult.test,
|
|
145
|
+
callbackResult.element,
|
|
146
|
+
])
|
|
147
|
+
|| hasSideEffect(callbackResult.test, context.sourceCode)
|
|
148
|
+
|| hasSideEffect(callbackResult.element, context.sourceCode)
|
|
149
|
+
) {
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const arrayText = getMemberExpressionObjectText(flatMapCallExpression.callee.object, context);
|
|
154
|
+
const parameterText = getArrowParameterText(callback, context);
|
|
155
|
+
const testText = getArrowBodyText(callbackResult.test, context);
|
|
156
|
+
const elementText = getArrowBodyText(callbackResult.element, context);
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
messageId: SUGGESTION_ID_FILTER_MAP,
|
|
160
|
+
fix: fixer => fixer.replaceText(
|
|
161
|
+
flatMapCallExpression,
|
|
162
|
+
`${arrayText}.filter(${parameterText} => ${testText}).map(${parameterText} => ${elementText})`,
|
|
163
|
+
),
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getProblemForFilterFlatMap(flatMapCallExpression, callbackResult, context) {
|
|
168
|
+
const filterCallExpression = flatMapCallExpression.callee.object;
|
|
169
|
+
if (!isFilterCallExpression(filterCallExpression)) {
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const problem = {
|
|
174
|
+
node: flatMapCallExpression.callee.property,
|
|
175
|
+
messageId: MESSAGE_ID,
|
|
176
|
+
data: {method: 'map'},
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
if (
|
|
180
|
+
hasTypeArguments(filterCallExpression)
|
|
181
|
+
|| hasTypeArguments(flatMapCallExpression)
|
|
182
|
+
|| isKnownNonArray(filterCallExpression.callee.object, context)
|
|
183
|
+
|| wouldRemoveComments(context, callbackResult.arrayExpression, [callbackResult.element])
|
|
184
|
+
) {
|
|
185
|
+
return problem;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
...problem,
|
|
190
|
+
* fix(fixer) {
|
|
191
|
+
yield fixer.replaceText(flatMapCallExpression.callee.property, 'map');
|
|
192
|
+
yield fixer.replaceText(callbackResult.arrayExpression, getArrowBodyText(callbackResult.element, context));
|
|
193
|
+
},
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getProblemForConditionalFlatMap(flatMapCallExpression, callback, callbackResult, context) {
|
|
198
|
+
const method = isSameIdentifier(callbackResult.element, callback.params[0]) ? 'filter' : 'filter().map';
|
|
199
|
+
const problem = {
|
|
200
|
+
node: flatMapCallExpression.callee.property,
|
|
201
|
+
messageId: MESSAGE_ID,
|
|
202
|
+
data: {method},
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
if (method === 'filter') {
|
|
206
|
+
if (
|
|
207
|
+
hasTypeArguments(flatMapCallExpression)
|
|
208
|
+
|| hasSideEffect(callbackResult.test, context.sourceCode)
|
|
209
|
+
|| wouldRemoveComments(context, flatMapCallExpression, [
|
|
210
|
+
flatMapCallExpression.callee.object,
|
|
211
|
+
callback.params[0],
|
|
212
|
+
callbackResult.test,
|
|
213
|
+
])
|
|
214
|
+
) {
|
|
215
|
+
return problem;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const arrayText = getMemberExpressionObjectText(flatMapCallExpression.callee.object, context);
|
|
219
|
+
const parameterText = getArrowParameterText(callback, context);
|
|
220
|
+
const testText = getArrowBodyText(callbackResult.test, context);
|
|
221
|
+
|
|
222
|
+
return {
|
|
223
|
+
...problem,
|
|
224
|
+
fix: fixer => fixer.replaceText(
|
|
225
|
+
flatMapCallExpression,
|
|
226
|
+
`${arrayText}.filter(${parameterText} => ${testText})`,
|
|
227
|
+
),
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const suggestion = getFilterMapSuggestion(flatMapCallExpression, callback, callbackResult, context);
|
|
232
|
+
return suggestion
|
|
233
|
+
? {
|
|
234
|
+
...problem,
|
|
235
|
+
suggest: [suggestion],
|
|
236
|
+
}
|
|
237
|
+
: problem;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function getProblem(flatMapCallExpression, context) {
|
|
241
|
+
if (
|
|
242
|
+
!isMethodCall(flatMapCallExpression, {
|
|
243
|
+
method: 'flatMap',
|
|
244
|
+
argumentsLength: 1,
|
|
245
|
+
optionalCall: false,
|
|
246
|
+
optionalMember: false,
|
|
247
|
+
computed: false,
|
|
248
|
+
})
|
|
249
|
+
|| isKnownNonArray(flatMapCallExpression.callee.object, context)
|
|
250
|
+
) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
const filterCallExpression = flatMapCallExpression.callee.object;
|
|
255
|
+
if (
|
|
256
|
+
isFilterCallExpression(filterCallExpression)
|
|
257
|
+
&& isKnownNonArray(filterCallExpression.callee.object, context)
|
|
258
|
+
) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const [callback] = flatMapCallExpression.arguments;
|
|
263
|
+
if (!isSimpleSingleParameterArrowCallback(callback)) {
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const callbackResult = getCallbackResult(callback);
|
|
268
|
+
if (!callbackResult) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (callbackResult.type === 'map') {
|
|
273
|
+
return getProblemForFilterFlatMap(flatMapCallExpression, callbackResult, context) ?? {
|
|
274
|
+
node: flatMapCallExpression.callee.property,
|
|
275
|
+
messageId: MESSAGE_ID,
|
|
276
|
+
data: {method: 'map'},
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
return getProblemForConditionalFlatMap(flatMapCallExpression, callback, callbackResult, context);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
284
|
+
const create = context => {
|
|
285
|
+
context.on('CallExpression', callExpression => getProblem(callExpression, context));
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
289
|
+
const config = {
|
|
290
|
+
create,
|
|
291
|
+
meta: {
|
|
292
|
+
type: 'suggestion',
|
|
293
|
+
docs: {
|
|
294
|
+
description: 'Disallow `Array#flatMap()` callbacks that only wrap a single item.',
|
|
295
|
+
recommended: true,
|
|
296
|
+
},
|
|
297
|
+
fixable: 'code',
|
|
298
|
+
hasSuggestions: true,
|
|
299
|
+
schema: [],
|
|
300
|
+
messages,
|
|
301
|
+
languages: [
|
|
302
|
+
'js/js',
|
|
303
|
+
],
|
|
304
|
+
},
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
export default config;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {addParenthesesToReturnOrThrowExpression, removeSpacesAfter} from './fix/index.js';
|
|
2
2
|
import {isParenthesized, needsSemicolon, isOnSameLine} from './utils/index.js';
|
|
3
3
|
|
|
4
4
|
const MESSAGE_ID = 'no-unnecessary-await';
|
|
@@ -74,7 +74,7 @@ const create = context => {
|
|
|
74
74
|
!isOnSameLine(awaitToken, valueNode, context)
|
|
75
75
|
&& !isParenthesized(node, context)
|
|
76
76
|
) {
|
|
77
|
-
yield
|
|
77
|
+
yield addParenthesesToReturnOrThrowExpression(fixer, node.parent, context);
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
yield fixer.remove(awaitToken);
|