"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; Object.defineProperty(exports, "__esModule", { value: true }); const utils_1 = require("@typescript-eslint/utils"); const tsutils = __importStar(require("tsutils")); const ts = __importStar(require("typescript")); const util = __importStar(require("../util")); exports.default = util.createRule({ name: 'no-unnecessary-boolean-literal-compare', meta: { docs: { description: 'Disallow unnecessary equality comparisons against boolean literals', recommended: 'strict', requiresTypeChecking: true, }, fixable: 'code', messages: { direct: 'This expression unnecessarily compares a boolean value to a boolean instead of using it directly.', negated: 'This expression unnecessarily compares a boolean value to a boolean instead of negating it.', comparingNullableToTrueDirect: 'This expression unnecessarily compares a nullable boolean value to true instead of using it directly.', comparingNullableToTrueNegated: 'This expression unnecessarily compares a nullable boolean value to true instead of negating it.', comparingNullableToFalse: 'This expression unnecessarily compares a nullable boolean value to false instead of using the ?? operator to provide a default.', }, schema: [ { type: 'object', properties: { allowComparingNullableBooleansToTrue: { type: 'boolean', }, allowComparingNullableBooleansToFalse: { type: 'boolean', }, }, additionalProperties: false, }, ], type: 'suggestion', }, defaultOptions: [ { allowComparingNullableBooleansToTrue: true, allowComparingNullableBooleansToFalse: true, }, ], create(context, [options]) { const parserServices = util.getParserServices(context); const checker = parserServices.program.getTypeChecker(); function getBooleanComparison(node) { const comparison = deconstructComparison(node); if (!comparison) { return undefined; } const expressionType = checker.getTypeAtLocation(parserServices.esTreeNodeToTSNodeMap.get(comparison.expression)); if (isBooleanType(expressionType)) { return Object.assign(Object.assign({}, comparison), { expressionIsNullableBoolean: false }); } if (isNullableBoolean(expressionType)) { return Object.assign(Object.assign({}, comparison), { expressionIsNullableBoolean: true }); } return undefined; } function isBooleanType(expressionType) { 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) { 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) { 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 !== utils_1.AST_NODE_TYPES.Literal || typeof against.value !== 'boolean') { continue; } const { value: literalBooleanInComparison } = against; const negated = !comparisonType.isPositive; return { literalBooleanInComparison, forTruthy: literalBooleanInComparison ? !negated : negated, expression, negated, range: expression.range[0] < against.range[0] ? [expression.range[1], against.range[1]] : [against.range[0], expression.range[0]], }; } return undefined; } function nodeIsUnaryNegation(node) { return (node.type === utils_1.AST_NODE_TYPES.UnaryExpression && node.prefix && node.operator === '!'); } return { BinaryExpression(node) { const comparison = getBooleanComparison(node); if (comparison === undefined) { return; } if (comparison.expressionIsNullableBoolean) { if (comparison.literalBooleanInComparison && options.allowComparingNullableBooleansToTrue) { return; } if (!comparison.literalBooleanInComparison && options.allowComparingNullableBooleansToFalse) { return; } } context.report({ fix: function* (fixer) { yield fixer.removeRange(comparison.range); // if the expression `exp` isn't nullable, or we're comparing to `true`, // we can just replace the entire comparison with `exp` or `!exp` if (!comparison.expressionIsNullableBoolean || comparison.literalBooleanInComparison) { if (!comparison.forTruthy) { yield fixer.insertTextBefore(node, '!'); } return; } // if we're here, then the expression is a nullable boolean and we're // comparing to a literal `false` // if we're doing `== false` or `=== false`, then we need to negate the expression if (!comparison.negated) { const { parent } = node; // if the parent is a negation, we can instead just get rid of the parent's negation. // i.e. instead of resulting in `!(!(exp))`, we can just result in `exp` if (parent != null && nodeIsUnaryNegation(parent)) { // remove from the beginning of the parent to the beginning of this node yield fixer.removeRange([parent.range[0], node.range[0]]); // remove from the end of the node to the end of the parent yield fixer.removeRange([node.range[1], parent.range[1]]); } else { yield fixer.insertTextBefore(node, '!'); } } // provide the default `true` yield fixer.insertTextBefore(node, '('); yield fixer.insertTextAfter(node, ' ?? true)'); }, messageId: comparison.expressionIsNullableBoolean ? comparison.literalBooleanInComparison ? comparison.negated ? 'comparingNullableToTrueNegated' : 'comparingNullableToTrueDirect' : 'comparingNullableToFalse' : comparison.negated ? 'negated' : 'direct', node, }); }, }; }, }); function getEqualsKind(operator) { switch (operator) { case '==': return { isPositive: true, isStrict: false, }; case '===': return { isPositive: true, isStrict: true, }; case '!=': return { isPositive: false, isStrict: false, }; case '!==': return { isPositive: false, isStrict: true, }; default: return undefined; } } //# sourceMappingURL=no-unnecessary-boolean-literal-compare.js.map