Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork2.8k
fix(eslint-plugin): [no-unnecessary-condition] improve error message for literal comparisons#10194
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Changes fromall commits
c7adb29
87c3a53
dd15c0f
07a6e23
7065d0d
0b93e52
347dcd4
eb9ad53
File filter
Filter by extension
Conversations
Uh oh!
There was an error while loading.Please reload this page.
Jump to
Uh oh!
There was an error while loading.Please reload this page.
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -32,9 +32,11 @@ const valueIsPseudoBigInt = ( | ||
return typeof value === 'object'; | ||
}; | ||
const getValueOfLiteralType = ( | ||
type: ts.LiteralType, | ||
): bigint | number | string => { | ||
if (valueIsPseudoBigInt(type.value)) { | ||
returnpseudoBigIntToBigInt(type.value); | ||
} | ||
return type.value; | ||
}; | ||
@@ -43,13 +45,12 @@ const isFalsyBigInt = (type: ts.Type): boolean => { | ||
return ( | ||
tsutils.isLiteralType(type) && | ||
valueIsPseudoBigInt(type.value) && | ||
!getValueOfLiteralType(type) | ||
); | ||
}; | ||
const isTruthyLiteral = (type: ts.Type): boolean => | ||
tsutils.isTrueLiteralType(type) || | ||
(type.isLiteral() && !!getValueOfLiteralType(type)); | ||
const isPossiblyFalsy = (type: ts.Type): boolean => | ||
tsutils | ||
@@ -89,13 +90,83 @@ const isPossiblyNullish = (type: ts.Type): boolean => | ||
const isAlwaysNullish = (type: ts.Type): boolean => | ||
tsutils.unionTypeParts(type).every(isNullishType); | ||
MemberAuthor
| ||
function toStaticValue( | ||
type: ts.Type, | ||
): | ||
| { value: bigint | boolean | number | string | null | undefined } | ||
| undefined { | ||
// type.isLiteral() only covers numbers/bigints and strings, hence the rest of the branches. | ||
if (tsutils.isBooleanLiteralType(type)) { | ||
// Using `type.intrinsicName` instead of `type.value` because `type.value` | ||
// is `undefined`, contrary to what the type guard tells us. | ||
// See https://github.com/JoshuaKGoldberg/ts-api-utils/issues/528 | ||
return { value: type.intrinsicName === 'true' }; | ||
} | ||
if (type.flags === ts.TypeFlags.Undefined) { | ||
return { value: undefined }; | ||
} | ||
if (type.flags === ts.TypeFlags.Null) { | ||
return { value: null }; | ||
} | ||
if (type.isLiteral()) { | ||
return { value: getValueOfLiteralType(type) }; | ||
} | ||
return undefined; | ||
} | ||
function pseudoBigIntToBigInt(value: ts.PseudoBigInt): bigint { | ||
return BigInt((value.negative ? '-' : '') + value.base10Value); | ||
} | ||
const BOOL_OPERATORS = new Set([ | ||
'<', | ||
'>', | ||
'<=', | ||
'>=', | ||
'==', | ||
'===', | ||
'!=', | ||
'!==', | ||
] as const); | ||
type BoolOperator = typeof BOOL_OPERATORS extends Set<infer T> ? T : never; | ||
function isBoolOperator(operator: string): operator is BoolOperator { | ||
return (BOOL_OPERATORS as Set<string>).has(operator); | ||
} | ||
function booleanComparison( | ||
left: unknown, | ||
operator: BoolOperator, | ||
right: unknown, | ||
): boolean { | ||
switch (operator) { | ||
case '!=': | ||
// eslint-disable-next-line eqeqeq -- intentionally comparing with loose equality | ||
return left != right; | ||
case '!==': | ||
return left !== right; | ||
case '<': | ||
// @ts-expect-error: we don't care if the comparison seems unintentional. | ||
return left < right; | ||
case '<=': | ||
// @ts-expect-error: we don't care if the comparison seems unintentional. | ||
return left <= right; | ||
case '==': | ||
// eslint-disable-next-line eqeqeq -- intentionally comparing with loose equality | ||
return left == right; | ||
case '===': | ||
return left === right; | ||
case '>': | ||
// @ts-expect-error: we don't care if the comparison seems unintentional. | ||
return left > right; | ||
case '>=': | ||
// @ts-expect-error: we don't care if the comparison seems unintentional. | ||
return left >= right; | ||
} | ||
} | ||
// #endregion | ||
export type Options = [ | ||
@@ -141,7 +212,7 @@ export default createRule<Options, MessageId>({ | ||
alwaysTruthyFunc: | ||
'This callback should return a conditional, but return is always truthy.', | ||
literalBooleanExpression: | ||
'Unnecessary conditional,comparison is always {{trueOrFalse}}. Bothsides of thecomparison always have aliteraltype.', | ||
never: 'Unnecessary conditional, value is `never`.', | ||
neverNullish: | ||
'Unnecessary conditional, expected left-hand side of `??` operator to be possibly null or undefined.', | ||
@@ -397,19 +468,6 @@ export default createRule<Options, MessageId>({ | ||
* - https://github.com/microsoft/TypeScript/issues/32627 | ||
* - https://github.com/microsoft/TypeScript/issues/37160 (handled) | ||
*/ | ||
function checkIfBoolExpressionIsNecessaryConditional( | ||
node: TSESTree.Node, | ||
left: TSESTree.Node, | ||
@@ -418,10 +476,27 @@ export default createRule<Options, MessageId>({ | ||
): void { | ||
const leftType = getConstrainedTypeAtLocation(services, left); | ||
const rightType = getConstrainedTypeAtLocation(services, right); | ||
const leftStaticValue = toStaticValue(leftType); | ||
const rightStaticValue = toStaticValue(rightType); | ||
if (leftStaticValue != null && rightStaticValue != null) { | ||
const conditionIsTrue = booleanComparison( | ||
leftStaticValue.value, | ||
operator, | ||
rightStaticValue.value, | ||
); | ||
context.report({ | ||
node, | ||
messageId: 'literalBooleanExpression', | ||
data: { | ||
trueOrFalse: conditionIsTrue ? 'true' : 'false', | ||
}, | ||
}); | ||
return; | ||
} | ||
// Workaround for https://github.com/microsoft/TypeScript/issues/37160 | ||
if (isStrictNullChecks) { | ||
const UNDEFINED = ts.TypeFlags.Undefined; | ||
Uh oh!
There was an error while loading.Please reload this page.
Uh oh!
There was an error while loading.Please reload this page.