.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / 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 // Requirements
9 //------------------------------------------------------------------------------
10
11 const astUtils = require("./utils/ast-utils");
12
13 //------------------------------------------------------------------------------
14 // Helpers
15 //------------------------------------------------------------------------------
16
17 const PRECEDENCE_OF_ASSIGNMENT_EXPR = astUtils.getPrecedence({ type: "AssignmentExpression" });
18
19 //------------------------------------------------------------------------------
20 // Rule Definition
21 //------------------------------------------------------------------------------
22
23 module.exports = {
24     meta: {
25         type: "suggestion",
26
27         docs: {
28             description: "require destructuring from arrays and/or objects",
29             category: "ECMAScript 6",
30             recommended: false,
31             url: "https://eslint.org/docs/rules/prefer-destructuring"
32         },
33
34         fixable: "code",
35
36         schema: [
37             {
38
39                 /*
40                  * old support {array: Boolean, object: Boolean}
41                  * new support {VariableDeclarator: {}, AssignmentExpression: {}}
42                  */
43                 oneOf: [
44                     {
45                         type: "object",
46                         properties: {
47                             VariableDeclarator: {
48                                 type: "object",
49                                 properties: {
50                                     array: {
51                                         type: "boolean"
52                                     },
53                                     object: {
54                                         type: "boolean"
55                                     }
56                                 },
57                                 additionalProperties: false
58                             },
59                             AssignmentExpression: {
60                                 type: "object",
61                                 properties: {
62                                     array: {
63                                         type: "boolean"
64                                     },
65                                     object: {
66                                         type: "boolean"
67                                     }
68                                 },
69                                 additionalProperties: false
70                             }
71                         },
72                         additionalProperties: false
73                     },
74                     {
75                         type: "object",
76                         properties: {
77                             array: {
78                                 type: "boolean"
79                             },
80                             object: {
81                                 type: "boolean"
82                             }
83                         },
84                         additionalProperties: false
85                     }
86                 ]
87             },
88             {
89                 type: "object",
90                 properties: {
91                     enforceForRenamedProperties: {
92                         type: "boolean"
93                     }
94                 },
95                 additionalProperties: false
96             }
97         ],
98
99         messages: {
100             preferDestructuring: "Use {{type}} destructuring."
101         }
102     },
103     create(context) {
104
105         const enabledTypes = context.options[0];
106         const enforceForRenamedProperties = context.options[1] && context.options[1].enforceForRenamedProperties;
107         let normalizedOptions = {
108             VariableDeclarator: { array: true, object: true },
109             AssignmentExpression: { array: true, object: true }
110         };
111
112         if (enabledTypes) {
113             normalizedOptions = typeof enabledTypes.array !== "undefined" || typeof enabledTypes.object !== "undefined"
114                 ? { VariableDeclarator: enabledTypes, AssignmentExpression: enabledTypes }
115                 : enabledTypes;
116         }
117
118         //--------------------------------------------------------------------------
119         // Helpers
120         //--------------------------------------------------------------------------
121
122         // eslint-disable-next-line jsdoc/require-description
123         /**
124          * @param {string} nodeType "AssignmentExpression" or "VariableDeclarator"
125          * @param {string} destructuringType "array" or "object"
126          * @returns {boolean} `true` if the destructuring type should be checked for the given node
127          */
128         function shouldCheck(nodeType, destructuringType) {
129             return normalizedOptions &&
130                 normalizedOptions[nodeType] &&
131                 normalizedOptions[nodeType][destructuringType];
132         }
133
134         /**
135          * Determines if the given node is accessing an array index
136          *
137          * This is used to differentiate array index access from object property
138          * access.
139          * @param {ASTNode} node the node to evaluate
140          * @returns {boolean} whether or not the node is an integer
141          */
142         function isArrayIndexAccess(node) {
143             return Number.isInteger(node.property.value);
144         }
145
146         /**
147          * Report that the given node should use destructuring
148          * @param {ASTNode} reportNode the node to report
149          * @param {string} type the type of destructuring that should have been done
150          * @param {Function|null} fix the fix function or null to pass to context.report
151          * @returns {void}
152          */
153         function report(reportNode, type, fix) {
154             context.report({
155                 node: reportNode,
156                 messageId: "preferDestructuring",
157                 data: { type },
158                 fix
159             });
160         }
161
162         /**
163          * Determines if a node should be fixed into object destructuring
164          *
165          * The fixer only fixes the simplest case of object destructuring,
166          * like: `let x = a.x`;
167          *
168          * Assignment expression is not fixed.
169          * Array destructuring is not fixed.
170          * Renamed property is not fixed.
171          * @param {ASTNode} node the node to evaluate
172          * @returns {boolean} whether or not the node should be fixed
173          */
174         function shouldFix(node) {
175             return node.type === "VariableDeclarator" &&
176                 node.id.type === "Identifier" &&
177                 node.init.type === "MemberExpression" &&
178                 !node.init.computed &&
179                 node.init.property.type === "Identifier" &&
180                 node.id.name === node.init.property.name;
181         }
182
183         /**
184          * Fix a node into object destructuring.
185          * This function only handles the simplest case of object destructuring,
186          * see {@link shouldFix}.
187          * @param {SourceCodeFixer} fixer the fixer object
188          * @param {ASTNode} node the node to be fixed.
189          * @returns {Object} a fix for the node
190          */
191         function fixIntoObjectDestructuring(fixer, node) {
192             const rightNode = node.init;
193             const sourceCode = context.getSourceCode();
194
195             // Don't fix if that would remove any comments. Only comments inside `rightNode.object` can be preserved.
196             if (sourceCode.getCommentsInside(node).length > sourceCode.getCommentsInside(rightNode.object).length) {
197                 return null;
198             }
199
200             let objectText = sourceCode.getText(rightNode.object);
201
202             if (astUtils.getPrecedence(rightNode.object) < PRECEDENCE_OF_ASSIGNMENT_EXPR) {
203                 objectText = `(${objectText})`;
204             }
205
206             return fixer.replaceText(
207                 node,
208                 `{${rightNode.property.name}} = ${objectText}`
209             );
210         }
211
212         /**
213          * Check that the `prefer-destructuring` rules are followed based on the
214          * given left- and right-hand side of the assignment.
215          *
216          * Pulled out into a separate method so that VariableDeclarators and
217          * AssignmentExpressions can share the same verification logic.
218          * @param {ASTNode} leftNode the left-hand side of the assignment
219          * @param {ASTNode} rightNode the right-hand side of the assignment
220          * @param {ASTNode} reportNode the node to report the error on
221          * @returns {void}
222          */
223         function performCheck(leftNode, rightNode, reportNode) {
224             if (rightNode.type !== "MemberExpression" || rightNode.object.type === "Super") {
225                 return;
226             }
227
228             if (isArrayIndexAccess(rightNode)) {
229                 if (shouldCheck(reportNode.type, "array")) {
230                     report(reportNode, "array", null);
231                 }
232                 return;
233             }
234
235             const fix = shouldFix(reportNode)
236                 ? fixer => fixIntoObjectDestructuring(fixer, reportNode)
237                 : null;
238
239             if (shouldCheck(reportNode.type, "object") && enforceForRenamedProperties) {
240                 report(reportNode, "object", fix);
241                 return;
242             }
243
244             if (shouldCheck(reportNode.type, "object")) {
245                 const property = rightNode.property;
246
247                 if (
248                     (property.type === "Literal" && leftNode.name === property.value) ||
249                     (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed)
250                 ) {
251                     report(reportNode, "object", fix);
252                 }
253             }
254         }
255
256         /**
257          * Check if a given variable declarator is coming from an property access
258          * that should be using destructuring instead
259          * @param {ASTNode} node the variable declarator to check
260          * @returns {void}
261          */
262         function checkVariableDeclarator(node) {
263
264             // Skip if variable is declared without assignment
265             if (!node.init) {
266                 return;
267             }
268
269             // We only care about member expressions past this point
270             if (node.init.type !== "MemberExpression") {
271                 return;
272             }
273
274             performCheck(node.id, node.init, node);
275         }
276
277         /**
278          * Run the `prefer-destructuring` check on an AssignmentExpression
279          * @param {ASTNode} node the AssignmentExpression node
280          * @returns {void}
281          */
282         function checkAssignmentExpression(node) {
283             if (node.operator === "=") {
284                 performCheck(node.left, node.right, node);
285             }
286         }
287
288         //--------------------------------------------------------------------------
289         // Public
290         //--------------------------------------------------------------------------
291
292         return {
293             VariableDeclarator: checkVariableDeclarator,
294             AssignmentExpression: checkAssignmentExpression
295         };
296     }
297 };