eslint-plugin-unicorn 69.0.0 → 71.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 +26 -25
- package/readme.md +15 -1
- package/rules/ast/call-or-new-expression.js +2 -8
- package/rules/ast/is-member-expression.js +2 -8
- package/rules/comment-content.js +4 -4
- package/rules/consistent-boolean-name.js +727 -42
- package/rules/consistent-compound-words.js +1 -2
- package/rules/consistent-json-file-read.js +2 -5
- package/rules/dom-node-dataset.js +3 -7
- package/rules/escape-case.js +4 -4
- package/rules/expiring-todo-comments.js +25 -2
- package/rules/explicit-length-check.js +3 -3
- package/rules/filename-case.js +4 -4
- 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/remove-argument.js +34 -8
- package/rules/fix/remove-statement.js +1 -3
- package/rules/fix/switch-new-expression-to-call-expression.js +2 -2
- package/rules/id-match.js +2 -2
- package/rules/index.js +10 -0
- package/rules/isolated-functions.js +203 -19
- package/rules/name-replacements.js +1 -2
- package/rules/no-anonymous-default-export.js +1 -3
- package/rules/no-array-callback-reference.js +2 -10
- package/rules/no-async-promise-finally.js +111 -0
- package/rules/no-chained-comparison.js +1 -1
- package/rules/no-collection-bracket-access.js +27 -4
- package/rules/no-computed-property-existence-check.js +1 -4
- package/rules/no-declarations-before-early-exit.js +6 -5
- package/rules/no-error-property-assignment.js +3 -3
- package/rules/no-for-loop.js +4 -4
- package/rules/no-keyword-prefix.js +2 -2
- package/rules/no-manually-wrapped-comments.js +5 -1
- package/rules/no-mismatched-map-key.js +13 -20
- package/rules/no-named-default.js +2 -5
- package/rules/no-negated-array-predicate.js +6 -13
- package/rules/no-negated-comparison.js +31 -71
- package/rules/no-negated-condition.js +2 -2
- package/rules/no-negation-in-equality-check.js +4 -7
- package/rules/no-non-function-verb-prefix.js +25 -2
- package/rules/no-process-exit.js +6 -6
- package/rules/no-return-array-push.js +9 -2
- package/rules/no-static-only-class.js +2 -8
- package/rules/no-thenable.js +9 -15
- package/rules/no-typeof-undefined.js +2 -2
- package/rules/no-undeclared-class-members.js +1 -5
- package/rules/no-unnecessary-array-flat-map.js +308 -0
- package/rules/no-unnecessary-await.js +2 -2
- package/rules/no-unnecessary-fetch-options.js +624 -0
- package/rules/no-unnecessary-global-this.js +5 -9
- package/rules/no-unsafe-promise-all-settled-values.js +865 -0
- package/rules/no-unused-properties.js +0 -1
- package/rules/no-useless-coercion.js +1 -2
- 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/no-useless-undefined.js +2 -2
- package/rules/prefer-abort-signal-any.js +1391 -0
- package/rules/prefer-array-find.js +2 -5
- package/rules/prefer-array-flat-map.js +6 -11
- package/rules/prefer-array-from-range.js +98 -0
- package/rules/prefer-bigint-literals.js +1 -0
- package/rules/prefer-block-statement-over-iife.js +159 -0
- package/rules/prefer-boolean-return.js +20 -3
- package/rules/prefer-continue.js +6 -2
- package/rules/prefer-default-parameters.js +2 -2
- package/rules/prefer-early-return.js +3 -1
- package/rules/prefer-else-if.js +5 -9
- package/rules/prefer-group-by.js +576 -0
- package/rules/prefer-has-check.js +6 -3
- package/rules/prefer-https.js +3 -3
- package/rules/prefer-identifier-import-export-specifiers.js +1 -3
- package/rules/prefer-iterator-helpers.js +196 -0
- package/rules/prefer-minimal-ternary.js +4 -4
- package/rules/prefer-number-coercion.js +1 -1
- package/rules/prefer-object-define-properties.js +1 -3
- package/rules/prefer-object-iterable-methods.js +57 -14
- package/rules/prefer-observer-apis.js +2 -5
- package/rules/prefer-private-class-fields.js +1 -3
- package/rules/prefer-simple-sort-comparator.js +6 -6
- package/rules/prefer-simplified-conditions.js +584 -0
- package/rules/prefer-single-object-destructuring.js +2 -5
- package/rules/prefer-single-replace.js +2 -2
- package/rules/prefer-spread.js +25 -32
- package/rules/prefer-string-pad-start-end.js +1 -6
- package/rules/prefer-string-raw.js +2 -2
- package/rules/prefer-string-repeat.js +16 -9
- package/rules/prefer-string-replace-all.js +1 -1
- package/rules/prefer-ternary.js +5 -5
- package/rules/prefer-toggle-attribute.js +3 -3
- package/rules/prefer-top-level-await.js +6 -1
- package/rules/prefer-uint8array-base64.js +3 -1
- package/rules/require-proxy-trap-boolean-return.js +2 -2
- package/rules/rule/to-eslint-create.js +1 -2
- package/rules/rule/to-eslint-listener.js +1 -3
- package/rules/rule/to-eslint-problem.js +1 -3
- package/rules/rule/to-eslint-rule-fixer.js +1 -3
- package/rules/rule/to-eslint-rule.js +1 -3
- package/rules/rule/to-eslint-rules.js +1 -3
- package/rules/rule/unicorn-context.js +1 -3
- package/rules/rule/unicorn-listeners.js +1 -3
- package/rules/shared/array-range.js +66 -0
- package/rules/shared/iterator-helpers.js +43 -1
- package/rules/shared/no-array-mutate-rule.js +1 -3
- package/rules/shared/regexp-escape.js +4 -4
- package/rules/utils/can-tokens-be-adjacent.js +95 -0
- package/rules/utils/comparison.js +65 -0
- package/rules/utils/escape-string.js +2 -8
- package/rules/utils/get-available-variable-name.js +2 -13
- package/rules/utils/get-precedence.js +112 -0
- package/rules/utils/get-token-store.js +1 -3
- package/rules/utils/global-reference-tracker.js +3 -6
- package/rules/utils/index.js +4 -0
- package/rules/utils/is-boolean.js +103 -30
- package/rules/utils/is-identifier-name.js +5 -0
- package/rules/utils/is-promise-type.js +1 -3
- package/rules/utils/is-react-hook-name.js +5 -0
- package/rules/utils/parentheses/get-parent-syntax-opening-parenthesis.js +1 -3
- package/rules/utils/parentheses/iterate-surrounding-parentheses.js +1 -1
- package/rules/utils/should-add-parentheses-to-await-expression-argument.js +4 -9
- package/rules/utils/should-add-parentheses-to-call-expression-callee.js +6 -12
- package/rules/utils/should-add-parentheses-to-unary-expression.js +7 -14
- package/rules/utils/to-location.js +1 -3
- package/rules/utils/track-branch-exits.js +8 -7
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
import {
|
|
2
|
+
findVariable,
|
|
3
|
+
getPropertyName,
|
|
4
|
+
getStaticValue,
|
|
5
|
+
hasSideEffect,
|
|
6
|
+
isCommaToken,
|
|
7
|
+
isCommentToken,
|
|
8
|
+
} from '@eslint-community/eslint-utils';
|
|
9
|
+
import {
|
|
10
|
+
isCallExpression,
|
|
11
|
+
isNewExpression,
|
|
12
|
+
isReferenceIdentifier,
|
|
13
|
+
} from './ast/index.js';
|
|
14
|
+
import {
|
|
15
|
+
getArgumentRemovalRange,
|
|
16
|
+
removeObjectProperty,
|
|
17
|
+
} from './fix/index.js';
|
|
18
|
+
import {
|
|
19
|
+
getBaseTypes,
|
|
20
|
+
getTypeSymbol,
|
|
21
|
+
isDefaultLibrarySymbol,
|
|
22
|
+
isGlobalIdentifier,
|
|
23
|
+
unwrapTypeScriptExpression,
|
|
24
|
+
} from './utils/index.js';
|
|
25
|
+
|
|
26
|
+
const MESSAGE_ID_EMPTY_OPTIONS = 'empty-options';
|
|
27
|
+
const MESSAGE_ID_PROPERTY = 'property';
|
|
28
|
+
|
|
29
|
+
const messages = {
|
|
30
|
+
[MESSAGE_ID_EMPTY_OPTIONS]: 'Remove unnecessary empty fetch options.',
|
|
31
|
+
[MESSAGE_ID_PROPERTY]: 'Remove unnecessary `{{property}}` fetch option.',
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const requestInitProperties = new Set([
|
|
35
|
+
'attributionReporting',
|
|
36
|
+
'body',
|
|
37
|
+
'browsingTopics',
|
|
38
|
+
'cache',
|
|
39
|
+
'credentials',
|
|
40
|
+
'duplex',
|
|
41
|
+
'headers',
|
|
42
|
+
'integrity',
|
|
43
|
+
'keepalive',
|
|
44
|
+
'method',
|
|
45
|
+
'mode',
|
|
46
|
+
'priority',
|
|
47
|
+
'redirect',
|
|
48
|
+
'referrer',
|
|
49
|
+
'referrerPolicy',
|
|
50
|
+
'signal',
|
|
51
|
+
'window',
|
|
52
|
+
]);
|
|
53
|
+
|
|
54
|
+
const defaultValues = new Map([
|
|
55
|
+
['cache', 'default'],
|
|
56
|
+
['credentials', 'same-origin'],
|
|
57
|
+
['integrity', ''],
|
|
58
|
+
['keepalive', false],
|
|
59
|
+
['method', 'GET'],
|
|
60
|
+
['mode', 'cors'],
|
|
61
|
+
['redirect', 'follow'],
|
|
62
|
+
['referrer', 'about:client'],
|
|
63
|
+
['referrerPolicy', ''],
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
const request = 'request';
|
|
67
|
+
const nonRequest = 'non-request';
|
|
68
|
+
const unknown = 'unknown';
|
|
69
|
+
|
|
70
|
+
const isGlobalIdentifierNamed = (node, name, context) =>
|
|
71
|
+
node.type === 'Identifier'
|
|
72
|
+
&& node.name === name
|
|
73
|
+
&& isGlobalIdentifier(node, context);
|
|
74
|
+
|
|
75
|
+
const isStaticStringLiteral = node =>
|
|
76
|
+
node.type === 'Literal'
|
|
77
|
+
&& typeof node.value === 'string';
|
|
78
|
+
|
|
79
|
+
const getStaticPropertyName = (property, context) =>
|
|
80
|
+
property.type === 'Property'
|
|
81
|
+
? getPropertyName(property, context.sourceCode.getScope(property)) ?? undefined
|
|
82
|
+
: undefined;
|
|
83
|
+
|
|
84
|
+
const getStaticValueForNode = (node, context) =>
|
|
85
|
+
getStaticValue(unwrapTypeScriptExpression(node), context.sourceCode.getScope(node));
|
|
86
|
+
|
|
87
|
+
const hasCommentsInside = (node, context) =>
|
|
88
|
+
context.sourceCode.getCommentsInside(node).length > 0;
|
|
89
|
+
|
|
90
|
+
const hasSideEffectValue = (node, context) =>
|
|
91
|
+
hasSideEffect(unwrapTypeScriptExpression(node), context.sourceCode, {considerGetters: true});
|
|
92
|
+
|
|
93
|
+
const hasSideEffectProperty = (property, context) =>
|
|
94
|
+
hasSideEffectValue(property.value, context)
|
|
95
|
+
|| (
|
|
96
|
+
property.computed
|
|
97
|
+
&& hasSideEffectValue(property.key, context)
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
function hasReferenceDeclaredAfter(node, context) {
|
|
101
|
+
node = unwrapTypeScriptExpression(node);
|
|
102
|
+
|
|
103
|
+
if (node.type.startsWith('TS')) {
|
|
104
|
+
return false;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const {sourceCode} = context;
|
|
108
|
+
if (isReferenceIdentifier(node)) {
|
|
109
|
+
const variable = findVariable(sourceCode.getScope(node), node);
|
|
110
|
+
const definition = variable?.defs[0]?.name;
|
|
111
|
+
return definition
|
|
112
|
+
? sourceCode.getRange(definition)[0] > sourceCode.getRange(node)[0]
|
|
113
|
+
: false;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
for (const key of sourceCode.visitorKeys[node.type] ?? []) {
|
|
117
|
+
const value = node[key];
|
|
118
|
+
|
|
119
|
+
if (Array.isArray(value)) {
|
|
120
|
+
if (value.some(node => node && hasReferenceDeclaredAfter(node, context))) {
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (value?.type && hasReferenceDeclaredAfter(value, context)) {
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const isUnsafeToRemoveProperty = (property, context) =>
|
|
136
|
+
hasSideEffectProperty(property, context)
|
|
137
|
+
|| hasReferenceDeclaredAfter(property.value, context)
|
|
138
|
+
|| (
|
|
139
|
+
property.computed
|
|
140
|
+
&& hasReferenceDeclaredAfter(property.key, context)
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const hasCommentsInRange = (range, context) => {
|
|
144
|
+
const {sourceCode} = context;
|
|
145
|
+
|
|
146
|
+
return sourceCode.getAllComments().some(comment => {
|
|
147
|
+
const commentRange = sourceCode.getRange(comment);
|
|
148
|
+
return commentRange[0] >= range[0] && commentRange[1] <= range[1];
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
function getFinalArgumentRemovalRange(node, context) {
|
|
153
|
+
const {sourceCode} = context;
|
|
154
|
+
const range = getArgumentRemovalRange(node, context);
|
|
155
|
+
const tokenAfter = sourceCode.getTokenAfter(node);
|
|
156
|
+
|
|
157
|
+
if (isCommaToken(tokenAfter)) {
|
|
158
|
+
range[1] = sourceCode.getRange(tokenAfter)[1];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return range;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function hasCommentAfterTrailingComma(node, context) {
|
|
165
|
+
const {sourceCode} = context;
|
|
166
|
+
const tokenAfter = sourceCode.getTokenAfter(node);
|
|
167
|
+
return isCommaToken(tokenAfter)
|
|
168
|
+
&& isCommentToken(sourceCode.getTokenAfter(tokenAfter, {includeComments: true}));
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const canRemoveFinalArgumentWithoutComments = (node, context) =>
|
|
172
|
+
!hasCommentsInRange(getFinalArgumentRemovalRange(node, context), context)
|
|
173
|
+
&& !isCommentToken(context.sourceCode.getTokenAfter(node, {includeComments: true}))
|
|
174
|
+
&& !hasCommentAfterTrailingComma(node, context);
|
|
175
|
+
|
|
176
|
+
const removeFinalArgument = (fixer, node, context) =>
|
|
177
|
+
fixer.removeRange(getFinalArgumentRemovalRange(node, context));
|
|
178
|
+
|
|
179
|
+
function getPropertyLineRemovalRange(property, context) {
|
|
180
|
+
const {sourceCode} = context;
|
|
181
|
+
const location = sourceCode.getLoc(property);
|
|
182
|
+
if (location.start.line !== location.end.line) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const line = sourceCode.lines[location.start.line - 1];
|
|
187
|
+
if (line.slice(0, location.start.column).trim() !== '') {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const tokenAfter = sourceCode.getTokenAfter(property);
|
|
192
|
+
const tokenAfterLocation = sourceCode.getLoc(tokenAfter);
|
|
193
|
+
const commaOnSameLine = isCommaToken(tokenAfter) && tokenAfterLocation.start.line === location.end.line;
|
|
194
|
+
if (
|
|
195
|
+
property.parent.properties.at(-1) !== property
|
|
196
|
+
&& !commaOnSameLine
|
|
197
|
+
) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const endColumn = commaOnSameLine
|
|
202
|
+
? tokenAfterLocation.end.column
|
|
203
|
+
: location.end.column;
|
|
204
|
+
if (line.slice(endColumn).trim() !== '') {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return [
|
|
209
|
+
sourceCode.getIndexFromLoc({line: location.start.line, column: 0}),
|
|
210
|
+
location.start.line < sourceCode.lines.length
|
|
211
|
+
? sourceCode.getIndexFromLoc({line: location.start.line + 1, column: 0})
|
|
212
|
+
: sourceCode.text.length,
|
|
213
|
+
];
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getPropertyInlineRemovalRange(property, context) {
|
|
217
|
+
const {sourceCode} = context;
|
|
218
|
+
const location = sourceCode.getLoc(property);
|
|
219
|
+
if (location.start.line !== location.end.line) {
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const previousToken = sourceCode.getTokenBefore(property);
|
|
224
|
+
const nextToken = sourceCode.getTokenAfter(property);
|
|
225
|
+
const isLastProperty = property.parent.properties.at(-1) === property;
|
|
226
|
+
|
|
227
|
+
if (isLastProperty) {
|
|
228
|
+
if (!isCommaToken(previousToken)) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const previousTokenLocation = sourceCode.getLoc(previousToken);
|
|
233
|
+
if (previousTokenLocation.end.line !== location.start.line) {
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const endToken = isCommaToken(nextToken)
|
|
238
|
+
? sourceCode.getTokenAfter(nextToken)
|
|
239
|
+
: nextToken;
|
|
240
|
+
|
|
241
|
+
const end = endToken && sourceCode.getLoc(endToken).start.line === location.end.line
|
|
242
|
+
? sourceCode.getRange(endToken)[0]
|
|
243
|
+
: sourceCode.getRange(property)[1];
|
|
244
|
+
|
|
245
|
+
return [
|
|
246
|
+
sourceCode.getRange(previousToken)[0],
|
|
247
|
+
end,
|
|
248
|
+
];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (!isCommaToken(nextToken)) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const nextTokenLocation = sourceCode.getLoc(nextToken);
|
|
256
|
+
if (nextTokenLocation.start.line !== location.end.line) {
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
const tokenAfterComma = sourceCode.getTokenAfter(nextToken);
|
|
261
|
+
const end = tokenAfterComma && sourceCode.getLoc(tokenAfterComma).start.line === location.end.line
|
|
262
|
+
? sourceCode.getRange(tokenAfterComma)[0]
|
|
263
|
+
: sourceCode.getRange(nextToken)[1];
|
|
264
|
+
|
|
265
|
+
return [
|
|
266
|
+
sourceCode.getRange(property)[0],
|
|
267
|
+
end,
|
|
268
|
+
];
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const isStaticUndefined = (node, context) => {
|
|
272
|
+
const result = getStaticValueForNode(node, context);
|
|
273
|
+
return result ? result.value === undefined : false;
|
|
274
|
+
};
|
|
275
|
+
|
|
276
|
+
const isStaticNull = (node, context) =>
|
|
277
|
+
getStaticValueForNode(node, context)?.value === null;
|
|
278
|
+
|
|
279
|
+
const isEmptyHeaders = (node, context) => {
|
|
280
|
+
node = unwrapTypeScriptExpression(node);
|
|
281
|
+
|
|
282
|
+
if (node.type === 'ObjectExpression') {
|
|
283
|
+
return node.properties.length === 0;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (node.type === 'ArrayExpression') {
|
|
287
|
+
return node.elements.length === 0;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
return isNewExpression(node, {
|
|
291
|
+
name: 'Headers',
|
|
292
|
+
argumentsLength: 0,
|
|
293
|
+
})
|
|
294
|
+
&& isGlobalIdentifierNamed(node.callee, 'Headers', context);
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
function getObjectPropertyNames(properties, context) {
|
|
298
|
+
const names = [];
|
|
299
|
+
const seen = new Set();
|
|
300
|
+
|
|
301
|
+
for (const property of properties) {
|
|
302
|
+
const name = getStaticPropertyName(property, context);
|
|
303
|
+
if (name === undefined || seen.has(name)) {
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
seen.add(name);
|
|
308
|
+
names.push(name);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return names;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const getTypeName = (type, program) => {
|
|
315
|
+
const symbol = getTypeSymbol(type);
|
|
316
|
+
return isDefaultLibrarySymbol(symbol, program) ? symbol.getName() : undefined;
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
function combineTypeStates(states) {
|
|
320
|
+
if (states.includes(request)) {
|
|
321
|
+
return request;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (states.length > 0 && states.every(state => state === nonRequest)) {
|
|
325
|
+
return nonRequest;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return unknown;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function getInputTypeState(type, checker, program, seen = new Set()) {
|
|
332
|
+
type = checker.getNonNullableType(type);
|
|
333
|
+
|
|
334
|
+
if (seen.has(type)) {
|
|
335
|
+
return unknown;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
seen.add(type);
|
|
339
|
+
|
|
340
|
+
if (type.isUnion()) {
|
|
341
|
+
return combineTypeStates(type.types.map(type => getInputTypeState(type, checker, program, seen)));
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
if (type.isIntersection()) {
|
|
345
|
+
return combineTypeStates(type.types.map(type => getInputTypeState(type, checker, program, seen)));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
const constraint = checker.getBaseConstraintOfType(type);
|
|
349
|
+
if (constraint) {
|
|
350
|
+
return getInputTypeState(constraint, checker, program, seen);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (type.intrinsicName === 'string' || type.isStringLiteral?.()) {
|
|
354
|
+
return nonRequest;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
const typeName = getTypeName(type, program);
|
|
358
|
+
if (typeName === 'Request') {
|
|
359
|
+
return request;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (typeName === 'URL' || typeName === 'String') {
|
|
363
|
+
return nonRequest;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return combineTypeStates(getBaseTypes(type, checker).map(type => getInputTypeState(type, checker, program, seen)));
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
function getInputState(node, context) {
|
|
370
|
+
node = unwrapTypeScriptExpression(node);
|
|
371
|
+
|
|
372
|
+
if (
|
|
373
|
+
isStaticStringLiteral(node)
|
|
374
|
+
|| (
|
|
375
|
+
node.type === 'TemplateLiteral'
|
|
376
|
+
&& node.expressions.length === 0
|
|
377
|
+
)
|
|
378
|
+
|| (
|
|
379
|
+
isNewExpression(node, {
|
|
380
|
+
name: 'URL',
|
|
381
|
+
minimumArguments: 1,
|
|
382
|
+
})
|
|
383
|
+
&& isGlobalIdentifierNamed(node.callee, 'URL', context)
|
|
384
|
+
)
|
|
385
|
+
) {
|
|
386
|
+
return nonRequest;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (
|
|
390
|
+
isNewExpression(node, {
|
|
391
|
+
name: 'Request',
|
|
392
|
+
minimumArguments: 1,
|
|
393
|
+
})
|
|
394
|
+
&& isGlobalIdentifierNamed(node.callee, 'Request', context)
|
|
395
|
+
) {
|
|
396
|
+
return request;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const {parserServices} = context.sourceCode;
|
|
400
|
+
if (!parserServices?.program) {
|
|
401
|
+
return unknown;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
return getInputTypeState(
|
|
406
|
+
parserServices.getTypeAtLocation(node),
|
|
407
|
+
parserServices.program.getTypeChecker(),
|
|
408
|
+
parserServices.program,
|
|
409
|
+
);
|
|
410
|
+
} catch {
|
|
411
|
+
return unknown;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const isDefaultValue = (propertyName, value, context) => {
|
|
416
|
+
if (!defaultValues.has(propertyName)) {
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const staticValue = getStaticValueForNode(value, context)?.value;
|
|
421
|
+
const defaultValue = defaultValues.get(propertyName);
|
|
422
|
+
|
|
423
|
+
return propertyName === 'method' && typeof staticValue === 'string'
|
|
424
|
+
? staticValue.toUpperCase() === defaultValue
|
|
425
|
+
: staticValue === defaultValue;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
function isUnnecessaryProperty(property, propertyName, inputState, context) {
|
|
429
|
+
if (
|
|
430
|
+
requestInitProperties.has(propertyName)
|
|
431
|
+
&& isStaticUndefined(property.value, context)
|
|
432
|
+
) {
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
if (
|
|
437
|
+
propertyName === 'body'
|
|
438
|
+
&& isStaticNull(property.value, context)
|
|
439
|
+
) {
|
|
440
|
+
return true;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (inputState !== nonRequest) {
|
|
444
|
+
return false;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
if (
|
|
448
|
+
propertyName === 'headers'
|
|
449
|
+
&& isEmptyHeaders(property.value, context)
|
|
450
|
+
) {
|
|
451
|
+
return true;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return isDefaultValue(propertyName, property.value, context);
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
const getFix = (property, optionsNode, optionsArgument, context) => function * (fixer, {abort}) {
|
|
458
|
+
if (
|
|
459
|
+
hasCommentsInside(optionsNode, context)
|
|
460
|
+
|| isUnsafeToRemoveProperty(property, context)
|
|
461
|
+
) {
|
|
462
|
+
return abort();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
if (
|
|
466
|
+
optionsNode.properties.length === 1
|
|
467
|
+
&& optionsArgument.parent.arguments.at(-1) === optionsArgument
|
|
468
|
+
) {
|
|
469
|
+
if (!canRemoveFinalArgumentWithoutComments(optionsArgument, context)) {
|
|
470
|
+
return abort();
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
yield removeFinalArgument(fixer, optionsArgument, context);
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
if (optionsNode.properties.length === 1) {
|
|
478
|
+
yield fixer.replaceText(optionsNode, '{}');
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const lineRemovalRange = getPropertyLineRemovalRange(property, context);
|
|
483
|
+
if (lineRemovalRange) {
|
|
484
|
+
yield fixer.removeRange(lineRemovalRange);
|
|
485
|
+
return;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
const inlineRemovalRange = getPropertyInlineRemovalRange(property, context);
|
|
489
|
+
if (inlineRemovalRange) {
|
|
490
|
+
yield fixer.removeRange(inlineRemovalRange);
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
yield removeObjectProperty(fixer, property, context);
|
|
495
|
+
};
|
|
496
|
+
|
|
497
|
+
function getWholeOptionsFix(unnecessaryProperties, optionsNode, optionsArgument, context) {
|
|
498
|
+
if (
|
|
499
|
+
unnecessaryProperties.length !== optionsNode.properties.length
|
|
500
|
+
|| hasCommentsInside(optionsNode, context)
|
|
501
|
+
|| unnecessaryProperties.some(({property}) => isUnsafeToRemoveProperty(property, context))
|
|
502
|
+
) {
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (optionsArgument.parent.arguments.at(-1) !== optionsArgument) {
|
|
507
|
+
return fixer => fixer.replaceText(optionsNode, '{}');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (!canRemoveFinalArgumentWithoutComments(optionsArgument, context)) {
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return fixer => removeFinalArgument(fixer, optionsArgument, context);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function * getOptionsProblems(input, optionsArgument, context) {
|
|
518
|
+
const optionsNode = unwrapTypeScriptExpression(optionsArgument);
|
|
519
|
+
|
|
520
|
+
if (optionsNode.type !== 'ObjectExpression') {
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
const {properties} = optionsNode;
|
|
525
|
+
|
|
526
|
+
if (properties.length === 0) {
|
|
527
|
+
if (optionsArgument.parent.arguments.at(-1) !== optionsArgument) {
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
yield {
|
|
532
|
+
node: optionsNode,
|
|
533
|
+
messageId: MESSAGE_ID_EMPTY_OPTIONS,
|
|
534
|
+
fix: canRemoveFinalArgumentWithoutComments(optionsArgument, context)
|
|
535
|
+
? fixer => removeFinalArgument(fixer, optionsArgument, context)
|
|
536
|
+
: undefined,
|
|
537
|
+
};
|
|
538
|
+
return;
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
const propertyNames = getObjectPropertyNames(properties, context);
|
|
542
|
+
if (!propertyNames) {
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
const inputState = getInputState(input, context);
|
|
547
|
+
const unnecessaryProperties = [];
|
|
548
|
+
|
|
549
|
+
for (const [index, property] of properties.entries()) {
|
|
550
|
+
const propertyName = propertyNames[index];
|
|
551
|
+
if (!isUnnecessaryProperty(property, propertyName, inputState, context)) {
|
|
552
|
+
continue;
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
unnecessaryProperties.push({property, propertyName});
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const wholeOptionsFix = getWholeOptionsFix(unnecessaryProperties, optionsNode, optionsArgument, context);
|
|
559
|
+
|
|
560
|
+
for (const [index, {property, propertyName}] of unnecessaryProperties.entries()) {
|
|
561
|
+
const fix = index === 0 && wholeOptionsFix
|
|
562
|
+
? wholeOptionsFix
|
|
563
|
+
: getFix(property, optionsNode, optionsArgument, context);
|
|
564
|
+
|
|
565
|
+
yield {
|
|
566
|
+
node: property.key,
|
|
567
|
+
messageId: MESSAGE_ID_PROPERTY,
|
|
568
|
+
data: {property: propertyName},
|
|
569
|
+
fix,
|
|
570
|
+
};
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** @param {import('eslint').Rule.RuleContext} context */
|
|
575
|
+
const create = context => {
|
|
576
|
+
context.on('CallExpression', function * (callExpression) {
|
|
577
|
+
if (
|
|
578
|
+
!isCallExpression(callExpression, {
|
|
579
|
+
name: 'fetch',
|
|
580
|
+
minimumArguments: 2,
|
|
581
|
+
optional: false,
|
|
582
|
+
})
|
|
583
|
+
|| !isGlobalIdentifierNamed(callExpression.callee, 'fetch', context)
|
|
584
|
+
) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
yield * getOptionsProblems(callExpression.arguments[0], callExpression.arguments[1], context);
|
|
589
|
+
});
|
|
590
|
+
|
|
591
|
+
context.on('NewExpression', function * (newExpression) {
|
|
592
|
+
if (
|
|
593
|
+
!isNewExpression(newExpression, {
|
|
594
|
+
name: 'Request',
|
|
595
|
+
minimumArguments: 2,
|
|
596
|
+
})
|
|
597
|
+
|| !isGlobalIdentifierNamed(newExpression.callee, 'Request', context)
|
|
598
|
+
) {
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
yield * getOptionsProblems(newExpression.arguments[0], newExpression.arguments[1], context);
|
|
603
|
+
});
|
|
604
|
+
};
|
|
605
|
+
|
|
606
|
+
/** @type {import('eslint').Rule.RuleModule} */
|
|
607
|
+
const config = {
|
|
608
|
+
create,
|
|
609
|
+
meta: {
|
|
610
|
+
type: 'suggestion',
|
|
611
|
+
docs: {
|
|
612
|
+
description: 'Disallow unnecessary options in `fetch()` and `new Request()`.',
|
|
613
|
+
recommended: 'unopinionated',
|
|
614
|
+
},
|
|
615
|
+
fixable: 'code',
|
|
616
|
+
schema: [],
|
|
617
|
+
messages,
|
|
618
|
+
languages: [
|
|
619
|
+
'js/js',
|
|
620
|
+
],
|
|
621
|
+
},
|
|
622
|
+
};
|
|
623
|
+
|
|
624
|
+
export default config;
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
isIdentifierName,
|
|
3
|
-
isKeyword,
|
|
4
|
-
isReservedWord,
|
|
5
|
-
isStrictReservedWord,
|
|
6
|
-
} from '@babel/helper-validator-identifier';
|
|
1
|
+
import reservedIdentifiers from 'reserved-identifiers';
|
|
7
2
|
import {findVariable} from '@eslint-community/eslint-utils';
|
|
8
3
|
import {isStringLiteral} from './ast/index.js';
|
|
9
4
|
import {
|
|
@@ -12,6 +7,7 @@ import {
|
|
|
12
7
|
isLeftHandSide,
|
|
13
8
|
isControlFlowTest,
|
|
14
9
|
isBooleanExpression,
|
|
10
|
+
isIdentifierName,
|
|
15
11
|
} from './utils/index.js';
|
|
16
12
|
|
|
17
13
|
const MESSAGE_ID = 'no-unnecessary-global-this';
|
|
@@ -53,11 +49,11 @@ function isActiveGlobal(name, node, context) {
|
|
|
53
49
|
&& variable.defs.length === 0;
|
|
54
50
|
}
|
|
55
51
|
|
|
52
|
+
const reserved = reservedIdentifiers();
|
|
53
|
+
|
|
56
54
|
function canUseBareIdentifier(name) {
|
|
57
55
|
return isIdentifierName(name)
|
|
58
|
-
&& !
|
|
59
|
-
&& !isReservedWord(name, true)
|
|
60
|
-
&& !isStrictReservedWord(name, true);
|
|
56
|
+
&& (!reserved.has(name) || name === 'eval' || name === 'arguments');
|
|
61
57
|
}
|
|
62
58
|
|
|
63
59
|
function getStaticPropertyName(node) {
|