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
88 preferDestructuring
: "Use {{type}} destructuring."
93 const enabledTypes
= context
.options
[0];
94 const enforceForRenamedProperties
= context
.options
[1] && context
.options
[1].enforceForRenamedProperties
;
95 let normalizedOptions
= {
96 VariableDeclarator
: { array
: true, object
: true },
97 AssignmentExpression
: { array
: true, object
: true }
101 normalizedOptions
= typeof enabledTypes
.array
!== "undefined" || typeof enabledTypes
.object
!== "undefined"
102 ? { VariableDeclarator
: enabledTypes
, AssignmentExpression
: enabledTypes
}
106 //--------------------------------------------------------------------------
108 //--------------------------------------------------------------------------
110 // eslint-disable-next-line jsdoc/require-description
112 * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator"
113 * @param {string} destructuringType "array" or "object"
114 * @returns {boolean} `true` if the destructuring type should be checked for the given node
116 function shouldCheck(nodeType
, destructuringType
) {
117 return normalizedOptions
&&
118 normalizedOptions
[nodeType
] &&
119 normalizedOptions
[nodeType
][destructuringType
];
123 * Determines if the given node is accessing an array index
125 * This is used to differentiate array index access from object property
127 * @param {ASTNode} node the node to evaluate
128 * @returns {boolean} whether or not the node is an integer
130 function isArrayIndexAccess(node
) {
131 return Number
.isInteger(node
.property
.value
);
135 * Report that the given node should use destructuring
136 * @param {ASTNode} reportNode the node to report
137 * @param {string} type the type of destructuring that should have been done
138 * @param {Function|null} fix the fix function or null to pass to context.report
141 function report(reportNode
, type
, fix
) {
144 messageId
: "preferDestructuring",
151 * Determines if a node should be fixed into object destructuring
153 * The fixer only fixes the simplest case of object destructuring,
154 * like: `let x = a.x`;
156 * Assignment expression is not fixed.
157 * Array destructuring is not fixed.
158 * Renamed property is not fixed.
159 * @param {ASTNode} node the the node to evaluate
160 * @returns {boolean} whether or not the node should be fixed
162 function shouldFix(node
) {
163 return node
.type
=== "VariableDeclarator" &&
164 node
.id
.type
=== "Identifier" &&
165 node
.init
.type
=== "MemberExpression" &&
166 node
.id
.name
=== node
.init
.property
.name
;
170 * Fix a node into object destructuring.
171 * This function only handles the simplest case of object destructuring,
172 * see {@link shouldFix}.
173 * @param {SourceCodeFixer} fixer the fixer object
174 * @param {ASTNode} node the node to be fixed.
175 * @returns {Object} a fix for the node
177 function fixIntoObjectDestructuring(fixer
, node
) {
178 const rightNode
= node
.init
;
179 const sourceCode
= context
.getSourceCode();
181 return fixer
.replaceText(
183 `{${rightNode.property.name}} = ${sourceCode.getText(rightNode.object)}`
188 * Check that the `prefer-destructuring` rules are followed based on the
189 * given left- and right-hand side of the assignment.
191 * Pulled out into a separate method so that VariableDeclarators and
192 * AssignmentExpressions can share the same verification logic.
193 * @param {ASTNode} leftNode the left-hand side of the assignment
194 * @param {ASTNode} rightNode the right-hand side of the assignment
195 * @param {ASTNode} reportNode the node to report the error on
198 function performCheck(leftNode
, rightNode
, reportNode
) {
199 if (rightNode
.type
!== "MemberExpression" || rightNode
.object
.type
=== "Super") {
203 if (isArrayIndexAccess(rightNode
)) {
204 if (shouldCheck(reportNode
.type
, "array")) {
205 report(reportNode
, "array", null);
210 const fix
= shouldFix(reportNode
)
211 ? fixer
=> fixIntoObjectDestructuring(fixer
, reportNode
)
214 if (shouldCheck(reportNode
.type
, "object") && enforceForRenamedProperties
) {
215 report(reportNode
, "object", fix
);
219 if (shouldCheck(reportNode
.type
, "object")) {
220 const property
= rightNode
.property
;
223 (property
.type
=== "Literal" && leftNode
.name
=== property
.value
) ||
224 (property
.type
=== "Identifier" && leftNode
.name
=== property
.name
&& !rightNode
.computed
)
226 report(reportNode
, "object", fix
);
232 * Check if a given variable declarator is coming from an property access
233 * that should be using destructuring instead
234 * @param {ASTNode} node the variable declarator to check
237 function checkVariableDeclarator(node
) {
239 // Skip if variable is declared without assignment
244 // We only care about member expressions past this point
245 if (node
.init
.type
!== "MemberExpression") {
249 performCheck(node
.id
, node
.init
, node
);
253 * Run the `prefer-destructuring` check on an AssignmentExpression
254 * @param {ASTNode} node the AssignmentExpression node
257 function checkAssigmentExpression(node
) {
258 if (node
.operator
=== "=") {
259 performCheck(node
.left
, node
.right
, node
);
263 //--------------------------------------------------------------------------
265 //--------------------------------------------------------------------------
268 VariableDeclarator
: checkVariableDeclarator
,
269 AssignmentExpression
: checkAssigmentExpression