minor adjustment to readme
[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: {
150                         line: commaToken.loc.end.line,
151                         column: commaToken.loc.start.column
152                     },
153                     messageId: "unexpectedLineBeforeAndAfterComma",
154                     fix: getFixerFunction(styleType, previousItemToken, commaToken, currentItemToken)
155                 });
156
157             } else if (style === "first" && !astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
158
159                 context.report({
160                     node: reportItem,
161                     messageId: "expectedCommaFirst",
162                     fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
163                 });
164
165             } else if (style === "last" && astUtils.isTokenOnSameLine(commaToken, currentItemToken)) {
166
167                 context.report({
168                     node: reportItem,
169                     loc: {
170                         line: commaToken.loc.end.line,
171                         column: commaToken.loc.end.column
172                     },
173                     messageId: "expectedCommaLast",
174                     fix: getFixerFunction(style, previousItemToken, commaToken, currentItemToken)
175                 });
176             }
177         }
178
179         /**
180          * Checks the comma placement with regards to a declaration/property/element
181          * @param {ASTNode} node The binary expression node to check
182          * @param {string} property The property of the node containing child nodes.
183          * @private
184          * @returns {void}
185          */
186         function validateComma(node, property) {
187             const items = node[property],
188                 arrayLiteral = (node.type === "ArrayExpression" || node.type === "ArrayPattern");
189
190             if (items.length > 1 || arrayLiteral) {
191
192                 // seed as opening [
193                 let previousItemToken = sourceCode.getFirstToken(node);
194
195                 items.forEach(item => {
196                     const commaToken = item ? sourceCode.getTokenBefore(item) : previousItemToken,
197                         currentItemToken = item ? sourceCode.getFirstToken(item) : sourceCode.getTokenAfter(commaToken),
198                         reportItem = item || currentItemToken;
199
200                     /*
201                      * This works by comparing three token locations:
202                      * - previousItemToken is the last token of the previous item
203                      * - commaToken is the location of the comma before the current item
204                      * - currentItemToken is the first token of the current item
205                      *
206                      * These values get switched around if item is undefined.
207                      * previousItemToken will refer to the last token not belonging
208                      * to the current item, which could be a comma or an opening
209                      * square bracket. currentItemToken could be a comma.
210                      *
211                      * All comparisons are done based on these tokens directly, so
212                      * they are always valid regardless of an undefined item.
213                      */
214                     if (astUtils.isCommaToken(commaToken)) {
215                         validateCommaItemSpacing(previousItemToken, commaToken,
216                             currentItemToken, reportItem);
217                     }
218
219                     if (item) {
220                         const tokenAfterItem = sourceCode.getTokenAfter(item, astUtils.isNotClosingParenToken);
221
222                         previousItemToken = tokenAfterItem
223                             ? sourceCode.getTokenBefore(tokenAfterItem)
224                             : sourceCode.ast.tokens[sourceCode.ast.tokens.length - 1];
225                     }
226                 });
227
228                 /*
229                  * Special case for array literals that have empty last items, such
230                  * as [ 1, 2, ]. These arrays only have two items show up in the
231                  * AST, so we need to look at the token to verify that there's no
232                  * dangling comma.
233                  */
234                 if (arrayLiteral) {
235
236                     const lastToken = sourceCode.getLastToken(node),
237                         nextToLastToken = sourceCode.getTokenBefore(lastToken);
238
239                     if (astUtils.isCommaToken(nextToLastToken)) {
240                         validateCommaItemSpacing(
241                             sourceCode.getTokenBefore(nextToLastToken),
242                             nextToLastToken,
243                             lastToken,
244                             lastToken
245                         );
246                     }
247                 }
248             }
249         }
250
251         //--------------------------------------------------------------------------
252         // Public
253         //--------------------------------------------------------------------------
254
255         const nodes = {};
256
257         if (!exceptions.VariableDeclaration) {
258             nodes.VariableDeclaration = function(node) {
259                 validateComma(node, "declarations");
260             };
261         }
262         if (!exceptions.ObjectExpression) {
263             nodes.ObjectExpression = function(node) {
264                 validateComma(node, "properties");
265             };
266         }
267         if (!exceptions.ObjectPattern) {
268             nodes.ObjectPattern = function(node) {
269                 validateComma(node, "properties");
270             };
271         }
272         if (!exceptions.ArrayExpression) {
273             nodes.ArrayExpression = function(node) {
274                 validateComma(node, "elements");
275             };
276         }
277         if (!exceptions.ArrayPattern) {
278             nodes.ArrayPattern = function(node) {
279                 validateComma(node, "elements");
280             };
281         }
282         if (!exceptions.FunctionDeclaration) {
283             nodes.FunctionDeclaration = function(node) {
284                 validateComma(node, "params");
285             };
286         }
287         if (!exceptions.FunctionExpression) {
288             nodes.FunctionExpression = function(node) {
289                 validateComma(node, "params");
290             };
291         }
292         if (!exceptions.ArrowFunctionExpression) {
293             nodes.ArrowFunctionExpression = function(node) {
294                 validateComma(node, "params");
295             };
296         }
297         if (!exceptions.CallExpression) {
298             nodes.CallExpression = function(node) {
299                 validateComma(node, "arguments");
300             };
301         }
302         if (!exceptions.ImportDeclaration) {
303             nodes.ImportDeclaration = function(node) {
304                 validateComma(node, "specifiers");
305             };
306         }
307         if (!exceptions.NewExpression) {
308             nodes.NewExpression = function(node) {
309                 validateComma(node, "arguments");
310             };
311         }
312
313         return nodes;
314     }
315 };