.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / prefer-object-spread.js
diff --git a/.config/coc/extensions/node_modules/coc-prettier/node_modules/eslint/lib/rules/prefer-object-spread.js b/.config/coc/extensions/node_modules/coc-prettier/node_modules/eslint/lib/rules/prefer-object-spread.js
new file mode 100644 (file)
index 0000000..ab252c7
--- /dev/null
@@ -0,0 +1,299 @@
+/**
+ * @fileoverview Prefers object spread property over Object.assign
+ * @author Sharmila Jesupaul
+ * See LICENSE file in root directory for full license.
+ */
+
+"use strict";
+
+const { CALL, ReferenceTracker } = require("eslint-utils");
+const {
+    isCommaToken,
+    isOpeningParenToken,
+    isClosingParenToken,
+    isParenthesised
+} = require("./utils/ast-utils");
+
+const ANY_SPACE = /\s/u;
+
+/**
+ * Helper that checks if the Object.assign call has array spread
+ * @param {ASTNode} node The node that the rule warns on
+ * @returns {boolean} - Returns true if the Object.assign call has array spread
+ */
+function hasArraySpread(node) {
+    return node.arguments.some(arg => arg.type === "SpreadElement");
+}
+
+/**
+ * Determines whether the given node is an accessor property (getter/setter).
+ * @param {ASTNode} node Node to check.
+ * @returns {boolean} `true` if the node is a getter or a setter.
+ */
+function isAccessorProperty(node) {
+    return node.type === "Property" &&
+        (node.kind === "get" || node.kind === "set");
+}
+
+/**
+ * Determines whether the given object expression node has accessor properties (getters/setters).
+ * @param {ASTNode} node `ObjectExpression` node to check.
+ * @returns {boolean} `true` if the node has at least one getter/setter.
+ */
+function hasAccessors(node) {
+    return node.properties.some(isAccessorProperty);
+}
+
+/**
+ * Determines whether the given call expression node has object expression arguments with accessor properties (getters/setters).
+ * @param {ASTNode} node `CallExpression` node to check.
+ * @returns {boolean} `true` if the node has at least one argument that is an object expression with at least one getter/setter.
+ */
+function hasArgumentsWithAccessors(node) {
+    return node.arguments
+        .filter(arg => arg.type === "ObjectExpression")
+        .some(hasAccessors);
+}
+
+/**
+ * Helper that checks if the node needs parentheses to be valid JS.
+ * The default is to wrap the node in parentheses to avoid parsing errors.
+ * @param {ASTNode} node The node that the rule warns on
+ * @param {Object} sourceCode in context sourcecode object
+ * @returns {boolean} - Returns true if the node needs parentheses
+ */
+function needsParens(node, sourceCode) {
+    const parent = node.parent;
+
+    switch (parent.type) {
+        case "VariableDeclarator":
+        case "ArrayExpression":
+        case "ReturnStatement":
+        case "CallExpression":
+        case "Property":
+            return false;
+        case "AssignmentExpression":
+            return parent.left === node && !isParenthesised(sourceCode, node);
+        default:
+            return !isParenthesised(sourceCode, node);
+    }
+}
+
+/**
+ * Determines if an argument needs parentheses. The default is to not add parens.
+ * @param {ASTNode} node The node to be checked.
+ * @param {Object} sourceCode in context sourcecode object
+ * @returns {boolean} True if the node needs parentheses
+ */
+function argNeedsParens(node, sourceCode) {
+    switch (node.type) {
+        case "AssignmentExpression":
+        case "ArrowFunctionExpression":
+        case "ConditionalExpression":
+            return !isParenthesised(sourceCode, node);
+        default:
+            return false;
+    }
+}
+
+/**
+ * Get the parenthesis tokens of a given ObjectExpression node.
+ * This includes the braces of the object literal and enclosing parentheses.
+ * @param {ASTNode} node The node to get.
+ * @param {Token} leftArgumentListParen The opening paren token of the argument list.
+ * @param {SourceCode} sourceCode The source code object to get tokens.
+ * @returns {Token[]} The parenthesis tokens of the node. This is sorted by the location.
+ */
+function getParenTokens(node, leftArgumentListParen, sourceCode) {
+    const parens = [sourceCode.getFirstToken(node), sourceCode.getLastToken(node)];
+    let leftNext = sourceCode.getTokenBefore(node);
+    let rightNext = sourceCode.getTokenAfter(node);
+
+    // Note: don't include the parens of the argument list.
+    while (
+        leftNext &&
+        rightNext &&
+        leftNext.range[0] > leftArgumentListParen.range[0] &&
+        isOpeningParenToken(leftNext) &&
+        isClosingParenToken(rightNext)
+    ) {
+        parens.push(leftNext, rightNext);
+        leftNext = sourceCode.getTokenBefore(leftNext);
+        rightNext = sourceCode.getTokenAfter(rightNext);
+    }
+
+    return parens.sort((a, b) => a.range[0] - b.range[0]);
+}
+
+/**
+ * Get the range of a given token and around whitespaces.
+ * @param {Token} token The token to get range.
+ * @param {SourceCode} sourceCode The source code object to get tokens.
+ * @returns {number} The end of the range of the token and around whitespaces.
+ */
+function getStartWithSpaces(token, sourceCode) {
+    const text = sourceCode.text;
+    let start = token.range[0];
+
+    // If the previous token is a line comment then skip this step to avoid commenting this token out.
+    {
+        const prevToken = sourceCode.getTokenBefore(token, { includeComments: true });
+
+        if (prevToken && prevToken.type === "Line") {
+            return start;
+        }
+    }
+
+    // Detect spaces before the token.
+    while (ANY_SPACE.test(text[start - 1] || "")) {
+        start -= 1;
+    }
+
+    return start;
+}
+
+/**
+ * Get the range of a given token and around whitespaces.
+ * @param {Token} token The token to get range.
+ * @param {SourceCode} sourceCode The source code object to get tokens.
+ * @returns {number} The start of the range of the token and around whitespaces.
+ */
+function getEndWithSpaces(token, sourceCode) {
+    const text = sourceCode.text;
+    let end = token.range[1];
+
+    // Detect spaces after the token.
+    while (ANY_SPACE.test(text[end] || "")) {
+        end += 1;
+    }
+
+    return end;
+}
+
+/**
+ * Autofixes the Object.assign call to use an object spread instead.
+ * @param {ASTNode|null} node The node that the rule warns on, i.e. the Object.assign call
+ * @param {string} sourceCode sourceCode of the Object.assign call
+ * @returns {Function} autofixer - replaces the Object.assign with a spread object.
+ */
+function defineFixer(node, sourceCode) {
+    return function *(fixer) {
+        const leftParen = sourceCode.getTokenAfter(node.callee, isOpeningParenToken);
+        const rightParen = sourceCode.getLastToken(node);
+
+        // Remove everything before the opening paren: callee `Object.assign`, type arguments, and whitespace between the callee and the paren.
+        yield fixer.removeRange([node.range[0], leftParen.range[0]]);
+
+        // Replace the parens of argument list to braces.
+        if (needsParens(node, sourceCode)) {
+            yield fixer.replaceText(leftParen, "({");
+            yield fixer.replaceText(rightParen, "})");
+        } else {
+            yield fixer.replaceText(leftParen, "{");
+            yield fixer.replaceText(rightParen, "}");
+        }
+
+        // Process arguments.
+        for (const argNode of node.arguments) {
+            const innerParens = getParenTokens(argNode, leftParen, sourceCode);
+            const left = innerParens.shift();
+            const right = innerParens.pop();
+
+            if (argNode.type === "ObjectExpression") {
+                const maybeTrailingComma = sourceCode.getLastToken(argNode, 1);
+                const maybeArgumentComma = sourceCode.getTokenAfter(right);
+
+                /*
+                 * Make bare this object literal.
+                 * And remove spaces inside of the braces for better formatting.
+                 */
+                for (const innerParen of innerParens) {
+                    yield fixer.remove(innerParen);
+                }
+                const leftRange = [left.range[0], getEndWithSpaces(left, sourceCode)];
+                const rightRange = [
+                    Math.max(getStartWithSpaces(right, sourceCode), leftRange[1]), // Ensure ranges don't overlap
+                    right.range[1]
+                ];
+
+                yield fixer.removeRange(leftRange);
+                yield fixer.removeRange(rightRange);
+
+                // Remove the comma of this argument if it's duplication.
+                if (
+                    (argNode.properties.length === 0 || isCommaToken(maybeTrailingComma)) &&
+                    isCommaToken(maybeArgumentComma)
+                ) {
+                    yield fixer.remove(maybeArgumentComma);
+                }
+            } else {
+
+                // Make spread.
+                if (argNeedsParens(argNode, sourceCode)) {
+                    yield fixer.insertTextBefore(left, "...(");
+                    yield fixer.insertTextAfter(right, ")");
+                } else {
+                    yield fixer.insertTextBefore(left, "...");
+                }
+            }
+        }
+    };
+}
+
+module.exports = {
+    meta: {
+        type: "suggestion",
+
+        docs: {
+            description:
+                "disallow using Object.assign with an object literal as the first argument and prefer the use of object spread instead.",
+            category: "Stylistic Issues",
+            recommended: false,
+            url: "https://eslint.org/docs/rules/prefer-object-spread"
+        },
+
+        schema: [],
+        fixable: "code",
+
+        messages: {
+            useSpreadMessage: "Use an object spread instead of `Object.assign` eg: `{ ...foo }`.",
+            useLiteralMessage: "Use an object literal instead of `Object.assign`. eg: `{ foo: bar }`."
+        }
+    },
+
+    create(context) {
+        const sourceCode = context.getSourceCode();
+
+        return {
+            Program() {
+                const scope = context.getScope();
+                const tracker = new ReferenceTracker(scope);
+                const trackMap = {
+                    Object: {
+                        assign: { [CALL]: true }
+                    }
+                };
+
+                // Iterate all calls of `Object.assign` (only of the global variable `Object`).
+                for (const { node } of tracker.iterateGlobalReferences(trackMap)) {
+                    if (
+                        node.arguments.length >= 1 &&
+                        node.arguments[0].type === "ObjectExpression" &&
+                        !hasArraySpread(node) &&
+                        !(
+                            node.arguments.length > 1 &&
+                            hasArgumentsWithAccessors(node)
+                        )
+                    ) {
+                        const messageId = node.arguments.length === 1
+                            ? "useLiteralMessage"
+                            : "useSpreadMessage";
+                        const fix = defineFixer(node, sourceCode);
+
+                        context.report({ node, messageId, fix });
+                    }
+                }
+            }
+        };
+    }
+};