@typescript-eslint/eslint-plugin 8.24.2-alpha.3 → 8.25.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 (42) hide show
  1. package/dist/index.d.ts +1 -1
  2. package/dist/rules/index.d.ts +1 -1
  3. package/dist/rules/no-deprecated.d.ts.map +1 -1
  4. package/dist/rules/no-deprecated.js +11 -1
  5. package/dist/rules/no-floating-promises.d.ts.map +1 -1
  6. package/dist/rules/no-floating-promises.js +1 -4
  7. package/dist/rules/no-inferrable-types.d.ts.map +1 -1
  8. package/dist/rules/no-inferrable-types.js +4 -6
  9. package/dist/rules/no-invalid-void-type.d.ts.map +1 -1
  10. package/dist/rules/no-invalid-void-type.js +22 -0
  11. package/dist/rules/no-misused-spread.d.ts +3 -2
  12. package/dist/rules/no-misused-spread.d.ts.map +1 -1
  13. package/dist/rules/no-misused-spread.js +50 -0
  14. package/dist/rules/prefer-find.d.ts.map +1 -1
  15. package/dist/rules/prefer-find.js +8 -11
  16. package/dist/rules/prefer-nullish-coalescing.d.ts.map +1 -1
  17. package/dist/rules/prefer-nullish-coalescing.js +61 -26
  18. package/dist/rules/prefer-promise-reject-errors.d.ts.map +1 -1
  19. package/dist/rules/prefer-promise-reject-errors.js +2 -7
  20. package/dist/rules/prefer-string-starts-ends-with.d.ts.map +1 -1
  21. package/dist/rules/prefer-string-starts-ends-with.js +3 -11
  22. package/dist/rules/return-await.d.ts.map +1 -1
  23. package/dist/rules/return-await.js +1 -10
  24. package/dist/rules/strict-boolean-expressions.d.ts +2 -1
  25. package/dist/rules/strict-boolean-expressions.d.ts.map +1 -1
  26. package/dist/rules/strict-boolean-expressions.js +457 -512
  27. package/dist/rules/unified-signatures.d.ts.map +1 -1
  28. package/dist/rules/unified-signatures.js +10 -3
  29. package/dist/util/hasOverloadSignatures.d.ts +7 -0
  30. package/dist/util/hasOverloadSignatures.d.ts.map +1 -0
  31. package/dist/util/hasOverloadSignatures.js +47 -0
  32. package/dist/util/index.d.ts +3 -0
  33. package/dist/util/index.d.ts.map +1 -1
  34. package/dist/util/index.js +3 -0
  35. package/dist/util/isHigherPrecedenceThanAwait.d.ts +3 -0
  36. package/dist/util/isHigherPrecedenceThanAwait.d.ts.map +1 -0
  37. package/dist/util/isHigherPrecedenceThanAwait.js +46 -0
  38. package/dist/util/skipChainExpression.d.ts +3 -0
  39. package/dist/util/skipChainExpression.d.ts.map +1 -0
  40. package/dist/util/skipChainExpression.js +7 -0
  41. package/docs/rules/consistent-type-definitions.mdx +43 -4
  42. package/package.json +7 -7
@@ -48,27 +48,27 @@ exports.default = (0, util_1.createRule)({
48
48
  },
49
49
  hasSuggestions: true,
50
50
  messages: {
51
- conditionErrorAny: 'Unexpected any value in conditional. ' +
51
+ conditionErrorAny: 'Unexpected any value in {{context}}. ' +
52
52
  'An explicit comparison or type conversion is required.',
53
- conditionErrorNullableBoolean: 'Unexpected nullable boolean value in conditional. ' +
53
+ conditionErrorNullableBoolean: 'Unexpected nullable boolean value in {{context}}. ' +
54
54
  'Please handle the nullish case explicitly.',
55
- conditionErrorNullableEnum: 'Unexpected nullable enum value in conditional. ' +
55
+ conditionErrorNullableEnum: 'Unexpected nullable enum value in {{context}}. ' +
56
56
  'Please handle the nullish/zero/NaN cases explicitly.',
57
- conditionErrorNullableNumber: 'Unexpected nullable number value in conditional. ' +
57
+ conditionErrorNullableNumber: 'Unexpected nullable number value in {{context}}. ' +
58
58
  'Please handle the nullish/zero/NaN cases explicitly.',
59
- conditionErrorNullableObject: 'Unexpected nullable object value in conditional. ' +
59
+ conditionErrorNullableObject: 'Unexpected nullable object value in {{context}}. ' +
60
60
  'An explicit null check is required.',
61
- conditionErrorNullableString: 'Unexpected nullable string value in conditional. ' +
61
+ conditionErrorNullableString: 'Unexpected nullable string value in {{context}}. ' +
62
62
  'Please handle the nullish/empty cases explicitly.',
63
63
  conditionErrorNullish: 'Unexpected nullish value in conditional. ' +
64
64
  'The condition is always false.',
65
- conditionErrorNumber: 'Unexpected number value in conditional. ' +
65
+ conditionErrorNumber: 'Unexpected number value in {{context}}. ' +
66
66
  'An explicit zero/NaN check is required.',
67
- conditionErrorObject: 'Unexpected object value in conditional. ' +
67
+ conditionErrorObject: 'Unexpected object value in {{context}}. ' +
68
68
  'The condition is always true.',
69
69
  conditionErrorOther: 'Unexpected value in conditional. ' +
70
70
  'A boolean expression is required.',
71
- conditionErrorString: 'Unexpected string value in conditional. ' +
71
+ conditionErrorString: 'Unexpected string value in {{context}}. ' +
72
72
  'An explicit empty string check is required.',
73
73
  conditionFixCastBoolean: 'Explicitly convert value to a boolean (`Boolean(value)`)',
74
74
  conditionFixCompareArrayLengthNonzero: "Change condition to check array's length (`value.length > 0`)",
@@ -86,7 +86,6 @@ exports.default = (0, util_1.createRule)({
86
86
  explicitBooleanReturnType: 'Add an explicit `boolean` return type annotation.',
87
87
  noStrictNullCheck: 'This rule requires the `strictNullChecks` compiler option to be turned on to function correctly.',
88
88
  predicateCannotBeAsync: "Predicate function should not be 'async'; expected a boolean return type.",
89
- predicateReturnsNonBoolean: 'Predicate function should return a boolean.',
90
89
  },
91
90
  schema: [
92
91
  {
@@ -217,8 +216,6 @@ exports.default = (0, util_1.createRule)({
217
216
  /**
218
217
  * Dedicated function to check array method predicate calls. Reports predicate
219
218
  * arguments that don't return a boolean value.
220
- *
221
- * Ignores the `allow*` options and requires a boolean value.
222
219
  */
223
220
  function checkArrayMethodCallPredicate(predicateNode) {
224
221
  const isFunctionExpression = utils_1.ASTUtils.isFunction(predicateNode);
@@ -239,36 +236,46 @@ exports.default = (0, util_1.createRule)({
239
236
  }
240
237
  return type;
241
238
  });
242
- if (returnTypes.every(returnType => isBooleanType(returnType))) {
239
+ const flattenTypes = [
240
+ ...new Set(returnTypes.flatMap(type => tsutils.unionTypeParts(type))),
241
+ ];
242
+ const types = inspectVariantTypes(flattenTypes);
243
+ const reportType = determineReportType(types);
244
+ if (reportType == null) {
243
245
  return;
244
246
  }
245
- const canFix = isFunctionExpression && !predicateNode.returnType;
247
+ const suggestions = [];
248
+ if (isFunctionExpression &&
249
+ predicateNode.body.type !== utils_1.AST_NODE_TYPES.BlockStatement) {
250
+ suggestions.push(...getSuggestionsForConditionError(predicateNode.body, reportType));
251
+ }
252
+ if (isFunctionExpression && !predicateNode.returnType) {
253
+ suggestions.push({
254
+ messageId: 'explicitBooleanReturnType',
255
+ fix: fixer => {
256
+ if (predicateNode.type === utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
257
+ (0, util_1.isParenlessArrowFunction)(predicateNode, context.sourceCode)) {
258
+ return [
259
+ fixer.insertTextBefore(predicateNode.params[0], '('),
260
+ fixer.insertTextAfter(predicateNode.params[0], '): boolean'),
261
+ ];
262
+ }
263
+ if (predicateNode.params.length === 0) {
264
+ const closingBracket = (0, util_1.nullThrows)(context.sourceCode.getFirstToken(predicateNode, token => token.value === ')'), 'function expression has to have a closing parenthesis.');
265
+ return fixer.insertTextAfter(closingBracket, ': boolean');
266
+ }
267
+ const lastClosingParenthesis = (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(predicateNode.params[predicateNode.params.length - 1], token => token.value === ')'), 'function expression has to have a closing parenthesis.');
268
+ return fixer.insertTextAfter(lastClosingParenthesis, ': boolean');
269
+ },
270
+ });
271
+ }
246
272
  return context.report({
247
273
  node: predicateNode,
248
- messageId: 'predicateReturnsNonBoolean',
249
- suggest: canFix
250
- ? [
251
- {
252
- messageId: 'explicitBooleanReturnType',
253
- fix: fixer => {
254
- if (predicateNode.type ===
255
- utils_1.AST_NODE_TYPES.ArrowFunctionExpression &&
256
- (0, util_1.isParenlessArrowFunction)(predicateNode, context.sourceCode)) {
257
- return [
258
- fixer.insertTextBefore(predicateNode.params[0], '('),
259
- fixer.insertTextAfter(predicateNode.params[0], '): boolean'),
260
- ];
261
- }
262
- if (predicateNode.params.length === 0) {
263
- const closingBracket = (0, util_1.nullThrows)(context.sourceCode.getFirstToken(predicateNode, token => token.value === ')'), 'function expression has to have a closing parenthesis.');
264
- return fixer.insertTextAfter(closingBracket, ': boolean');
265
- }
266
- const lastClosingParenthesis = (0, util_1.nullThrows)(context.sourceCode.getTokenAfter(predicateNode.params[predicateNode.params.length - 1], token => token.value === ')'), 'function expression has to have a closing parenthesis.');
267
- return fixer.insertTextAfter(lastClosingParenthesis, ': boolean');
268
- },
269
- },
270
- ]
271
- : null,
274
+ messageId: reportType,
275
+ data: {
276
+ context: 'array predicate return type',
277
+ },
278
+ suggest: suggestions,
272
279
  });
273
280
  }
274
281
  /**
@@ -297,30 +304,23 @@ exports.default = (0, util_1.createRule)({
297
304
  }
298
305
  checkNode(node);
299
306
  }
300
- /**
301
- * This function does the actual type check on a node.
302
- * It analyzes the type of a node and checks if it is allowed in a boolean context.
303
- */
304
- function checkNode(node) {
305
- const type = (0, util_1.getConstrainedTypeAtLocation)(services, node);
306
- const types = inspectVariantTypes(tsutils.unionTypeParts(type));
307
+ function determineReportType(types) {
307
308
  const is = (...wantedTypes) => types.size === wantedTypes.length &&
308
309
  wantedTypes.every(type => types.has(type));
309
310
  // boolean
310
311
  if (is('boolean') || is('truthy boolean')) {
311
- // boolean is always okay
312
- return;
312
+ // boolean is always ok
313
+ return undefined;
313
314
  }
314
315
  // never
315
316
  if (is('never')) {
316
317
  // never is always okay
317
- return;
318
+ return undefined;
318
319
  }
319
320
  // nullish
320
321
  if (is('nullish')) {
321
322
  // condition is always false
322
- context.report({ node, messageId: 'conditionErrorNullish' });
323
- return;
323
+ return 'conditionErrorNullish';
324
324
  }
325
325
  // Known edge case: boolean `true` and nullish values are always valid boolean expressions
326
326
  if (is('nullish', 'truthy boolean')) {
@@ -328,60 +328,9 @@ exports.default = (0, util_1.createRule)({
328
328
  }
329
329
  // nullable boolean
330
330
  if (is('nullish', 'boolean')) {
331
- if (!options.allowNullableBoolean) {
332
- if (isLogicalNegationExpression(node.parent)) {
333
- // if (!nullableBoolean)
334
- context.report({
335
- node,
336
- messageId: 'conditionErrorNullableBoolean',
337
- suggest: [
338
- {
339
- messageId: 'conditionFixDefaultFalse',
340
- fix: (0, util_1.getWrappingFixer)({
341
- node,
342
- sourceCode: context.sourceCode,
343
- wrap: code => `${code} ?? false`,
344
- }),
345
- },
346
- {
347
- messageId: 'conditionFixCompareFalse',
348
- fix: (0, util_1.getWrappingFixer)({
349
- node: node.parent,
350
- innerNode: node,
351
- sourceCode: context.sourceCode,
352
- wrap: code => `${code} === false`,
353
- }),
354
- },
355
- ],
356
- });
357
- }
358
- else {
359
- // if (nullableBoolean)
360
- context.report({
361
- node,
362
- messageId: 'conditionErrorNullableBoolean',
363
- suggest: [
364
- {
365
- messageId: 'conditionFixDefaultFalse',
366
- fix: (0, util_1.getWrappingFixer)({
367
- node,
368
- sourceCode: context.sourceCode,
369
- wrap: code => `${code} ?? false`,
370
- }),
371
- },
372
- {
373
- messageId: 'conditionFixCompareTrue',
374
- fix: (0, util_1.getWrappingFixer)({
375
- node,
376
- sourceCode: context.sourceCode,
377
- wrap: code => `${code} === true`,
378
- }),
379
- },
380
- ],
381
- });
382
- }
383
- }
384
- return;
331
+ return !options.allowNullableBoolean
332
+ ? 'conditionErrorNullableBoolean'
333
+ : undefined;
385
334
  }
386
335
  // Known edge case: truthy primitives and nullish values are always valid boolean expressions
387
336
  if ((options.allowNumber && is('nullish', 'truthy number')) ||
@@ -390,387 +339,33 @@ exports.default = (0, util_1.createRule)({
390
339
  }
391
340
  // string
392
341
  if (is('string') || is('truthy string')) {
393
- if (!options.allowString) {
394
- if (isLogicalNegationExpression(node.parent)) {
395
- // if (!string)
396
- context.report({
397
- node,
398
- messageId: 'conditionErrorString',
399
- suggest: [
400
- {
401
- messageId: 'conditionFixCompareStringLength',
402
- fix: (0, util_1.getWrappingFixer)({
403
- node: node.parent,
404
- innerNode: node,
405
- sourceCode: context.sourceCode,
406
- wrap: code => `${code}.length === 0`,
407
- }),
408
- },
409
- {
410
- messageId: 'conditionFixCompareEmptyString',
411
- fix: (0, util_1.getWrappingFixer)({
412
- node: node.parent,
413
- innerNode: node,
414
- sourceCode: context.sourceCode,
415
- wrap: code => `${code} === ""`,
416
- }),
417
- },
418
- {
419
- messageId: 'conditionFixCastBoolean',
420
- fix: (0, util_1.getWrappingFixer)({
421
- node: node.parent,
422
- innerNode: node,
423
- sourceCode: context.sourceCode,
424
- wrap: code => `!Boolean(${code})`,
425
- }),
426
- },
427
- ],
428
- });
429
- }
430
- else {
431
- // if (string)
432
- context.report({
433
- node,
434
- messageId: 'conditionErrorString',
435
- suggest: [
436
- {
437
- messageId: 'conditionFixCompareStringLength',
438
- fix: (0, util_1.getWrappingFixer)({
439
- node,
440
- sourceCode: context.sourceCode,
441
- wrap: code => `${code}.length > 0`,
442
- }),
443
- },
444
- {
445
- messageId: 'conditionFixCompareEmptyString',
446
- fix: (0, util_1.getWrappingFixer)({
447
- node,
448
- sourceCode: context.sourceCode,
449
- wrap: code => `${code} !== ""`,
450
- }),
451
- },
452
- {
453
- messageId: 'conditionFixCastBoolean',
454
- fix: (0, util_1.getWrappingFixer)({
455
- node,
456
- sourceCode: context.sourceCode,
457
- wrap: code => `Boolean(${code})`,
458
- }),
459
- },
460
- ],
461
- });
462
- }
463
- }
464
- return;
342
+ return !options.allowString ? 'conditionErrorString' : undefined;
465
343
  }
466
344
  // nullable string
467
345
  if (is('nullish', 'string')) {
468
- if (!options.allowNullableString) {
469
- if (isLogicalNegationExpression(node.parent)) {
470
- // if (!nullableString)
471
- context.report({
472
- node,
473
- messageId: 'conditionErrorNullableString',
474
- suggest: [
475
- {
476
- messageId: 'conditionFixCompareNullish',
477
- fix: (0, util_1.getWrappingFixer)({
478
- node: node.parent,
479
- innerNode: node,
480
- sourceCode: context.sourceCode,
481
- wrap: code => `${code} == null`,
482
- }),
483
- },
484
- {
485
- messageId: 'conditionFixDefaultEmptyString',
486
- fix: (0, util_1.getWrappingFixer)({
487
- node,
488
- sourceCode: context.sourceCode,
489
- wrap: code => `${code} ?? ""`,
490
- }),
491
- },
492
- {
493
- messageId: 'conditionFixCastBoolean',
494
- fix: (0, util_1.getWrappingFixer)({
495
- node: node.parent,
496
- innerNode: node,
497
- sourceCode: context.sourceCode,
498
- wrap: code => `!Boolean(${code})`,
499
- }),
500
- },
501
- ],
502
- });
503
- }
504
- else {
505
- // if (nullableString)
506
- context.report({
507
- node,
508
- messageId: 'conditionErrorNullableString',
509
- suggest: [
510
- {
511
- messageId: 'conditionFixCompareNullish',
512
- fix: (0, util_1.getWrappingFixer)({
513
- node,
514
- sourceCode: context.sourceCode,
515
- wrap: code => `${code} != null`,
516
- }),
517
- },
518
- {
519
- messageId: 'conditionFixDefaultEmptyString',
520
- fix: (0, util_1.getWrappingFixer)({
521
- node,
522
- sourceCode: context.sourceCode,
523
- wrap: code => `${code} ?? ""`,
524
- }),
525
- },
526
- {
527
- messageId: 'conditionFixCastBoolean',
528
- fix: (0, util_1.getWrappingFixer)({
529
- node,
530
- sourceCode: context.sourceCode,
531
- wrap: code => `Boolean(${code})`,
532
- }),
533
- },
534
- ],
535
- });
536
- }
537
- }
538
- return;
346
+ return !options.allowNullableString
347
+ ? 'conditionErrorNullableString'
348
+ : undefined;
539
349
  }
540
350
  // number
541
351
  if (is('number') || is('truthy number')) {
542
- if (!options.allowNumber) {
543
- if (isArrayLengthExpression(node, checker, services)) {
544
- if (isLogicalNegationExpression(node.parent)) {
545
- // if (!array.length)
546
- context.report({
547
- node,
548
- messageId: 'conditionErrorNumber',
549
- suggest: [
550
- {
551
- messageId: 'conditionFixCompareArrayLengthZero',
552
- fix: (0, util_1.getWrappingFixer)({
553
- node: node.parent,
554
- innerNode: node,
555
- sourceCode: context.sourceCode,
556
- wrap: code => `${code} === 0`,
557
- }),
558
- },
559
- ],
560
- });
561
- }
562
- else {
563
- // if (array.length)
564
- context.report({
565
- node,
566
- messageId: 'conditionErrorNumber',
567
- suggest: [
568
- {
569
- messageId: 'conditionFixCompareArrayLengthNonzero',
570
- fix: (0, util_1.getWrappingFixer)({
571
- node,
572
- sourceCode: context.sourceCode,
573
- wrap: code => `${code} > 0`,
574
- }),
575
- },
576
- ],
577
- });
578
- }
579
- }
580
- else if (isLogicalNegationExpression(node.parent)) {
581
- // if (!number)
582
- context.report({
583
- node,
584
- messageId: 'conditionErrorNumber',
585
- suggest: [
586
- {
587
- messageId: 'conditionFixCompareZero',
588
- fix: (0, util_1.getWrappingFixer)({
589
- node: node.parent,
590
- innerNode: node,
591
- sourceCode: context.sourceCode,
592
- // TODO: we have to compare to 0n if the type is bigint
593
- wrap: code => `${code} === 0`,
594
- }),
595
- },
596
- {
597
- // TODO: don't suggest this for bigint because it can't be NaN
598
- messageId: 'conditionFixCompareNaN',
599
- fix: (0, util_1.getWrappingFixer)({
600
- node: node.parent,
601
- innerNode: node,
602
- sourceCode: context.sourceCode,
603
- wrap: code => `Number.isNaN(${code})`,
604
- }),
605
- },
606
- {
607
- messageId: 'conditionFixCastBoolean',
608
- fix: (0, util_1.getWrappingFixer)({
609
- node: node.parent,
610
- innerNode: node,
611
- sourceCode: context.sourceCode,
612
- wrap: code => `!Boolean(${code})`,
613
- }),
614
- },
615
- ],
616
- });
617
- }
618
- else {
619
- // if (number)
620
- context.report({
621
- node,
622
- messageId: 'conditionErrorNumber',
623
- suggest: [
624
- {
625
- messageId: 'conditionFixCompareZero',
626
- fix: (0, util_1.getWrappingFixer)({
627
- node,
628
- sourceCode: context.sourceCode,
629
- wrap: code => `${code} !== 0`,
630
- }),
631
- },
632
- {
633
- messageId: 'conditionFixCompareNaN',
634
- fix: (0, util_1.getWrappingFixer)({
635
- node,
636
- sourceCode: context.sourceCode,
637
- wrap: code => `!Number.isNaN(${code})`,
638
- }),
639
- },
640
- {
641
- messageId: 'conditionFixCastBoolean',
642
- fix: (0, util_1.getWrappingFixer)({
643
- node,
644
- sourceCode: context.sourceCode,
645
- wrap: code => `Boolean(${code})`,
646
- }),
647
- },
648
- ],
649
- });
650
- }
651
- }
652
- return;
352
+ return !options.allowNumber ? 'conditionErrorNumber' : undefined;
653
353
  }
654
354
  // nullable number
655
355
  if (is('nullish', 'number')) {
656
- if (!options.allowNullableNumber) {
657
- if (isLogicalNegationExpression(node.parent)) {
658
- // if (!nullableNumber)
659
- context.report({
660
- node,
661
- messageId: 'conditionErrorNullableNumber',
662
- suggest: [
663
- {
664
- messageId: 'conditionFixCompareNullish',
665
- fix: (0, util_1.getWrappingFixer)({
666
- node: node.parent,
667
- innerNode: node,
668
- sourceCode: context.sourceCode,
669
- wrap: code => `${code} == null`,
670
- }),
671
- },
672
- {
673
- messageId: 'conditionFixDefaultZero',
674
- fix: (0, util_1.getWrappingFixer)({
675
- node,
676
- sourceCode: context.sourceCode,
677
- wrap: code => `${code} ?? 0`,
678
- }),
679
- },
680
- {
681
- messageId: 'conditionFixCastBoolean',
682
- fix: (0, util_1.getWrappingFixer)({
683
- node: node.parent,
684
- innerNode: node,
685
- sourceCode: context.sourceCode,
686
- wrap: code => `!Boolean(${code})`,
687
- }),
688
- },
689
- ],
690
- });
691
- }
692
- else {
693
- // if (nullableNumber)
694
- context.report({
695
- node,
696
- messageId: 'conditionErrorNullableNumber',
697
- suggest: [
698
- {
699
- messageId: 'conditionFixCompareNullish',
700
- fix: (0, util_1.getWrappingFixer)({
701
- node,
702
- sourceCode: context.sourceCode,
703
- wrap: code => `${code} != null`,
704
- }),
705
- },
706
- {
707
- messageId: 'conditionFixDefaultZero',
708
- fix: (0, util_1.getWrappingFixer)({
709
- node,
710
- sourceCode: context.sourceCode,
711
- wrap: code => `${code} ?? 0`,
712
- }),
713
- },
714
- {
715
- messageId: 'conditionFixCastBoolean',
716
- fix: (0, util_1.getWrappingFixer)({
717
- node,
718
- sourceCode: context.sourceCode,
719
- wrap: code => `Boolean(${code})`,
720
- }),
721
- },
722
- ],
723
- });
724
- }
725
- }
726
- return;
356
+ return !options.allowNullableNumber
357
+ ? 'conditionErrorNullableNumber'
358
+ : undefined;
727
359
  }
728
360
  // object
729
361
  if (is('object')) {
730
- // condition is always true
731
- context.report({ node, messageId: 'conditionErrorObject' });
732
- return;
362
+ return 'conditionErrorObject';
733
363
  }
734
364
  // nullable object
735
365
  if (is('nullish', 'object')) {
736
- if (!options.allowNullableObject) {
737
- if (isLogicalNegationExpression(node.parent)) {
738
- // if (!nullableObject)
739
- context.report({
740
- node,
741
- messageId: 'conditionErrorNullableObject',
742
- suggest: [
743
- {
744
- messageId: 'conditionFixCompareNullish',
745
- fix: (0, util_1.getWrappingFixer)({
746
- node: node.parent,
747
- innerNode: node,
748
- sourceCode: context.sourceCode,
749
- wrap: code => `${code} == null`,
750
- }),
751
- },
752
- ],
753
- });
754
- }
755
- else {
756
- // if (nullableObject)
757
- context.report({
758
- node,
759
- messageId: 'conditionErrorNullableObject',
760
- suggest: [
761
- {
762
- messageId: 'conditionFixCompareNullish',
763
- fix: (0, util_1.getWrappingFixer)({
764
- node,
765
- sourceCode: context.sourceCode,
766
- wrap: code => `${code} != null`,
767
- }),
768
- },
769
- ],
770
- });
771
- }
772
- }
773
- return;
366
+ return !options.allowNullableObject
367
+ ? 'conditionErrorNullableObject'
368
+ : undefined;
774
369
  }
775
370
  // nullable enum
776
371
  if (is('nullish', 'number', 'enum') ||
@@ -782,65 +377,415 @@ exports.default = (0, util_1.createRule)({
782
377
  is('nullish', 'truthy number', 'string', 'enum') ||
783
378
  is('nullish', 'truthy string', 'number', 'enum') ||
784
379
  is('nullish', 'number', 'string', 'enum')) {
785
- if (!options.allowNullableEnum) {
380
+ return !options.allowNullableEnum
381
+ ? 'conditionErrorNullableEnum'
382
+ : undefined;
383
+ }
384
+ // any
385
+ if (is('any')) {
386
+ return !options.allowAny ? 'conditionErrorAny' : undefined;
387
+ }
388
+ return 'conditionErrorOther';
389
+ }
390
+ function getSuggestionsForConditionError(node, conditionError) {
391
+ switch (conditionError) {
392
+ case 'conditionErrorAny':
393
+ return [
394
+ {
395
+ messageId: 'conditionFixCastBoolean',
396
+ fix: (0, util_1.getWrappingFixer)({
397
+ node,
398
+ sourceCode: context.sourceCode,
399
+ wrap: code => `Boolean(${code})`,
400
+ }),
401
+ },
402
+ ];
403
+ case 'conditionErrorNullableBoolean':
404
+ if (isLogicalNegationExpression(node.parent)) {
405
+ // if (!nullableBoolean)
406
+ return [
407
+ {
408
+ messageId: 'conditionFixDefaultFalse',
409
+ fix: (0, util_1.getWrappingFixer)({
410
+ node,
411
+ sourceCode: context.sourceCode,
412
+ wrap: code => `${code} ?? false`,
413
+ }),
414
+ },
415
+ {
416
+ messageId: 'conditionFixCompareFalse',
417
+ fix: (0, util_1.getWrappingFixer)({
418
+ node: node.parent,
419
+ innerNode: node,
420
+ sourceCode: context.sourceCode,
421
+ wrap: code => `${code} === false`,
422
+ }),
423
+ },
424
+ ];
425
+ }
426
+ // if (nullableBoolean)
427
+ return [
428
+ {
429
+ messageId: 'conditionFixDefaultFalse',
430
+ fix: (0, util_1.getWrappingFixer)({
431
+ node,
432
+ sourceCode: context.sourceCode,
433
+ wrap: code => `${code} ?? false`,
434
+ }),
435
+ },
436
+ {
437
+ messageId: 'conditionFixCompareTrue',
438
+ fix: (0, util_1.getWrappingFixer)({
439
+ node,
440
+ sourceCode: context.sourceCode,
441
+ wrap: code => `${code} === true`,
442
+ }),
443
+ },
444
+ ];
445
+ case 'conditionErrorNullableEnum':
446
+ if (isLogicalNegationExpression(node.parent)) {
447
+ return [
448
+ {
449
+ messageId: 'conditionFixCompareNullish',
450
+ fix: (0, util_1.getWrappingFixer)({
451
+ node: node.parent,
452
+ innerNode: node,
453
+ sourceCode: context.sourceCode,
454
+ wrap: code => `${code} == null`,
455
+ }),
456
+ },
457
+ ];
458
+ }
459
+ return [
460
+ {
461
+ messageId: 'conditionFixCompareNullish',
462
+ fix: (0, util_1.getWrappingFixer)({
463
+ node,
464
+ sourceCode: context.sourceCode,
465
+ wrap: code => `${code} != null`,
466
+ }),
467
+ },
468
+ ];
469
+ case 'conditionErrorNullableNumber':
470
+ if (isLogicalNegationExpression(node.parent)) {
471
+ // if (!nullableNumber)
472
+ return [
473
+ {
474
+ messageId: 'conditionFixCompareNullish',
475
+ fix: (0, util_1.getWrappingFixer)({
476
+ node: node.parent,
477
+ innerNode: node,
478
+ sourceCode: context.sourceCode,
479
+ wrap: code => `${code} == null`,
480
+ }),
481
+ },
482
+ {
483
+ messageId: 'conditionFixDefaultZero',
484
+ fix: (0, util_1.getWrappingFixer)({
485
+ node,
486
+ sourceCode: context.sourceCode,
487
+ wrap: code => `${code} ?? 0`,
488
+ }),
489
+ },
490
+ {
491
+ messageId: 'conditionFixCastBoolean',
492
+ fix: (0, util_1.getWrappingFixer)({
493
+ node: node.parent,
494
+ innerNode: node,
495
+ sourceCode: context.sourceCode,
496
+ wrap: code => `!Boolean(${code})`,
497
+ }),
498
+ },
499
+ ];
500
+ }
501
+ // if (nullableNumber)
502
+ return [
503
+ {
504
+ messageId: 'conditionFixCompareNullish',
505
+ fix: (0, util_1.getWrappingFixer)({
506
+ node,
507
+ sourceCode: context.sourceCode,
508
+ wrap: code => `${code} != null`,
509
+ }),
510
+ },
511
+ {
512
+ messageId: 'conditionFixDefaultZero',
513
+ fix: (0, util_1.getWrappingFixer)({
514
+ node,
515
+ sourceCode: context.sourceCode,
516
+ wrap: code => `${code} ?? 0`,
517
+ }),
518
+ },
519
+ {
520
+ messageId: 'conditionFixCastBoolean',
521
+ fix: (0, util_1.getWrappingFixer)({
522
+ node,
523
+ sourceCode: context.sourceCode,
524
+ wrap: code => `Boolean(${code})`,
525
+ }),
526
+ },
527
+ ];
528
+ case 'conditionErrorNullableObject':
786
529
  if (isLogicalNegationExpression(node.parent)) {
787
- context.report({
788
- node,
789
- messageId: 'conditionErrorNullableEnum',
790
- suggest: [
530
+ // if (!nullableObject)
531
+ return [
532
+ {
533
+ messageId: 'conditionFixCompareNullish',
534
+ fix: (0, util_1.getWrappingFixer)({
535
+ node: node.parent,
536
+ innerNode: node,
537
+ sourceCode: context.sourceCode,
538
+ wrap: code => `${code} == null`,
539
+ }),
540
+ },
541
+ ];
542
+ }
543
+ // if (nullableObject)
544
+ return [
545
+ {
546
+ messageId: 'conditionFixCompareNullish',
547
+ fix: (0, util_1.getWrappingFixer)({
548
+ node,
549
+ sourceCode: context.sourceCode,
550
+ wrap: code => `${code} != null`,
551
+ }),
552
+ },
553
+ ];
554
+ case 'conditionErrorNullableString':
555
+ if (isLogicalNegationExpression(node.parent)) {
556
+ // if (!nullableString)
557
+ return [
558
+ {
559
+ messageId: 'conditionFixCompareNullish',
560
+ fix: (0, util_1.getWrappingFixer)({
561
+ node: node.parent,
562
+ innerNode: node,
563
+ sourceCode: context.sourceCode,
564
+ wrap: code => `${code} == null`,
565
+ }),
566
+ },
567
+ {
568
+ messageId: 'conditionFixDefaultEmptyString',
569
+ fix: (0, util_1.getWrappingFixer)({
570
+ node,
571
+ sourceCode: context.sourceCode,
572
+ wrap: code => `${code} ?? ""`,
573
+ }),
574
+ },
575
+ {
576
+ messageId: 'conditionFixCastBoolean',
577
+ fix: (0, util_1.getWrappingFixer)({
578
+ node: node.parent,
579
+ innerNode: node,
580
+ sourceCode: context.sourceCode,
581
+ wrap: code => `!Boolean(${code})`,
582
+ }),
583
+ },
584
+ ];
585
+ }
586
+ // if (nullableString)
587
+ return [
588
+ {
589
+ messageId: 'conditionFixCompareNullish',
590
+ fix: (0, util_1.getWrappingFixer)({
591
+ node,
592
+ sourceCode: context.sourceCode,
593
+ wrap: code => `${code} != null`,
594
+ }),
595
+ },
596
+ {
597
+ messageId: 'conditionFixDefaultEmptyString',
598
+ fix: (0, util_1.getWrappingFixer)({
599
+ node,
600
+ sourceCode: context.sourceCode,
601
+ wrap: code => `${code} ?? ""`,
602
+ }),
603
+ },
604
+ {
605
+ messageId: 'conditionFixCastBoolean',
606
+ fix: (0, util_1.getWrappingFixer)({
607
+ node,
608
+ sourceCode: context.sourceCode,
609
+ wrap: code => `Boolean(${code})`,
610
+ }),
611
+ },
612
+ ];
613
+ case 'conditionErrorNumber':
614
+ if (isArrayLengthExpression(node, checker, services)) {
615
+ if (isLogicalNegationExpression(node.parent)) {
616
+ // if (!array.length)
617
+ return [
791
618
  {
792
- messageId: 'conditionFixCompareNullish',
619
+ messageId: 'conditionFixCompareArrayLengthZero',
793
620
  fix: (0, util_1.getWrappingFixer)({
794
621
  node: node.parent,
795
622
  innerNode: node,
796
623
  sourceCode: context.sourceCode,
797
- wrap: code => `${code} == null`,
624
+ wrap: code => `${code} === 0`,
798
625
  }),
799
626
  },
800
- ],
801
- });
627
+ ];
628
+ }
629
+ // if (array.length)
630
+ return [
631
+ {
632
+ messageId: 'conditionFixCompareArrayLengthNonzero',
633
+ fix: (0, util_1.getWrappingFixer)({
634
+ node,
635
+ sourceCode: context.sourceCode,
636
+ wrap: code => `${code} > 0`,
637
+ }),
638
+ },
639
+ ];
802
640
  }
803
- else {
804
- context.report({
805
- node,
806
- messageId: 'conditionErrorNullableEnum',
807
- suggest: [
808
- {
809
- messageId: 'conditionFixCompareNullish',
810
- fix: (0, util_1.getWrappingFixer)({
811
- node,
812
- sourceCode: context.sourceCode,
813
- wrap: code => `${code} != null`,
814
- }),
815
- },
816
- ],
817
- });
641
+ if (isLogicalNegationExpression(node.parent)) {
642
+ // if (!number)
643
+ return [
644
+ {
645
+ messageId: 'conditionFixCompareZero',
646
+ fix: (0, util_1.getWrappingFixer)({
647
+ node: node.parent,
648
+ innerNode: node,
649
+ sourceCode: context.sourceCode,
650
+ // TODO: we have to compare to 0n if the type is bigint
651
+ wrap: code => `${code} === 0`,
652
+ }),
653
+ },
654
+ {
655
+ // TODO: don't suggest this for bigint because it can't be NaN
656
+ messageId: 'conditionFixCompareNaN',
657
+ fix: (0, util_1.getWrappingFixer)({
658
+ node: node.parent,
659
+ innerNode: node,
660
+ sourceCode: context.sourceCode,
661
+ wrap: code => `Number.isNaN(${code})`,
662
+ }),
663
+ },
664
+ {
665
+ messageId: 'conditionFixCastBoolean',
666
+ fix: (0, util_1.getWrappingFixer)({
667
+ node: node.parent,
668
+ innerNode: node,
669
+ sourceCode: context.sourceCode,
670
+ wrap: code => `!Boolean(${code})`,
671
+ }),
672
+ },
673
+ ];
818
674
  }
819
- }
820
- return;
821
- }
822
- // any
823
- if (is('any')) {
824
- if (!options.allowAny) {
825
- context.report({
826
- node,
827
- messageId: 'conditionErrorAny',
828
- suggest: [
675
+ // if (number)
676
+ return [
677
+ {
678
+ messageId: 'conditionFixCompareZero',
679
+ fix: (0, util_1.getWrappingFixer)({
680
+ node,
681
+ sourceCode: context.sourceCode,
682
+ wrap: code => `${code} !== 0`,
683
+ }),
684
+ },
685
+ {
686
+ messageId: 'conditionFixCompareNaN',
687
+ fix: (0, util_1.getWrappingFixer)({
688
+ node,
689
+ sourceCode: context.sourceCode,
690
+ wrap: code => `!Number.isNaN(${code})`,
691
+ }),
692
+ },
693
+ {
694
+ messageId: 'conditionFixCastBoolean',
695
+ fix: (0, util_1.getWrappingFixer)({
696
+ node,
697
+ sourceCode: context.sourceCode,
698
+ wrap: code => `Boolean(${code})`,
699
+ }),
700
+ },
701
+ ];
702
+ case 'conditionErrorString':
703
+ if (isLogicalNegationExpression(node.parent)) {
704
+ // if (!string)
705
+ return [
706
+ {
707
+ messageId: 'conditionFixCompareStringLength',
708
+ fix: (0, util_1.getWrappingFixer)({
709
+ node: node.parent,
710
+ innerNode: node,
711
+ sourceCode: context.sourceCode,
712
+ wrap: code => `${code}.length === 0`,
713
+ }),
714
+ },
715
+ {
716
+ messageId: 'conditionFixCompareEmptyString',
717
+ fix: (0, util_1.getWrappingFixer)({
718
+ node: node.parent,
719
+ innerNode: node,
720
+ sourceCode: context.sourceCode,
721
+ wrap: code => `${code} === ""`,
722
+ }),
723
+ },
829
724
  {
830
725
  messageId: 'conditionFixCastBoolean',
831
726
  fix: (0, util_1.getWrappingFixer)({
832
- node,
727
+ node: node.parent,
728
+ innerNode: node,
833
729
  sourceCode: context.sourceCode,
834
- wrap: code => `Boolean(${code})`,
730
+ wrap: code => `!Boolean(${code})`,
835
731
  }),
836
732
  },
837
- ],
838
- });
839
- }
840
- return;
733
+ ];
734
+ }
735
+ // if (string)
736
+ return [
737
+ {
738
+ messageId: 'conditionFixCompareStringLength',
739
+ fix: (0, util_1.getWrappingFixer)({
740
+ node,
741
+ sourceCode: context.sourceCode,
742
+ wrap: code => `${code}.length > 0`,
743
+ }),
744
+ },
745
+ {
746
+ messageId: 'conditionFixCompareEmptyString',
747
+ fix: (0, util_1.getWrappingFixer)({
748
+ node,
749
+ sourceCode: context.sourceCode,
750
+ wrap: code => `${code} !== ""`,
751
+ }),
752
+ },
753
+ {
754
+ messageId: 'conditionFixCastBoolean',
755
+ fix: (0, util_1.getWrappingFixer)({
756
+ node,
757
+ sourceCode: context.sourceCode,
758
+ wrap: code => `Boolean(${code})`,
759
+ }),
760
+ },
761
+ ];
762
+ case 'conditionErrorObject':
763
+ case 'conditionErrorNullish':
764
+ case 'conditionErrorOther':
765
+ return [];
766
+ default:
767
+ conditionError;
768
+ throw new Error('Unreachable');
769
+ }
770
+ }
771
+ /**
772
+ * This function does the actual type check on a node.
773
+ * It analyzes the type of a node and checks if it is allowed in a boolean context.
774
+ */
775
+ function checkNode(node) {
776
+ const type = (0, util_1.getConstrainedTypeAtLocation)(services, node);
777
+ const types = inspectVariantTypes(tsutils.unionTypeParts(type));
778
+ const reportType = determineReportType(types);
779
+ if (reportType != null) {
780
+ context.report({
781
+ node,
782
+ messageId: reportType,
783
+ data: {
784
+ context: 'conditional',
785
+ },
786
+ suggest: getSuggestionsForConditionError(node, reportType),
787
+ });
841
788
  }
842
- // other
843
- context.report({ node, messageId: 'conditionErrorOther' });
844
789
  }
845
790
  /**
846
791
  * Check union variants for the types we care about