minor adjustment to readme
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / accessor-pairs.js
1 /**
2  * @fileoverview Rule to flag wrapping non-iife in parens
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 `Identifier` node which was named a given name.
91  * @param {ASTNode} node A node to check.
92  * @param {string} name An expected name of the node.
93  * @returns {boolean} `true` if the node is an `Identifier` node which was named as expected.
94  */
95 function isIdentifier(node, name) {
96     return node.type === "Identifier" && node.name === name;
97 }
98
99 /**
100  * Checks whether or not a given node is an argument of a specified method call.
101  * @param {ASTNode} node A node to check.
102  * @param {number} index An expected index of the node in arguments.
103  * @param {string} object An expected name of the object of the method.
104  * @param {string} property An expected name of the method.
105  * @returns {boolean} `true` if the node is an argument of the specified method call.
106  */
107 function isArgumentOfMethodCall(node, index, object, property) {
108     const parent = node.parent;
109
110     return (
111         parent.type === "CallExpression" &&
112         parent.callee.type === "MemberExpression" &&
113         parent.callee.computed === false &&
114         isIdentifier(parent.callee.object, object) &&
115         isIdentifier(parent.callee.property, property) &&
116         parent.arguments[index] === node
117     );
118 }
119
120 /**
121  * Checks whether or not a given node is a property descriptor.
122  * @param {ASTNode} node A node to check.
123  * @returns {boolean} `true` if the node is a property descriptor.
124  */
125 function isPropertyDescriptor(node) {
126
127     // Object.defineProperty(obj, "foo", {set: ...})
128     if (isArgumentOfMethodCall(node, 2, "Object", "defineProperty") ||
129         isArgumentOfMethodCall(node, 2, "Reflect", "defineProperty")
130     ) {
131         return true;
132     }
133
134     /*
135      * Object.defineProperties(obj, {foo: {set: ...}})
136      * Object.create(proto, {foo: {set: ...}})
137      */
138     const grandparent = node.parent.parent;
139
140     return grandparent.type === "ObjectExpression" && (
141         isArgumentOfMethodCall(grandparent, 1, "Object", "create") ||
142         isArgumentOfMethodCall(grandparent, 1, "Object", "defineProperties")
143     );
144 }
145
146 //------------------------------------------------------------------------------
147 // Rule Definition
148 //------------------------------------------------------------------------------
149
150 module.exports = {
151     meta: {
152         type: "suggestion",
153
154         docs: {
155             description: "enforce getter and setter pairs in objects and classes",
156             category: "Best Practices",
157             recommended: false,
158             url: "https://eslint.org/docs/rules/accessor-pairs"
159         },
160
161         schema: [{
162             type: "object",
163             properties: {
164                 getWithoutSet: {
165                     type: "boolean",
166                     default: false
167                 },
168                 setWithoutGet: {
169                     type: "boolean",
170                     default: true
171                 },
172                 enforceForClassMembers: {
173                     type: "boolean",
174                     default: false
175                 }
176             },
177             additionalProperties: false
178         }],
179
180         messages: {
181             missingGetterInPropertyDescriptor: "Getter is not present in property descriptor.",
182             missingSetterInPropertyDescriptor: "Setter is not present in property descriptor.",
183             missingGetterInObjectLiteral: "Getter is not present for {{ name }}.",
184             missingSetterInObjectLiteral: "Setter is not present for {{ name }}.",
185             missingGetterInClass: "Getter is not present for class {{ name }}.",
186             missingSetterInClass: "Setter is not present for class {{ name }}."
187         }
188     },
189     create(context) {
190         const config = context.options[0] || {};
191         const checkGetWithoutSet = config.getWithoutSet === true;
192         const checkSetWithoutGet = config.setWithoutGet !== false;
193         const enforceForClassMembers = config.enforceForClassMembers === true;
194         const sourceCode = context.getSourceCode();
195
196         /**
197          * Reports the given node.
198          * @param {ASTNode} node The node to report.
199          * @param {string} messageKind "missingGetter" or "missingSetter".
200          * @returns {void}
201          * @private
202          */
203         function report(node, messageKind) {
204             if (node.type === "Property") {
205                 context.report({
206                     node,
207                     messageId: `${messageKind}InObjectLiteral`,
208                     loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
209                     data: { name: astUtils.getFunctionNameWithKind(node.value) }
210                 });
211             } else if (node.type === "MethodDefinition") {
212                 context.report({
213                     node,
214                     messageId: `${messageKind}InClass`,
215                     loc: astUtils.getFunctionHeadLoc(node.value, sourceCode),
216                     data: { name: astUtils.getFunctionNameWithKind(node.value) }
217                 });
218             } else {
219                 context.report({
220                     node,
221                     messageId: `${messageKind}InPropertyDescriptor`
222                 });
223             }
224         }
225
226         /**
227          * Reports each of the nodes in the given list using the same messageId.
228          * @param {ASTNode[]} nodes Nodes to report.
229          * @param {string} messageKind "missingGetter" or "missingSetter".
230          * @returns {void}
231          * @private
232          */
233         function reportList(nodes, messageKind) {
234             for (const node of nodes) {
235                 report(node, messageKind);
236             }
237         }
238
239         /**
240          * Creates a new `AccessorData` object for the given getter or setter node.
241          * @param {ASTNode} node A getter or setter node.
242          * @returns {AccessorData} New `AccessorData` object that contains the given node.
243          * @private
244          */
245         function createAccessorData(node) {
246             const name = astUtils.getStaticPropertyName(node);
247             const key = (name !== null) ? name : sourceCode.getTokens(node.key);
248
249             return {
250                 key,
251                 getters: node.kind === "get" ? [node] : [],
252                 setters: node.kind === "set" ? [node] : []
253             };
254         }
255
256         /**
257          * Merges the given `AccessorData` object into the given accessors list.
258          * @param {AccessorData[]} accessors The list to merge into.
259          * @param {AccessorData} accessorData The object to merge.
260          * @returns {AccessorData[]} The same instance with the merged object.
261          * @private
262          */
263         function mergeAccessorData(accessors, accessorData) {
264             const equalKeyElement = accessors.find(a => areEqualKeys(a.key, accessorData.key));
265
266             if (equalKeyElement) {
267                 equalKeyElement.getters.push(...accessorData.getters);
268                 equalKeyElement.setters.push(...accessorData.setters);
269             } else {
270                 accessors.push(accessorData);
271             }
272
273             return accessors;
274         }
275
276         /**
277          * Checks accessor pairs in the given list of nodes.
278          * @param {ASTNode[]} nodes The list to check.
279          * @returns {void}
280          * @private
281          */
282         function checkList(nodes) {
283             const accessors = nodes
284                 .filter(isAccessorKind)
285                 .map(createAccessorData)
286                 .reduce(mergeAccessorData, []);
287
288             for (const { getters, setters } of accessors) {
289                 if (checkSetWithoutGet && setters.length && !getters.length) {
290                     reportList(setters, "missingGetter");
291                 }
292                 if (checkGetWithoutSet && getters.length && !setters.length) {
293                     reportList(getters, "missingSetter");
294                 }
295             }
296         }
297
298         /**
299          * Checks accessor pairs in an object literal.
300          * @param {ASTNode} node `ObjectExpression` node to check.
301          * @returns {void}
302          * @private
303          */
304         function checkObjectLiteral(node) {
305             checkList(node.properties.filter(p => p.type === "Property"));
306         }
307
308         /**
309          * Checks accessor pairs in a property descriptor.
310          * @param {ASTNode} node Property descriptor `ObjectExpression` node to check.
311          * @returns {void}
312          * @private
313          */
314         function checkPropertyDescriptor(node) {
315             const namesToCheck = node.properties
316                 .filter(p => p.type === "Property" && p.kind === "init" && !p.computed)
317                 .map(({ key }) => key.name);
318
319             const hasGetter = namesToCheck.includes("get");
320             const hasSetter = namesToCheck.includes("set");
321
322             if (checkSetWithoutGet && hasSetter && !hasGetter) {
323                 report(node, "missingGetter");
324             }
325             if (checkGetWithoutSet && hasGetter && !hasSetter) {
326                 report(node, "missingSetter");
327             }
328         }
329
330         /**
331          * Checks the given object expression as an object literal and as a possible property descriptor.
332          * @param {ASTNode} node `ObjectExpression` node to check.
333          * @returns {void}
334          * @private
335          */
336         function checkObjectExpression(node) {
337             checkObjectLiteral(node);
338             if (isPropertyDescriptor(node)) {
339                 checkPropertyDescriptor(node);
340             }
341         }
342
343         /**
344          * Checks the given class body.
345          * @param {ASTNode} node `ClassBody` node to check.
346          * @returns {void}
347          * @private
348          */
349         function checkClassBody(node) {
350             const methodDefinitions = node.body.filter(m => m.type === "MethodDefinition");
351
352             checkList(methodDefinitions.filter(m => m.static));
353             checkList(methodDefinitions.filter(m => !m.static));
354         }
355
356         const listeners = {};
357
358         if (checkSetWithoutGet || checkGetWithoutSet) {
359             listeners.ObjectExpression = checkObjectExpression;
360             if (enforceForClassMembers) {
361                 listeners.ClassBody = checkClassBody;
362             }
363         }
364
365         return listeners;
366     }
367 };