1/** 2 * @fileoverview Rule to flag statements that use != and == instead of !== and === 3 * @author Nicholas C. Zakas 4 */ 5 6"use strict"; 7 8//------------------------------------------------------------------------------ 9// Requirements 10//------------------------------------------------------------------------------ 11 12const astUtils = require("./utils/ast-utils"); 13 14//------------------------------------------------------------------------------ 15// Rule Definition 16//------------------------------------------------------------------------------ 17 18module.exports = { 19 meta: { 20 type: "suggestion", 21 22 docs: { 23 description: "require the use of `===` and `!==`", 24 category: "Best Practices", 25 recommended: false, 26 url: "https://eslint.org/docs/rules/eqeqeq" 27 }, 28 29 schema: { 30 anyOf: [ 31 { 32 type: "array", 33 items: [ 34 { 35 enum: ["always"] 36 }, 37 { 38 type: "object", 39 properties: { 40 null: { 41 enum: ["always", "never", "ignore"] 42 } 43 }, 44 additionalProperties: false 45 } 46 ], 47 additionalItems: false 48 }, 49 { 50 type: "array", 51 items: [ 52 { 53 enum: ["smart", "allow-null"] 54 } 55 ], 56 additionalItems: false 57 } 58 ] 59 }, 60 61 fixable: "code", 62 63 messages: { 64 unexpected: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'." 65 } 66 }, 67 68 create(context) { 69 const config = context.options[0] || "always"; 70 const options = context.options[1] || {}; 71 const sourceCode = context.getSourceCode(); 72 73 const nullOption = (config === "always") 74 ? options.null || "always" 75 : "ignore"; 76 const enforceRuleForNull = (nullOption === "always"); 77 const enforceInverseRuleForNull = (nullOption === "never"); 78 79 /** 80 * Checks if an expression is a typeof expression 81 * @param {ASTNode} node The node to check 82 * @returns {boolean} if the node is a typeof expression 83 */ 84 function isTypeOf(node) { 85 return node.type === "UnaryExpression" && node.operator === "typeof"; 86 } 87 88 /** 89 * Checks if either operand of a binary expression is a typeof operation 90 * @param {ASTNode} node The node to check 91 * @returns {boolean} if one of the operands is typeof 92 * @private 93 */ 94 function isTypeOfBinary(node) { 95 return isTypeOf(node.left) || isTypeOf(node.right); 96 } 97 98 /** 99 * Checks if operands are literals of the same type (via typeof) 100 * @param {ASTNode} node The node to check 101 * @returns {boolean} if operands are of same type 102 * @private 103 */ 104 function areLiteralsAndSameType(node) { 105 return node.left.type === "Literal" && node.right.type === "Literal" && 106 typeof node.left.value === typeof node.right.value; 107 } 108 109 /** 110 * Checks if one of the operands is a literal null 111 * @param {ASTNode} node The node to check 112 * @returns {boolean} if operands are null 113 * @private 114 */ 115 function isNullCheck(node) { 116 return astUtils.isNullLiteral(node.right) || astUtils.isNullLiteral(node.left); 117 } 118 119 /** 120 * Reports a message for this rule. 121 * @param {ASTNode} node The binary expression node that was checked 122 * @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==') 123 * @returns {void} 124 * @private 125 */ 126 function report(node, expectedOperator) { 127 const operatorToken = sourceCode.getFirstTokenBetween( 128 node.left, 129 node.right, 130 token => token.value === node.operator 131 ); 132 133 context.report({ 134 node, 135 loc: operatorToken.loc, 136 messageId: "unexpected", 137 data: { expectedOperator, actualOperator: node.operator }, 138 fix(fixer) { 139 140 // If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix. 141 if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) { 142 return fixer.replaceText(operatorToken, expectedOperator); 143 } 144 return null; 145 } 146 }); 147 } 148 149 return { 150 BinaryExpression(node) { 151 const isNull = isNullCheck(node); 152 153 if (node.operator !== "==" && node.operator !== "!=") { 154 if (enforceInverseRuleForNull && isNull) { 155 report(node, node.operator.slice(0, -1)); 156 } 157 return; 158 } 159 160 if (config === "smart" && (isTypeOfBinary(node) || 161 areLiteralsAndSameType(node) || isNull)) { 162 return; 163 } 164 165 if (!enforceRuleForNull && isNull) { 166 return; 167 } 168 169 report(node, `${node.operator}=`); 170 } 171 }; 172 173 } 174}; 175