You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
			
				
					269 lines
				
				9.7 KiB
			
		
		
			
		
	
	
					269 lines
				
				9.7 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @fileoverview Prefer destructuring from arrays and objects
							 | 
						||
| 
								 | 
							
								 * @author Alex LaFroscia
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								"use strict";
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								//------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								// Rule Definition
							 | 
						||
| 
								 | 
							
								//------------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								module.exports = {
							 | 
						||
| 
								 | 
							
								    meta: {
							 | 
						||
| 
								 | 
							
								        type: "suggestion",
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        docs: {
							 | 
						||
| 
								 | 
							
								            description: "require destructuring from arrays and/or objects",
							 | 
						||
| 
								 | 
							
								            category: "ECMAScript 6",
							 | 
						||
| 
								 | 
							
								            recommended: false,
							 | 
						||
| 
								 | 
							
								            url: "https://eslint.org/docs/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
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        ]
							 | 
						||
| 
								 | 
							
								    },
							 | 
						||
| 
								 | 
							
								    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
							 | 
						||
| 
								 | 
							
								        //--------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        // eslint-disable-next-line jsdoc/require-description
							 | 
						||
| 
								 | 
							
								        /**
							 | 
						||
| 
								 | 
							
								         * @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,
							 | 
						||
| 
								 | 
							
								                message: "Use {{type}} destructuring.",
							 | 
						||
| 
								 | 
							
								                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 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.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.getSourceCode();
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								            return fixer.replaceText(
							 | 
						||
| 
								 | 
							
								                node,
							 | 
						||
| 
								 | 
							
								                `{${rightNode.property.name}} = ${sourceCode.getText(rightNode.object)}`
							 | 
						||
| 
								 | 
							
								            );
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        /**
							 | 
						||
| 
								 | 
							
								         * 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") {
							 | 
						||
| 
								 | 
							
								                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 checkAssigmentExpression(node) {
							 | 
						||
| 
								 | 
							
								            if (node.operator === "=") {
							 | 
						||
| 
								 | 
							
								                performCheck(node.left, node.right, node);
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        //--------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								        // Public
							 | 
						||
| 
								 | 
							
								        //--------------------------------------------------------------------------
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								        return {
							 | 
						||
| 
								 | 
							
								            VariableDeclarator: checkVariableDeclarator,
							 | 
						||
| 
								 | 
							
								            AssignmentExpression: checkAssigmentExpression
							 | 
						||
| 
								 | 
							
								        };
							 | 
						||
| 
								 | 
							
								    }
							 | 
						||
| 
								 | 
							
								};
							 |