eslint-plugin-unicorn 68.0.0 → 69.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 +3 -1
- package/readme.md +12 -0
- package/rules/ast/is-empty-object-expression.js +2 -2
- package/rules/comment-content.js +136 -18
- package/rules/consistent-boolean-name.js +31 -4
- package/rules/consistent-conditional-object-spread.js +9 -3
- package/rules/consistent-tuple-labels.js +49 -0
- package/rules/custom-error-definition.js +26 -7
- package/rules/index.js +12 -0
- package/rules/no-chained-comparison.js +1 -1
- package/rules/no-computed-property-existence-check.js +6 -0
- package/rules/no-incorrect-template-string-interpolation.js +82 -30
- package/rules/no-instanceof-builtins.js +59 -13
- package/rules/no-invalid-well-known-symbol-methods.js +205 -0
- package/rules/no-late-current-target-access.js +9 -71
- package/rules/no-late-event-control.js +127 -0
- package/rules/no-nonstandard-builtin-properties.js +2 -0
- package/rules/no-object-methods-with-collections.js +4 -289
- package/rules/no-return-array-push.js +20 -5
- package/rules/no-top-level-side-effects.js +8 -1
- package/rules/no-unnecessary-global-this.js +36 -1
- package/rules/no-unreadable-object-destructuring.js +15 -0
- package/rules/no-unsafe-string-replacement.js +31 -0
- package/rules/no-unused-array-method-return.js +11 -2
- package/rules/no-useless-boolean-cast.js +58 -1
- package/rules/no-useless-concat.js +27 -3
- package/rules/no-useless-else.js +16 -2
- package/rules/no-useless-iterator-to-array.js +165 -115
- package/rules/prefer-abort-signal-timeout.js +395 -0
- package/rules/prefer-aggregate-error.js +478 -0
- package/rules/prefer-at.js +5 -6
- package/rules/prefer-code-point.js +58 -8
- package/rules/prefer-continue.js +16 -0
- package/rules/prefer-dom-node-replace-children.js +353 -0
- package/rules/prefer-error-is-error.js +181 -0
- package/rules/prefer-has-check.js +1 -4
- package/rules/prefer-hoisting-branch-code.js +9 -7
- package/rules/prefer-iterator-concat.js +21 -1
- package/rules/prefer-minimal-ternary.js +15 -11
- package/rules/prefer-modern-dom-apis.js +1 -45
- package/rules/prefer-observer-apis.js +498 -0
- package/rules/prefer-promise-try.js +238 -0
- package/rules/prefer-set-methods.js +337 -0
- package/rules/prefer-toggle-attribute.js +293 -0
- package/rules/prefer-url-search-parameters.js +411 -0
- package/rules/shared/late-event-handler.js +82 -0
- package/rules/shared/name-replacements.js +6 -0
- package/rules/utils/builtin-collection-type.js +267 -0
- package/rules/utils/index.js +6 -0
- package/rules/utils/is-boolean.js +3 -1
- package/rules/utils/is-event.js +127 -0
- package/rules/utils/should-report-replace-children-receiver.js +210 -0
- package/rules/utils/type-helpers.js +166 -8
|
@@ -1,11 +1,60 @@
|
|
|
1
1
|
import {isFunction, isMethodCall} from './ast/index.js';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
hasOptionalChainElement,
|
|
4
|
+
isGlobalBooleanCall,
|
|
5
|
+
isNullishType,
|
|
6
|
+
isSameIdentifier,
|
|
7
|
+
unwrapTypeScriptExpression,
|
|
8
|
+
} from './utils/index.js';
|
|
3
9
|
|
|
4
10
|
const MESSAGE_ID = 'no-useless-boolean-cast';
|
|
5
11
|
const messages = {
|
|
6
12
|
[MESSAGE_ID]: '`Boolean()` is unnecessary in `Array#{{method}}()` callbacks.',
|
|
7
13
|
};
|
|
8
14
|
|
|
15
|
+
const isNullishOrVoidType = type =>
|
|
16
|
+
type.intrinsicName === 'void' || isNullishType(type);
|
|
17
|
+
|
|
18
|
+
// When type information is available, the `Boolean()` cast is meaningful if the argument's type includes `null`/`undefined`/`void`, since removing it would widen the predicate's return type. Returns `false` when type information is unavailable, so it only ever keeps more casts than the syntactic check alone, never fewer.
|
|
19
|
+
function hasNullishOrVoidType(node, context) {
|
|
20
|
+
const {parserServices} = context.sourceCode;
|
|
21
|
+
if (!parserServices?.program) {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const type = parserServices.getTypeAtLocation(node);
|
|
27
|
+
const types = type.isUnion() ? type.types : [type];
|
|
28
|
+
return types.some(type => isNullishOrVoidType(type));
|
|
29
|
+
} catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isOptionalChainResult(node) {
|
|
35
|
+
node = unwrapTypeScriptExpression(node);
|
|
36
|
+
|
|
37
|
+
if (node?.type === 'ChainExpression') {
|
|
38
|
+
return hasOptionalChainElement(node);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (node?.type === 'LogicalExpression') {
|
|
42
|
+
return node.operator === '&&'
|
|
43
|
+
? isOptionalChainResult(node.left) || isOptionalChainResult(node.right)
|
|
44
|
+
: isOptionalChainResult(node.right);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (node?.type === 'ConditionalExpression') {
|
|
48
|
+
return isOptionalChainResult(node.consequent) || isOptionalChainResult(node.alternate);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (node?.type === 'SequenceExpression') {
|
|
52
|
+
return isOptionalChainResult(node.expressions.at(-1));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
9
58
|
const predicateMethods = [
|
|
10
59
|
'every',
|
|
11
60
|
'filter',
|
|
@@ -79,6 +128,14 @@ const create = context => {
|
|
|
79
128
|
return;
|
|
80
129
|
}
|
|
81
130
|
|
|
131
|
+
// `Boolean()` normalizes a possibly-`undefined` value, so it is not useless. Detect this syntactically via optional chaining, and, when type information is available, via the resolved type.
|
|
132
|
+
if (
|
|
133
|
+
isOptionalChainResult(argument)
|
|
134
|
+
|| hasNullishOrVoidType(argument, context)
|
|
135
|
+
) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
82
139
|
return {
|
|
83
140
|
node: booleanCall,
|
|
84
141
|
messageId: MESSAGE_ID,
|
|
@@ -30,6 +30,11 @@ function getStringValue(node) {
|
|
|
30
30
|
// Legacy octal (`\1`, `\012`) and `\8`/`\9` escapes are valid in sloppy-mode string literals but are syntax errors inside template literals.
|
|
31
31
|
const hasTemplateIncompatibleEscape = raw => /(?<=(?:^|[^\\])(?:\\\\)*)\\(?:[1-9]|0\d)/v.test(raw);
|
|
32
32
|
|
|
33
|
+
// Whether the string contains a `${…}` placeholder. The regex mirrors ESLint's `no-template-curly-in-string`, which we defer to, so an empty `${}` is intentionally not matched.
|
|
34
|
+
const hasTemplatePlaceholder = string => /\$\{[^}]+\}/u.test(string);
|
|
35
|
+
|
|
36
|
+
const formsTemplatePlaceholderBoundary = (leftRaw, rightRaw) => leftRaw.endsWith('$') && rightRaw.startsWith('{');
|
|
37
|
+
|
|
33
38
|
// The raw inner content of a literal as it would appear inside a template literal.
|
|
34
39
|
function toTemplateElementRaw(node, sourceCode) {
|
|
35
40
|
if (node.type === 'TemplateLiteral') {
|
|
@@ -69,9 +74,28 @@ const create = context => {
|
|
|
69
74
|
|
|
70
75
|
const leftValue = getStringValue(left);
|
|
71
76
|
const rightValue = getStringValue(right);
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
77
|
+
|
|
78
|
+
// Merging into a single string that contains a `${…}` placeholder produces an ambiguous template-like literal that `no-template-curly-in-string` flags. The split is likely intentional, so leave it alone.
|
|
79
|
+
if (
|
|
80
|
+
leftValue !== undefined
|
|
81
|
+
&& rightValue !== undefined
|
|
82
|
+
&& hasTemplatePlaceholder(leftValue + rightValue)
|
|
83
|
+
) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
let replacement;
|
|
88
|
+
if (leftValue === undefined || rightValue === undefined) {
|
|
89
|
+
const leftRaw = toTemplateElementRaw(left, sourceCode);
|
|
90
|
+
const rightRaw = toTemplateElementRaw(right, sourceCode);
|
|
91
|
+
if (formsTemplatePlaceholderBoundary(leftRaw, rightRaw)) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
replacement = `\`${leftRaw}${rightRaw}\``;
|
|
96
|
+
} else {
|
|
97
|
+
replacement = escapeString(leftValue + rightValue);
|
|
98
|
+
}
|
|
75
99
|
|
|
76
100
|
const operatorToken = sourceCode.getTokenBefore(right, token => token.type === 'Punctuator' && token.value === '+');
|
|
77
101
|
|
package/rules/no-useless-else.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
hasCommentInRange,
|
|
3
3
|
hasDirectBlockScopedDeclaration,
|
|
4
|
-
hasMultilineToken,
|
|
5
4
|
needsSemicolon,
|
|
6
5
|
trackBranchExits,
|
|
7
6
|
} from './utils/index.js';
|
|
@@ -25,6 +24,7 @@ const statementListParentTypes = new Set([
|
|
|
25
24
|
const asiHazardCharacters = new Set([
|
|
26
25
|
'[',
|
|
27
26
|
'(',
|
|
27
|
+
'<',
|
|
28
28
|
'/',
|
|
29
29
|
'`',
|
|
30
30
|
'+',
|
|
@@ -38,6 +38,20 @@ const getLineIndent = (sourceCode, index) => {
|
|
|
38
38
|
|
|
39
39
|
const startsWithAsiHazard = token => asiHazardCharacters.has(token.value[0]);
|
|
40
40
|
|
|
41
|
+
const isJsxChildTextToken = (token, sourceCode) =>
|
|
42
|
+
token.type === 'JSXText'
|
|
43
|
+
&& sourceCode.getNodeByRangeIndex(sourceCode.getRange(token)[0]).type === 'JSXText';
|
|
44
|
+
|
|
45
|
+
// A multiline token's internal whitespace can be meaningful, so it can't be safely reindented.
|
|
46
|
+
// JSX child text is safe because JSX collapses line-leading and line-trailing whitespace at compile time.
|
|
47
|
+
const hasReindentUnsafeMultilineToken = (node, context) => {
|
|
48
|
+
const {sourceCode} = context;
|
|
49
|
+
return sourceCode.getTokens(node).some(token =>
|
|
50
|
+
!isJsxChildTextToken(token, sourceCode)
|
|
51
|
+
&& sourceCode.getLoc(token).start.line !== sourceCode.getLoc(token).end.line,
|
|
52
|
+
);
|
|
53
|
+
};
|
|
54
|
+
|
|
41
55
|
const getBlockBodyText = (blockStatement, ifStatement, sourceCode) => {
|
|
42
56
|
const openingBrace = sourceCode.getFirstToken(blockStatement);
|
|
43
57
|
const closingBrace = sourceCode.getLastToken(blockStatement);
|
|
@@ -156,7 +170,7 @@ const fix = (ifStatement, context) => fixer => {
|
|
|
156
170
|
hasDirectBlockScopedDeclaration(alternate)
|
|
157
171
|
|| (
|
|
158
172
|
alternate.type === 'BlockStatement'
|
|
159
|
-
&&
|
|
173
|
+
&& hasReindentUnsafeMultilineToken(alternate, context)
|
|
160
174
|
)
|
|
161
175
|
|| !isSafeToMoveAlternate(ifStatement, context)
|
|
162
176
|
) {
|
|
@@ -15,7 +15,7 @@ const MESSAGE_ID_SUGGESTION_ITERATOR_METHOD = 'iterator-method/suggestion';
|
|
|
15
15
|
|
|
16
16
|
const messages = {
|
|
17
17
|
[MESSAGE_ID_ITERABLE_ACCEPTING]: '`{{description}}` accepts an iterable, `.toArray()` is unnecessary.',
|
|
18
|
-
[MESSAGE_ID_FOR_OF]: '`for…of` can iterate over an iterable, `.toArray()` is unnecessary.',
|
|
18
|
+
[MESSAGE_ID_FOR_OF]: '`for…of`/`for await…of` can iterate over an iterable, `.toArray()` is unnecessary.',
|
|
19
19
|
[MESSAGE_ID_YIELD_STAR]: '`yield*` can delegate to an iterable, `.toArray()` is unnecessary.',
|
|
20
20
|
[MESSAGE_ID_SPREAD]: 'Spread works on iterables, `.toArray()` is unnecessary.',
|
|
21
21
|
[MESSAGE_ID_ITERATOR_METHOD]: '`Iterator` has a `.{{method}}()` method, `.toArray()` is unnecessary.',
|
|
@@ -45,6 +45,154 @@ const isToArrayCall = node => isMethodCall(node, {
|
|
|
45
45
|
optionalMember: false,
|
|
46
46
|
});
|
|
47
47
|
|
|
48
|
+
const getRemoveToArrayFix = (toArrayCall, context) => {
|
|
49
|
+
if (context.sourceCode.getCommentsInside(toArrayCall).length > 0) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return fixer => removeMethodCall(fixer, toArrayCall, context);
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const getRemoveToArraySuggestion = (toArrayCall, context, messageId, data) => {
|
|
57
|
+
const fix = getRemoveToArrayFix(toArrayCall, context);
|
|
58
|
+
if (!fix) {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
messageId,
|
|
64
|
+
...(data && {data}),
|
|
65
|
+
fix,
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const getArrayFromProblem = (node, context) => {
|
|
70
|
+
if (
|
|
71
|
+
!isMethodCall(node, {
|
|
72
|
+
objects: ['Array', ...typedArray],
|
|
73
|
+
method: 'from',
|
|
74
|
+
minimumArguments: 1,
|
|
75
|
+
optionalCall: false,
|
|
76
|
+
optionalMember: false,
|
|
77
|
+
})
|
|
78
|
+
|| !isToArrayCall(node.arguments[0])
|
|
79
|
+
) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const toArrayCall = node.arguments[0];
|
|
84
|
+
const fix = getRemoveToArrayFix(toArrayCall, context);
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
node: toArrayCall.callee.property,
|
|
88
|
+
messageId: MESSAGE_ID_ITERABLE_ACCEPTING,
|
|
89
|
+
data: {description: `${node.callee.object.name}.${node.callee.property.name}(…)`},
|
|
90
|
+
...(fix && {fix}),
|
|
91
|
+
};
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const getObjectFromEntriesProblem = (node, context) => {
|
|
95
|
+
if (
|
|
96
|
+
!isMethodCall(node, {
|
|
97
|
+
object: 'Object',
|
|
98
|
+
method: 'fromEntries',
|
|
99
|
+
minimumArguments: 1,
|
|
100
|
+
optionalCall: false,
|
|
101
|
+
optionalMember: false,
|
|
102
|
+
})
|
|
103
|
+
|| !isToArrayCall(node.arguments[0])
|
|
104
|
+
) {
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const toArrayCall = node.arguments[0];
|
|
109
|
+
const fix = getRemoveToArrayFix(toArrayCall, context);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
node: toArrayCall.callee.property,
|
|
113
|
+
messageId: MESSAGE_ID_ITERABLE_ACCEPTING,
|
|
114
|
+
data: {description: `${node.callee.object.name}.${node.callee.property.name}(…)`},
|
|
115
|
+
...(fix && {fix}),
|
|
116
|
+
};
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const getPromiseProblem = (node, context) => {
|
|
120
|
+
// Suggestion only — passing an iterator directly can change a sync throw
|
|
121
|
+
// into an async rejection when iteration fails.
|
|
122
|
+
if (
|
|
123
|
+
!isMethodCall(node, {
|
|
124
|
+
object: 'Promise',
|
|
125
|
+
methods: ['all', 'allSettled', 'any', 'race'],
|
|
126
|
+
argumentsLength: 1,
|
|
127
|
+
optionalCall: false,
|
|
128
|
+
optionalMember: false,
|
|
129
|
+
})
|
|
130
|
+
|| !isToArrayCall(node.arguments[0])
|
|
131
|
+
) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const toArrayCall = node.arguments[0];
|
|
136
|
+
const suggestion = getRemoveToArraySuggestion(toArrayCall, context, MESSAGE_ID_SUGGESTION_ITERABLE_ACCEPTING);
|
|
137
|
+
|
|
138
|
+
return {
|
|
139
|
+
node: toArrayCall.callee.property,
|
|
140
|
+
messageId: MESSAGE_ID_ITERABLE_ACCEPTING,
|
|
141
|
+
data: {description: `${node.callee.object.name}.${node.callee.property.name}(…)`},
|
|
142
|
+
...(suggestion && {suggest: [suggestion]}),
|
|
143
|
+
};
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
const getIteratorMethodProblem = (node, context) => {
|
|
147
|
+
if (
|
|
148
|
+
!(
|
|
149
|
+
isMethodCall(node, {
|
|
150
|
+
methods: callbackOnlyIteratorMethods,
|
|
151
|
+
maximumArguments: 1,
|
|
152
|
+
optionalCall: false,
|
|
153
|
+
optionalMember: false,
|
|
154
|
+
})
|
|
155
|
+
|| isMethodCall(node, {
|
|
156
|
+
method: reduceMethod,
|
|
157
|
+
argumentsLength: 2,
|
|
158
|
+
optionalCall: false,
|
|
159
|
+
optionalMember: false,
|
|
160
|
+
})
|
|
161
|
+
)
|
|
162
|
+
|| !isToArrayCall(node.callee.object)
|
|
163
|
+
) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// If the callback is a function/arrow with enough parameters to reference
|
|
168
|
+
// the `array` argument, `.toArray()` may be intentional.
|
|
169
|
+
const callback = node.arguments[0];
|
|
170
|
+
const method = node.callee.property.name;
|
|
171
|
+
const isReduceCall = method === reduceMethod;
|
|
172
|
+
const arrayParameterIndex = isReduceCall ? 3 : 2;
|
|
173
|
+
if (
|
|
174
|
+
callback
|
|
175
|
+
&& (callback.type === 'ArrowFunctionExpression' || callback.type === 'FunctionExpression')
|
|
176
|
+
&& (
|
|
177
|
+
callback.params.length > arrayParameterIndex
|
|
178
|
+
// A rest parameter can capture the trailing `array` argument too.
|
|
179
|
+
|| callback.params.at(-1)?.type === 'RestElement'
|
|
180
|
+
)
|
|
181
|
+
) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const toArrayCall = node.callee.object;
|
|
186
|
+
const suggestion = getRemoveToArraySuggestion(toArrayCall, context, MESSAGE_ID_SUGGESTION_ITERATOR_METHOD, {method});
|
|
187
|
+
|
|
188
|
+
return {
|
|
189
|
+
node: toArrayCall.callee.property,
|
|
190
|
+
messageId: MESSAGE_ID_ITERATOR_METHOD,
|
|
191
|
+
data: {method},
|
|
192
|
+
...(suggestion && {suggest: [suggestion]}),
|
|
193
|
+
};
|
|
194
|
+
};
|
|
195
|
+
|
|
48
196
|
/** @param {import('eslint').Rule.RuleContext} context */
|
|
49
197
|
const create = context => {
|
|
50
198
|
// Case 1: `new Set(iterator.toArray())`, `new Map(iterator.toArray())`, etc.
|
|
@@ -60,126 +208,22 @@ const create = context => {
|
|
|
60
208
|
}
|
|
61
209
|
|
|
62
210
|
const toArrayCall = node.arguments[0];
|
|
211
|
+
const fix = getRemoveToArrayFix(toArrayCall, context);
|
|
63
212
|
|
|
64
213
|
return {
|
|
65
214
|
node: toArrayCall.callee.property,
|
|
66
215
|
messageId: MESSAGE_ID_ITERABLE_ACCEPTING,
|
|
67
216
|
data: {description: `new ${node.callee.name}(…)`},
|
|
68
|
-
fix
|
|
217
|
+
...(fix && {fix}),
|
|
69
218
|
};
|
|
70
219
|
});
|
|
71
220
|
|
|
72
221
|
// Case 2: Call expressions — static methods and iterator prototype methods.
|
|
73
|
-
context.on('CallExpression', node =>
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
(
|
|
79
|
-
isMethodCall(node, {
|
|
80
|
-
objects: ['Array', ...typedArray],
|
|
81
|
-
method: 'from',
|
|
82
|
-
minimumArguments: 1,
|
|
83
|
-
optionalCall: false,
|
|
84
|
-
optionalMember: false,
|
|
85
|
-
})
|
|
86
|
-
|| isMethodCall(node, {
|
|
87
|
-
object: 'Object',
|
|
88
|
-
method: 'fromEntries',
|
|
89
|
-
minimumArguments: 1,
|
|
90
|
-
optionalCall: false,
|
|
91
|
-
optionalMember: false,
|
|
92
|
-
})
|
|
93
|
-
)
|
|
94
|
-
&& isToArrayCall(node.arguments[0])
|
|
95
|
-
) {
|
|
96
|
-
const toArrayCall = node.arguments[0];
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
node: toArrayCall.callee.property,
|
|
100
|
-
messageId: MESSAGE_ID_ITERABLE_ACCEPTING,
|
|
101
|
-
data: {description: `${node.callee.object.name}.${node.callee.property.name}(…)`},
|
|
102
|
-
fix: fixer => removeMethodCall(fixer, toArrayCall, context),
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// Case 2b: `Promise.all(iterator.toArray())`, etc.
|
|
107
|
-
// Suggestion only — passing an iterator directly can change a sync throw
|
|
108
|
-
// into an async rejection when iteration fails.
|
|
109
|
-
if (
|
|
110
|
-
isMethodCall(node, {
|
|
111
|
-
object: 'Promise',
|
|
112
|
-
methods: ['all', 'allSettled', 'any', 'race'],
|
|
113
|
-
argumentsLength: 1,
|
|
114
|
-
optionalCall: false,
|
|
115
|
-
optionalMember: false,
|
|
116
|
-
})
|
|
117
|
-
&& isToArrayCall(node.arguments[0])
|
|
118
|
-
) {
|
|
119
|
-
const toArrayCall = node.arguments[0];
|
|
120
|
-
|
|
121
|
-
return {
|
|
122
|
-
node: toArrayCall.callee.property,
|
|
123
|
-
messageId: MESSAGE_ID_ITERABLE_ACCEPTING,
|
|
124
|
-
data: {description: `${node.callee.object.name}.${node.callee.property.name}(…)`},
|
|
125
|
-
suggest: [
|
|
126
|
-
{
|
|
127
|
-
messageId: MESSAGE_ID_SUGGESTION_ITERABLE_ACCEPTING,
|
|
128
|
-
fix: fixer => removeMethodCall(fixer, toArrayCall, context),
|
|
129
|
-
},
|
|
130
|
-
],
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Case 2c: `iterator.toArray().every(fn)`, `.find(fn)`, `.forEach(fn)`, `.some(fn)`, `.reduce(fn, init)`
|
|
135
|
-
// Suggestion only — Array callbacks receive a 3rd `array` argument
|
|
136
|
-
// (and `reduce` a 4th) that Iterator callbacks do not.
|
|
137
|
-
if (
|
|
138
|
-
(
|
|
139
|
-
isMethodCall(node, {
|
|
140
|
-
methods: callbackOnlyIteratorMethods,
|
|
141
|
-
maximumArguments: 1,
|
|
142
|
-
optionalCall: false,
|
|
143
|
-
optionalMember: false,
|
|
144
|
-
})
|
|
145
|
-
|| isMethodCall(node, {
|
|
146
|
-
method: reduceMethod,
|
|
147
|
-
argumentsLength: 2,
|
|
148
|
-
optionalCall: false,
|
|
149
|
-
optionalMember: false,
|
|
150
|
-
})
|
|
151
|
-
)
|
|
152
|
-
&& isToArrayCall(node.callee.object)
|
|
153
|
-
) {
|
|
154
|
-
// If the callback is a function/arrow with enough parameters to reference
|
|
155
|
-
// the `array` argument, `.toArray()` may be intentional.
|
|
156
|
-
const callback = node.arguments[0];
|
|
157
|
-
const isReduceCall = node.callee.property.name === reduceMethod;
|
|
158
|
-
const arrayParameterIndex = isReduceCall ? 3 : 2;
|
|
159
|
-
if (
|
|
160
|
-
callback
|
|
161
|
-
&& (callback.type === 'ArrowFunctionExpression' || callback.type === 'FunctionExpression')
|
|
162
|
-
&& callback.params.length > arrayParameterIndex
|
|
163
|
-
) {
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
const callerObject = node.callee.object;
|
|
168
|
-
|
|
169
|
-
return {
|
|
170
|
-
node: callerObject.callee.property,
|
|
171
|
-
messageId: MESSAGE_ID_ITERATOR_METHOD,
|
|
172
|
-
data: {method: node.callee.property.name},
|
|
173
|
-
suggest: [
|
|
174
|
-
{
|
|
175
|
-
messageId: MESSAGE_ID_SUGGESTION_ITERATOR_METHOD,
|
|
176
|
-
data: {method: node.callee.property.name},
|
|
177
|
-
fix: fixer => removeMethodCall(fixer, callerObject, context),
|
|
178
|
-
},
|
|
179
|
-
],
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
});
|
|
222
|
+
context.on('CallExpression', node =>
|
|
223
|
+
getArrayFromProblem(node, context)
|
|
224
|
+
?? getObjectFromEntriesProblem(node, context)
|
|
225
|
+
?? getPromiseProblem(node, context)
|
|
226
|
+
?? getIteratorMethodProblem(node, context));
|
|
183
227
|
|
|
184
228
|
// Case 3: `for (const x of iterator.toArray())`
|
|
185
229
|
context.on('ForOfStatement', node => {
|
|
@@ -187,10 +231,12 @@ const create = context => {
|
|
|
187
231
|
return;
|
|
188
232
|
}
|
|
189
233
|
|
|
234
|
+
const fix = getRemoveToArrayFix(node.right, context);
|
|
235
|
+
|
|
190
236
|
return {
|
|
191
237
|
node: node.right.callee.property,
|
|
192
238
|
messageId: MESSAGE_ID_FOR_OF,
|
|
193
|
-
fix
|
|
239
|
+
...(fix && {fix}),
|
|
194
240
|
};
|
|
195
241
|
});
|
|
196
242
|
|
|
@@ -200,10 +246,12 @@ const create = context => {
|
|
|
200
246
|
return;
|
|
201
247
|
}
|
|
202
248
|
|
|
249
|
+
const fix = getRemoveToArrayFix(node.argument, context);
|
|
250
|
+
|
|
203
251
|
return {
|
|
204
252
|
node: node.argument.callee.property,
|
|
205
253
|
messageId: MESSAGE_ID_YIELD_STAR,
|
|
206
|
-
fix
|
|
254
|
+
...(fix && {fix}),
|
|
207
255
|
};
|
|
208
256
|
});
|
|
209
257
|
|
|
@@ -223,10 +271,12 @@ const create = context => {
|
|
|
223
271
|
return;
|
|
224
272
|
}
|
|
225
273
|
|
|
274
|
+
const fix = getRemoveToArrayFix(node.argument, context);
|
|
275
|
+
|
|
226
276
|
return {
|
|
227
277
|
node: node.argument.callee.property,
|
|
228
278
|
messageId: MESSAGE_ID_SPREAD,
|
|
229
|
-
fix
|
|
279
|
+
...(fix && {fix}),
|
|
230
280
|
};
|
|
231
281
|
});
|
|
232
282
|
};
|