302 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			302 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/**
 | 
						|
 * @fileoverview Prefer destructuring from arrays and objects
 | 
						|
 * @author Alex LaFroscia
 | 
						|
 */
 | 
						|
"use strict";
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
// Requirements
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
const astUtils = require("./utils/ast-utils");
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
// Helpers
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
const PRECEDENCE_OF_ASSIGNMENT_EXPR = astUtils.getPrecedence({ type: "AssignmentExpression" });
 | 
						|
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
// Rule Definition
 | 
						|
//------------------------------------------------------------------------------
 | 
						|
 | 
						|
/** @type {import('../shared/types').Rule} */
 | 
						|
module.exports = {
 | 
						|
    meta: {
 | 
						|
        type: "suggestion",
 | 
						|
 | 
						|
        docs: {
 | 
						|
            description: "Require destructuring from arrays and/or objects",
 | 
						|
            recommended: false,
 | 
						|
            url: "https://eslint.org/docs/latest/rules/prefer-destructuring"
 | 
						|
        },
 | 
						|
 | 
						|
        fixable: "code",
 | 
						|
 | 
						|
        schema: [
 | 
						|
            {
 | 
						|
 | 
						|
                /*
 | 
						|
                 * old support {array: Boolean, object: Boolean}
 | 
						|
                 * new support {VariableDeclarator: {}, AssignmentExpression: {}}
 | 
						|
                 */
 | 
						|
                oneOf: [
 | 
						|
                    {
 | 
						|
                        type: "object",
 | 
						|
                        properties: {
 | 
						|
                            VariableDeclarator: {
 | 
						|
                                type: "object",
 | 
						|
                                properties: {
 | 
						|
                                    array: {
 | 
						|
                                        type: "boolean"
 | 
						|
                                    },
 | 
						|
                                    object: {
 | 
						|
                                        type: "boolean"
 | 
						|
                                    }
 | 
						|
                                },
 | 
						|
                                additionalProperties: false
 | 
						|
                            },
 | 
						|
                            AssignmentExpression: {
 | 
						|
                                type: "object",
 | 
						|
                                properties: {
 | 
						|
                                    array: {
 | 
						|
                                        type: "boolean"
 | 
						|
                                    },
 | 
						|
                                    object: {
 | 
						|
                                        type: "boolean"
 | 
						|
                                    }
 | 
						|
                                },
 | 
						|
                                additionalProperties: false
 | 
						|
                            }
 | 
						|
                        },
 | 
						|
                        additionalProperties: false
 | 
						|
                    },
 | 
						|
                    {
 | 
						|
                        type: "object",
 | 
						|
                        properties: {
 | 
						|
                            array: {
 | 
						|
                                type: "boolean"
 | 
						|
                            },
 | 
						|
                            object: {
 | 
						|
                                type: "boolean"
 | 
						|
                            }
 | 
						|
                        },
 | 
						|
                        additionalProperties: false
 | 
						|
                    }
 | 
						|
                ]
 | 
						|
            },
 | 
						|
            {
 | 
						|
                type: "object",
 | 
						|
                properties: {
 | 
						|
                    enforceForRenamedProperties: {
 | 
						|
                        type: "boolean"
 | 
						|
                    }
 | 
						|
                },
 | 
						|
                additionalProperties: false
 | 
						|
            }
 | 
						|
        ],
 | 
						|
 | 
						|
        messages: {
 | 
						|
            preferDestructuring: "Use {{type}} destructuring."
 | 
						|
        }
 | 
						|
    },
 | 
						|
    create(context) {
 | 
						|
 | 
						|
        const enabledTypes = context.options[0];
 | 
						|
        const enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties;
 | 
						|
        let normalizedOptions = {
 | 
						|
            VariableDeclarator: { array: true, object: true },
 | 
						|
            AssignmentExpression: { array: true, object: true }
 | 
						|
        };
 | 
						|
 | 
						|
        if (enabledTypes) {
 | 
						|
            normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined"
 | 
						|
                ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes }
 | 
						|
                : enabledTypes;
 | 
						|
        }
 | 
						|
 | 
						|
        //--------------------------------------------------------------------------
 | 
						|
        // Helpers
 | 
						|
        //--------------------------------------------------------------------------
 | 
						|
 | 
						|
        /**
 | 
						|
         * Checks if destructuring type should be checked.
 | 
						|
         * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator"
 | 
						|
         * @param {string} destructuringType "array" or "object"
 | 
						|
         * @returns {boolean} `true` if the destructuring type should be checked for the given node
 | 
						|
         */
 | 
						|
        function shouldCheck(nodeType, destructuringType) {
 | 
						|
            return normalizedOptions &&
 | 
						|
                normalizedOptions[nodeType] &&
 | 
						|
                normalizedOptions[nodeType][destructuringType];
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * Determines if the given node is accessing an array index
 | 
						|
         *
 | 
						|
         * This is used to differentiate array index access from object property
 | 
						|
         * access.
 | 
						|
         * @param {ASTNode} node the node to evaluate
 | 
						|
         * @returns {boolean} whether or not the node is an integer
 | 
						|
         */
 | 
						|
        function isArrayIndexAccess(node) {
 | 
						|
            return Number.isInteger(node.property.value);
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * Report that the given node should use destructuring
 | 
						|
         * @param {ASTNode} reportNode the node to report
 | 
						|
         * @param {string} type the type of destructuring that should have been done
 | 
						|
         * @param {Function|null} fix the fix function or null to pass to context.report
 | 
						|
         * @returns {void}
 | 
						|
         */
 | 
						|
        function report(reportNode, type, fix) {
 | 
						|
            context.report({
 | 
						|
                node: reportNode,
 | 
						|
                messageId: "preferDestructuring",
 | 
						|
                data: { type },
 | 
						|
                fix
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * Determines if a node should be fixed into object destructuring
 | 
						|
         *
 | 
						|
         * The fixer only fixes the simplest case of object destructuring,
 | 
						|
         * like: `let x = a.x`;
 | 
						|
         *
 | 
						|
         * Assignment expression is not fixed.
 | 
						|
         * Array destructuring is not fixed.
 | 
						|
         * Renamed property is not fixed.
 | 
						|
         * @param {ASTNode} node the node to evaluate
 | 
						|
         * @returns {boolean} whether or not the node should be fixed
 | 
						|
         */
 | 
						|
        function shouldFix(node) {
 | 
						|
            return node.type === "VariableDeclarator" &&
 | 
						|
                node.id.type === "Identifier" &&
 | 
						|
                node.init.type === "MemberExpression" &&
 | 
						|
                !node.init.computed &&
 | 
						|
                node.init.property.type === "Identifier" &&
 | 
						|
                node.id.name === node.init.property.name;
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * Fix a node into object destructuring.
 | 
						|
         * This function only handles the simplest case of object destructuring,
 | 
						|
         * see {@link shouldFix}.
 | 
						|
         * @param {SourceCodeFixer} fixer the fixer object
 | 
						|
         * @param {ASTNode} node the node to be fixed.
 | 
						|
         * @returns {Object} a fix for the node
 | 
						|
         */
 | 
						|
        function fixIntoObjectDestructuring(fixer, node) {
 | 
						|
            const rightNode = node.init;
 | 
						|
            const sourceCode = context.sourceCode;
 | 
						|
 | 
						|
            // Don't fix if that would remove any comments. Only comments inside `rightNode.object` can be preserved.
 | 
						|
            if (sourceCode.getCommentsInside(node).length > sourceCode.getCommentsInside(rightNode.object).length) {
 | 
						|
                return null;
 | 
						|
            }
 | 
						|
 | 
						|
            let objectText = sourceCode.getText(rightNode.object);
 | 
						|
 | 
						|
            if (astUtils.getPrecedence(rightNode.object) < PRECEDENCE_OF_ASSIGNMENT_EXPR) {
 | 
						|
                objectText = `(${objectText})`;
 | 
						|
            }
 | 
						|
 | 
						|
            return fixer.replaceText(
 | 
						|
                node,
 | 
						|
                `{${rightNode.property.name}} = ${objectText}`
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * Check that the `prefer-destructuring` rules are followed based on the
 | 
						|
         * given left- and right-hand side of the assignment.
 | 
						|
         *
 | 
						|
         * Pulled out into a separate method so that VariableDeclarators and
 | 
						|
         * AssignmentExpressions can share the same verification logic.
 | 
						|
         * @param {ASTNode} leftNode the left-hand side of the assignment
 | 
						|
         * @param {ASTNode} rightNode the right-hand side of the assignment
 | 
						|
         * @param {ASTNode} reportNode the node to report the error on
 | 
						|
         * @returns {void}
 | 
						|
         */
 | 
						|
        function performCheck(leftNode, rightNode, reportNode) {
 | 
						|
            if (
 | 
						|
                rightNode.type !== "MemberExpression" ||
 | 
						|
                rightNode.object.type === "Super" ||
 | 
						|
                rightNode.property.type === "PrivateIdentifier"
 | 
						|
            ) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            if (isArrayIndexAccess(rightNode)) {
 | 
						|
                if (shouldCheck(reportNode.type, "array")) {
 | 
						|
                    report(reportNode, "array", null);
 | 
						|
                }
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            const fix = shouldFix(reportNode)
 | 
						|
                ? fixer => fixIntoObjectDestructuring(fixer, reportNode)
 | 
						|
                : null;
 | 
						|
 | 
						|
            if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) {
 | 
						|
                report(reportNode, "object", fix);
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            if (shouldCheck(reportNode.type, "object")) {
 | 
						|
                const property = rightNode.property;
 | 
						|
 | 
						|
                if (
 | 
						|
                    (property.type === "Literal" && leftNode.name === property.value) ||
 | 
						|
                    (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed)
 | 
						|
                ) {
 | 
						|
                    report(reportNode, "object", fix);
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * Check if a given variable declarator is coming from an property access
 | 
						|
         * that should be using destructuring instead
 | 
						|
         * @param {ASTNode} node the variable declarator to check
 | 
						|
         * @returns {void}
 | 
						|
         */
 | 
						|
        function checkVariableDeclarator(node) {
 | 
						|
 | 
						|
            // Skip if variable is declared without assignment
 | 
						|
            if (!node.init) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            // We only care about member expressions past this point
 | 
						|
            if (node.init.type !== "MemberExpression") {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            performCheck(node.id, node.init, node);
 | 
						|
        }
 | 
						|
 | 
						|
        /**
 | 
						|
         * Run the `prefer-destructuring` check on an AssignmentExpression
 | 
						|
         * @param {ASTNode} node the AssignmentExpression node
 | 
						|
         * @returns {void}
 | 
						|
         */
 | 
						|
        function checkAssignmentExpression(node) {
 | 
						|
            if (node.operator === "=") {
 | 
						|
                performCheck(node.left, node.right, node);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        //--------------------------------------------------------------------------
 | 
						|
        // Public
 | 
						|
        //--------------------------------------------------------------------------
 | 
						|
 | 
						|
        return {
 | 
						|
            VariableDeclarator: checkVariableDeclarator,
 | 
						|
            AssignmentExpression: checkAssignmentExpression
 | 
						|
        };
 | 
						|
    }
 | 
						|
};
 |