📄 no-unnecessary-boolean-literal-compare¶
📊 Analysis Summary¶
| Metric | Count |
|---|---|
| 🔧 Functions | 9 |
| 📦 Imports | 8 |
| 📐 Interfaces | 3 |
| 📑 Type Aliases | 2 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 packages/eslint-plugin/src/rules/no-unnecessary-boolean-literal-compare.ts
📤 Default Export¶
| Property | Value |
|---|---|
name |
'no-unnecessary-boolean-literal-compare' |
meta.type |
'suggestion' |
meta.docs.description |
'Disallow unnecessary equality comparisons against boolean literals' |
meta.docs.recommended |
'strict' |
meta.docs.requiresTypeChecking |
true |
meta.fixable |
'code' |
meta.messages.comparingNullableToFalse |
'This expression unnecessarily compares a nullable boolean value to false instead of using the ?? operator to provide... |
meta.messages.comparingNullableToTrueDirect |
'This expression unnecessarily compares a nullable boolean value to true instead of using it directly.' |
meta.messages.comparingNullableToTrueNegated |
'This expression unnecessarily compares a nullable boolean value to true instead of negating it.' |
meta.messages.direct |
'This expression unnecessarily compares a boolean value to a boolean instead of using it directly.' |
meta.messages.negated |
'This expression unnecessarily compares a boolean value to a boolean instead of negating it.' |
meta.messages.noStrictNullCheck |
'This rule requires the strictNullChecks compiler option to be turned on to function correctly.' |
meta.schema |
[ { type: 'object', additionalProperties: false, properties: { allowComparingNullableBooleansToFalse: { type: 'boolea... |
defaultOptions |
[ { allowComparingNullableBooleansToFalse: true, allowComparingNullableBooleansToTrue: true, allowRuleToRunWithoutStr... |
Entry point: create — documented under Functions.
📦 Imports¶
| Name | Source |
|---|---|
TSESTree |
@typescript-eslint/utils |
AST_NODE_TYPES |
@typescript-eslint/utils |
createRule |
../util |
getConstraintInfo |
../util |
getParserServices |
../util |
isConditionalTest |
../util |
isStrongPrecedenceNode |
../util |
isWeakPrecedenceParent |
../util |
Functions¶
create(context: any, [options]: any): { BinaryExpression(node: any): void; }¶
Parameters:
contextany[options]any
Returns: { BinaryExpression(node: any): void; }
Calls:
getParserServices (from ../util)services.program.getTypeCheckerservices.program.getCompilerOptionstsutils.isStrictCompilerOptionEnabledcontext.reportdeconstructComparisongetConstraintInfo (from ../util)services.getTypeAtLocationisBooleanTypeisNullableBooleantsutils.isTypeFlagSetexpressionType.isUniontypes.filternonNullishTypes.everygetEqualsKindgetBooleanComparisonnodeIsUnaryNegationbooleanXorcontext.sourceCode.getTextisStrongPrecedenceNode (from ../util)isConditionalTest (from ../util)parenthesizeisWeakPrecedenceParent (from ../util)fixer.replaceText
Internal Comments:
/**
* checks if the expressionType is a union that
* 1) contains at least one nullish type (null or undefined)
* 2) contains at least once boolean type (true or false or boolean)
* 3) does not contain any types besides nullish and boolean types
*/
// Whether the truth table of the overall expression being replaced (x2)
// is negated, _ignoring the nullish cases_. (x2)
// we'll build up the replacement text from the compared expression outwards. (x2)
// In maybeNullish === false, nullish values have the same truth table
// as `true`.
Code
create(context, [options]) {
const services = getParserServices(context);
const checker = services.program.getTypeChecker();
const compilerOptions = services.program.getCompilerOptions();
const isStrictNullChecks = tsutils.isStrictCompilerOptionEnabled(
compilerOptions,
'strictNullChecks',
);
if (
!isStrictNullChecks &&
options.allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing !== true
) {
context.report({
loc: {
start: { column: 0, line: 0 },
end: { column: 0, line: 0 },
},
messageId: 'noStrictNullCheck',
});
}
function getBooleanComparison(
node: TSESTree.BinaryExpression,
): BooleanComparisonWithTypeInformation | undefined {
const comparison = deconstructComparison(node);
if (!comparison) {
return undefined;
}
const { constraintType, isTypeParameter } = getConstraintInfo(
checker,
services.getTypeAtLocation(comparison.expression),
);
if (isTypeParameter && constraintType == null) {
return undefined;
}
if (isBooleanType(constraintType)) {
return {
...comparison,
expressionIsNullableBoolean: false,
};
}
if (isNullableBoolean(constraintType)) {
return {
...comparison,
expressionIsNullableBoolean: true,
};
}
return undefined;
}
function isBooleanType(expressionType: ts.Type): boolean {
return tsutils.isTypeFlagSet(
expressionType,
ts.TypeFlags.Boolean | ts.TypeFlags.BooleanLiteral,
);
}
/**
* checks if the expressionType is a union that
* 1) contains at least one nullish type (null or undefined)
* 2) contains at least once boolean type (true or false or boolean)
* 3) does not contain any types besides nullish and boolean types
*/
function isNullableBoolean(expressionType: ts.Type): boolean {
if (!expressionType.isUnion()) {
return false;
}
const { types } = expressionType;
const nonNullishTypes = types.filter(
type =>
!tsutils.isTypeFlagSet(
type,
ts.TypeFlags.Undefined | ts.TypeFlags.Null,
),
);
const hasNonNullishType = nonNullishTypes.length > 0;
if (!hasNonNullishType) {
return false;
}
const hasNullableType = nonNullishTypes.length < types.length;
if (!hasNullableType) {
return false;
}
const allNonNullishTypesAreBoolean = nonNullishTypes.every(isBooleanType);
if (!allNonNullishTypesAreBoolean) {
return false;
}
return true;
}
function deconstructComparison(
node: TSESTree.BinaryExpression,
): BooleanComparison | undefined {
const comparisonType = getEqualsKind(node.operator);
if (!comparisonType) {
return undefined;
}
for (const [against, expression] of [
[node.right, node.left],
[node.left, node.right],
]) {
if (
against.type !== AST_NODE_TYPES.Literal ||
typeof against.value !== 'boolean'
) {
continue;
}
const booleanLiteral = against.value ? 'true' : 'false';
const negated = !comparisonType.isPositive;
return {
booleanLiteral,
expression,
negated,
};
}
return undefined;
}
function nodeIsUnaryNegation(node: TSESTree.Node): boolean {
return (
node.type === AST_NODE_TYPES.UnaryExpression && node.operator === '!'
);
}
return {
BinaryExpression(node): void {
const comparison = getBooleanComparison(node);
if (comparison == null) {
return;
}
if (comparison.expressionIsNullableBoolean) {
if (
comparison.booleanLiteral === 'true' &&
options.allowComparingNullableBooleansToTrue
) {
return;
}
if (
comparison.booleanLiteral === 'false' &&
options.allowComparingNullableBooleansToFalse
) {
return;
}
}
context.report({
node,
messageId: comparison.expressionIsNullableBoolean
? comparison.booleanLiteral === 'true'
? comparison.negated
? 'comparingNullableToTrueNegated'
: 'comparingNullableToTrueDirect'
: 'comparingNullableToFalse'
: comparison.negated
? 'negated'
: 'direct',
fix(fixer) {
const isWrappedInUnaryNegation = nodeIsUnaryNegation(node.parent);
const mutatedNode = isWrappedInUnaryNegation ? node.parent : node;
// Whether the truth table of the overall expression being replaced
// is negated, _ignoring the nullish cases_.
const isOverallNegated = booleanXor(
isWrappedInUnaryNegation,
comparison.negated,
comparison.booleanLiteral === 'false',
);
// we'll build up the replacement text from the compared expression outwards.
let replacementText = context.sourceCode.getText(
comparison.expression,
);
let mayNeedParentheses = !isStrongPrecedenceNode(
comparison.expression,
);
const fixWouldReturnExpressionDirectly =
!isOverallNegated && comparison.expressionIsNullableBoolean;
if (
fixWouldReturnExpressionDirectly &&
!isConditionalTest(mutatedNode)
) {
if (mayNeedParentheses) {
replacementText = parenthesize(replacementText);
}
replacementText = `${replacementText} ?? false`;
mayNeedParentheses = true;
} else {
// In maybeNullish === false, nullish values have the same truth table
// as `true`.
if (
comparison.expressionIsNullableBoolean &&
comparison.booleanLiteral === 'false'
) {
if (mayNeedParentheses) {
replacementText = parenthesize(replacementText);
}
replacementText = `${replacementText} ?? true`;
mayNeedParentheses = true;
}
if (isOverallNegated) {
if (mayNeedParentheses) {
replacementText = parenthesize(replacementText);
}
replacementText = `!${replacementText}`;
mayNeedParentheses = false;
}
}
if (mayNeedParentheses && isWeakPrecedenceParent(mutatedNode)) {
replacementText = parenthesize(replacementText);
}
return fixer.replaceText(mutatedNode, replacementText);
},
});
},
};
}
getEqualsKind(operator: string): EqualsKind | undefined¶
Parameters:
operatorstring
Returns: EqualsKind | undefined
Code
function getEqualsKind(operator: string): EqualsKind | undefined {
switch (operator) {
case '!=':
return {
isPositive: false,
isStrict: false,
};
case '!==':
return {
isPositive: false,
isStrict: true,
};
case '==':
return {
isPositive: true,
isStrict: false,
};
case '===':
return {
isPositive: true,
isStrict: true,
};
default:
return undefined;
}
}
booleanXor(arg0: boolean, args: boolean[]): boolean¶
Parameters:
arg0booleanargsboolean[]
Returns: boolean
Calls:
args.reduceBoolean
Internal Comments:
Code
parenthesize(text: string): string¶
Parameters:
textstring
Returns: string
Internal helpers¶
Declared inside another function in this file.
getBooleanComparison(node: TSESTree.BinaryExpression): BooleanComparisonWithTypeInformation | undefined¶
Parameters:
nodeTSESTree.BinaryExpression
Returns: BooleanComparisonWithTypeInformation | undefined
Calls:
deconstructComparisongetConstraintInfo (from ../util)services.getTypeAtLocationisBooleanTypeisNullableBoolean
Code
function getBooleanComparison(
node: TSESTree.BinaryExpression,
): BooleanComparisonWithTypeInformation | undefined {
const comparison = deconstructComparison(node);
if (!comparison) {
return undefined;
}
const { constraintType, isTypeParameter } = getConstraintInfo(
checker,
services.getTypeAtLocation(comparison.expression),
);
if (isTypeParameter && constraintType == null) {
return undefined;
}
if (isBooleanType(constraintType)) {
return {
...comparison,
expressionIsNullableBoolean: false,
};
}
if (isNullableBoolean(constraintType)) {
return {
...comparison,
expressionIsNullableBoolean: true,
};
}
return undefined;
}
isBooleanType(expressionType: ts.Type): boolean¶
Parameters:
expressionTypets.Type
Returns: boolean
Calls:
tsutils.isTypeFlagSet
Code
isNullableBoolean(expressionType: ts.Type): boolean¶
checks if the expressionType is a union that 1) contains at least one nullish type (null or undefined) 2) contains at least once boolean type (true or false or boolean) 3) does not contain any types besides nullish and boolean types
Raw JSDoc
Calls:
expressionType.isUniontypes.filtertsutils.isTypeFlagSetnonNullishTypes.every
Code
function isNullableBoolean(expressionType: ts.Type): boolean {
if (!expressionType.isUnion()) {
return false;
}
const { types } = expressionType;
const nonNullishTypes = types.filter(
type =>
!tsutils.isTypeFlagSet(
type,
ts.TypeFlags.Undefined | ts.TypeFlags.Null,
),
);
const hasNonNullishType = nonNullishTypes.length > 0;
if (!hasNonNullishType) {
return false;
}
const hasNullableType = nonNullishTypes.length < types.length;
if (!hasNullableType) {
return false;
}
const allNonNullishTypesAreBoolean = nonNullishTypes.every(isBooleanType);
if (!allNonNullishTypesAreBoolean) {
return false;
}
return true;
}
deconstructComparison(node: TSESTree.BinaryExpression): BooleanComparison | undefined¶
Parameters:
nodeTSESTree.BinaryExpression
Returns: BooleanComparison | undefined
Calls:
getEqualsKind
Code
function deconstructComparison(
node: TSESTree.BinaryExpression,
): BooleanComparison | undefined {
const comparisonType = getEqualsKind(node.operator);
if (!comparisonType) {
return undefined;
}
for (const [against, expression] of [
[node.right, node.left],
[node.left, node.right],
]) {
if (
against.type !== AST_NODE_TYPES.Literal ||
typeof against.value !== 'boolean'
) {
continue;
}
const booleanLiteral = against.value ? 'true' : 'false';
const negated = !comparisonType.isPositive;
return {
booleanLiteral,
expression,
negated,
};
}
return undefined;
}
nodeIsUnaryNegation(node: TSESTree.Node): boolean¶
Parameters:
nodeTSESTree.Node
Returns: boolean
Code
Interfaces¶
BooleanComparison¶
Interface Code
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
expression |
TSESTree.Expression \| TSESTree.PrivateIdentifier |
✗ | not shown |
booleanLiteral |
'false' \| 'true' |
✗ | not shown |
negated |
boolean |
✗ | not shown |
BooleanComparisonWithTypeInformation¶
Interface Code
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
expressionIsNullableBoolean |
boolean |
✗ | not shown |
EqualsKind¶
Properties¶
| Name | Type | Optional | Description |
|---|---|---|---|
isPositive |
boolean |
✗ | not shown |
isStrict |
boolean |
✗ | not shown |
Type Aliases¶
MessageIds¶
type MessageIds = | 'comparingNullableToFalse'
| 'comparingNullableToTrueDirect'
| 'comparingNullableToTrueNegated'
| 'direct'
| 'negated'
| 'noStrictNullCheck';
Options¶
type Options = [
{
allowComparingNullableBooleansToFalse?: boolean;
allowComparingNullableBooleansToTrue?: boolean;
allowRuleToRunWithoutStrictNullChecksIKnowWhatIAmDoing?: boolean;
},
];
Generated by Syntax Scribe