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