UNPKG

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