1 | /**
|
2 | * @fileoverview Prefer destructuring from arrays and objects
|
3 | * @author Alex LaFroscia
|
4 | */
|
5 | ;
|
6 |
|
7 | //------------------------------------------------------------------------------
|
8 | // Rule Definition
|
9 | //------------------------------------------------------------------------------
|
10 |
|
11 | module.exports = {
|
12 | meta: {
|
13 | docs: {
|
14 | description: "require destructuring from arrays and/or objects",
|
15 | category: "ECMAScript 6",
|
16 | recommended: false,
|
17 | url: "https://eslint.org/docs/rules/prefer-destructuring"
|
18 | },
|
19 | schema: [
|
20 | {
|
21 |
|
22 | /*
|
23 | * old support {array: Boolean, object: Boolean}
|
24 | * new support {VariableDeclarator: {}, AssignmentExpression: {}}
|
25 | */
|
26 | oneOf: [
|
27 | {
|
28 | type: "object",
|
29 | properties: {
|
30 | VariableDeclarator: {
|
31 | type: "object",
|
32 | properties: {
|
33 | array: {
|
34 | type: "boolean"
|
35 | },
|
36 | object: {
|
37 | type: "boolean"
|
38 | }
|
39 | },
|
40 | additionalProperties: false
|
41 | },
|
42 | AssignmentExpression: {
|
43 | type: "object",
|
44 | properties: {
|
45 | array: {
|
46 | type: "boolean"
|
47 | },
|
48 | object: {
|
49 | type: "boolean"
|
50 | }
|
51 | },
|
52 | additionalProperties: false
|
53 | }
|
54 | },
|
55 | additionalProperties: false
|
56 | },
|
57 | {
|
58 | type: "object",
|
59 | properties: {
|
60 | array: {
|
61 | type: "boolean"
|
62 | },
|
63 | object: {
|
64 | type: "boolean"
|
65 | }
|
66 | },
|
67 | additionalProperties: false
|
68 | }
|
69 | ]
|
70 | },
|
71 | {
|
72 | type: "object",
|
73 | properties: {
|
74 | enforceForRenamedProperties: {
|
75 | type: "boolean"
|
76 | }
|
77 | },
|
78 | additionalProperties: false
|
79 | }
|
80 | ]
|
81 | },
|
82 | create(context) {
|
83 |
|
84 | const enabledTypes = context.options[0];
|
85 | const enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties;
|
86 | let normalizedOptions = {
|
87 | VariableDeclarator: { array: true, object: true },
|
88 | AssignmentExpression: { array: true, object: true }
|
89 | };
|
90 |
|
91 | if (enabledTypes) {
|
92 | normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined"
|
93 | ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes }
|
94 | : enabledTypes;
|
95 | }
|
96 |
|
97 | //--------------------------------------------------------------------------
|
98 | // Helpers
|
99 | //--------------------------------------------------------------------------
|
100 |
|
101 | /**
|
102 | * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator"
|
103 | * @param {string} destructuringType "array" or "object"
|
104 | * @returns {boolean} `true` if the destructuring type should be checked for the given node
|
105 | */
|
106 | function shouldCheck(nodeType, destructuringType) {
|
107 | return normalizedOptions &&
|
108 | normalizedOptions[nodeType] &&
|
109 | normalizedOptions[nodeType][destructuringType];
|
110 | }
|
111 |
|
112 | /**
|
113 | * Determines if the given node is accessing an array index
|
114 | *
|
115 | * This is used to differentiate array index access from object property
|
116 | * access.
|
117 | *
|
118 | * @param {ASTNode} node the node to evaluate
|
119 | * @returns {boolean} whether or not the node is an integer
|
120 | */
|
121 | function isArrayIndexAccess(node) {
|
122 | return Number.isInteger(node.property.value);
|
123 | }
|
124 |
|
125 | /**
|
126 | * Report that the given node should use destructuring
|
127 | *
|
128 | * @param {ASTNode} reportNode the node to report
|
129 | * @param {string} type the type of destructuring that should have been done
|
130 | * @returns {void}
|
131 | */
|
132 | function report(reportNode, type) {
|
133 | context.report({ node: reportNode, message: "Use {{type}} destructuring.", data: { type } });
|
134 | }
|
135 |
|
136 | /**
|
137 | * Check that the `prefer-destructuring` rules are followed based on the
|
138 | * given left- and right-hand side of the assignment.
|
139 | *
|
140 | * Pulled out into a separate method so that VariableDeclarators and
|
141 | * AssignmentExpressions can share the same verification logic.
|
142 | *
|
143 | * @param {ASTNode} leftNode the left-hand side of the assignment
|
144 | * @param {ASTNode} rightNode the right-hand side of the assignment
|
145 | * @param {ASTNode} reportNode the node to report the error on
|
146 | * @returns {void}
|
147 | */
|
148 | function performCheck(leftNode, rightNode, reportNode) {
|
149 | if (rightNode.type !== "MemberExpression" || rightNode.object.type === "Super") {
|
150 | return;
|
151 | }
|
152 |
|
153 | if (isArrayIndexAccess(rightNode)) {
|
154 | if (shouldCheck(reportNode.type, "array")) {
|
155 | report(reportNode, "array");
|
156 | }
|
157 | return;
|
158 | }
|
159 |
|
160 | if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) {
|
161 | report(reportNode, "object");
|
162 | return;
|
163 | }
|
164 |
|
165 | if (shouldCheck(reportNode.type, "object")) {
|
166 | const property = rightNode.property;
|
167 |
|
168 | if (
|
169 | (property.type === "Literal" && leftNode.name === property.value) ||
|
170 | (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed)
|
171 | ) {
|
172 | report(reportNode, "object");
|
173 | }
|
174 | }
|
175 | }
|
176 |
|
177 | /**
|
178 | * Check if a given variable declarator is coming from an property access
|
179 | * that should be using destructuring instead
|
180 | *
|
181 | * @param {ASTNode} node the variable declarator to check
|
182 | * @returns {void}
|
183 | */
|
184 | function checkVariableDeclarator(node) {
|
185 |
|
186 | // Skip if variable is declared without assignment
|
187 | if (!node.init) {
|
188 | return;
|
189 | }
|
190 |
|
191 | // We only care about member expressions past this point
|
192 | if (node.init.type !== "MemberExpression") {
|
193 | return;
|
194 | }
|
195 |
|
196 | performCheck(node.id, node.init, node);
|
197 | }
|
198 |
|
199 | /**
|
200 | * Run the `prefer-destructuring` check on an AssignmentExpression
|
201 | *
|
202 | * @param {ASTNode} node the AssignmentExpression node
|
203 | * @returns {void}
|
204 | */
|
205 | function checkAssigmentExpression(node) {
|
206 | if (node.operator === "=") {
|
207 | performCheck(node.left, node.right, node);
|
208 | }
|
209 | }
|
210 |
|
211 | //--------------------------------------------------------------------------
|
212 | // Public
|
213 | //--------------------------------------------------------------------------
|
214 |
|
215 | return {
|
216 | VariableDeclarator: checkVariableDeclarator,
|
217 | AssignmentExpression: checkAssigmentExpression
|
218 | };
|
219 | }
|
220 | };
|