123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176 |
- /**
- * @fileoverview Rule to flag unnecessary double negation in Boolean contexts
- * @author Brandon Mills
- */
- "use strict";
- //------------------------------------------------------------------------------
- // Requirements
- //------------------------------------------------------------------------------
- const astUtils = require("./utils/ast-utils");
- //------------------------------------------------------------------------------
- // Rule Definition
- //------------------------------------------------------------------------------
- module.exports = {
- meta: {
- type: "suggestion",
- docs: {
- description: "disallow unnecessary boolean casts",
- category: "Possible Errors",
- recommended: true,
- url: "https://eslint.org/docs/rules/no-extra-boolean-cast"
- },
- schema: [],
- fixable: "code",
- messages: {
- unexpectedCall: "Redundant Boolean call.",
- unexpectedNegation: "Redundant double negation."
- }
- },
- create(context) {
- const sourceCode = context.getSourceCode();
- // Node types which have a test which will coerce values to booleans.
- const BOOLEAN_NODE_TYPES = [
- "IfStatement",
- "DoWhileStatement",
- "WhileStatement",
- "ConditionalExpression",
- "ForStatement"
- ];
- /**
- * Check if a node is in a context where its value would be coerced to a boolean at runtime.
- * @param {ASTNode} node The node
- * @param {ASTNode} parent Its parent
- * @returns {boolean} If it is in a boolean context
- */
- function isInBooleanContext(node, parent) {
- return (
- (BOOLEAN_NODE_TYPES.indexOf(parent.type) !== -1 &&
- node === parent.test) ||
- // !<bool>
- (parent.type === "UnaryExpression" &&
- parent.operator === "!")
- );
- }
- /**
- * Check if a node has comments inside.
- * @param {ASTNode} node The node to check.
- * @returns {boolean} `true` if it has comments inside.
- */
- function hasCommentsInside(node) {
- return Boolean(sourceCode.getCommentsInside(node).length);
- }
- return {
- UnaryExpression(node) {
- const ancestors = context.getAncestors(),
- parent = ancestors.pop(),
- grandparent = ancestors.pop();
- // Exit early if it's guaranteed not to match
- if (node.operator !== "!" ||
- parent.type !== "UnaryExpression" ||
- parent.operator !== "!") {
- return;
- }
- if (isInBooleanContext(parent, grandparent) ||
- // Boolean(<bool>) and new Boolean(<bool>)
- ((grandparent.type === "CallExpression" || grandparent.type === "NewExpression") &&
- grandparent.callee.type === "Identifier" &&
- grandparent.callee.name === "Boolean")
- ) {
- context.report({
- node: parent,
- messageId: "unexpectedNegation",
- fix: fixer => {
- if (hasCommentsInside(parent)) {
- return null;
- }
- let prefix = "";
- const tokenBefore = sourceCode.getTokenBefore(parent);
- const firstReplacementToken = sourceCode.getFirstToken(node.argument);
- if (tokenBefore && tokenBefore.range[1] === parent.range[0] &&
- !astUtils.canTokensBeAdjacent(tokenBefore, firstReplacementToken)) {
- prefix = " ";
- }
- return fixer.replaceText(parent, prefix + sourceCode.getText(node.argument));
- }
- });
- }
- },
- CallExpression(node) {
- const parent = node.parent;
- if (node.callee.type !== "Identifier" || node.callee.name !== "Boolean") {
- return;
- }
- if (isInBooleanContext(node, parent)) {
- context.report({
- node,
- messageId: "unexpectedCall",
- fix: fixer => {
- if (!node.arguments.length) {
- if (parent.type === "UnaryExpression" && parent.operator === "!") {
- // !Boolean() -> true
- if (hasCommentsInside(parent)) {
- return null;
- }
- const replacement = "true";
- let prefix = "";
- const tokenBefore = sourceCode.getTokenBefore(parent);
- if (tokenBefore && tokenBefore.range[1] === parent.range[0] &&
- !astUtils.canTokensBeAdjacent(tokenBefore, replacement)) {
- prefix = " ";
- }
- return fixer.replaceText(parent, prefix + replacement);
- }
- // Boolean() -> false
- if (hasCommentsInside(node)) {
- return null;
- }
- return fixer.replaceText(node, "false");
- }
- if (node.arguments.length > 1 || node.arguments[0].type === "SpreadElement" ||
- hasCommentsInside(node)) {
- return null;
- }
- const argument = node.arguments[0];
- if (astUtils.getPrecedence(argument) < astUtils.getPrecedence(node.parent)) {
- return fixer.replaceText(node, `(${sourceCode.getText(argument)})`);
- }
- return fixer.replaceText(node, sourceCode.getText(argument));
- }
- });
- }
- }
- };
- }
- };
|