| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362 | 
							- /**
 
-  * @fileoverview Rule to require or disallow yoda comparisons
 
-  * @author Nicholas C. Zakas
 
-  */
 
- "use strict";
 
- //--------------------------------------------------------------------------
 
- // Requirements
 
- //--------------------------------------------------------------------------
 
- const astUtils = require("./utils/ast-utils");
 
- //--------------------------------------------------------------------------
 
- // Helpers
 
- //--------------------------------------------------------------------------
 
- /**
 
-  * Determines whether an operator is a comparison operator.
 
-  * @param {string} operator The operator to check.
 
-  * @returns {boolean} Whether or not it is a comparison operator.
 
-  */
 
- function isComparisonOperator(operator) {
 
-     return /^(==|===|!=|!==|<|>|<=|>=)$/u.test(operator);
 
- }
 
- /**
 
-  * Determines whether an operator is an equality operator.
 
-  * @param {string} operator The operator to check.
 
-  * @returns {boolean} Whether or not it is an equality operator.
 
-  */
 
- function isEqualityOperator(operator) {
 
-     return /^(==|===)$/u.test(operator);
 
- }
 
- /**
 
-  * Determines whether an operator is one used in a range test.
 
-  * Allowed operators are `<` and `<=`.
 
-  * @param {string} operator The operator to check.
 
-  * @returns {boolean} Whether the operator is used in range tests.
 
-  */
 
- function isRangeTestOperator(operator) {
 
-     return ["<", "<="].indexOf(operator) >= 0;
 
- }
 
- /**
 
-  * Determines whether a non-Literal node is a negative number that should be
 
-  * treated as if it were a single Literal node.
 
-  * @param {ASTNode} node Node to test.
 
-  * @returns {boolean} True if the node is a negative number that looks like a
 
-  *                    real literal and should be treated as such.
 
-  */
 
- function isNegativeNumericLiteral(node) {
 
-     return (
 
-         node.type === "UnaryExpression" &&
 
-         node.operator === "-" &&
 
-         node.prefix &&
 
-         astUtils.isNumericLiteral(node.argument)
 
-     );
 
- }
 
- /**
 
-  * Determines whether a node is a Template Literal which can be determined statically.
 
-  * @param {ASTNode} node Node to test
 
-  * @returns {boolean} True if the node is a Template Literal without expression.
 
-  */
 
- function isStaticTemplateLiteral(node) {
 
-     return node.type === "TemplateLiteral" && node.expressions.length === 0;
 
- }
 
- /**
 
-  * Determines whether a non-Literal node should be treated as a single Literal node.
 
-  * @param {ASTNode} node Node to test
 
-  * @returns {boolean} True if the node should be treated as a single Literal node.
 
-  */
 
- function looksLikeLiteral(node) {
 
-     return isNegativeNumericLiteral(node) || isStaticTemplateLiteral(node);
 
- }
 
- /**
 
-  * Attempts to derive a Literal node from nodes that are treated like literals.
 
-  * @param {ASTNode} node Node to normalize.
 
-  * @returns {ASTNode} One of the following options.
 
-  *  1. The original node if the node is already a Literal
 
-  *  2. A normalized Literal node with the negative number as the value if the
 
-  *     node represents a negative number literal.
 
-  *  3. A normalized Literal node with the string as the value if the node is
 
-  *     a Template Literal without expression.
 
-  *  4. Otherwise `null`.
 
-  */
 
- function getNormalizedLiteral(node) {
 
-     if (node.type === "Literal") {
 
-         return node;
 
-     }
 
-     if (isNegativeNumericLiteral(node)) {
 
-         return {
 
-             type: "Literal",
 
-             value: -node.argument.value,
 
-             raw: `-${node.argument.value}`
 
-         };
 
-     }
 
-     if (isStaticTemplateLiteral(node)) {
 
-         return {
 
-             type: "Literal",
 
-             value: node.quasis[0].value.cooked,
 
-             raw: node.quasis[0].value.raw
 
-         };
 
-     }
 
-     return null;
 
- }
 
- //------------------------------------------------------------------------------
 
- // Rule Definition
 
- //------------------------------------------------------------------------------
 
- module.exports = {
 
-     meta: {
 
-         type: "suggestion",
 
-         docs: {
 
-             description: 'require or disallow "Yoda" conditions',
 
-             category: "Best Practices",
 
-             recommended: false,
 
-             url: "https://eslint.org/docs/rules/yoda"
 
-         },
 
-         schema: [
 
-             {
 
-                 enum: ["always", "never"]
 
-             },
 
-             {
 
-                 type: "object",
 
-                 properties: {
 
-                     exceptRange: {
 
-                         type: "boolean",
 
-                         default: false
 
-                     },
 
-                     onlyEquality: {
 
-                         type: "boolean",
 
-                         default: false
 
-                     }
 
-                 },
 
-                 additionalProperties: false
 
-             }
 
-         ],
 
-         fixable: "code",
 
-         messages: {
 
-             expected:
 
-                 "Expected literal to be on the {{expectedSide}} side of {{operator}}."
 
-         }
 
-     },
 
-     create(context) {
 
-         // Default to "never" (!always) if no option
 
-         const always = context.options[0] === "always";
 
-         const exceptRange =
 
-             context.options[1] && context.options[1].exceptRange;
 
-         const onlyEquality =
 
-             context.options[1] && context.options[1].onlyEquality;
 
-         const sourceCode = context.getSourceCode();
 
-         /**
 
-          * Determines whether node represents a range test.
 
-          * A range test is a "between" test like `(0 <= x && x < 1)` or an "outside"
 
-          * test like `(x < 0 || 1 <= x)`. It must be wrapped in parentheses, and
 
-          * both operators must be `<` or `<=`. Finally, the literal on the left side
 
-          * must be less than or equal to the literal on the right side so that the
 
-          * test makes any sense.
 
-          * @param {ASTNode} node LogicalExpression node to test.
 
-          * @returns {boolean} Whether node is a range test.
 
-          */
 
-         function isRangeTest(node) {
 
-             const left = node.left,
 
-                 right = node.right;
 
-             /**
 
-              * Determines whether node is of the form `0 <= x && x < 1`.
 
-              * @returns {boolean} Whether node is a "between" range test.
 
-              */
 
-             function isBetweenTest() {
 
-                 if (node.operator === "&&" && astUtils.isSameReference(left.right, right.left)) {
 
-                     const leftLiteral = getNormalizedLiteral(left.left);
 
-                     const rightLiteral = getNormalizedLiteral(right.right);
 
-                     if (leftLiteral === null && rightLiteral === null) {
 
-                         return false;
 
-                     }
 
-                     if (rightLiteral === null || leftLiteral === null) {
 
-                         return true;
 
-                     }
 
-                     if (leftLiteral.value <= rightLiteral.value) {
 
-                         return true;
 
-                     }
 
-                 }
 
-                 return false;
 
-             }
 
-             /**
 
-              * Determines whether node is of the form `x < 0 || 1 <= x`.
 
-              * @returns {boolean} Whether node is an "outside" range test.
 
-              */
 
-             function isOutsideTest() {
 
-                 if (node.operator === "||" && astUtils.isSameReference(left.left, right.right)) {
 
-                     const leftLiteral = getNormalizedLiteral(left.right);
 
-                     const rightLiteral = getNormalizedLiteral(right.left);
 
-                     if (leftLiteral === null && rightLiteral === null) {
 
-                         return false;
 
-                     }
 
-                     if (rightLiteral === null || leftLiteral === null) {
 
-                         return true;
 
-                     }
 
-                     if (leftLiteral.value <= rightLiteral.value) {
 
-                         return true;
 
-                     }
 
-                 }
 
-                 return false;
 
-             }
 
-             /**
 
-              * Determines whether node is wrapped in parentheses.
 
-              * @returns {boolean} Whether node is preceded immediately by an open
 
-              *                    paren token and followed immediately by a close
 
-              *                    paren token.
 
-              */
 
-             function isParenWrapped() {
 
-                 return astUtils.isParenthesised(sourceCode, node);
 
-             }
 
-             return (
 
-                 node.type === "LogicalExpression" &&
 
-                 left.type === "BinaryExpression" &&
 
-                 right.type === "BinaryExpression" &&
 
-                 isRangeTestOperator(left.operator) &&
 
-                 isRangeTestOperator(right.operator) &&
 
-                 (isBetweenTest() || isOutsideTest()) &&
 
-                 isParenWrapped()
 
-             );
 
-         }
 
-         const OPERATOR_FLIP_MAP = {
 
-             "===": "===",
 
-             "!==": "!==",
 
-             "==": "==",
 
-             "!=": "!=",
 
-             "<": ">",
 
-             ">": "<",
 
-             "<=": ">=",
 
-             ">=": "<="
 
-         };
 
-         /**
 
-          * Returns a string representation of a BinaryExpression node with its sides/operator flipped around.
 
-          * @param {ASTNode} node The BinaryExpression node
 
-          * @returns {string} A string representation of the node with the sides and operator flipped
 
-          */
 
-         function getFlippedString(node) {
 
-             const operatorToken = sourceCode.getFirstTokenBetween(
 
-                 node.left,
 
-                 node.right,
 
-                 token => token.value === node.operator
 
-             );
 
-             const lastLeftToken = sourceCode.getTokenBefore(operatorToken);
 
-             const firstRightToken = sourceCode.getTokenAfter(operatorToken);
 
-             const source = sourceCode.getText();
 
-             const leftText = source.slice(
 
-                 node.range[0],
 
-                 lastLeftToken.range[1]
 
-             );
 
-             const textBeforeOperator = source.slice(
 
-                 lastLeftToken.range[1],
 
-                 operatorToken.range[0]
 
-             );
 
-             const textAfterOperator = source.slice(
 
-                 operatorToken.range[1],
 
-                 firstRightToken.range[0]
 
-             );
 
-             const rightText = source.slice(
 
-                 firstRightToken.range[0],
 
-                 node.range[1]
 
-             );
 
-             const tokenBefore = sourceCode.getTokenBefore(node);
 
-             const tokenAfter = sourceCode.getTokenAfter(node);
 
-             let prefix = "";
 
-             let suffix = "";
 
-             if (
 
-                 tokenBefore &&
 
-                 tokenBefore.range[1] === node.range[0] &&
 
-                 !astUtils.canTokensBeAdjacent(tokenBefore, firstRightToken)
 
-             ) {
 
-                 prefix = " ";
 
-             }
 
-             if (
 
-                 tokenAfter &&
 
-                 node.range[1] === tokenAfter.range[0] &&
 
-                 !astUtils.canTokensBeAdjacent(lastLeftToken, tokenAfter)
 
-             ) {
 
-                 suffix = " ";
 
-             }
 
-             return (
 
-                 prefix +
 
-                 rightText +
 
-                 textBeforeOperator +
 
-                 OPERATOR_FLIP_MAP[operatorToken.value] +
 
-                 textAfterOperator +
 
-                 leftText +
 
-                 suffix
 
-             );
 
-         }
 
-         //--------------------------------------------------------------------------
 
-         // Public
 
-         //--------------------------------------------------------------------------
 
-         return {
 
-             BinaryExpression(node) {
 
-                 const expectedLiteral = always ? node.left : node.right;
 
-                 const expectedNonLiteral = always ? node.right : node.left;
 
-                 // If `expectedLiteral` is not a literal, and `expectedNonLiteral` is a literal, raise an error.
 
-                 if (
 
-                     (expectedNonLiteral.type === "Literal" ||
 
-                         looksLikeLiteral(expectedNonLiteral)) &&
 
-                     !(
 
-                         expectedLiteral.type === "Literal" ||
 
-                         looksLikeLiteral(expectedLiteral)
 
-                     ) &&
 
-                     !(!isEqualityOperator(node.operator) && onlyEquality) &&
 
-                     isComparisonOperator(node.operator) &&
 
-                     !(exceptRange && isRangeTest(context.getAncestors().pop()))
 
-                 ) {
 
-                     context.report({
 
-                         node,
 
-                         messageId: "expected",
 
-                         data: {
 
-                             operator: node.operator,
 
-                             expectedSide: always ? "left" : "right"
 
-                         },
 
-                         fix: fixer =>
 
-                             fixer.replaceText(node, getFlippedString(node))
 
-                     });
 
-                 }
 
-             }
 
-         };
 
-     }
 
- };
 
 
  |