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 +1 -1
- package/rules/consistent-boolean-name.js +181 -19
package/package.json
CHANGED
|
@@ -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 =
|
|
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
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
|
|
569
|
-
|
|
570
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
1092
|
+
const stateFromTypeAnnotation = getDirectTypeAnnotationBooleanState(node.typeAnnotation, context, scope);
|
|
1093
|
+
if (stateFromTypeAnnotation !== unknown) {
|
|
1094
|
+
return stateFromTypeAnnotation;
|
|
1095
|
+
}
|
|
937
1096
|
|
|
938
|
-
return
|
|
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
|
|
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 (
|
|
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
|
-
|
|
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 (
|
|
1295
|
+
if (booleanState === nonBoolean) {
|
|
1134
1296
|
context.report({
|
|
1135
1297
|
node: node.key,
|
|
1136
1298
|
messageId: MESSAGE_ID_NON_BOOLEAN_PREFIX,
|