massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / comma-dangle.js
1 /**
2  * @fileoverview Rule to forbid or enforce dangling commas.
3  * @author Ian Christian Myers
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Requirements
10 //------------------------------------------------------------------------------
11
12 const astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 const DEFAULT_OPTIONS = Object.freeze({
19     arrays: "never",
20     objects: "never",
21     imports: "never",
22     exports: "never",
23     functions: "never"
24 });
25
26 /**
27  * Checks whether or not a trailing comma is allowed in a given node.
28  * If the `lastItem` is `RestElement` or `RestProperty`, it disallows trailing commas.
29  * @param {ASTNode} lastItem The node of the last element in the given node.
30  * @returns {boolean} `true` if a trailing comma is allowed.
31  */
32 function isTrailingCommaAllowed(lastItem) {
33     return !(
34         lastItem.type === "RestElement" ||
35         lastItem.type === "RestProperty" ||
36         lastItem.type === "ExperimentalRestProperty"
37     );
38 }
39
40 /**
41  * Normalize option value.
42  * @param {string|Object|undefined} optionValue The 1st option value to normalize.
43  * @param {number} ecmaVersion The normalized ECMAScript version.
44  * @returns {Object} The normalized option value.
45  */
46 function normalizeOptions(optionValue, ecmaVersion) {
47     if (typeof optionValue === "string") {
48         return {
49             arrays: optionValue,
50             objects: optionValue,
51             imports: optionValue,
52             exports: optionValue,
53             functions: (!ecmaVersion || ecmaVersion < 8) ? "ignore" : optionValue
54         };
55     }
56     if (typeof optionValue === "object" && optionValue !== null) {
57         return {
58             arrays: optionValue.arrays || DEFAULT_OPTIONS.arrays,
59             objects: optionValue.objects || DEFAULT_OPTIONS.objects,
60             imports: optionValue.imports || DEFAULT_OPTIONS.imports,
61             exports: optionValue.exports || DEFAULT_OPTIONS.exports,
62             functions: optionValue.functions || DEFAULT_OPTIONS.functions
63         };
64     }
65
66     return DEFAULT_OPTIONS;
67 }
68
69 //------------------------------------------------------------------------------
70 // Rule Definition
71 //------------------------------------------------------------------------------
72
73 module.exports = {
74     meta: {
75         type: "layout",
76
77         docs: {
78             description: "require or disallow trailing commas",
79             category: "Stylistic Issues",
80             recommended: false,
81             url: "https://eslint.org/docs/rules/comma-dangle"
82         },
83
84         fixable: "code",
85
86         schema: {
87             definitions: {
88                 value: {
89                     enum: [
90                         "always-multiline",
91                         "always",
92                         "never",
93                         "only-multiline"
94                     ]
95                 },
96                 valueWithIgnore: {
97                     enum: [
98                         "always-multiline",
99                         "always",
100                         "ignore",
101                         "never",
102                         "only-multiline"
103                     ]
104                 }
105             },
106             type: "array",
107             items: [
108                 {
109                     oneOf: [
110                         {
111                             $ref: "#/definitions/value"
112                         },
113                         {
114                             type: "object",
115                             properties: {
116                                 arrays: { $ref: "#/definitions/valueWithIgnore" },
117                                 objects: { $ref: "#/definitions/valueWithIgnore" },
118                                 imports: { $ref: "#/definitions/valueWithIgnore" },
119                                 exports: { $ref: "#/definitions/valueWithIgnore" },
120                                 functions: { $ref: "#/definitions/valueWithIgnore" }
121                             },
122                             additionalProperties: false
123                         }
124                     ]
125                 }
126             ]
127         },
128
129         messages: {
130             unexpected: "Unexpected trailing comma.",
131             missing: "Missing trailing comma."
132         }
133     },
134
135     create(context) {
136         const options = normalizeOptions(context.options[0], context.parserOptions.ecmaVersion);
137
138         const sourceCode = context.getSourceCode();
139
140         /**
141          * Gets the last item of the given node.
142          * @param {ASTNode} node The node to get.
143          * @returns {ASTNode|null} The last node or null.
144          */
145         function getLastItem(node) {
146
147             /**
148              * Returns the last element of an array
149              * @param {any[]} array The input array
150              * @returns {any} The last element
151              */
152             function last(array) {
153                 return array[array.length - 1];
154             }
155
156             switch (node.type) {
157                 case "ObjectExpression":
158                 case "ObjectPattern":
159                     return last(node.properties);
160                 case "ArrayExpression":
161                 case "ArrayPattern":
162                     return last(node.elements);
163                 case "ImportDeclaration":
164                 case "ExportNamedDeclaration":
165                     return last(node.specifiers);
166                 case "FunctionDeclaration":
167                 case "FunctionExpression":
168                 case "ArrowFunctionExpression":
169                     return last(node.params);
170                 case "CallExpression":
171                 case "NewExpression":
172                     return last(node.arguments);
173                 default:
174                     return null;
175             }
176         }
177
178         /**
179          * Gets the trailing comma token of the given node.
180          * If the trailing comma does not exist, this returns the token which is
181          * the insertion point of the trailing comma token.
182          * @param {ASTNode} node The node to get.
183          * @param {ASTNode} lastItem The last item of the node.
184          * @returns {Token} The trailing comma token or the insertion point.
185          */
186         function getTrailingToken(node, lastItem) {
187             switch (node.type) {
188                 case "ObjectExpression":
189                 case "ArrayExpression":
190                 case "CallExpression":
191                 case "NewExpression":
192                     return sourceCode.getLastToken(node, 1);
193                 default: {
194                     const nextToken = sourceCode.getTokenAfter(lastItem);
195
196                     if (astUtils.isCommaToken(nextToken)) {
197                         return nextToken;
198                     }
199                     return sourceCode.getLastToken(lastItem);
200                 }
201             }
202         }
203
204         /**
205          * Checks whether or not a given node is multiline.
206          * This rule handles a given node as multiline when the closing parenthesis
207          * and the last element are not on the same line.
208          * @param {ASTNode} node A node to check.
209          * @returns {boolean} `true` if the node is multiline.
210          */
211         function isMultiline(node) {
212             const lastItem = getLastItem(node);
213
214             if (!lastItem) {
215                 return false;
216             }
217
218             const penultimateToken = getTrailingToken(node, lastItem);
219             const lastToken = sourceCode.getTokenAfter(penultimateToken);
220
221             return lastToken.loc.end.line !== penultimateToken.loc.end.line;
222         }
223
224         /**
225          * Reports a trailing comma if it exists.
226          * @param {ASTNode} node A node to check. Its type is one of
227          *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
228          *   ImportDeclaration, and ExportNamedDeclaration.
229          * @returns {void}
230          */
231         function forbidTrailingComma(node) {
232             const lastItem = getLastItem(node);
233
234             if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
235                 return;
236             }
237
238             const trailingToken = getTrailingToken(node, lastItem);
239
240             if (astUtils.isCommaToken(trailingToken)) {
241                 context.report({
242                     node: lastItem,
243                     loc: trailingToken.loc,
244                     messageId: "unexpected",
245                     fix(fixer) {
246                         return fixer.remove(trailingToken);
247                     }
248                 });
249             }
250         }
251
252         /**
253          * Reports the last element of a given node if it does not have a trailing
254          * comma.
255          *
256          * If a given node is `ArrayPattern` which has `RestElement`, the trailing
257          * comma is disallowed, so report if it exists.
258          * @param {ASTNode} node A node to check. Its type is one of
259          *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
260          *   ImportDeclaration, and ExportNamedDeclaration.
261          * @returns {void}
262          */
263         function forceTrailingComma(node) {
264             const lastItem = getLastItem(node);
265
266             if (!lastItem || (node.type === "ImportDeclaration" && lastItem.type !== "ImportSpecifier")) {
267                 return;
268             }
269             if (!isTrailingCommaAllowed(lastItem)) {
270                 forbidTrailingComma(node);
271                 return;
272             }
273
274             const trailingToken = getTrailingToken(node, lastItem);
275
276             if (trailingToken.value !== ",") {
277                 context.report({
278                     node: lastItem,
279                     loc: {
280                         start: trailingToken.loc.end,
281                         end: astUtils.getNextLocation(sourceCode, trailingToken.loc.end)
282                     },
283                     messageId: "missing",
284                     fix(fixer) {
285                         return fixer.insertTextAfter(trailingToken, ",");
286                     }
287                 });
288             }
289         }
290
291         /**
292          * If a given node is multiline, reports the last element of a given node
293          * when it does not have a trailing comma.
294          * Otherwise, reports a trailing comma if it exists.
295          * @param {ASTNode} node A node to check. Its type is one of
296          *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
297          *   ImportDeclaration, and ExportNamedDeclaration.
298          * @returns {void}
299          */
300         function forceTrailingCommaIfMultiline(node) {
301             if (isMultiline(node)) {
302                 forceTrailingComma(node);
303             } else {
304                 forbidTrailingComma(node);
305             }
306         }
307
308         /**
309          * Only if a given node is not multiline, reports the last element of a given node
310          * when it does not have a trailing comma.
311          * Otherwise, reports a trailing comma if it exists.
312          * @param {ASTNode} node A node to check. Its type is one of
313          *   ObjectExpression, ObjectPattern, ArrayExpression, ArrayPattern,
314          *   ImportDeclaration, and ExportNamedDeclaration.
315          * @returns {void}
316          */
317         function allowTrailingCommaIfMultiline(node) {
318             if (!isMultiline(node)) {
319                 forbidTrailingComma(node);
320             }
321         }
322
323         const predicate = {
324             always: forceTrailingComma,
325             "always-multiline": forceTrailingCommaIfMultiline,
326             "only-multiline": allowTrailingCommaIfMultiline,
327             never: forbidTrailingComma,
328             ignore: () => {}
329         };
330
331         return {
332             ObjectExpression: predicate[options.objects],
333             ObjectPattern: predicate[options.objects],
334
335             ArrayExpression: predicate[options.arrays],
336             ArrayPattern: predicate[options.arrays],
337
338             ImportDeclaration: predicate[options.imports],
339
340             ExportNamedDeclaration: predicate[options.exports],
341
342             FunctionDeclaration: predicate[options.functions],
343             FunctionExpression: predicate[options.functions],
344             ArrowFunctionExpression: predicate[options.functions],
345             CallExpression: predicate[options.functions],
346             NewExpression: predicate[options.functions]
347         };
348     }
349 };