source: imaps-frontend/node_modules/eslint/lib/rules/yoda.js@ d565449

main
Last change on this file since d565449 was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

  • Property mode set to 100644
File size: 11.9 KB
RevLine 
[d565449]1/**
2 * @fileoverview Rule to require or disallow yoda comparisons
3 * @author Nicholas C. Zakas
4 */
5"use strict";
6
7//--------------------------------------------------------------------------
8// Requirements
9//--------------------------------------------------------------------------
10
11const astUtils = require("./utils/ast-utils");
12
13//--------------------------------------------------------------------------
14// Helpers
15//--------------------------------------------------------------------------
16
17/**
18 * Determines whether an operator is a comparison operator.
19 * @param {string} operator The operator to check.
20 * @returns {boolean} Whether or not it is a comparison operator.
21 */
22function isComparisonOperator(operator) {
23 return /^(==|===|!=|!==|<|>|<=|>=)$/u.test(operator);
24}
25
26/**
27 * Determines whether an operator is an equality operator.
28 * @param {string} operator The operator to check.
29 * @returns {boolean} Whether or not it is an equality operator.
30 */
31function isEqualityOperator(operator) {
32 return /^(==|===)$/u.test(operator);
33}
34
35/**
36 * Determines whether an operator is one used in a range test.
37 * Allowed operators are `<` and `<=`.
38 * @param {string} operator The operator to check.
39 * @returns {boolean} Whether the operator is used in range tests.
40 */
41function isRangeTestOperator(operator) {
42 return ["<", "<="].includes(operator);
43}
44
45/**
46 * Determines whether a non-Literal node is a negative number that should be
47 * treated as if it were a single Literal node.
48 * @param {ASTNode} node Node to test.
49 * @returns {boolean} True if the node is a negative number that looks like a
50 * real literal and should be treated as such.
51 */
52function isNegativeNumericLiteral(node) {
53 return (
54 node.type === "UnaryExpression" &&
55 node.operator === "-" &&
56 node.prefix &&
57 astUtils.isNumericLiteral(node.argument)
58 );
59}
60
61/**
62 * Determines whether a non-Literal node should be treated as a single Literal node.
63 * @param {ASTNode} node Node to test
64 * @returns {boolean} True if the node should be treated as a single Literal node.
65 */
66function looksLikeLiteral(node) {
67 return isNegativeNumericLiteral(node) || astUtils.isStaticTemplateLiteral(node);
68}
69
70/**
71 * Attempts to derive a Literal node from nodes that are treated like literals.
72 * @param {ASTNode} node Node to normalize.
73 * @returns {ASTNode} One of the following options.
74 * 1. The original node if the node is already a Literal
75 * 2. A normalized Literal node with the negative number as the value if the
76 * node represents a negative number literal.
77 * 3. A normalized Literal node with the string as the value if the node is
78 * a Template Literal without expression.
79 * 4. Otherwise `null`.
80 */
81function getNormalizedLiteral(node) {
82 if (node.type === "Literal") {
83 return node;
84 }
85
86 if (isNegativeNumericLiteral(node)) {
87 return {
88 type: "Literal",
89 value: -node.argument.value,
90 raw: `-${node.argument.value}`
91 };
92 }
93
94 if (astUtils.isStaticTemplateLiteral(node)) {
95 return {
96 type: "Literal",
97 value: node.quasis[0].value.cooked,
98 raw: node.quasis[0].value.raw
99 };
100 }
101
102 return null;
103}
104
105//------------------------------------------------------------------------------
106// Rule Definition
107//------------------------------------------------------------------------------
108
109/** @type {import('../shared/types').Rule} */
110module.exports = {
111 meta: {
112 type: "suggestion",
113
114 docs: {
115 description: 'Require or disallow "Yoda" conditions',
116 recommended: false,
117 url: "https://eslint.org/docs/latest/rules/yoda"
118 },
119
120 schema: [
121 {
122 enum: ["always", "never"]
123 },
124 {
125 type: "object",
126 properties: {
127 exceptRange: {
128 type: "boolean",
129 default: false
130 },
131 onlyEquality: {
132 type: "boolean",
133 default: false
134 }
135 },
136 additionalProperties: false
137 }
138 ],
139
140 fixable: "code",
141 messages: {
142 expected:
143 "Expected literal to be on the {{expectedSide}} side of {{operator}}."
144 }
145 },
146
147 create(context) {
148
149 // Default to "never" (!always) if no option
150 const always = context.options[0] === "always";
151 const exceptRange =
152 context.options[1] && context.options[1].exceptRange;
153 const onlyEquality =
154 context.options[1] && context.options[1].onlyEquality;
155
156 const sourceCode = context.sourceCode;
157
158 /**
159 * Determines whether node represents a range test.
160 * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside"
161 * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and
162 * both operators must be `<` or `<=`. Finally, the literal on the left side
163 * must be less than or equal to the literal on the right side so that the
164 * test makes any sense.
165 * @param {ASTNode} node LogicalExpression node to test.
166 * @returns {boolean} Whether node is a range test.
167 */
168 function isRangeTest(node) {
169 const left = node.left,
170 right = node.right;
171
172 /**
173 * Determines whether node is of the form `0 <= x && x < 1`.
174 * @returns {boolean} Whether node is a "between" range test.
175 */
176 function isBetweenTest() {
177 if (node.operator === "&&" && astUtils.isSameReference(left.right, right.left)) {
178 const leftLiteral = getNormalizedLiteral(left.left);
179 const rightLiteral = getNormalizedLiteral(right.right);
180
181 if (leftLiteral === null && rightLiteral === null) {
182 return false;
183 }
184
185 if (rightLiteral === null || leftLiteral === null) {
186 return true;
187 }
188
189 if (leftLiteral.value <= rightLiteral.value) {
190 return true;
191 }
192 }
193 return false;
194 }
195
196 /**
197 * Determines whether node is of the form `x < 0 || 1 <= x`.
198 * @returns {boolean} Whether node is an "outside" range test.
199 */
200 function isOutsideTest() {
201 if (node.operator === "||" && astUtils.isSameReference(left.left, right.right)) {
202 const leftLiteral = getNormalizedLiteral(left.right);
203 const rightLiteral = getNormalizedLiteral(right.left);
204
205 if (leftLiteral === null && rightLiteral === null) {
206 return false;
207 }
208
209 if (rightLiteral === null || leftLiteral === null) {
210 return true;
211 }
212
213 if (leftLiteral.value <= rightLiteral.value) {
214 return true;
215 }
216 }
217
218 return false;
219 }
220
221 /**
222 * Determines whether node is wrapped in parentheses.
223 * @returns {boolean} Whether node is preceded immediately by an open
224 * paren token and followed immediately by a close
225 * paren token.
226 */
227 function isParenWrapped() {
228 return astUtils.isParenthesised(sourceCode, node);
229 }
230
231 return (
232 node.type === "LogicalExpression" &&
233 left.type === "BinaryExpression" &&
234 right.type === "BinaryExpression" &&
235 isRangeTestOperator(left.operator) &&
236 isRangeTestOperator(right.operator) &&
237 (isBetweenTest() || isOutsideTest()) &&
238 isParenWrapped()
239 );
240 }
241
242 const OPERATOR_FLIP_MAP = {
243 "===": "===",
244 "!==": "!==",
245 "==": "==",
246 "!=": "!=",
247 "<": ">",
248 ">": "<",
249 "<=": ">=",
250 ">=": "<="
251 };
252
253 /**
254 * Returns a string representation of a BinaryExpression node with its sides/operator flipped around.
255 * @param {ASTNode} node The BinaryExpression node
256 * @returns {string} A string representation of the node with the sides and operator flipped
257 */
258 function getFlippedString(node) {
259 const operatorToken = sourceCode.getFirstTokenBetween(
260 node.left,
261 node.right,
262 token => token.value === node.operator
263 );
264 const lastLeftToken = sourceCode.getTokenBefore(operatorToken);
265 const firstRightToken = sourceCode.getTokenAfter(operatorToken);
266
267 const source = sourceCode.getText();
268
269 const leftText = source.slice(
270 node.range[0],
271 lastLeftToken.range[1]
272 );
273 const textBeforeOperator = source.slice(
274 lastLeftToken.range[1],
275 operatorToken.range[0]
276 );
277 const textAfterOperator = source.slice(
278 operatorToken.range[1],
279 firstRightToken.range[0]
280 );
281 const rightText = source.slice(
282 firstRightToken.range[0],
283 node.range[1]
284 );
285
286 const tokenBefore = sourceCode.getTokenBefore(node);
287 const tokenAfter = sourceCode.getTokenAfter(node);
288 let prefix = "";
289 let suffix = "";
290
291 if (
292 tokenBefore &&
293 tokenBefore.range[1] === node.range[0] &&
294 !astUtils.canTokensBeAdjacent(tokenBefore, firstRightToken)
295 ) {
296 prefix = " ";
297 }
298
299 if (
300 tokenAfter &&
301 node.range[1] === tokenAfter.range[0] &&
302 !astUtils.canTokensBeAdjacent(lastLeftToken, tokenAfter)
303 ) {
304 suffix = " ";
305 }
306
307 return (
308 prefix +
309 rightText +
310 textBeforeOperator +
311 OPERATOR_FLIP_MAP[operatorToken.value] +
312 textAfterOperator +
313 leftText +
314 suffix
315 );
316 }
317
318 //--------------------------------------------------------------------------
319 // Public
320 //--------------------------------------------------------------------------
321
322 return {
323 BinaryExpression(node) {
324 const expectedLiteral = always ? node.left : node.right;
325 const expectedNonLiteral = always ? node.right : node.left;
326
327 // If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error.
328 if (
329 (expectedNonLiteral.type === "Literal" ||
330 looksLikeLiteral(expectedNonLiteral)) &&
331 !(
332 expectedLiteral.type === "Literal" ||
333 looksLikeLiteral(expectedLiteral)
334 ) &&
335 !(!isEqualityOperator(node.operator) && onlyEquality) &&
336 isComparisonOperator(node.operator) &&
337 !(exceptRange && isRangeTest(node.parent))
338 ) {
339 context.report({
340 node,
341 messageId: "expected",
342 data: {
343 operator: node.operator,
344 expectedSide: always ? "left" : "right"
345 },
346 fix: fixer =>
347 fixer.replaceText(node, getFlippedString(node))
348 });
349 }
350 }
351 };
352 }
353};
Note: See TracBrowser for help on using the repository browser.