.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / yoda.js
diff --git a/.config/coc/extensions/node_modules/coc-prettier/node_modules/eslint/lib/rules/yoda.js b/.config/coc/extensions/node_modules/coc-prettier/node_modules/eslint/lib/rules/yoda.js
new file mode 100644 (file)
index 0000000..2fca757
--- /dev/null
@@ -0,0 +1,362 @@
+/**
+ * @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))
+                    });
+                }
+            }
+        };
+    }
+};