.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / yoda.js
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
11 const 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  */
22 function 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  */
31 function 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  */
41 function isRangeTestOperator(operator) {
42     return ["<", "<="].indexOf(operator) >= 0;
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  */
52 function 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 node is a Template Literal which can be determined statically.
63  * @param {ASTNode} node Node to test
64  * @returns {boolean} True if the node is a Template Literal without expression.
65  */
66 function isStaticTemplateLiteral(node) {
67     return node.type === "TemplateLiteral" && node.expressions.length === 0;
68 }
69
70 /**
71  * Determines whether a non-Literal node should be treated as a single Literal node.
72  * @param {ASTNode} node Node to test
73  * @returns {boolean} True if the node should be treated as a single Literal node.
74  */
75 function looksLikeLiteral(node) {
76     return isNegativeNumericLiteral(node) || isStaticTemplateLiteral(node);
77 }
78
79 /**
80  * Attempts to derive a Literal node from nodes that are treated like literals.
81  * @param {ASTNode} node Node to normalize.
82  * @returns {ASTNode} One of the following options.
83  *  1. The original node if the node is already a Literal
84  *  2. A normalized Literal node with the negative number as the value if the
85  *     node represents a negative number literal.
86  *  3. A normalized Literal node with the string as the value if the node is
87  *     a Template Literal without expression.
88  *  4. Otherwise `null`.
89  */
90 function getNormalizedLiteral(node) {
91     if (node.type === "Literal") {
92         return node;
93     }
94
95     if (isNegativeNumericLiteral(node)) {
96         return {
97             type: "Literal",
98             value: -node.argument.value,
99             raw: `-${node.argument.value}`
100         };
101     }
102
103     if (isStaticTemplateLiteral(node)) {
104         return {
105             type: "Literal",
106             value: node.quasis[0].value.cooked,
107             raw: node.quasis[0].value.raw
108         };
109     }
110
111     return null;
112 }
113
114 //------------------------------------------------------------------------------
115 // Rule Definition
116 //------------------------------------------------------------------------------
117
118 module.exports = {
119     meta: {
120         type: "suggestion",
121
122         docs: {
123             description: 'require or disallow "Yoda" conditions',
124             category: "Best Practices",
125             recommended: false,
126             url: "https://eslint.org/docs/rules/yoda"
127         },
128
129         schema: [
130             {
131                 enum: ["always", "never"]
132             },
133             {
134                 type: "object",
135                 properties: {
136                     exceptRange: {
137                         type: "boolean",
138                         default: false
139                     },
140                     onlyEquality: {
141                         type: "boolean",
142                         default: false
143                     }
144                 },
145                 additionalProperties: false
146             }
147         ],
148
149         fixable: "code",
150         messages: {
151             expected:
152                 "Expected literal to be on the {{expectedSide}} side of {{operator}}."
153         }
154     },
155
156     create(context) {
157
158         // Default to "never" (!always) if no option
159         const always = context.options[0] === "always";
160         const exceptRange =
161             context.options[1] && context.options[1].exceptRange;
162         const onlyEquality =
163             context.options[1] && context.options[1].onlyEquality;
164
165         const sourceCode = context.getSourceCode();
166
167         /**
168          * Determines whether node represents a range test.
169          * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside"
170          * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and
171          * both operators must be `<` or `<=`. Finally, the literal on the left side
172          * must be less than or equal to the literal on the right side so that the
173          * test makes any sense.
174          * @param {ASTNode} node LogicalExpression node to test.
175          * @returns {boolean} Whether node is a range test.
176          */
177         function isRangeTest(node) {
178             const left = node.left,
179                 right = node.right;
180
181             /**
182              * Determines whether node is of the form `0 <= x && x < 1`.
183              * @returns {boolean} Whether node is a "between" range test.
184              */
185             function isBetweenTest() {
186                 if (node.operator === "&&" && astUtils.isSameReference(left.right, right.left)) {
187                     const leftLiteral = getNormalizedLiteral(left.left);
188                     const rightLiteral = getNormalizedLiteral(right.right);
189
190                     if (leftLiteral === null && rightLiteral === null) {
191                         return false;
192                     }
193
194                     if (rightLiteral === null || leftLiteral === null) {
195                         return true;
196                     }
197
198                     if (leftLiteral.value <= rightLiteral.value) {
199                         return true;
200                     }
201                 }
202                 return false;
203             }
204
205             /**
206              * Determines whether node is of the form `x < 0 || 1 <= x`.
207              * @returns {boolean} Whether node is an "outside" range test.
208              */
209             function isOutsideTest() {
210                 if (node.operator === "||" && astUtils.isSameReference(left.left, right.right)) {
211                     const leftLiteral = getNormalizedLiteral(left.right);
212                     const rightLiteral = getNormalizedLiteral(right.left);
213
214                     if (leftLiteral === null && rightLiteral === null) {
215                         return false;
216                     }
217
218                     if (rightLiteral === null || leftLiteral === null) {
219                         return true;
220                     }
221
222                     if (leftLiteral.value <= rightLiteral.value) {
223                         return true;
224                     }
225                 }
226
227                 return false;
228             }
229
230             /**
231              * Determines whether node is wrapped in parentheses.
232              * @returns {boolean} Whether node is preceded immediately by an open
233              *                    paren token and followed immediately by a close
234              *                    paren token.
235              */
236             function isParenWrapped() {
237                 return astUtils.isParenthesised(sourceCode, node);
238             }
239
240             return (
241                 node.type === "LogicalExpression" &&
242                 left.type === "BinaryExpression" &&
243                 right.type === "BinaryExpression" &&
244                 isRangeTestOperator(left.operator) &&
245                 isRangeTestOperator(right.operator) &&
246                 (isBetweenTest() || isOutsideTest()) &&
247                 isParenWrapped()
248             );
249         }
250
251         const OPERATOR_FLIP_MAP = {
252             "===": "===",
253             "!==": "!==",
254             "==": "==",
255             "!=": "!=",
256             "<": ">",
257             ">": "<",
258             "<=": ">=",
259             ">=": "<="
260         };
261
262         /**
263          * Returns a string representation of a BinaryExpression node with its sides/operator flipped around.
264          * @param {ASTNode} node The BinaryExpression node
265          * @returns {string} A string representation of the node with the sides and operator flipped
266          */
267         function getFlippedString(node) {
268             const operatorToken = sourceCode.getFirstTokenBetween(
269                 node.left,
270                 node.right,
271                 token => token.value === node.operator
272             );
273             const lastLeftToken = sourceCode.getTokenBefore(operatorToken);
274             const firstRightToken = sourceCode.getTokenAfter(operatorToken);
275
276             const source = sourceCode.getText();
277
278             const leftText = source.slice(
279                 node.range[0],
280                 lastLeftToken.range[1]
281             );
282             const textBeforeOperator = source.slice(
283                 lastLeftToken.range[1],
284                 operatorToken.range[0]
285             );
286             const textAfterOperator = source.slice(
287                 operatorToken.range[1],
288                 firstRightToken.range[0]
289             );
290             const rightText = source.slice(
291                 firstRightToken.range[0],
292                 node.range[1]
293             );
294
295             const tokenBefore = sourceCode.getTokenBefore(node);
296             const tokenAfter = sourceCode.getTokenAfter(node);
297             let prefix = "";
298             let suffix = "";
299
300             if (
301                 tokenBefore &&
302                 tokenBefore.range[1] === node.range[0] &&
303                 !astUtils.canTokensBeAdjacent(tokenBefore, firstRightToken)
304             ) {
305                 prefix = " ";
306             }
307
308             if (
309                 tokenAfter &&
310                 node.range[1] === tokenAfter.range[0] &&
311                 !astUtils.canTokensBeAdjacent(lastLeftToken, tokenAfter)
312             ) {
313                 suffix = " ";
314             }
315
316             return (
317                 prefix +
318                 rightText +
319                 textBeforeOperator +
320                 OPERATOR_FLIP_MAP[operatorToken.value] +
321                 textAfterOperator +
322                 leftText +
323                 suffix
324             );
325         }
326
327         //--------------------------------------------------------------------------
328         // Public
329         //--------------------------------------------------------------------------
330
331         return {
332             BinaryExpression(node) {
333                 const expectedLiteral = always ? node.left : node.right;
334                 const expectedNonLiteral = always ? node.right : node.left;
335
336                 // If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error.
337                 if (
338                     (expectedNonLiteral.type === "Literal" ||
339                         looksLikeLiteral(expectedNonLiteral)) &&
340                     !(
341                         expectedLiteral.type === "Literal" ||
342                         looksLikeLiteral(expectedLiteral)
343                     ) &&
344                     !(!isEqualityOperator(node.operator) && onlyEquality) &&
345                     isComparisonOperator(node.operator) &&
346                     !(exceptRange && isRangeTest(context.getAncestors().pop()))
347                 ) {
348                     context.report({
349                         node,
350                         messageId: "expected",
351                         data: {
352                             operator: node.operator,
353                             expectedSide: always ? "left" : "right"
354                         },
355                         fix: fixer =>
356                             fixer.replaceText(node, getFlippedString(node))
357                     });
358                 }
359             }
360         };
361     }
362 };