]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/prefer-destructuring.js
first commit
[pve-eslint.git] / eslint / lib / rules / prefer-destructuring.js
1 /**
2 * @fileoverview Prefer destructuring from arrays and objects
3 * @author Alex LaFroscia
4 */
5 "use strict";
6
7 //------------------------------------------------------------------------------
8 // Rule Definition
9 //------------------------------------------------------------------------------
10
11 module.exports = {
12 meta: {
13 type: "suggestion",
14
15 docs: {
16 description: "require destructuring from arrays and/or objects",
17 category: "ECMAScript 6",
18 recommended: false,
19 url: "https://eslint.org/docs/rules/prefer-destructuring"
20 },
21
22 fixable: "code",
23
24 schema: [
25 {
26
27 /*
28 * old support {array: Boolean, object: Boolean}
29 * new support {VariableDeclarator: {}, AssignmentExpression: {}}
30 */
31 oneOf: [
32 {
33 type: "object",
34 properties: {
35 VariableDeclarator: {
36 type: "object",
37 properties: {
38 array: {
39 type: "boolean"
40 },
41 object: {
42 type: "boolean"
43 }
44 },
45 additionalProperties: false
46 },
47 AssignmentExpression: {
48 type: "object",
49 properties: {
50 array: {
51 type: "boolean"
52 },
53 object: {
54 type: "boolean"
55 }
56 },
57 additionalProperties: false
58 }
59 },
60 additionalProperties: false
61 },
62 {
63 type: "object",
64 properties: {
65 array: {
66 type: "boolean"
67 },
68 object: {
69 type: "boolean"
70 }
71 },
72 additionalProperties: false
73 }
74 ]
75 },
76 {
77 type: "object",
78 properties: {
79 enforceForRenamedProperties: {
80 type: "boolean"
81 }
82 },
83 additionalProperties: false
84 }
85 ],
86
87 messages: {
88 preferDestructuring: "Use {{type}} destructuring."
89 }
90 },
91 create(context) {
92
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 }
98 };
99
100 if (enabledTypes) {
101 normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined"
102 ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes }
103 : enabledTypes;
104 }
105
106 //--------------------------------------------------------------------------
107 // Helpers
108 //--------------------------------------------------------------------------
109
110 // eslint-disable-next-line jsdoc/require-description
111 /**
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
115 */
116 function shouldCheck(nodeType, destructuringType) {
117 return normalizedOptions &&
118 normalizedOptions[nodeType] &&
119 normalizedOptions[nodeType][destructuringType];
120 }
121
122 /**
123 * Determines if the given node is accessing an array index
124 *
125 * This is used to differentiate array index access from object property
126 * access.
127 * @param {ASTNode} node the node to evaluate
128 * @returns {boolean} whether or not the node is an integer
129 */
130 function isArrayIndexAccess(node) {
131 return Number.isInteger(node.property.value);
132 }
133
134 /**
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
139 * @returns {void}
140 */
141 function report(reportNode, type, fix) {
142 context.report({
143 node: reportNode,
144 messageId: "preferDestructuring",
145 data: { type },
146 fix
147 });
148 }
149
150 /**
151 * Determines if a node should be fixed into object destructuring
152 *
153 * The fixer only fixes the simplest case of object destructuring,
154 * like: `let x = a.x`;
155 *
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
161 */
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;
167 }
168
169 /**
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
176 */
177 function fixIntoObjectDestructuring(fixer, node) {
178 const rightNode = node.init;
179 const sourceCode = context.getSourceCode();
180
181 return fixer.replaceText(
182 node,
183 `{${rightNode.property.name}} = ${sourceCode.getText(rightNode.object)}`
184 );
185 }
186
187 /**
188 * Check that the `prefer-destructuring` rules are followed based on the
189 * given left- and right-hand side of the assignment.
190 *
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
196 * @returns {void}
197 */
198 function performCheck(leftNode, rightNode, reportNode) {
199 if (rightNode.type !== "MemberExpression" || rightNode.object.type === "Super") {
200 return;
201 }
202
203 if (isArrayIndexAccess(rightNode)) {
204 if (shouldCheck(reportNode.type, "array")) {
205 report(reportNode, "array", null);
206 }
207 return;
208 }
209
210 const fix = shouldFix(reportNode)
211 ? fixer => fixIntoObjectDestructuring(fixer, reportNode)
212 : null;
213
214 if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) {
215 report(reportNode, "object", fix);
216 return;
217 }
218
219 if (shouldCheck(reportNode.type, "object")) {
220 const property = rightNode.property;
221
222 if (
223 (property.type === "Literal" && leftNode.name === property.value) ||
224 (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed)
225 ) {
226 report(reportNode, "object", fix);
227 }
228 }
229 }
230
231 /**
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
235 * @returns {void}
236 */
237 function checkVariableDeclarator(node) {
238
239 // Skip if variable is declared without assignment
240 if (!node.init) {
241 return;
242 }
243
244 // We only care about member expressions past this point
245 if (node.init.type !== "MemberExpression") {
246 return;
247 }
248
249 performCheck(node.id, node.init, node);
250 }
251
252 /**
253 * Run the `prefer-destructuring` check on an AssignmentExpression
254 * @param {ASTNode} node the AssignmentExpression node
255 * @returns {void}
256 */
257 function checkAssigmentExpression(node) {
258 if (node.operator === "=") {
259 performCheck(node.left, node.right, node);
260 }
261 }
262
263 //--------------------------------------------------------------------------
264 // Public
265 //--------------------------------------------------------------------------
266
267 return {
268 VariableDeclarator: checkVariableDeclarator,
269 AssignmentExpression: checkAssigmentExpression
270 };
271 }
272 };