1/** 2 * @fileoverview Rule to disallow negating the left operand of relational operators 3 * @author Toru Nagashima 4 */ 5 6"use strict"; 7 8//------------------------------------------------------------------------------ 9// Requirements 10//------------------------------------------------------------------------------ 11 12const astUtils = require("./utils/ast-utils"); 13 14//------------------------------------------------------------------------------ 15// Helpers 16//------------------------------------------------------------------------------ 17 18/** 19 * Checks whether the given operator is `in` or `instanceof` 20 * @param {string} op The operator type to check. 21 * @returns {boolean} `true` if the operator is `in` or `instanceof` 22 */ 23function isInOrInstanceOfOperator(op) { 24 return op === "in" || op === "instanceof"; 25} 26 27/** 28 * Checks whether the given operator is an ordering relational operator or not. 29 * @param {string} op The operator type to check. 30 * @returns {boolean} `true` if the operator is an ordering relational operator. 31 */ 32function isOrderingRelationalOperator(op) { 33 return op === "<" || op === ">" || op === ">=" || op === "<="; 34} 35 36/** 37 * Checks whether the given node is a logical negation expression or not. 38 * @param {ASTNode} node The node to check. 39 * @returns {boolean} `true` if the node is a logical negation expression. 40 */ 41function isNegation(node) { 42 return node.type === "UnaryExpression" && node.operator === "!"; 43} 44 45//------------------------------------------------------------------------------ 46// Rule Definition 47//------------------------------------------------------------------------------ 48 49module.exports = { 50 meta: { 51 type: "problem", 52 53 docs: { 54 description: "disallow negating the left operand of relational operators", 55 category: "Possible Errors", 56 recommended: true, 57 url: "https://eslint.org/docs/rules/no-unsafe-negation", 58 suggestion: true 59 }, 60 61 schema: [ 62 { 63 type: "object", 64 properties: { 65 enforceForOrderingRelations: { 66 type: "boolean", 67 default: false 68 } 69 }, 70 additionalProperties: false 71 } 72 ], 73 74 fixable: null, 75 76 messages: { 77 unexpected: "Unexpected negating the left operand of '{{operator}}' operator.", 78 suggestNegatedExpression: "Negate '{{operator}}' expression instead of its left operand. This changes the current behavior.", 79 suggestParenthesisedNegation: "Wrap negation in '()' to make the intention explicit. This preserves the current behavior." 80 } 81 }, 82 83 create(context) { 84 const sourceCode = context.getSourceCode(); 85 const options = context.options[0] || {}; 86 const enforceForOrderingRelations = options.enforceForOrderingRelations === true; 87 88 return { 89 BinaryExpression(node) { 90 const operator = node.operator; 91 const orderingRelationRuleApplies = enforceForOrderingRelations && isOrderingRelationalOperator(operator); 92 93 if ( 94 (isInOrInstanceOfOperator(operator) || orderingRelationRuleApplies) && 95 isNegation(node.left) && 96 !astUtils.isParenthesised(sourceCode, node.left) 97 ) { 98 context.report({ 99 node, 100 loc: node.left.loc, 101 messageId: "unexpected", 102 data: { operator }, 103 suggest: [ 104 { 105 messageId: "suggestNegatedExpression", 106 data: { operator }, 107 fix(fixer) { 108 const negationToken = sourceCode.getFirstToken(node.left); 109 const fixRange = [negationToken.range[1], node.range[1]]; 110 const text = sourceCode.text.slice(fixRange[0], fixRange[1]); 111 112 return fixer.replaceTextRange(fixRange, `(${text})`); 113 } 114 }, 115 { 116 messageId: "suggestParenthesisedNegation", 117 fix(fixer) { 118 return fixer.replaceText(node.left, `(${sourceCode.getText(node.left)})`); 119 } 120 } 121 ] 122 }); 123 } 124 } 125 }; 126 } 127}; 128