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}=`);
 | 
						|
            }
 | 
						|
        };
 | 
						|
 | 
						|
    }
 | 
						|
};
 |