• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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