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.
Files changed (124) hide show
  1. package/package.json +26 -25
  2. package/readme.md +15 -1
  3. package/rules/ast/call-or-new-expression.js +2 -8
  4. package/rules/ast/is-member-expression.js +2 -8
  5. package/rules/comment-content.js +4 -4
  6. package/rules/consistent-boolean-name.js +727 -42
  7. package/rules/consistent-compound-words.js +1 -2
  8. package/rules/consistent-json-file-read.js +2 -5
  9. package/rules/dom-node-dataset.js +3 -7
  10. package/rules/escape-case.js +4 -4
  11. package/rules/expiring-todo-comments.js +25 -2
  12. package/rules/explicit-length-check.js +3 -3
  13. package/rules/filename-case.js +4 -4
  14. package/rules/fix/{add-parenthesizes-to-return-or-throw-expression.js → add-parentheses-to-return-or-throw-expression.js} +1 -1
  15. package/rules/fix/index.js +1 -1
  16. package/rules/fix/remove-argument.js +34 -8
  17. package/rules/fix/remove-statement.js +1 -3
  18. package/rules/fix/switch-new-expression-to-call-expression.js +2 -2
  19. package/rules/id-match.js +2 -2
  20. package/rules/index.js +10 -0
  21. package/rules/isolated-functions.js +203 -19
  22. package/rules/name-replacements.js +1 -2
  23. package/rules/no-anonymous-default-export.js +1 -3
  24. package/rules/no-array-callback-reference.js +2 -10
  25. package/rules/no-async-promise-finally.js +111 -0
  26. package/rules/no-chained-comparison.js +1 -1
  27. package/rules/no-collection-bracket-access.js +27 -4
  28. package/rules/no-computed-property-existence-check.js +1 -4
  29. package/rules/no-declarations-before-early-exit.js +6 -5
  30. package/rules/no-error-property-assignment.js +3 -3
  31. package/rules/no-for-loop.js +4 -4
  32. package/rules/no-keyword-prefix.js +2 -2
  33. package/rules/no-manually-wrapped-comments.js +5 -1
  34. package/rules/no-mismatched-map-key.js +13 -20
  35. package/rules/no-named-default.js +2 -5
  36. package/rules/no-negated-array-predicate.js +6 -13
  37. package/rules/no-negated-comparison.js +31 -71
  38. package/rules/no-negated-condition.js +2 -2
  39. package/rules/no-negation-in-equality-check.js +4 -7
  40. package/rules/no-non-function-verb-prefix.js +25 -2
  41. package/rules/no-process-exit.js +6 -6
  42. package/rules/no-return-array-push.js +9 -2
  43. package/rules/no-static-only-class.js +2 -8
  44. package/rules/no-thenable.js +9 -15
  45. package/rules/no-typeof-undefined.js +2 -2
  46. package/rules/no-undeclared-class-members.js +1 -5
  47. package/rules/no-unnecessary-array-flat-map.js +308 -0
  48. package/rules/no-unnecessary-await.js +2 -2
  49. package/rules/no-unnecessary-fetch-options.js +624 -0
  50. package/rules/no-unnecessary-global-this.js +5 -9
  51. package/rules/no-unsafe-promise-all-settled-values.js +865 -0
  52. package/rules/no-unused-properties.js +0 -1
  53. package/rules/no-useless-coercion.js +1 -2
  54. package/rules/no-useless-else.js +32 -1
  55. package/rules/no-useless-iterator-to-array.js +42 -16
  56. package/rules/no-useless-spread.js +2 -2
  57. package/rules/no-useless-undefined.js +2 -2
  58. package/rules/prefer-abort-signal-any.js +1391 -0
  59. package/rules/prefer-array-find.js +2 -5
  60. package/rules/prefer-array-flat-map.js +6 -11
  61. package/rules/prefer-array-from-range.js +98 -0
  62. package/rules/prefer-bigint-literals.js +1 -0
  63. package/rules/prefer-block-statement-over-iife.js +159 -0
  64. package/rules/prefer-boolean-return.js +20 -3
  65. package/rules/prefer-continue.js +6 -2
  66. package/rules/prefer-default-parameters.js +2 -2
  67. package/rules/prefer-early-return.js +3 -1
  68. package/rules/prefer-else-if.js +5 -9
  69. package/rules/prefer-group-by.js +576 -0
  70. package/rules/prefer-has-check.js +6 -3
  71. package/rules/prefer-https.js +3 -3
  72. package/rules/prefer-identifier-import-export-specifiers.js +1 -3
  73. package/rules/prefer-iterator-helpers.js +196 -0
  74. package/rules/prefer-minimal-ternary.js +4 -4
  75. package/rules/prefer-number-coercion.js +1 -1
  76. package/rules/prefer-object-define-properties.js +1 -3
  77. package/rules/prefer-object-iterable-methods.js +57 -14
  78. package/rules/prefer-observer-apis.js +2 -5
  79. package/rules/prefer-private-class-fields.js +1 -3
  80. package/rules/prefer-simple-sort-comparator.js +6 -6
  81. package/rules/prefer-simplified-conditions.js +584 -0
  82. package/rules/prefer-single-object-destructuring.js +2 -5
  83. package/rules/prefer-single-replace.js +2 -2
  84. package/rules/prefer-spread.js +25 -32
  85. package/rules/prefer-string-pad-start-end.js +1 -6
  86. package/rules/prefer-string-raw.js +2 -2
  87. package/rules/prefer-string-repeat.js +16 -9
  88. package/rules/prefer-string-replace-all.js +1 -1
  89. package/rules/prefer-ternary.js +5 -5
  90. package/rules/prefer-toggle-attribute.js +3 -3
  91. package/rules/prefer-top-level-await.js +6 -1
  92. package/rules/prefer-uint8array-base64.js +3 -1
  93. package/rules/require-proxy-trap-boolean-return.js +2 -2
  94. package/rules/rule/to-eslint-create.js +1 -2
  95. package/rules/rule/to-eslint-listener.js +1 -3
  96. package/rules/rule/to-eslint-problem.js +1 -3
  97. package/rules/rule/to-eslint-rule-fixer.js +1 -3
  98. package/rules/rule/to-eslint-rule.js +1 -3
  99. package/rules/rule/to-eslint-rules.js +1 -3
  100. package/rules/rule/unicorn-context.js +1 -3
  101. package/rules/rule/unicorn-listeners.js +1 -3
  102. package/rules/shared/array-range.js +66 -0
  103. package/rules/shared/iterator-helpers.js +43 -1
  104. package/rules/shared/no-array-mutate-rule.js +1 -3
  105. package/rules/shared/regexp-escape.js +4 -4
  106. package/rules/utils/can-tokens-be-adjacent.js +95 -0
  107. package/rules/utils/comparison.js +65 -0
  108. package/rules/utils/escape-string.js +2 -8
  109. package/rules/utils/get-available-variable-name.js +2 -13
  110. package/rules/utils/get-precedence.js +112 -0
  111. package/rules/utils/get-token-store.js +1 -3
  112. package/rules/utils/global-reference-tracker.js +3 -6
  113. package/rules/utils/index.js +4 -0
  114. package/rules/utils/is-boolean.js +103 -30
  115. package/rules/utils/is-identifier-name.js +5 -0
  116. package/rules/utils/is-promise-type.js +1 -3
  117. package/rules/utils/is-react-hook-name.js +5 -0
  118. package/rules/utils/parentheses/get-parent-syntax-opening-parenthesis.js +1 -3
  119. package/rules/utils/parentheses/iterate-surrounding-parentheses.js +1 -1
  120. package/rules/utils/should-add-parentheses-to-await-expression-argument.js +4 -9
  121. package/rules/utils/should-add-parentheses-to-call-expression-callee.js +6 -12
  122. package/rules/utils/should-add-parentheses-to-unary-expression.js +7 -14
  123. package/rules/utils/to-location.js +1 -3
  124. 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
- && !isKeyword(name)
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) {