.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / accessor-pairs.js
1 /**
2  * @fileoverview Rule to enforce getter and setter pairs in objects and classes.
3  * @author Gyandeep Singh
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Typedefs
16 //------------------------------------------------------------------------------
17
18 /**
19  * Property name if it can be computed statically, otherwise the list of the tokens of the key node.
20  * @typedef {string|Token[]} Key
21  */
22
23 /**
24  * Accessor nodes with the same key.
25  * @typedef {Object} AccessorData
26  * @property {Key} key Accessor's key
27  * @property {ASTNode[]} getters List of getter nodes.
28  * @property {ASTNode[]} setters List of setter nodes.
29  */
30
31 //------------------------------------------------------------------------------
32 // Helpers
33 //------------------------------------------------------------------------------
34
35 /**
36  * Checks whether or not the given lists represent the equal tokens in the same order.
37  * Tokens are compared by their properties, not by instance.
38  * @param {Token[]} left First list of tokens.
39  * @param {Token[]} right Second list of tokens.
40  * @returns {boolean} `true` if the lists have same tokens.
41  */
42 function areEqualTokenLists(left, right) {
43     if (left.length !== right.length) {
44         return false;
45     }
46
47     for (let i = 0; i < left.length; i++) {
48         const leftToken = left[i],
49             rightToken = right[i];
50
51         if (leftToken.type !== rightToken.type || leftToken.value !== rightToken.value) {
52             return false;
53         }
54     }
55
56     return true;
57 }
58
59 /**
60  * Checks whether or not the given keys are equal.
61  * @param {Key} left First key.
62  * @param {Key} right Second key.
63  * @returns {boolean} `true` if the keys are equal.
64  */
65 function areEqualKeys(left, right) {
66     if (typeof left === "string" && typeof right === "string") {
67
68         // Statically computed names.
69         return left === right;
70     }
71     if (Array.isArray(left) && Array.isArray(right)) {
72
73         // Token lists.
74         return areEqualTokenLists(left, right);
75     }
76
77     return false;
78 }
79
80 /**
81  * Checks whether or not a given node is of an accessor kind ('get' or 'set').
82  * @param {ASTNode} node A node to check.
83  * @returns {boolean} `true` if the node is of an accessor kind.
84  */
85 function isAccessorKind(node) {
86     return node.kind === "get" || node.kind === "set";
87 }
88
89 /**
90  * Checks whether or not a given node is an argument of a specified method call.
91  * @param {ASTNode} node A node to check.
92  * @param {number} index An expected index of the node in arguments.
93  * @param {string} object An expected name of the object of the method.
94  * @param {string} property An expected name of the method.
95  * @returns {boolean} `true` if the node is an argument of the specified method call.
96  */
97 function isArgumentOfMethodCall(node, index, object, property) {
98     const parent = node.parent;
99
100     return (
101         parent.type === "CallExpression" &&
102         astUtils.isSpecificMemberAccess(parent.callee, object, property) &&
103         parent.arguments[index] === node
104     );
105 }
106
107 /**
108  * Checks whether or not a given node is a property descriptor.
109  * @param {ASTNode} node A node to check.
110  * @returns {boolean} `true` if the node is a property descriptor.
111  */
112 function isPropertyDescriptor(node) {
113
114     // Object.defineProperty(obj, "foo", {set: ...})
115     if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") ||
116         isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty")
117     ) {
118         return true;
119     }
120
121     /*
122      * Object.defineProperties(obj, {foo: {set: ...}})
123      * Object.create(proto, {foo: {set: ...}})
124      */
125     const grandparent = node.parent.parent;
126
127     return grandparent.type === "ObjectExpression" && (
128         isArgumentOfMethodCall(grandparent, 1, "Object", "create") ||
129         isArgumentOfMethodCall(grandparent, 1, "Object", "defineProperties")
130     );
131 }
132
133 //------------------------------------------------------------------------------
134 // Rule Definition
135 //------------------------------------------------------------------------------
136
137 module.exports = {
138     meta: {
139         type: "suggestion",
140
141         docs: {
142             description: "enforce getter and setter pairs in objects and classes",
143             category: "Best Practices",
144             recommended: false,
145             url: "https://eslint.org/docs/rules/accessor-pairs"
146         },
147
148         schema: [{
149             type: "object",
150             properties: {
151                 getWithoutSet: {
152                     type: "boolean",
153                     default: false
154                 },
155                 setWithoutGet: {
156                     type: "boolean",
157                     default: true
158                 },
159                 enforceForClassMembers: {
160                     type: "boolean",
161                     default: true
162                 }
163             },
164             additionalProperties: false
165         }],
166
167         messages: {
168             missingGetterInPropertyDescriptor: "Getter is not present in property descriptor.",
169             missingSetterInPropertyDescriptor: "Setter is not present in property descriptor.",
170             missingGetterInObjectLiteral: "Getter is not present for {{ name }}.",
171             missingSetterInObjectLiteral: "Setter is not present for {{ name }}.",
172             missingGetterInClass: "Getter is not present for class {{ name }}.",
173             missingSetterInClass: "Setter is not present for class {{ name }}."
174         }
175     },
176     create(context) {
177         const config = context.options[0] || {};
178         const checkGetWithoutSet = config.getWithoutSet === true;
179         const checkSetWithoutGet = config.setWithoutGet !== false;
180         const enforceForClassMembers = config.enforceForClassMembers !== false;
181         const sourceCode = context.getSourceCode();
182
183         /**
184          * Reports the given node.
185          * @param {ASTNode} node The node to report.
186          * @param {string} messageKind "missingGetter" or "missingSetter".
187          * @returns {void}
188          * @private
189          */
190         function report(node, messageKind) {
191             if (node.type === "Property") {
192                 context.report({
193                     node,
194                     messageId: `${messageKind}InObjectLiteral`,
195                     loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
196                     data: { name: astUtils.getFunctionNameWithKind(node.value) }
197                 });
198             } else if (node.type === "MethodDefinition") {
199                 context.report({
200                     node,
201                     messageId: `${messageKind}InClass`,
202                     loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
203                     data: { name: astUtils.getFunctionNameWithKind(node.value) }
204                 });
205             } else {
206                 context.report({
207                     node,
208                     messageId: `${messageKind}InPropertyDescriptor`
209                 });
210             }
211         }
212
213         /**
214          * Reports each of the nodes in the given list using the same messageId.
215          * @param {ASTNode[]} nodes Nodes to report.
216          * @param {string} messageKind "missingGetter" or "missingSetter".
217          * @returns {void}
218          * @private
219          */
220         function reportList(nodes, messageKind) {
221             for (const node of nodes) {
222                 report(node, messageKind);
223             }
224         }
225
226         /**
227          * Creates a new `AccessorData` object for the given getter or setter node.
228          * @param {ASTNode} node A getter or setter node.
229          * @returns {AccessorData} New `AccessorData` object that contains the given node.
230          * @private
231          */
232         function createAccessorData(node) {
233             const name = astUtils.getStaticPropertyName(node);
234             const key = (name !== null) ? name : sourceCode.getTokens(node.key);
235
236             return {
237                 key,
238                 getters: node.kind === "get" ? [node] : [],
239                 setters: node.kind === "set" ? [node] : []
240             };
241         }
242
243         /**
244          * Merges the given `AccessorData` object into the given accessors list.
245          * @param {AccessorData[]} accessors The list to merge into.
246          * @param {AccessorData} accessorData The object to merge.
247          * @returns {AccessorData[]} The same instance with the merged object.
248          * @private
249          */
250         function mergeAccessorData(accessors, accessorData) {
251             const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key));
252
253             if (equalKeyElement) {
254                 equalKeyElement.getters.push(...accessorData.getters);
255                 equalKeyElement.setters.push(...accessorData.setters);
256             } else {
257                 accessors.push(accessorData);
258             }
259
260             return accessors;
261         }
262
263         /**
264          * Checks accessor pairs in the given list of nodes.
265          * @param {ASTNode[]} nodes The list to check.
266          * @returns {void}
267          * @private
268          */
269         function checkList(nodes) {
270             const accessors = nodes
271                 .filter(isAccessorKind)
272                 .map(createAccessorData)
273                 .reduce(mergeAccessorData, []);
274
275             for (const { getters, setters } of accessors) {
276                 if (checkSetWithoutGet && setters.length && !getters.length) {
277                     reportList(setters, "missingGetter");
278                 }
279                 if (checkGetWithoutSet && getters.length && !setters.length) {
280                     reportList(getters, "missingSetter");
281                 }
282             }
283         }
284
285         /**
286          * Checks accessor pairs in an object literal.
287          * @param {ASTNode} node `ObjectExpression` node to check.
288          * @returns {void}
289          * @private
290          */
291         function checkObjectLiteral(node) {
292             checkList(node.properties.filter(p => p.type === "Property"));
293         }
294
295         /**
296          * Checks accessor pairs in a property descriptor.
297          * @param {ASTNode} node Property descriptor `ObjectExpression` node to check.
298          * @returns {void}
299          * @private
300          */
301         function checkPropertyDescriptor(node) {
302             const namesToCheck = node.properties
303                 .filter(p => p.type === "Property" && p.kind === "init" && !p.computed)
304                 .map(({ key }) => key.name);
305
306             const hasGetter = namesToCheck.includes("get");
307             const hasSetter = namesToCheck.includes("set");
308
309             if (checkSetWithoutGet && hasSetter && !hasGetter) {
310                 report(node, "missingGetter");
311             }
312             if (checkGetWithoutSet && hasGetter && !hasSetter) {
313                 report(node, "missingSetter");
314             }
315         }
316
317         /**
318          * Checks the given object expression as an object literal and as a possible property descriptor.
319          * @param {ASTNode} node `ObjectExpression` node to check.
320          * @returns {void}
321          * @private
322          */
323         function checkObjectExpression(node) {
324             checkObjectLiteral(node);
325             if (isPropertyDescriptor(node)) {
326                 checkPropertyDescriptor(node);
327             }
328         }
329
330         /**
331          * Checks the given class body.
332          * @param {ASTNode} node `ClassBody` node to check.
333          * @returns {void}
334          * @private
335          */
336         function checkClassBody(node) {
337             const methodDefinitions = node.body.filter(m => m.type === "MethodDefinition");
338
339             checkList(methodDefinitions.filter(m => m.static));
340             checkList(methodDefinitions.filter(m => !m.static));
341         }
342
343         const listeners = {};
344
345         if (checkSetWithoutGet || checkGetWithoutSet) {
346             listeners.ObjectExpression = checkObjectExpression;
347             if (enforceForClassMembers) {
348                 listeners.ClassBody = checkClassBody;
349             }
350         }
351
352         return listeners;
353     }
354 };