UNPKG

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