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,865 @@
1
+ import {findVariable, getPropertyName} from '@eslint-community/eslint-utils';
2
+ import {isMethodCall} from './ast/index.js';
3
+
4
+ const MESSAGE_ID = 'no-unsafe-promise-all-settled-values';
5
+
6
+ const messages = {
7
+ [MESSAGE_ID]: 'Do not read `.value` from `Promise.allSettled()` results without checking that the result is fulfilled.',
8
+ };
9
+
10
+ const unsafePromiseSettledResultTypeNames = new Set([
11
+ 'PromiseSettledResult',
12
+ 'PromiseRejectedResult',
13
+ ]);
14
+
15
+ const promiseFulfilledResultTypeNames = new Set([
16
+ 'PromiseFulfilledResult',
17
+ ]);
18
+
19
+ const transparentExpressionTypes = new Set([
20
+ 'ChainExpression',
21
+ 'ParenthesizedExpression',
22
+ 'TSAsExpression',
23
+ 'TSSatisfiesExpression',
24
+ 'TSNonNullExpression',
25
+ 'TSTypeAssertion',
26
+ ]);
27
+
28
+ const isFunction = node =>
29
+ node?.type === 'ArrowFunctionExpression'
30
+ || node?.type === 'FunctionExpression'
31
+ || node?.type === 'FunctionDeclaration';
32
+
33
+ const unwrapExpression = node => {
34
+ while (node && transparentExpressionTypes.has(node.type)) {
35
+ node = node.expression;
36
+ }
37
+
38
+ return node;
39
+ };
40
+
41
+ const isPromiseAllSettledCall = node =>
42
+ isMethodCall(unwrapExpression(node), {
43
+ object: 'Promise',
44
+ method: 'allSettled',
45
+ argumentsLength: 1,
46
+ optionalCall: false,
47
+ optionalMember: false,
48
+ });
49
+
50
+ const isAwaitedPromiseAllSettledCall = node => {
51
+ node = unwrapExpression(node);
52
+ return node?.type === 'AwaitExpression' && isPromiseAllSettledCall(node.argument);
53
+ };
54
+
55
+ const isReturnValue = node =>
56
+ node?.parent.type === 'ReturnStatement'
57
+ && node.parent.argument === node;
58
+
59
+ const isFilterCall = node =>
60
+ isMethodCall(unwrapExpression(node), {
61
+ method: 'filter',
62
+ minimumArguments: 1,
63
+ maximumArguments: 2,
64
+ optionalCall: false,
65
+ optionalMember: false,
66
+ });
67
+
68
+ function getTypeName(typeName) {
69
+ if (typeName.type === 'Identifier') {
70
+ return typeName.name;
71
+ }
72
+
73
+ if (typeName.type === 'TSQualifiedName') {
74
+ return getTypeName(typeName.right);
75
+ }
76
+ }
77
+
78
+ function getArrayElementTypeAnnotation(node) {
79
+ node = node?.type === 'TSTypeAnnotation' ? node.typeAnnotation : node;
80
+
81
+ while (node?.type === 'TSParenthesizedType' || node?.type === 'TSTypeOperator') {
82
+ node = node.typeAnnotation;
83
+ }
84
+
85
+ if (node?.type === 'TSArrayType') {
86
+ return node.elementType;
87
+ }
88
+
89
+ if (
90
+ node?.type === 'TSTypeReference'
91
+ && node.typeName.type === 'Identifier'
92
+ && (node.typeName.name === 'Array' || node.typeName.name === 'ReadonlyArray')
93
+ ) {
94
+ return node.typeArguments?.params[0] ?? node.typeParameters?.params[0];
95
+ }
96
+ }
97
+
98
+ function isUnsafePromiseSettledResultTypeAnnotation(node) {
99
+ if (node?.type === 'TSTypeReference') {
100
+ const typeName = getTypeName(node.typeName);
101
+ return unsafePromiseSettledResultTypeNames.has(typeName);
102
+ }
103
+
104
+ if (node?.type === 'TSUnionType') {
105
+ return node.types.some(type => isUnsafePromiseSettledResultTypeAnnotation(type));
106
+ }
107
+
108
+ return false;
109
+ }
110
+
111
+ function isUnsafePromiseSettledResultArrayTypeAnnotation(node) {
112
+ return isUnsafePromiseSettledResultTypeAnnotation(getArrayElementTypeAnnotation(node));
113
+ }
114
+
115
+ function isPromiseFulfilledResultTypeAnnotation(node) {
116
+ if (node?.type === 'TSTypeReference') {
117
+ const typeName = getTypeName(node.typeName);
118
+ return promiseFulfilledResultTypeNames.has(typeName);
119
+ }
120
+
121
+ if (node?.type === 'TSUnionType') {
122
+ return node.types.every(type => isPromiseFulfilledResultTypeAnnotation(type));
123
+ }
124
+
125
+ return false;
126
+ }
127
+
128
+ function isPromiseFulfilledResultArrayTypeAnnotation(node) {
129
+ return isPromiseFulfilledResultTypeAnnotation(getArrayElementTypeAnnotation(node));
130
+ }
131
+
132
+ function isUnsafePromiseSettledResultType(type) {
133
+ if (type.isUnion()) {
134
+ return type.types.some(type => isUnsafePromiseSettledResultType(type));
135
+ }
136
+
137
+ const symbol = type.aliasSymbol ?? type.getSymbol();
138
+ return unsafePromiseSettledResultTypeNames.has(symbol?.getName());
139
+ }
140
+
141
+ function isPromiseFulfilledResultType(type) {
142
+ if (type.isUnion()) {
143
+ return type.types.every(type => isPromiseFulfilledResultType(type));
144
+ }
145
+
146
+ const symbol = type.aliasSymbol ?? type.getSymbol();
147
+ return promiseFulfilledResultTypeNames.has(symbol?.getName());
148
+ }
149
+
150
+ function getArrayElementType(type, checker) {
151
+ type = checker.getNonNullableType(type);
152
+
153
+ if (type.isUnion()) {
154
+ const elementTypes = type.types.map(type => getArrayElementType(type, checker));
155
+ return elementTypes.every(Boolean) ? checker.getUnionType(elementTypes) : undefined;
156
+ }
157
+
158
+ if (!(checker.isArrayType(type) || checker.isTupleType(type))) {
159
+ return;
160
+ }
161
+
162
+ return checker.getTypeArguments(type)[0];
163
+ }
164
+
165
+ function getTypeInformation(node, context) {
166
+ const {parserServices} = context.sourceCode;
167
+ if (!parserServices?.program) {
168
+ return;
169
+ }
170
+
171
+ try {
172
+ return {
173
+ checker: parserServices.program.getTypeChecker(),
174
+ type: parserServices.getTypeAtLocation(node),
175
+ };
176
+ } catch {}
177
+ }
178
+
179
+ function isPromiseSettledResultArrayFromTypeInformation(node, context) {
180
+ const typeInformation = getTypeInformation(node, context);
181
+ if (!typeInformation) {
182
+ return false;
183
+ }
184
+
185
+ const {type, checker} = typeInformation;
186
+ const elementType = getArrayElementType(type, checker);
187
+ return Boolean(elementType && isUnsafePromiseSettledResultType(elementType));
188
+ }
189
+
190
+ function isPromiseFulfilledResultFromTypeInformation(node, context) {
191
+ const typeInformation = getTypeInformation(node, context);
192
+ if (!typeInformation) {
193
+ return false;
194
+ }
195
+
196
+ const {type} = typeInformation;
197
+ return isPromiseFulfilledResultType(type);
198
+ }
199
+
200
+ function isPromiseFulfilledResultArrayFromTypeInformation(node, context) {
201
+ const typeInformation = getTypeInformation(node, context);
202
+ if (!typeInformation) {
203
+ return false;
204
+ }
205
+
206
+ const {type, checker} = typeInformation;
207
+ const elementType = getArrayElementType(type, checker);
208
+ return Boolean(elementType && isPromiseFulfilledResultType(elementType));
209
+ }
210
+
211
+ const isTypeAssertion = node =>
212
+ node?.type === 'TSAsExpression'
213
+ || node?.type === 'TSTypeAssertion';
214
+
215
+ const isPromiseSettledResultArrayTypeAssertion = node =>
216
+ isTypeAssertion(node)
217
+ && isUnsafePromiseSettledResultArrayTypeAnnotation(node.typeAnnotation);
218
+
219
+ function isPromiseSettledResultVariable(node, context, visitedVariables) {
220
+ const variable = findVariable(context.sourceCode.getScope(node), node);
221
+ if (
222
+ !variable
223
+ || visitedVariables.has(variable)
224
+ || variable.defs.length !== 1
225
+ ) {
226
+ return false;
227
+ }
228
+
229
+ visitedVariables.add(variable);
230
+
231
+ const [definition] = variable.defs;
232
+ const isPromiseSettledResult = Boolean(
233
+ isUnsafePromiseSettledResultArrayTypeAnnotation(definition.name?.typeAnnotation)
234
+ || (
235
+ definition.type === 'Variable'
236
+ && definition.parent.kind === 'const'
237
+ && definition.node.id === definition.name
238
+ && definition.node.init
239
+ && isPromiseSettledResultArray(definition.node.init, context, visitedVariables)
240
+ ),
241
+ );
242
+
243
+ visitedVariables.delete(variable);
244
+
245
+ return isPromiseSettledResult;
246
+ }
247
+
248
+ function isPromiseFulfilledResultVariable(node, context, visitedVariables) {
249
+ const variable = findVariable(context.sourceCode.getScope(node), node);
250
+ if (
251
+ !variable
252
+ || visitedVariables.has(variable)
253
+ || variable.defs.length !== 1
254
+ ) {
255
+ return false;
256
+ }
257
+
258
+ visitedVariables.add(variable);
259
+
260
+ const [definition] = variable.defs;
261
+ const isPromiseFulfilledResult = Boolean(
262
+ isPromiseFulfilledResultArrayTypeAnnotation(definition.name?.typeAnnotation)
263
+ || (
264
+ definition.type === 'Variable'
265
+ && definition.parent.kind === 'const'
266
+ && definition.node.id === definition.name
267
+ && definition.node.init
268
+ && isKnownFulfilledResultArray(definition.node.init, context, visitedVariables)
269
+ ),
270
+ );
271
+
272
+ visitedVariables.delete(variable);
273
+
274
+ return isPromiseFulfilledResult;
275
+ }
276
+
277
+ function isPromiseSettledResultArray(node, context, visitedVariables = new Set()) {
278
+ if (isPromiseSettledResultArrayTypeAssertion(node)) {
279
+ return true;
280
+ }
281
+
282
+ node = unwrapExpression(node);
283
+
284
+ if (!node) {
285
+ return false;
286
+ }
287
+
288
+ if (isAwaitedPromiseAllSettledCall(node)) {
289
+ return true;
290
+ }
291
+
292
+ if (isUnsafePromiseSettledResultArrayTypeAnnotation(node.typeAnnotation) || isPromiseSettledResultArrayFromTypeInformation(node, context)) {
293
+ return true;
294
+ }
295
+
296
+ if (isFilterCall(node)) {
297
+ return isPromiseSettledResultArray(node.callee.object, context, visitedVariables);
298
+ }
299
+
300
+ if (node.type !== 'Identifier') {
301
+ return false;
302
+ }
303
+
304
+ return isPromiseSettledResultVariable(node, context, visitedVariables);
305
+ }
306
+
307
+ const getIdentifierName = node => {
308
+ node = unwrapExpression(node);
309
+ return node?.type === 'Identifier' ? node.name : undefined;
310
+ };
311
+
312
+ const getParameterStatusName = parameter => getParameterStatusIdentifier(parameter)?.name;
313
+
314
+ const getParameterStatusIdentifier = parameter => {
315
+ parameter = unwrapExpression(parameter);
316
+
317
+ if (parameter?.type !== 'ObjectPattern') {
318
+ return;
319
+ }
320
+
321
+ for (const property of parameter.properties) {
322
+ if (
323
+ property.type === 'Property'
324
+ && !property.computed
325
+ && getPropertyName(property) === 'status'
326
+ && property.value.type === 'Identifier'
327
+ ) {
328
+ return property.value;
329
+ }
330
+ }
331
+ };
332
+
333
+ const getStatusLiteral = node => {
334
+ if (node?.type === 'Literal' && (node.value === 'fulfilled' || node.value === 'rejected')) {
335
+ return node.value;
336
+ }
337
+ };
338
+
339
+ function isStatusReference(node, statusContext) {
340
+ node = unwrapExpression(node);
341
+
342
+ if (
343
+ statusContext.statusVariable
344
+ && isSameVariableReference(node, statusContext.statusVariable, statusContext.context)
345
+ ) {
346
+ return true;
347
+ }
348
+
349
+ if (
350
+ !statusContext.statusVariable
351
+ && statusContext.statusName
352
+ && node?.type === 'Identifier'
353
+ && node.name === statusContext.statusName
354
+ ) {
355
+ return true;
356
+ }
357
+
358
+ if (
359
+ statusContext.entryVariable
360
+ && node?.type === 'MemberExpression'
361
+ && isSameVariableReference(node.object, statusContext.entryVariable, statusContext.context)
362
+ && getPropertyName(node) === 'status'
363
+ ) {
364
+ return true;
365
+ }
366
+
367
+ return Boolean(
368
+ !statusContext.entryVariable
369
+ && statusContext.entryName
370
+ && node?.type === 'MemberExpression'
371
+ && getIdentifierName(node.object) === statusContext.entryName
372
+ && getPropertyName(node) === 'status',
373
+ );
374
+ }
375
+
376
+ function getStatusCheckKind(node, statusContext) {
377
+ node = unwrapExpression(node);
378
+
379
+ if (
380
+ node?.type !== 'BinaryExpression'
381
+ || !['===', '==', '!==', '!='].includes(node.operator)
382
+ ) {
383
+ return;
384
+ }
385
+
386
+ const statusLiteral = isStatusReference(node.left, statusContext)
387
+ ? getStatusLiteral(node.right)
388
+ : (isStatusReference(node.right, statusContext)
389
+ ? getStatusLiteral(node.left)
390
+ : undefined);
391
+
392
+ if (!statusLiteral) {
393
+ return;
394
+ }
395
+
396
+ const isEqualityCheck = node.operator === '===' || node.operator === '==';
397
+
398
+ if (
399
+ (isEqualityCheck && statusLiteral === 'fulfilled')
400
+ || (!isEqualityCheck && statusLiteral === 'rejected')
401
+ ) {
402
+ return 'fulfilled';
403
+ }
404
+
405
+ return 'unfulfilled';
406
+ }
407
+
408
+ const isStatusFulfilledCheck = (node, statusContext) =>
409
+ getStatusCheckKind(node, statusContext) === 'fulfilled';
410
+
411
+ const isStatusUnfulfilledCheck = (node, statusContext) =>
412
+ getStatusCheckKind(node, statusContext) === 'unfulfilled';
413
+
414
+ function hasFulfilledStatusCheckInConjunction(node, statusContext) {
415
+ node = unwrapExpression(node);
416
+
417
+ if (isStatusFulfilledCheck(node, statusContext)) {
418
+ return true;
419
+ }
420
+
421
+ return node?.type === 'LogicalExpression'
422
+ && node.operator === '&&'
423
+ && (
424
+ hasFulfilledStatusCheckInConjunction(node.left, statusContext)
425
+ || hasFulfilledStatusCheckInConjunction(node.right, statusContext)
426
+ );
427
+ }
428
+
429
+ function hasUnfulfilledStatusCheckInDisjunction(node, statusContext) {
430
+ node = unwrapExpression(node);
431
+
432
+ if (isStatusUnfulfilledCheck(node, statusContext)) {
433
+ return true;
434
+ }
435
+
436
+ return node?.type === 'LogicalExpression'
437
+ && node.operator === '||'
438
+ && (
439
+ hasUnfulfilledStatusCheckInDisjunction(node.left, statusContext)
440
+ || hasUnfulfilledStatusCheckInDisjunction(node.right, statusContext)
441
+ );
442
+ }
443
+
444
+ const isConditionalFulfilledGuard = (node, child, statusContext) =>
445
+ node.type === 'ConditionalExpression'
446
+ && (
447
+ (
448
+ node.consequent === child
449
+ && hasFulfilledStatusCheckInConjunction(node.test, statusContext)
450
+ ) || (
451
+ node.alternate === child
452
+ && hasUnfulfilledStatusCheckInDisjunction(node.test, statusContext)
453
+ )
454
+ );
455
+
456
+ const isLogicalFulfilledGuard = (node, child, statusContext) =>
457
+ node.type === 'LogicalExpression'
458
+ && (
459
+ (
460
+ node.operator === '&&'
461
+ && node.right === child
462
+ && hasFulfilledStatusCheckInConjunction(node.left, statusContext)
463
+ ) || (
464
+ node.operator === '||'
465
+ && node.right === child
466
+ && hasUnfulfilledStatusCheckInDisjunction(node.left, statusContext)
467
+ )
468
+ );
469
+
470
+ const isIfStatementFulfilledGuard = (node, child, statusContext) =>
471
+ node.type === 'IfStatement'
472
+ && (
473
+ (
474
+ node.consequent === child
475
+ && hasFulfilledStatusCheckInConjunction(node.test, statusContext)
476
+ ) || (
477
+ node.alternate === child
478
+ && hasUnfulfilledStatusCheckInDisjunction(node.test, statusContext)
479
+ )
480
+ );
481
+
482
+ function isAlwaysExitingStatement(node) {
483
+ node = unwrapExpression(node);
484
+
485
+ if (node?.type === 'ReturnStatement' || node?.type === 'ThrowStatement') {
486
+ return true;
487
+ }
488
+
489
+ if (node?.type === 'BlockStatement') {
490
+ return node.body.length > 0 && isAlwaysExitingStatement(node.body.at(-1));
491
+ }
492
+
493
+ return false;
494
+ }
495
+
496
+ function isGuardedByPreviousUnfulfilledExit(node, statusContext) {
497
+ const statements = node.parent?.body;
498
+ if (!Array.isArray(statements)) {
499
+ return false;
500
+ }
501
+
502
+ const nodeIndex = statements.indexOf(node);
503
+ const previousStatement = statements[nodeIndex - 1];
504
+
505
+ return previousStatement?.type === 'IfStatement'
506
+ && !previousStatement.alternate
507
+ && hasUnfulfilledStatusCheckInDisjunction(previousStatement.test, statusContext)
508
+ && isAlwaysExitingStatement(previousStatement.consequent);
509
+ }
510
+
511
+ const isFulfilledFilterCallback = callback => {
512
+ if (!isFunction(callback) || callback.params.length === 0) {
513
+ return false;
514
+ }
515
+
516
+ const entryName = getIdentifierName(callback.params[0]);
517
+ const statusName = getParameterStatusName(callback.params[0]);
518
+ const statusContext = {
519
+ entryName,
520
+ statusName,
521
+ };
522
+ const body = unwrapExpression(callback.body);
523
+
524
+ if (body?.type === 'BlockStatement') {
525
+ const [statement] = body.body;
526
+ return body.body.length === 1
527
+ && statement.type === 'ReturnStatement'
528
+ && statement.argument
529
+ && hasFulfilledStatusCheckInConjunction(statement.argument, statusContext);
530
+ }
531
+
532
+ return hasFulfilledStatusCheckInConjunction(body, statusContext);
533
+ };
534
+
535
+ function isKnownFulfilledResultArray(node, context, visitedVariables = new Set()) {
536
+ if (isTypeAssertion(node) && isPromiseFulfilledResultArrayTypeAnnotation(node.typeAnnotation)) {
537
+ return true;
538
+ }
539
+
540
+ node = unwrapExpression(node);
541
+
542
+ if (isPromiseFulfilledResultArrayFromTypeInformation(node, context)) {
543
+ return true;
544
+ }
545
+
546
+ if (node.type === 'Identifier') {
547
+ return isPromiseFulfilledResultVariable(node, context, visitedVariables);
548
+ }
549
+
550
+ return isFilterCall(node)
551
+ && hasFulfilledFilterInChain(node)
552
+ && isPromiseSettledResultArray(node, context);
553
+ }
554
+
555
+ function isGuardedByFulfilledCheck(node, readContext) {
556
+ for (
557
+ let child = node, current = node.parent;
558
+ current;
559
+ child = current, current = current.parent
560
+ ) {
561
+ if (current.type === 'ArrowFunctionExpression' || current.type === 'FunctionExpression') {
562
+ return false;
563
+ }
564
+
565
+ if (
566
+ isConditionalFulfilledGuard(current, child, readContext)
567
+ || isLogicalFulfilledGuard(current, child, readContext)
568
+ || isIfStatementFulfilledGuard(current, child, readContext)
569
+ ) {
570
+ return true;
571
+ }
572
+
573
+ if (isGuardedByPreviousUnfulfilledExit(current, readContext)) {
574
+ return true;
575
+ }
576
+
577
+ if (isReturnValue(current)) {
578
+ return false;
579
+ }
580
+ }
581
+
582
+ return false;
583
+ }
584
+
585
+ function isUnsafeValueRead(node, readContext) {
586
+ node = unwrapExpression(node);
587
+
588
+ return (
589
+ node?.type === 'MemberExpression'
590
+ && isSameVariableReference(node.object, readContext.entryVariable, readContext.context)
591
+ && getPropertyName(node) === 'value'
592
+ ) || (
593
+ node?.type === 'Identifier'
594
+ && isSameVariableReference(node, readContext.valueVariable, readContext.context)
595
+ );
596
+ }
597
+
598
+ function getUnsafeValueReadFromChildren(nodes, readContext) {
599
+ for (const child of nodes) {
600
+ if (!child?.type) {
601
+ continue;
602
+ }
603
+
604
+ if (isFunction(child) || child.type === 'ClassExpression' || child.type === 'ClassDeclaration') {
605
+ continue;
606
+ }
607
+
608
+ const unsafeValueRead = getUnsafeValueRead(child, readContext);
609
+ if (unsafeValueRead) {
610
+ return unsafeValueRead;
611
+ }
612
+ }
613
+ }
614
+
615
+ function getValueProperty(parameter) {
616
+ parameter = unwrapExpression(parameter);
617
+
618
+ if (parameter?.type !== 'ObjectPattern') {
619
+ return;
620
+ }
621
+
622
+ for (const property of parameter.properties) {
623
+ if (
624
+ property.type === 'Property'
625
+ && !property.computed
626
+ && getPropertyName(property) === 'value'
627
+ ) {
628
+ return property;
629
+ }
630
+ }
631
+ }
632
+
633
+ function getParameterValueIdentifier(parameter) {
634
+ const valueProperty = getValueProperty(parameter);
635
+ const valueNode = valueProperty?.value;
636
+
637
+ if (valueNode?.type === 'Identifier') {
638
+ return valueNode;
639
+ }
640
+
641
+ if (
642
+ valueNode?.type === 'AssignmentPattern'
643
+ && valueNode.left.type === 'Identifier'
644
+ ) {
645
+ return valueNode.left;
646
+ }
647
+ }
648
+
649
+ const shouldSkipChildNode = (node, key) =>
650
+ (
651
+ node.type === 'MemberExpression'
652
+ && key === 'property'
653
+ && !node.computed
654
+ ) || (
655
+ ['Property', 'PropertyDefinition', 'MethodDefinition'].includes(node.type)
656
+ && key === 'key'
657
+ && !node.computed
658
+ );
659
+
660
+ function getUnsafeValueRead(node, readContext) {
661
+ node = unwrapExpression(node);
662
+
663
+ if (
664
+ isUnsafeValueRead(node, readContext)
665
+ && !isGuardedByFulfilledCheck(node, readContext)
666
+ ) {
667
+ return node;
668
+ }
669
+
670
+ for (const [key, value] of Object.entries(node)) {
671
+ if (key === 'parent' || shouldSkipChildNode(node, key)) {
672
+ continue;
673
+ }
674
+
675
+ if (!value || typeof value !== 'object') {
676
+ continue;
677
+ }
678
+
679
+ const nodes = Array.isArray(value) ? value : [value];
680
+ const unsafeValueRead = getUnsafeValueReadFromChildren(nodes, readContext);
681
+ if (unsafeValueRead) {
682
+ return unsafeValueRead;
683
+ }
684
+ }
685
+ }
686
+
687
+ function isSameVariableReference(node, variable, context) {
688
+ node = unwrapExpression(node);
689
+
690
+ return node?.type === 'Identifier'
691
+ && variable
692
+ && findVariable(context.sourceCode.getScope(node), node) === variable;
693
+ }
694
+
695
+ function isDerivedFromVariableThroughFilters(node, variable, context) {
696
+ node = unwrapExpression(node);
697
+
698
+ if (isSameVariableReference(node, variable, context)) {
699
+ return true;
700
+ }
701
+
702
+ return isFilterCall(node)
703
+ && isDerivedFromVariableThroughFilters(node.callee.object, variable, context);
704
+ }
705
+
706
+ function hasFulfilledFilterInChain(node) {
707
+ node = unwrapExpression(node);
708
+
709
+ while (isFilterCall(node)) {
710
+ if (isFulfilledFilterCallback(node.arguments[0])) {
711
+ return true;
712
+ }
713
+
714
+ node = unwrapExpression(node.callee.object);
715
+ }
716
+
717
+ return false;
718
+ }
719
+
720
+ function getMapCallbackProblem(callExpression, context, {knownPromiseSettledResultArray = false} = {}) {
721
+ if (
722
+ !isMethodCall(callExpression, {
723
+ method: 'map',
724
+ minimumArguments: 1,
725
+ maximumArguments: 2,
726
+ optionalCall: false,
727
+ optionalMember: false,
728
+ })
729
+ || (!knownPromiseSettledResultArray && !isPromiseSettledResultArray(callExpression.callee.object, context))
730
+ || isKnownFulfilledResultArray(callExpression.callee.object, context)
731
+ ) {
732
+ return;
733
+ }
734
+
735
+ const [callback] = callExpression.arguments;
736
+ if (!isFunction(callback) || callback.params.length === 0) {
737
+ return;
738
+ }
739
+
740
+ const [parameter] = callback.params;
741
+ if (isPromiseFulfilledResultFromTypeInformation(parameter, context)) {
742
+ return;
743
+ }
744
+
745
+ const valueProperty = getValueProperty(parameter);
746
+ if (valueProperty && valueProperty.value.type !== 'Identifier') {
747
+ return {
748
+ node: valueProperty.key,
749
+ messageId: MESSAGE_ID,
750
+ };
751
+ }
752
+
753
+ const entryName = getIdentifierName(parameter);
754
+ const entryVariable = entryName && findVariable(context.sourceCode.getScope(parameter), parameter);
755
+ const valueIdentifier = getParameterValueIdentifier(parameter);
756
+ const valueVariable = valueIdentifier && findVariable(context.sourceCode.getScope(valueIdentifier), valueIdentifier);
757
+ const statusIdentifier = getParameterStatusIdentifier(parameter);
758
+ const statusName = statusIdentifier?.name;
759
+ const statusVariable = statusIdentifier && findVariable(context.sourceCode.getScope(statusIdentifier), statusIdentifier);
760
+ const unsafeValueRead = getUnsafeValueRead(callback.body, {
761
+ entryName,
762
+ entryVariable,
763
+ statusName,
764
+ statusVariable,
765
+ valueVariable,
766
+ context,
767
+ });
768
+ if (unsafeValueRead) {
769
+ return {
770
+ node: unsafeValueRead,
771
+ messageId: MESSAGE_ID,
772
+ };
773
+ }
774
+ }
775
+
776
+ function getThenMapCallbackProblem(node, resultsVariable, context) {
777
+ node = unwrapExpression(node);
778
+
779
+ if (!node?.type || isFunction(node) || node.type === 'ClassExpression' || node.type === 'ClassDeclaration') {
780
+ return;
781
+ }
782
+
783
+ if (
784
+ isMethodCall(node, {
785
+ method: 'map',
786
+ minimumArguments: 1,
787
+ maximumArguments: 2,
788
+ optionalCall: false,
789
+ optionalMember: false,
790
+ })
791
+ && isDerivedFromVariableThroughFilters(node.callee.object, resultsVariable, context)
792
+ && !hasFulfilledFilterInChain(node.callee.object)
793
+ ) {
794
+ const problem = getMapCallbackProblem(node, context, {knownPromiseSettledResultArray: true});
795
+ if (problem) {
796
+ return problem;
797
+ }
798
+ }
799
+
800
+ for (const [key, value] of Object.entries(node)) {
801
+ if (key === 'parent' || shouldSkipChildNode(node, key) || !value || typeof value !== 'object') {
802
+ continue;
803
+ }
804
+
805
+ const nodes = Array.isArray(value) ? value : [value];
806
+ for (const child of nodes) {
807
+ const problem = getThenMapCallbackProblem(child, resultsVariable, context);
808
+ if (problem) {
809
+ return problem;
810
+ }
811
+ }
812
+ }
813
+ }
814
+
815
+ function getThenCallbackProblem(callExpression, context) {
816
+ if (
817
+ !isMethodCall(callExpression, {
818
+ method: 'then',
819
+ minimumArguments: 1,
820
+ optionalCall: false,
821
+ optionalMember: false,
822
+ })
823
+ || !isPromiseAllSettledCall(callExpression.callee.object)
824
+ ) {
825
+ return;
826
+ }
827
+
828
+ const [callback] = callExpression.arguments;
829
+ if (!isFunction(callback) || callback.params.length === 0) {
830
+ return;
831
+ }
832
+
833
+ const [parameter] = callback.params;
834
+ const resultsName = getIdentifierName(parameter);
835
+ const resultsVariable = resultsName && findVariable(context.sourceCode.getScope(parameter), parameter);
836
+ if (!resultsVariable) {
837
+ return;
838
+ }
839
+
840
+ return getThenMapCallbackProblem(callback.body, resultsVariable, context);
841
+ }
842
+
843
+ /** @param {import('eslint').Rule.RuleContext} context */
844
+ const create = context => {
845
+ context.on('CallExpression', callExpression => getThenCallbackProblem(callExpression, context) ?? getMapCallbackProblem(callExpression, context));
846
+ };
847
+
848
+ /** @type {import('eslint').Rule.RuleModule} */
849
+ const config = {
850
+ create,
851
+ meta: {
852
+ type: 'problem',
853
+ docs: {
854
+ description: 'Disallow reading `.value` from `Promise.allSettled()` results without a fulfilled status guard.',
855
+ recommended: 'unopinionated',
856
+ },
857
+ schema: [],
858
+ messages,
859
+ languages: [
860
+ 'js/js',
861
+ ],
862
+ },
863
+ };
864
+
865
+ export default config;