]>
git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/yoda.js
2 * @fileoverview Rule to require or disallow yoda comparisons
3 * @author Nicholas C. Zakas
7 //--------------------------------------------------------------------------
9 //--------------------------------------------------------------------------
11 const astUtils
= require("./utils/ast-utils");
13 //--------------------------------------------------------------------------
15 //--------------------------------------------------------------------------
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.
22 function isComparisonOperator(operator
) {
23 return /^(==|===|!=|!==|<|>|<=|>=)$/u.test(operator
);
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.
31 function isEqualityOperator(operator
) {
32 return /^(==|===)$/u.test(operator
);
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.
41 function isRangeTestOperator(operator
) {
42 return ["<", "<="].indexOf(operator
) >= 0;
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.
52 function isNegativeNumericLiteral(node
) {
54 node
.type
=== "UnaryExpression" &&
55 node
.operator
=== "-" &&
57 astUtils
.isNumericLiteral(node
.argument
)
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.
66 function isStaticTemplateLiteral(node
) {
67 return node
.type
=== "TemplateLiteral" && node
.expressions
.length
=== 0;
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.
75 function looksLikeLiteral(node
) {
76 return isNegativeNumericLiteral(node
) || isStaticTemplateLiteral(node
);
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`.
90 function getNormalizedLiteral(node
) {
91 if (node
.type
=== "Literal") {
95 if (isNegativeNumericLiteral(node
)) {
98 value
: -node
.argument
.value
,
99 raw
: `-${node.argument.value}`
103 if (isStaticTemplateLiteral(node
)) {
106 value
: node
.quasis
[0].value
.cooked
,
107 raw
: node
.quasis
[0].value
.raw
114 //------------------------------------------------------------------------------
116 //------------------------------------------------------------------------------
123 description
: 'require or disallow "Yoda" conditions',
125 url
: "https://eslint.org/docs/rules/yoda"
130 enum: ["always", "never"]
144 additionalProperties
: false
151 "Expected literal to be on the {{expectedSide}} side of {{operator}}."
157 // Default to "never" (!always) if no option
158 const always
= context
.options
[0] === "always";
160 context
.options
[1] && context
.options
[1].exceptRange
;
162 context
.options
[1] && context
.options
[1].onlyEquality
;
164 const sourceCode
= context
.getSourceCode();
167 * Determines whether node represents a range test.
168 * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside"
169 * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and
170 * both operators must be `<` or `<=`. Finally, the literal on the left side
171 * must be less than or equal to the literal on the right side so that the
172 * test makes any sense.
173 * @param {ASTNode} node LogicalExpression node to test.
174 * @returns {boolean} Whether node is a range test.
176 function isRangeTest(node
) {
177 const left
= node
.left
,
181 * Determines whether node is of the form `0 <= x && x < 1`.
182 * @returns {boolean} Whether node is a "between" range test.
184 function isBetweenTest() {
185 if (node
.operator
=== "&&" && astUtils
.isSameReference(left
.right
, right
.left
)) {
186 const leftLiteral
= getNormalizedLiteral(left
.left
);
187 const rightLiteral
= getNormalizedLiteral(right
.right
);
189 if (leftLiteral
=== null && rightLiteral
=== null) {
193 if (rightLiteral
=== null || leftLiteral
=== null) {
197 if (leftLiteral
.value
<= rightLiteral
.value
) {
205 * Determines whether node is of the form `x < 0 || 1 <= x`.
206 * @returns {boolean} Whether node is an "outside" range test.
208 function isOutsideTest() {
209 if (node
.operator
=== "||" && astUtils
.isSameReference(left
.left
, right
.right
)) {
210 const leftLiteral
= getNormalizedLiteral(left
.right
);
211 const rightLiteral
= getNormalizedLiteral(right
.left
);
213 if (leftLiteral
=== null && rightLiteral
=== null) {
217 if (rightLiteral
=== null || leftLiteral
=== null) {
221 if (leftLiteral
.value
<= rightLiteral
.value
) {
230 * Determines whether node is wrapped in parentheses.
231 * @returns {boolean} Whether node is preceded immediately by an open
232 * paren token and followed immediately by a close
235 function isParenWrapped() {
236 return astUtils
.isParenthesised(sourceCode
, node
);
240 node
.type
=== "LogicalExpression" &&
241 left
.type
=== "BinaryExpression" &&
242 right
.type
=== "BinaryExpression" &&
243 isRangeTestOperator(left
.operator
) &&
244 isRangeTestOperator(right
.operator
) &&
245 (isBetweenTest() || isOutsideTest()) &&
250 const OPERATOR_FLIP_MAP
= {
262 * Returns a string representation of a BinaryExpression node with its sides/operator flipped around.
263 * @param {ASTNode} node The BinaryExpression node
264 * @returns {string} A string representation of the node with the sides and operator flipped
266 function getFlippedString(node
) {
267 const operatorToken
= sourceCode
.getFirstTokenBetween(
270 token
=> token
.value
=== node
.operator
272 const lastLeftToken
= sourceCode
.getTokenBefore(operatorToken
);
273 const firstRightToken
= sourceCode
.getTokenAfter(operatorToken
);
275 const source
= sourceCode
.getText();
277 const leftText
= source
.slice(
279 lastLeftToken
.range
[1]
281 const textBeforeOperator
= source
.slice(
282 lastLeftToken
.range
[1],
283 operatorToken
.range
[0]
285 const textAfterOperator
= source
.slice(
286 operatorToken
.range
[1],
287 firstRightToken
.range
[0]
289 const rightText
= source
.slice(
290 firstRightToken
.range
[0],
294 const tokenBefore
= sourceCode
.getTokenBefore(node
);
295 const tokenAfter
= sourceCode
.getTokenAfter(node
);
301 tokenBefore
.range
[1] === node
.range
[0] &&
302 !astUtils
.canTokensBeAdjacent(tokenBefore
, firstRightToken
)
309 node
.range
[1] === tokenAfter
.range
[0] &&
310 !astUtils
.canTokensBeAdjacent(lastLeftToken
, tokenAfter
)
319 OPERATOR_FLIP_MAP
[operatorToken
.value
] +
326 //--------------------------------------------------------------------------
328 //--------------------------------------------------------------------------
331 BinaryExpression(node
) {
332 const expectedLiteral
= always
? node
.left
: node
.right
;
333 const expectedNonLiteral
= always
? node
.right
: node
.left
;
335 // If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error.
337 (expectedNonLiteral
.type
=== "Literal" ||
338 looksLikeLiteral(expectedNonLiteral
)) &&
340 expectedLiteral
.type
=== "Literal" ||
341 looksLikeLiteral(expectedLiteral
)
343 !(!isEqualityOperator(node
.operator
) && onlyEquality
) &&
344 isComparisonOperator(node
.operator
) &&
345 !(exceptRange
&& isRangeTest(context
.getAncestors().pop()))
349 messageId
: "expected",
351 operator
: node
.operator
,
352 expectedSide
: always
? "left" : "right"
355 fixer
.replaceText(node
, getFlippedString(node
))