2 * @fileoverview Prefer destructuring from arrays and objects
3 * @author Alex LaFroscia
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
16 description: "require destructuring from arrays and/or objects",
17 category: "ECMAScript 6",
19 url: "https://eslint.org/docs/rules/prefer-destructuring"
28 * old support {array: Boolean, object: Boolean}
29 * new support {VariableDeclarator: {}, AssignmentExpression: {}}
45 additionalProperties: false
47 AssignmentExpression: {
57 additionalProperties: false
60 additionalProperties: false
72 additionalProperties: false
79 enforceForRenamedProperties: {
83 additionalProperties: false
89 const enabledTypes = context.options[0];
90 const enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties;
91 let normalizedOptions = {
92 VariableDeclarator: { array: true, object: true },
93 AssignmentExpression: { array: true, object: true }
97 normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined"
98 ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes }
102 //--------------------------------------------------------------------------
104 //--------------------------------------------------------------------------
106 // eslint-disable-next-line jsdoc/require-description
108 * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator"
109 * @param {string} destructuringType "array" or "object"
110 * @returns {boolean} `true` if the destructuring type should be checked for the given node
112 function shouldCheck(nodeType, destructuringType) {
113 return normalizedOptions &&
114 normalizedOptions[nodeType] &&
115 normalizedOptions[nodeType][destructuringType];
119 * Determines if the given node is accessing an array index
121 * This is used to differentiate array index access from object property
123 * @param {ASTNode} node the node to evaluate
124 * @returns {boolean} whether or not the node is an integer
126 function isArrayIndexAccess(node) {
127 return Number.isInteger(node.property.value);
131 * Report that the given node should use destructuring
132 * @param {ASTNode} reportNode the node to report
133 * @param {string} type the type of destructuring that should have been done
134 * @param {Function|null} fix the fix function or null to pass to context.report
137 function report(reportNode, type, fix) {
140 message: "Use {{type}} destructuring.",
147 * Determines if a node should be fixed into object destructuring
149 * The fixer only fixes the simplest case of object destructuring,
150 * like: `let x = a.x`;
152 * Assignment expression is not fixed.
153 * Array destructuring is not fixed.
154 * Renamed property is not fixed.
155 * @param {ASTNode} node the the node to evaluate
156 * @returns {boolean} whether or not the node should be fixed
158 function shouldFix(node) {
159 return node.type === "VariableDeclarator" &&
160 node.id.type === "Identifier" &&
161 node.init.type === "MemberExpression" &&
162 node.id.name === node.init.property.name;
166 * Fix a node into object destructuring.
167 * This function only handles the simplest case of object destructuring,
168 * see {@link shouldFix}.
169 * @param {SourceCodeFixer} fixer the fixer object
170 * @param {ASTNode} node the node to be fixed.
171 * @returns {Object} a fix for the node
173 function fixIntoObjectDestructuring(fixer, node) {
174 const rightNode = node.init;
175 const sourceCode = context.getSourceCode();
177 return fixer.replaceText(
179 `{${rightNode.property.name}} = ${sourceCode.getText(rightNode.object)}`
184 * Check that the `prefer-destructuring` rules are followed based on the
185 * given left- and right-hand side of the assignment.
187 * Pulled out into a separate method so that VariableDeclarators and
188 * AssignmentExpressions can share the same verification logic.
189 * @param {ASTNode} leftNode the left-hand side of the assignment
190 * @param {ASTNode} rightNode the right-hand side of the assignment
191 * @param {ASTNode} reportNode the node to report the error on
194 function performCheck(leftNode, rightNode, reportNode) {
195 if (rightNode.type !== "MemberExpression" || rightNode.object.type === "Super") {
199 if (isArrayIndexAccess(rightNode)) {
200 if (shouldCheck(reportNode.type, "array")) {
201 report(reportNode, "array", null);
206 const fix = shouldFix(reportNode)
207 ? fixer => fixIntoObjectDestructuring(fixer, reportNode)
210 if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) {
211 report(reportNode, "object", fix);
215 if (shouldCheck(reportNode.type, "object")) {
216 const property = rightNode.property;
219 (property.type === "Literal" && leftNode.name === property.value) ||
220 (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed)
222 report(reportNode, "object", fix);
228 * Check if a given variable declarator is coming from an property access
229 * that should be using destructuring instead
230 * @param {ASTNode} node the variable declarator to check
233 function checkVariableDeclarator(node) {
235 // Skip if variable is declared without assignment
240 // We only care about member expressions past this point
241 if (node.init.type !== "MemberExpression") {
245 performCheck(node.id, node.init, node);
249 * Run the `prefer-destructuring` check on an AssignmentExpression
250 * @param {ASTNode} node the AssignmentExpression node
253 function checkAssigmentExpression(node) {
254 if (node.operator === "=") {
255 performCheck(node.left, node.right, node);
259 //--------------------------------------------------------------------------
261 //--------------------------------------------------------------------------
264 VariableDeclarator: checkVariableDeclarator,
265 AssignmentExpression: checkAssigmentExpression