| /** |
| * @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 |
| //------------------------------------------------------------------------------ |
| |
| module.exports = { |
| meta: { |
| type: "suggestion", |
| |
| docs: { |
| description: "disallow ternary operators when simpler alternatives exist", |
| category: "Stylistic Issues", |
| recommended: false, |
| url: "https://eslint.org/docs/rules/no-unneeded-ternary" |
| }, |
| |
| schema: [ |
| { |
| type: "object", |
| properties: { |
| defaultAssignment: { |
| type: "boolean", |
| default: true |
| } |
| }, |
| additionalProperties: false |
| } |
| ], |
| |
| fixable: "code" |
| }, |
| |
| create(context) { |
| const options = context.options[0] || {}; |
| const defaultAssignment = options.defaultAssignment !== false; |
| const sourceCode = context.getSourceCode(); |
| |
| /** |
| * 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, |
| loc: node.consequent.loc.start, |
| message: "Unnecessary use of boolean literals in conditional expression.", |
| 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, |
| loc: node.consequent.loc.start, |
| message: "Unnecessary use of conditional expression for default assignment.", |
| fix: fixer => { |
| const shouldParenthesizeAlternate = ( |
| astUtils.getPrecedence(node.alternate) < OR_PRECEDENCE && |
| !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}`); |
| } |
| }); |
| } |
| } |
| }; |
| } |
| }; |