eslint-plugin-unicorn 71.0.0 → 71.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-unicorn",
3
- "version": "71.0.0",
3
+ "version": "71.1.0",
4
4
  "description": "More than 300 powerful ESLint rules",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -81,6 +81,7 @@ const unknownTypeAnnotationTypes = new Set([
81
81
  'TSNeverKeyword',
82
82
  'TSUnknownKeyword',
83
83
  ]);
84
+ const promiseValueTypeNames = new Set(['Promise', 'PromiseLike']);
84
85
  const nonBooleanExpressionTypes = new Set([
85
86
  'ArrayExpression',
86
87
  'ObjectExpression',
@@ -102,6 +103,7 @@ const typeScriptExpressionWrapperTypes = new Set([
102
103
  const isUpperCase = string => string === string.toUpperCase();
103
104
  const stripLeadingUnderscores = name => name.replace(/^_+/, '');
104
105
  const isScreamingCase = name => /^[A-Z][\dA-Z_]*$/.test(stripLeadingUnderscores(name));
106
+ const hasReactReferenceSuffix = name => /(?:Ref|Reference)$/.test(stripLeadingUnderscores(name));
105
107
 
106
108
  const isFunction = node => [
107
109
  'ArrowFunctionExpression',
@@ -256,6 +258,9 @@ const isDestructuredVariable = variable =>
256
258
  variable.defs.length > 0
257
259
  && variable.defs.every(definition => isDestructuredDefinition(definition));
258
260
 
261
+ const hasWriteAfterInitialization = variable =>
262
+ variable.references.some(reference => !reference.init && reference.isWrite());
263
+
259
264
  const isBooleanFunctionDefinition = (definition, context) =>
260
265
  definition.type === 'FunctionName'
261
266
  && isFunction(definition.node)
@@ -297,6 +302,14 @@ function getFunctionDefinitions(variable) {
297
302
  return overloadDefinitions.length > 0 ? overloadDefinitions : variable.defs;
298
303
  }
299
304
 
305
+ function hasAsyncFunctionImplementation(definitions) {
306
+ return definitions.some(definition => isFunction(definition.node) && definition.node.async);
307
+ }
308
+
309
+ function hasGeneratorFunctionImplementation(definitions) {
310
+ return definitions.some(definition => isFunction(definition.node) && definition.node.generator);
311
+ }
312
+
300
313
  function isReactHookFunctionBinding(variable, context) {
301
314
  if (
302
315
  variable.name === 'use'
@@ -332,6 +345,35 @@ function getNameForPrefixCheck(variable, context) {
332
345
  return isScreamingCase(hookName) ? hookName : lowerFirst(hookName);
333
346
  }
334
347
 
348
+ function isReactUseReferenceCall(node) {
349
+ if (node?.type !== 'CallExpression') {
350
+ return false;
351
+ }
352
+
353
+ const {callee} = node;
354
+ if (callee.type === 'Identifier') {
355
+ return callee.name === 'useRef';
356
+ }
357
+
358
+ return callee.type === 'MemberExpression'
359
+ && !callee.computed
360
+ && !callee.optional
361
+ && callee.object.type === 'Identifier'
362
+ && callee.object.name === 'React'
363
+ && callee.property.type === 'Identifier'
364
+ && callee.property.name === 'useRef';
365
+ }
366
+
367
+ function isBooleanReactReferenceVariable(variable, context) {
368
+ const definition = getSupportedVariableDefinition(variable);
369
+
370
+ return definition?.type === 'Variable'
371
+ && !hasWriteAfterInitialization(variable)
372
+ && hasReactReferenceSuffix(variable.name)
373
+ && isReactUseReferenceCall(definition.node.init)
374
+ && getExpressionBooleanState(definition.node.init.arguments[0], context) === boolean;
375
+ }
376
+
335
377
  function combineBooleanStates(states) {
336
378
  if (
337
379
  states.length === 0
@@ -444,6 +486,8 @@ function getTypeReferenceName(typeName) {
444
486
  }
445
487
  }
446
488
 
489
+ const getTypeArguments = node => node?.typeArguments?.params ?? node?.typeParameters?.params;
490
+
447
491
  const getTypeState = typeState => ({
448
492
  visitedTypeReferenceNames: new Set(),
449
493
  functionTypesAreBoolean: true,
@@ -480,7 +524,7 @@ function getTypeReferenceBooleanState(node, context, scope, typeState) {
480
524
  const definitionScope = context.sourceCode.getScope(definition.node);
481
525
 
482
526
  if (definition.node.type === 'TSTypeAliasDeclaration') {
483
- result = getTypeAnnotationBooleanState(definition.node.typeAnnotation, context, definitionScope, normalizedTypeState);
527
+ result = getDirectTypeAnnotationBooleanState(definition.node.typeAnnotation, context, definitionScope, normalizedTypeState);
484
528
  } else if (definition.node.type === 'TSInterfaceDeclaration') {
485
529
  result = getTypeMembersBooleanState(definition.node.body.body, context, definitionScope, normalizedTypeState);
486
530
  }
@@ -560,22 +604,119 @@ function getTypeAnnotationBooleanState(node, context, scope, typeState) {
560
604
  return getSimpleTypeAnnotationBooleanState(node);
561
605
  }
562
606
 
563
- function getFunctionBooleanState(node, context, visitedVariables = new Set()) {
564
- if (node.async || node.generator) {
565
- return nonBoolean;
607
+ function getPromisedTypeAnnotationBooleanState(node, context, scope, typeState) {
608
+ const normalizedTypeState = getTypeState(typeState);
609
+
610
+ if (
611
+ node?.type === 'TSTypeAnnotation'
612
+ || node?.type === 'TSParenthesizedType'
613
+ ) {
614
+ return getPromisedTypeAnnotationBooleanState(node.typeAnnotation, context, scope, normalizedTypeState);
566
615
  }
567
616
 
568
- const stateFromTypeInformation = getTypeInformationBooleanState(node, context);
569
- if (stateFromTypeInformation !== unknown) {
570
- return stateFromTypeInformation;
617
+ if (node?.type !== 'TSTypeReference') {
618
+ return unknown;
619
+ }
620
+
621
+ const name = getTypeReferenceName(node.typeName);
622
+ const typeArguments = getTypeArguments(node);
623
+ if (
624
+ name === 'Promise'
625
+ && typeArguments?.length === 1
626
+ && isGlobalTypeReferenceName(name, scope)
627
+ ) {
628
+ return getTypeAnnotationBooleanState(typeArguments[0], context, scope, normalizedTypeState);
629
+ }
630
+
631
+ return unknown;
632
+ }
633
+
634
+ function isGlobalTypeReferenceName(name, scope) {
635
+ return (resolveVariableName(name, scope)?.defs ?? []).every(definition => definition.type !== 'Type');
636
+ }
637
+
638
+ function isGlobalPromiseTypeReference(node, scope) {
639
+ if (node?.type !== 'TSTypeReference') {
640
+ return false;
641
+ }
642
+
643
+ const typeName = getTypeReferenceName(node.typeName);
644
+ return promiseValueTypeNames.has(typeName)
645
+ && getTypeArguments(node)?.length === 1
646
+ && isGlobalTypeReferenceName(typeName, scope);
647
+ }
648
+
649
+ function isGlobalPromiseTypeAnnotation(node, scope) {
650
+ if (
651
+ node?.type === 'TSTypeAnnotation'
652
+ || node?.type === 'TSParenthesizedType'
653
+ ) {
654
+ return isGlobalPromiseTypeAnnotation(node.typeAnnotation, scope);
655
+ }
656
+
657
+ if (node?.type === 'TSUnionType') {
658
+ const types = node.types.filter(type => !nullishTypeAnnotationTypes.has(type.type));
659
+ return types.length > 0 && types.every(type => isGlobalPromiseTypeAnnotation(type, scope));
660
+ }
661
+
662
+ return isGlobalPromiseTypeReference(node, scope);
663
+ }
664
+
665
+ function getPromisedTypeBooleanState(type, checker) {
666
+ const promisedType = checker.getPromisedTypeOfPromise(type);
667
+ return promisedType ? getTypeBooleanState(promisedType, checker, new Set(), false) : unknown;
668
+ }
669
+
670
+ function getAsyncFunctionTypeInformationBooleanState(node, context) {
671
+ const {parserServices} = context.sourceCode;
672
+ if (!parserServices?.program) {
673
+ return unknown;
674
+ }
675
+
676
+ try {
677
+ const checker = parserServices.program.getTypeChecker();
678
+ const typeScriptNode = parserServices.esTreeNodeToTSNodeMap.get(node);
679
+ const signature = checker.getSignatureFromDeclaration(typeScriptNode);
680
+ if (signature) {
681
+ return getPromisedTypeBooleanState(checker.getReturnTypeOfSignature(signature), checker);
682
+ }
683
+
684
+ return combineBooleanStates(
685
+ parserServices.getTypeAtLocation(node)
686
+ .getCallSignatures()
687
+ .map(signature => getPromisedTypeBooleanState(checker.getReturnTypeOfSignature(signature), checker)),
688
+ );
689
+ } catch {
690
+ return unknown;
691
+ }
692
+ }
693
+
694
+ function getFunctionBooleanState(node, context, visitedVariables = new Set(), isAsync = node.async) {
695
+ if (node.generator) {
696
+ return nonBoolean;
571
697
  }
572
698
 
573
699
  const scope = context.sourceCode.getScope(node);
574
- const stateFromReturnType = getTypeAnnotationBooleanState(node.returnType, context, scope, {functionTypesAreBoolean: false});
700
+ // Only actual async function implementations and their overload signatures get `Promise<T>` unwrapped as a predicate return. Promise-valued variables and unrelated type-only callable signatures stay outside this boundary.
701
+ const stateFromPromisedReturnType = isAsync
702
+ ? getPromisedTypeAnnotationBooleanState(node.returnType, context, scope, {functionTypesAreBoolean: false})
703
+ : unknown;
704
+ if (stateFromPromisedReturnType !== unknown) {
705
+ return stateFromPromisedReturnType;
706
+ }
707
+
708
+ const stateFromReturnType = isAsync ? unknown : getTypeAnnotationBooleanState(node.returnType, context, scope, {functionTypesAreBoolean: false});
575
709
  if (stateFromReturnType !== unknown) {
576
710
  return stateFromReturnType;
577
711
  }
578
712
 
713
+ const stateFromTypeInformation = isAsync
714
+ ? getAsyncFunctionTypeInformationBooleanState(node, context)
715
+ : getTypeInformationBooleanState(node, context);
716
+ if (stateFromTypeInformation !== unknown) {
717
+ return stateFromTypeInformation;
718
+ }
719
+
579
720
  if (!node.body) {
580
721
  return unknown;
581
722
  }
@@ -692,7 +833,7 @@ function getExpressionBooleanState(node, context, visitedVariables = new Set(),
692
833
  }
693
834
 
694
835
  const scope = context.sourceCode.getScope(node);
695
- const stateFromTypeAnnotation = getTypeAnnotationBooleanState(node.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
836
+ const stateFromTypeAnnotation = getDirectTypeAnnotationBooleanState(node.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
696
837
  if (stateFromTypeAnnotation !== unknown) {
697
838
  return stateFromTypeAnnotation;
698
839
  }
@@ -771,9 +912,17 @@ function getParameterBooleanState(definition, context, visitedVariables, functio
771
912
  : unknown;
772
913
  }
773
914
 
915
+ function getDirectTypeAnnotationBooleanState(node, context, scope, typeState) {
916
+ if (isGlobalPromiseTypeAnnotation(node, scope)) {
917
+ return nonBoolean;
918
+ }
919
+
920
+ return getTypeAnnotationBooleanState(node, context, scope, typeState);
921
+ }
922
+
774
923
  function getDefinitionBooleanState(definition, context, visitedVariables, functionValuesAreBoolean) {
775
924
  const scope = context.sourceCode.getScope(definition.name);
776
- const stateFromTypeAnnotation = getTypeAnnotationBooleanState(definition.name.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
925
+ const stateFromTypeAnnotation = getDirectTypeAnnotationBooleanState(definition.name.typeAnnotation, context, scope, {functionTypesAreBoolean: functionValuesAreBoolean});
777
926
  if (stateFromTypeAnnotation !== unknown) {
778
927
  return stateFromTypeAnnotation;
779
928
  }
@@ -816,7 +965,14 @@ function getVariableBooleanState(variable, context, visitedVariables = new Set()
816
965
  if (functionDefinitions && !functionValuesAreBoolean) {
817
966
  result = nonBoolean;
818
967
  } else if (functionDefinitions) {
819
- result = combineBooleanStates(functionDefinitions.map(definition => getFunctionBooleanState(definition.node, context, visitedVariables)));
968
+ if (hasGeneratorFunctionImplementation(variable.defs)) {
969
+ result = nonBoolean;
970
+ } else {
971
+ const hasAsyncImplementation = hasAsyncFunctionImplementation(variable.defs);
972
+ result = combineBooleanStates(functionDefinitions.map(definition =>
973
+ getFunctionBooleanState(definition.node, context, visitedVariables, hasAsyncImplementation || definition.node.async),
974
+ ));
975
+ }
820
976
  } else {
821
977
  result = getDefinitionBooleanState(definition, context, visitedVariables, functionValuesAreBoolean);
822
978
  }
@@ -933,15 +1089,16 @@ function getExplicitPropertyBooleanState(node, context) {
933
1089
 
934
1090
  if (propertyDefinitionTypes.has(node.type)) {
935
1091
  const scope = sourceCode.getScope(node);
936
- const stateFromTypeAnnotation = getTypeAnnotationBooleanState(node.typeAnnotation, context, scope);
1092
+ const stateFromTypeAnnotation = getDirectTypeAnnotationBooleanState(node.typeAnnotation, context, scope);
1093
+ if (stateFromTypeAnnotation !== unknown) {
1094
+ return stateFromTypeAnnotation;
1095
+ }
937
1096
 
938
- return stateFromTypeAnnotation === unknown
939
- ? getExpressionBooleanState(node.value, context)
940
- : stateFromTypeAnnotation;
1097
+ return getExpressionBooleanState(node.value, context);
941
1098
  }
942
1099
 
943
1100
  if (node.type === 'TSPropertySignature') {
944
- return getTypeAnnotationBooleanState(node.typeAnnotation, context, sourceCode.getScope(node));
1101
+ return getDirectTypeAnnotationBooleanState(node.typeAnnotation, context, sourceCode.getScope(node));
945
1102
  }
946
1103
 
947
1104
  if (node.type === 'TSMethodSignature') {
@@ -1061,8 +1218,12 @@ const create = context => {
1061
1218
 
1062
1219
  const nameForPrefixCheck = getNameForPrefixCheck(variable, context);
1063
1220
  const booleanPrefix = getBooleanPrefix(nameForPrefixCheck, prefixes);
1221
+ const booleanState = getVariableBooleanState(variable, context);
1064
1222
  if (booleanPrefix) {
1065
- if (getVariableBooleanState(variable, context) === nonBoolean) {
1223
+ if (
1224
+ booleanState === nonBoolean
1225
+ && !isBooleanReactReferenceVariable(variable, context)
1226
+ ) {
1066
1227
  const [definition] = variable.defs;
1067
1228
 
1068
1229
  context.report({
@@ -1079,7 +1240,7 @@ const create = context => {
1079
1240
  }
1080
1241
 
1081
1242
  if (
1082
- getVariableBooleanState(variable, context) === nonBoolean
1243
+ booleanState === nonBoolean
1083
1244
  || !isBooleanVariable(variable, context)
1084
1245
  ) {
1085
1246
  return;
@@ -1129,8 +1290,9 @@ const create = context => {
1129
1290
  }
1130
1291
 
1131
1292
  const booleanPrefix = getBooleanPrefix(name, prefixes);
1293
+ const booleanState = getPropertyBooleanState(node, context);
1132
1294
  if (booleanPrefix) {
1133
- if (getPropertyBooleanState(node, context) === nonBoolean) {
1295
+ if (booleanState === nonBoolean) {
1134
1296
  context.report({
1135
1297
  node: node.key,
1136
1298
  messageId: MESSAGE_ID_NON_BOOLEAN_PREFIX,