175 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			175 lines
		
	
	
		
			5.6 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @fileoverview Rule to flag statements that use != and == instead of !== and ===
 | |
|  * @author Nicholas C. Zakas
 | |
|  */
 | |
| 
 | |
| "use strict";
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Requirements
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| const astUtils = require("./utils/ast-utils");
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Rule Definition
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| /** @type {import('../shared/types').Rule} */
 | |
| module.exports = {
 | |
|     meta: {
 | |
|         type: "suggestion",
 | |
| 
 | |
|         docs: {
 | |
|             description: "Require the use of `===` and `!==`",
 | |
|             recommended: false,
 | |
|             url: "https://eslint.org/docs/latest/rules/eqeqeq"
 | |
|         },
 | |
| 
 | |
|         schema: {
 | |
|             anyOf: [
 | |
|                 {
 | |
|                     type: "array",
 | |
|                     items: [
 | |
|                         {
 | |
|                             enum: ["always"]
 | |
|                         },
 | |
|                         {
 | |
|                             type: "object",
 | |
|                             properties: {
 | |
|                                 null: {
 | |
|                                     enum: ["always", "never", "ignore"]
 | |
|                                 }
 | |
|                             },
 | |
|                             additionalProperties: false
 | |
|                         }
 | |
|                     ],
 | |
|                     additionalItems: false
 | |
|                 },
 | |
|                 {
 | |
|                     type: "array",
 | |
|                     items: [
 | |
|                         {
 | |
|                             enum: ["smart", "allow-null"]
 | |
|                         }
 | |
|                     ],
 | |
|                     additionalItems: false
 | |
|                 }
 | |
|             ]
 | |
|         },
 | |
| 
 | |
|         fixable: "code",
 | |
| 
 | |
|         messages: {
 | |
|             unexpected: "Expected '{{expectedOperator}}' and instead saw '{{actualOperator}}'."
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     create(context) {
 | |
|         const config = context.options[0] || "always";
 | |
|         const options = context.options[1] || {};
 | |
|         const sourceCode = context.sourceCode;
 | |
| 
 | |
|         const nullOption = (config === "always")
 | |
|             ? options.null || "always"
 | |
|             : "ignore";
 | |
|         const enforceRuleForNull = (nullOption === "always");
 | |
|         const enforceInverseRuleForNull = (nullOption === "never");
 | |
| 
 | |
|         /**
 | |
|          * Checks if an expression is a typeof expression
 | |
|          * @param {ASTNode} node The node to check
 | |
|          * @returns {boolean} if the node is a typeof expression
 | |
|          */
 | |
|         function isTypeOf(node) {
 | |
|             return node.type === "UnaryExpression" && node.operator === "typeof";
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Checks if either operand of a binary expression is a typeof operation
 | |
|          * @param {ASTNode} node The node to check
 | |
|          * @returns {boolean} if one of the operands is typeof
 | |
|          * @private
 | |
|          */
 | |
|         function isTypeOfBinary(node) {
 | |
|             return isTypeOf(node.left) || isTypeOf(node.right);
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Checks if operands are literals of the same type (via typeof)
 | |
|          * @param {ASTNode} node The node to check
 | |
|          * @returns {boolean} if operands are of same type
 | |
|          * @private
 | |
|          */
 | |
|         function areLiteralsAndSameType(node) {
 | |
|             return node.left.type === "Literal" && node.right.type === "Literal" &&
 | |
|                     typeof node.left.value === typeof node.right.value;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Checks if one of the operands is a literal null
 | |
|          * @param {ASTNode} node The node to check
 | |
|          * @returns {boolean} if operands are null
 | |
|          * @private
 | |
|          */
 | |
|         function isNullCheck(node) {
 | |
|             return astUtils.isNullLiteral(node.right) || astUtils.isNullLiteral(node.left);
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Reports a message for this rule.
 | |
|          * @param {ASTNode} node The binary expression node that was checked
 | |
|          * @param {string} expectedOperator The operator that was expected (either '==', '!=', '===', or '!==')
 | |
|          * @returns {void}
 | |
|          * @private
 | |
|          */
 | |
|         function report(node, expectedOperator) {
 | |
|             const operatorToken = sourceCode.getFirstTokenBetween(
 | |
|                 node.left,
 | |
|                 node.right,
 | |
|                 token => token.value === node.operator
 | |
|             );
 | |
| 
 | |
|             context.report({
 | |
|                 node,
 | |
|                 loc: operatorToken.loc,
 | |
|                 messageId: "unexpected",
 | |
|                 data: { expectedOperator, actualOperator: node.operator },
 | |
|                 fix(fixer) {
 | |
| 
 | |
|                     // If the comparison is a `typeof` comparison or both sides are literals with the same type, then it's safe to fix.
 | |
|                     if (isTypeOfBinary(node) || areLiteralsAndSameType(node)) {
 | |
|                         return fixer.replaceText(operatorToken, expectedOperator);
 | |
|                     }
 | |
|                     return null;
 | |
|                 }
 | |
|             });
 | |
|         }
 | |
| 
 | |
|         return {
 | |
|             BinaryExpression(node) {
 | |
|                 const isNull = isNullCheck(node);
 | |
| 
 | |
|                 if (node.operator !== "==" && node.operator !== "!=") {
 | |
|                     if (enforceInverseRuleForNull && isNull) {
 | |
|                         report(node, node.operator.slice(0, -1));
 | |
|                     }
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 if (config === "smart" && (isTypeOfBinary(node) ||
 | |
|                         areLiteralsAndSameType(node) || isNull)) {
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 if (!enforceRuleForNull && isNull) {
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 report(node, `${node.operator}=`);
 | |
|             }
 | |
|         };
 | |
| 
 | |
|     }
 | |
| };
 |