]>
Commit | Line | Data |
---|---|---|
eb39fafa DC |
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 | ||
12 | const astUtils = require("./utils/ast-utils"); | |
13 | ||
14 | //------------------------------------------------------------------------------ | |
15 | // Rule Definition | |
16 | //------------------------------------------------------------------------------ | |
17 | ||
34eeec05 | 18 | /** @type {import('../shared/types').Rule} */ |
eb39fafa DC |
19 | module.exports = { |
20 | meta: { | |
21 | type: "suggestion", | |
22 | ||
23 | docs: { | |
8f9d1d4d | 24 | description: "Require the use of `===` and `!==`", |
eb39fafa DC |
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 | |
609c276f | 81 | * @param {ASTNode} node The node to check |
eb39fafa DC |
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 | }; |