massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / comma-style.js
1 /**
2  * @fileoverview Comma style - enforces comma styles of two types: last and first
3  * @author Vignesh Anand aka vegetableman
4  */
5
6 "use strict";
7
8 const astUtils = require("./utils/ast-utils");
9
10 //------------------------------------------------------------------------------
11 // Rule Definition
12 //------------------------------------------------------------------------------
13
14 module.exports = {
15     meta: {
16         type: "layout",
17
18         docs: {
19             description: "enforce consistent comma style",
20             category: "Stylistic Issues",
21             recommended: false,
22             url: "https://eslint.org/docs/rules/comma-style"
23         },
24
25         fixable: "code",
26
27         schema: [
28             {
29                 enum: ["first", "last"]
30             },
31             {
32                 type: "object",
33                 properties: {
34                     exceptions: {
35                         type: "object",
36                         additionalProperties: {
37                             type: "boolean"
38                         }
39                     }
40                 },
41                 additionalProperties: false
42             }
43         ],
44
45         messages: {
46             unexpectedLineBeforeAndAfterComma: "Bad line breaking before and after ','.",
47             expectedCommaFirst: "',' should be placed first.",
48             expectedCommaLast: "',' should be placed last."
49         }
50     },
51
52     create(context) {
53         const style = context.options[0] || "last",
54             sourceCode = context.getSourceCode();
55         const exceptions = {
56             ArrayPattern: true,
57             ArrowFunctionExpression: true,
58             CallExpression: true,
59             FunctionDeclaration: true,
60             FunctionExpression: true,
61             ImportDeclaration: true,
62             ObjectPattern: true,
63             NewExpression: true
64         };
65
66         if (context.options.length === 2 && Object.prototype.hasOwnProperty.call(context.options[1], "exceptions")) {
67             const keys = Object.keys(context.options[1].exceptions);
68
69             for (let i = 0; i < keys.length; i++) {
70                 exceptions[keys[i]] = context.options[1].exceptions[keys[i]];
71             }
72         }
73
74         //--------------------------------------------------------------------------
75         // Helpers
76         //--------------------------------------------------------------------------
77
78         /**
79          * Modified text based on the style
80          * @param {string} styleType Style type
81          * @param {string} text Source code text
82          * @returns {string} modified text
83          * @private
84          */
85         function getReplacedText(styleType, text) {
86             switch (styleType) {
87                 case "between":
88                     return `,${text.replace(astUtils.LINEBREAK_MATCHER, "")}`;
89
90                 case "first":
91                     return `${text},`;
92
93                 case "last":
94                     return `,${text}`;
95
96                 default:
97                     return "";
98             }
99         }
100
101         /**
102          * Determines the fixer function for a given style.
103          * @param {string} styleType comma style
104          * @param {ASTNode} previousItemToken The token to check.
105          * @param {ASTNode} commaToken The token to check.
106          * @param {ASTNode} currentItemToken The token to check.
107          * @returns {Function} Fixer function
108          * @private
109          */
110         function getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken) {
111             const text =
112                 sourceCode.text.slice(previousItemToken.range[1], commaToken.range[0]) +
113                 sourceCode.text.slice(commaToken.range[1], currentItemToken.range[0]);
114             const range = [previousItemToken.range[1], currentItemToken.range[0]];
115
116             return function(fixer) {
117                 return fixer.replaceTextRange(range, getReplacedText(styleType, text));
118             };
119         }
120
121         /**
122          * Validates the spacing around single items in lists.
123          * @param {Token} previousItemToken The last token from the previous item.
124          * @param {Token} commaToken The token representing the comma.
125          * @param {Token} currentItemToken The first token of the current item.
126          * @param {Token} reportItem The item to use when reporting an error.
127          * @returns {void}
128          * @private
129          */
130         function validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem) {
131
132             // if single line
133             if (astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
134                     astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
135
136                 // do nothing.
137
138             } else if (!astUtils.isTokenOnSameLine(commaToken, currentItemToken) &&
139                     !astUtils.isTokenOnSameLine(previousItemToken, commaToken)) {
140
141                 const comment = sourceCode.getCommentsAfter(commaToken)[0];
142                 const styleType = comment && comment.type === "Block" && astUtils.isTokenOnSameLine(commaToken, comment)
143                     ? style
144                     : "between";
145
146                 // lone comma
147                 context.report({
148                     node: reportItem,
149                     loc: commaToken.loc,
150                     messageId: "unexpectedLineBeforeAndAfterComma",
151                     fix: getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken)
152                 });
153
154             } else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
155
156                 context.report({
157                     node: reportItem,
158                     loc: commaToken.loc,
159                     messageId: "expectedCommaFirst",
160                     fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
161                 });
162
163             } else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
164
165                 context.report({
166                     node: reportItem,
167                     loc: commaToken.loc,
168                     messageId: "expectedCommaLast",
169                     fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
170                 });
171             }
172         }
173
174         /**
175          * Checks the comma placement with regards to a declaration/property/element
176          * @param {ASTNode} node The binary expression node to check
177          * @param {string} property The property of the node containing child nodes.
178          * @private
179          * @returns {void}
180          */
181         function validateComma(node, property) {
182             const items = node[property],
183                 arrayLiteral = (node.type === "ArrayExpression" || node.type === "ArrayPattern");
184
185             if (items.length > 1 || arrayLiteral) {
186
187                 // seed as opening [
188                 let previousItemToken = sourceCode.getFirstToken(node);
189
190                 items.forEach(item => {
191                     const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken,
192                         currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken),
193                         reportItem = item || currentItemToken;
194
195                     /*
196                      * This works by comparing three token locations:
197                      * - previousItemToken is the last token of the previous item
198                      * - commaToken is the location of the comma before the current item
199                      * - currentItemToken is the first token of the current item
200                      *
201                      * These values get switched around if item is undefined.
202                      * previousItemToken will refer to the last token not belonging
203                      * to the current item, which could be a comma or an opening
204                      * square bracket. currentItemToken could be a comma.
205                      *
206                      * All comparisons are done based on these tokens directly, so
207                      * they are always valid regardless of an undefined item.
208                      */
209                     if (astUtils.isCommaToken(commaToken)) {
210                         validateCommaItemSpacing(previousItemToken, commaToken, currentItemToken, reportItem);
211                     }
212
213                     if (item) {
214                         const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken);
215
216                         previousItemToken = tokenAfterItem
217                             ? sourceCode.getTokenBefore(tokenAfterItem)
218                             : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
219                     } else {
220                         previousItemToken = currentItemToken;
221                     }
222                 });
223
224                 /*
225                  * Special case for array literals that have empty last items, such
226                  * as [ 1, 2, ]. These arrays only have two items show up in the
227                  * AST, so we need to look at the token to verify that there's no
228                  * dangling comma.
229                  */
230                 if (arrayLiteral) {
231
232                     const lastToken = sourceCode.getLastToken(node),
233                         nextToLastToken = sourceCode.getTokenBefore(lastToken);
234
235                     if (astUtils.isCommaToken(nextToLastToken)) {
236                         validateCommaItemSpacing(
237                             sourceCode.getTokenBefore(nextToLastToken),
238                             nextToLastToken,
239                             lastToken,
240                             lastToken
241                         );
242                     }
243                 }
244             }
245         }
246
247         //--------------------------------------------------------------------------
248         // Public
249         //--------------------------------------------------------------------------
250
251         const nodes = {};
252
253         if (!exceptions.VariableDeclaration) {
254             nodes.VariableDeclaration = function(node) {
255                 validateComma(node, "declarations");
256             };
257         }
258         if (!exceptions.ObjectExpression) {
259             nodes.ObjectExpression = function(node) {
260                 validateComma(node, "properties");
261             };
262         }
263         if (!exceptions.ObjectPattern) {
264             nodes.ObjectPattern = function(node) {
265                 validateComma(node, "properties");
266             };
267         }
268         if (!exceptions.ArrayExpression) {
269             nodes.ArrayExpression = function(node) {
270                 validateComma(node, "elements");
271             };
272         }
273         if (!exceptions.ArrayPattern) {
274             nodes.ArrayPattern = function(node) {
275                 validateComma(node, "elements");
276             };
277         }
278         if (!exceptions.FunctionDeclaration) {
279             nodes.FunctionDeclaration = function(node) {
280                 validateComma(node, "params");
281             };
282         }
283         if (!exceptions.FunctionExpression) {
284             nodes.FunctionExpression = function(node) {
285                 validateComma(node, "params");
286             };
287         }
288         if (!exceptions.ArrowFunctionExpression) {
289             nodes.ArrowFunctionExpression = function(node) {
290                 validateComma(node, "params");
291             };
292         }
293         if (!exceptions.CallExpression) {
294             nodes.CallExpression = function(node) {
295                 validateComma(node, "arguments");
296             };
297         }
298         if (!exceptions.ImportDeclaration) {
299             nodes.ImportDeclaration = function(node) {
300                 validateComma(node, "specifiers");
301             };
302         }
303         if (!exceptions.NewExpression) {
304             nodes.NewExpression = function(node) {
305                 validateComma(node, "arguments");
306             };
307         }
308
309         return nodes;
310     }
311 };