Skip to content
Permalink
9bfb9ba527
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
166 lines (142 sloc) 6.79 KB
/**
* @fileoverview Rule to flag no-unneeded-ternary
* @author Gyandeep Singh
*/
"use strict";
const astUtils = require("./utils/ast-utils");
// Operators that always result in a boolean value
const BOOLEAN_OPERATORS = new Set(["==", "===", "!=", "!==", ">", ">=", "<", "<=", "in", "instanceof"]);
const OPERATOR_INVERSES = {
"==": "!=",
"!=": "==",
"===": "!==",
"!==": "==="
// Operators like < and >= are not true inverses, since both will return false with NaN.
};
const OR_PRECEDENCE = astUtils.getPrecedence({ type: "LogicalExpression", operator: "||" });
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
/** @type {import('../shared/types').Rule} */
module.exports = {
meta: {
type: "suggestion",
docs: {
description: "Disallow ternary operators when simpler alternatives exist",
recommended: false,
url: "https://eslint.org/docs/latest/rules/no-unneeded-ternary"
},
schema: [
{
type: "object",
properties: {
defaultAssignment: {
type: "boolean",
default: true
}
},
additionalProperties: false
}
],
fixable: "code",
messages: {
unnecessaryConditionalExpression: "Unnecessary use of boolean literals in conditional expression.",
unnecessaryConditionalAssignment: "Unnecessary use of conditional expression for default assignment."
}
},
create(context) {
const options = context.options[0] || {};
const defaultAssignment = options.defaultAssignment !== false;
const sourceCode = context.sourceCode;
/**
* Test if the node is a boolean literal
* @param {ASTNode} node The node to report.
* @returns {boolean} True if the its a boolean literal
* @private
*/
function isBooleanLiteral(node) {
return node.type === "Literal" && typeof node.value === "boolean";
}
/**
* Creates an expression that represents the boolean inverse of the expression represented by the original node
* @param {ASTNode} node A node representing an expression
* @returns {string} A string representing an inverted expression
*/
function invertExpression(node) {
if (node.type === "BinaryExpression" && Object.prototype.hasOwnProperty.call(OPERATOR_INVERSES, node.operator)) {
const operatorToken = sourceCode.getFirstTokenBetween(
node.left,
node.right,
token => token.value === node.operator
);
const text = sourceCode.getText();
return text.slice(node.range[0],
operatorToken.range[0]) + OPERATOR_INVERSES[node.operator] + text.slice(operatorToken.range[1], node.range[1]);
}
if (astUtils.getPrecedence(node) < astUtils.getPrecedence({ type: "UnaryExpression" })) {
return `!(${astUtils.getParenthesisedText(sourceCode, node)})`;
}
return `!${astUtils.getParenthesisedText(sourceCode, node)}`;
}
/**
* Tests if a given node always evaluates to a boolean value
* @param {ASTNode} node An expression node
* @returns {boolean} True if it is determined that the node will always evaluate to a boolean value
*/
function isBooleanExpression(node) {
return node.type === "BinaryExpression" && BOOLEAN_OPERATORS.has(node.operator) ||
node.type === "UnaryExpression" && node.operator === "!";
}
/**
* Test if the node matches the pattern id ? id : expression
* @param {ASTNode} node The ConditionalExpression to check.
* @returns {boolean} True if the pattern is matched, and false otherwise
* @private
*/
function matchesDefaultAssignment(node) {
return node.test.type === "Identifier" &&
node.consequent.type === "Identifier" &&
node.test.name === node.consequent.name;
}
return {
ConditionalExpression(node) {
if (isBooleanLiteral(node.alternate) && isBooleanLiteral(node.consequent)) {
context.report({
node,
messageId: "unnecessaryConditionalExpression",
fix(fixer) {
if (node.consequent.value === node.alternate.value) {
// Replace `foo ? true : true` with just `true`, but don't replace `foo() ? true : true`
return node.test.type === "Identifier" ? fixer.replaceText(node, node.consequent.value.toString()) : null;
}
if (node.alternate.value) {
// Replace `foo() ? false : true` with `!(foo())`
return fixer.replaceText(node, invertExpression(node.test));
}
// Replace `foo ? true : false` with `foo` if `foo` is guaranteed to be a boolean, or `!!foo` otherwise.
return fixer.replaceText(node, isBooleanExpression(node.test) ? astUtils.getParenthesisedText(sourceCode, node.test) : `!${invertExpression(node.test)}`);
}
});
} else if (!defaultAssignment && matchesDefaultAssignment(node)) {
context.report({
node,
messageId: "unnecessaryConditionalAssignment",
fix(fixer) {
const shouldParenthesizeAlternate =
(
astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE ||
astUtils.isCoalesceExpression(node.alternate)
) &&
!astUtils.isParenthesised(sourceCode, node.alternate);
const alternateText = shouldParenthesizeAlternate
? `(${sourceCode.getText(node.alternate)})`
: astUtils.getParenthesisedText(sourceCode, node.alternate);
const testText = astUtils.getParenthesisedText(sourceCode, node.test);
return fixer.replaceText(node, `${testText} || ${alternateText}`);
}
});
}
}
};
}
};