massive update, probably broken
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / spaced-comment.js
1 /**
2  * @fileoverview Source code for spaced-comments rule
3  * @author Gyandeep Singh
4  */
5 "use strict";
6
7 const escapeRegExp = require("escape-string-regexp");
8 const astUtils = require("./utils/ast-utils");
9
10 //------------------------------------------------------------------------------
11 // Helpers
12 //------------------------------------------------------------------------------
13
14 /**
15  * Escapes the control characters of a given string.
16  * @param {string} s A string to escape.
17  * @returns {string} An escaped string.
18  */
19 function escape(s) {
20     return `(?:${escapeRegExp(s)})`;
21 }
22
23 /**
24  * Escapes the control characters of a given string.
25  * And adds a repeat flag.
26  * @param {string} s A string to escape.
27  * @returns {string} An escaped string.
28  */
29 function escapeAndRepeat(s) {
30     return `${escape(s)}+`;
31 }
32
33 /**
34  * Parses `markers` option.
35  * If markers don't include `"*"`, this adds `"*"` to allow JSDoc comments.
36  * @param {string[]} [markers] A marker list.
37  * @returns {string[]} A marker list.
38  */
39 function parseMarkersOption(markers) {
40
41     // `*` is a marker for JSDoc comments.
42     if (markers.indexOf("*") === -1) {
43         return markers.concat("*");
44     }
45
46     return markers;
47 }
48
49 /**
50  * Creates string pattern for exceptions.
51  * Generated pattern:
52  *
53  * 1. A space or an exception pattern sequence.
54  * @param {string[]} exceptions An exception pattern list.
55  * @returns {string} A regular expression string for exceptions.
56  */
57 function createExceptionsPattern(exceptions) {
58     let pattern = "";
59
60     /*
61      * A space or an exception pattern sequence.
62      * []                 ==> "\s"
63      * ["-"]              ==> "(?:\s|\-+$)"
64      * ["-", "="]         ==> "(?:\s|(?:\-+|=+)$)"
65      * ["-", "=", "--=="] ==> "(?:\s|(?:\-+|=+|(?:\-\-==)+)$)" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5Cs%7C(%3F%3A%5C-%2B%7C%3D%2B%7C(%3F%3A%5C-%5C-%3D%3D)%2B)%24)
66      */
67     if (exceptions.length === 0) {
68
69         // a space.
70         pattern += "\\s";
71     } else {
72
73         // a space or...
74         pattern += "(?:\\s|";
75
76         if (exceptions.length === 1) {
77
78             // a sequence of the exception pattern.
79             pattern += escapeAndRepeat(exceptions[0]);
80         } else {
81
82             // a sequence of one of the exception patterns.
83             pattern += "(?:";
84             pattern += exceptions.map(escapeAndRepeat).join("|");
85             pattern += ")";
86         }
87         pattern += `(?:$|[${Array.from(astUtils.LINEBREAKS).join("")}]))`;
88     }
89
90     return pattern;
91 }
92
93 /**
94  * Creates RegExp object for `always` mode.
95  * Generated pattern for beginning of comment:
96  *
97  * 1. First, a marker or nothing.
98  * 2. Next, a space or an exception pattern sequence.
99  * @param {string[]} markers A marker list.
100  * @param {string[]} exceptions An exception pattern list.
101  * @returns {RegExp} A RegExp object for the beginning of a comment in `always` mode.
102  */
103 function createAlwaysStylePattern(markers, exceptions) {
104     let pattern = "^";
105
106     /*
107      * A marker or nothing.
108      * ["*"]            ==> "\*?"
109      * ["*", "!"]       ==> "(?:\*|!)?"
110      * ["*", "/", "!<"] ==> "(?:\*|\/|(?:!<))?" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5C*%7C%5C%2F%7C(%3F%3A!%3C))%3F
111      */
112     if (markers.length === 1) {
113
114         // the marker.
115         pattern += escape(markers[0]);
116     } else {
117
118         // one of markers.
119         pattern += "(?:";
120         pattern += markers.map(escape).join("|");
121         pattern += ")";
122     }
123
124     pattern += "?"; // or nothing.
125     pattern += createExceptionsPattern(exceptions);
126
127     return new RegExp(pattern, "u");
128 }
129
130 /**
131  * Creates RegExp object for `never` mode.
132  * Generated pattern for beginning of comment:
133  *
134  * 1. First, a marker or nothing (captured).
135  * 2. Next, a space or a tab.
136  * @param {string[]} markers A marker list.
137  * @returns {RegExp} A RegExp object for `never` mode.
138  */
139 function createNeverStylePattern(markers) {
140     const pattern = `^(${markers.map(escape).join("|")})?[ \t]+`;
141
142     return new RegExp(pattern, "u");
143 }
144
145 //------------------------------------------------------------------------------
146 // Rule Definition
147 //------------------------------------------------------------------------------
148
149 module.exports = {
150     meta: {
151         type: "suggestion",
152
153         docs: {
154             description: "enforce consistent spacing after the `//` or `/*` in a comment",
155             category: "Stylistic Issues",
156             recommended: false,
157             url: "https://eslint.org/docs/rules/spaced-comment"
158         },
159
160         fixable: "whitespace",
161
162         schema: [
163             {
164                 enum: ["always", "never"]
165             },
166             {
167                 type: "object",
168                 properties: {
169                     exceptions: {
170                         type: "array",
171                         items: {
172                             type: "string"
173                         }
174                     },
175                     markers: {
176                         type: "array",
177                         items: {
178                             type: "string"
179                         }
180                     },
181                     line: {
182                         type: "object",
183                         properties: {
184                             exceptions: {
185                                 type: "array",
186                                 items: {
187                                     type: "string"
188                                 }
189                             },
190                             markers: {
191                                 type: "array",
192                                 items: {
193                                     type: "string"
194                                 }
195                             }
196                         },
197                         additionalProperties: false
198                     },
199                     block: {
200                         type: "object",
201                         properties: {
202                             exceptions: {
203                                 type: "array",
204                                 items: {
205                                     type: "string"
206                                 }
207                             },
208                             markers: {
209                                 type: "array",
210                                 items: {
211                                     type: "string"
212                                 }
213                             },
214                             balanced: {
215                                 type: "boolean",
216                                 default: false
217                             }
218                         },
219                         additionalProperties: false
220                     }
221                 },
222                 additionalProperties: false
223             }
224         ],
225
226         messages: {
227             unexpectedSpaceAfterMarker: "Unexpected space or tab after marker ({{refChar}}) in comment.",
228             expectedExceptionAfter: "Expected exception block, space or tab after '{{refChar}}' in comment.",
229             unexpectedSpaceBefore: "Unexpected space or tab before '*/' in comment.",
230             unexpectedSpaceAfter: "Unexpected space or tab after '{{refChar}}' in comment.",
231             expectedSpaceBefore: "Expected space or tab before '*/' in comment.",
232             expectedSpaceAfter: "Expected space or tab after '{{refChar}}' in comment."
233         }
234     },
235
236     create(context) {
237
238         const sourceCode = context.getSourceCode();
239
240         // Unless the first option is never, require a space
241         const requireSpace = context.options[0] !== "never";
242
243         /*
244          * Parse the second options.
245          * If markers don't include `"*"`, it's added automatically for JSDoc
246          * comments.
247          */
248         const config = context.options[1] || {};
249         const balanced = config.block && config.block.balanced;
250
251         const styleRules = ["block", "line"].reduce((rule, type) => {
252             const markers = parseMarkersOption(config[type] && config[type].markers || config.markers || []);
253             const exceptions = config[type] && config[type].exceptions || config.exceptions || [];
254             const endNeverPattern = "[ \t]+$";
255
256             // Create RegExp object for valid patterns.
257             rule[type] = {
258                 beginRegex: requireSpace ? createAlwaysStylePattern(markers, exceptions) : createNeverStylePattern(markers),
259                 endRegex: balanced && requireSpace ? new RegExp(`${createExceptionsPattern(exceptions)}$`, "u") : new RegExp(endNeverPattern, "u"),
260                 hasExceptions: exceptions.length > 0,
261                 captureMarker: new RegExp(`^(${markers.map(escape).join("|")})`, "u"),
262                 markers: new Set(markers)
263             };
264
265             return rule;
266         }, {});
267
268         /**
269          * Reports a beginning spacing error with an appropriate message.
270          * @param {ASTNode} node A comment node to check.
271          * @param {string} messageId An error message to report.
272          * @param {Array} match An array of match results for markers.
273          * @param {string} refChar Character used for reference in the error message.
274          * @returns {void}
275          */
276         function reportBegin(node, messageId, match, refChar) {
277             const type = node.type.toLowerCase(),
278                 commentIdentifier = type === "block" ? "/*" : "//";
279
280             context.report({
281                 node,
282                 fix(fixer) {
283                     const start = node.range[0];
284                     let end = start + 2;
285
286                     if (requireSpace) {
287                         if (match) {
288                             end += match[0].length;
289                         }
290                         return fixer.insertTextAfterRange([start, end], " ");
291                     }
292                     end += match[0].length;
293                     return fixer.replaceTextRange([start, end], commentIdentifier + (match[1] ? match[1] : ""));
294
295                 },
296                 messageId,
297                 data: { refChar }
298             });
299         }
300
301         /**
302          * Reports an ending spacing error with an appropriate message.
303          * @param {ASTNode} node A comment node to check.
304          * @param {string} messageId An error message to report.
305          * @param {string} match An array of the matched whitespace characters.
306          * @returns {void}
307          */
308         function reportEnd(node, messageId, match) {
309             context.report({
310                 node,
311                 fix(fixer) {
312                     if (requireSpace) {
313                         return fixer.insertTextAfterRange([node.range[0], node.range[1] - 2], " ");
314                     }
315                     const end = node.range[1] - 2,
316                         start = end - match[0].length;
317
318                     return fixer.replaceTextRange([start, end], "");
319
320                 },
321                 messageId
322             });
323         }
324
325         /**
326          * Reports a given comment if it's invalid.
327          * @param {ASTNode} node a comment node to check.
328          * @returns {void}
329          */
330         function checkCommentForSpace(node) {
331             const type = node.type.toLowerCase(),
332                 rule = styleRules[type],
333                 commentIdentifier = type === "block" ? "/*" : "//";
334
335             // Ignores empty comments and comments that consist only of a marker.
336             if (node.value.length === 0 || rule.markers.has(node.value)) {
337                 return;
338             }
339
340             const beginMatch = rule.beginRegex.exec(node.value);
341             const endMatch = rule.endRegex.exec(node.value);
342
343             // Checks.
344             if (requireSpace) {
345                 if (!beginMatch) {
346                     const hasMarker = rule.captureMarker.exec(node.value);
347                     const marker = hasMarker ? commentIdentifier + hasMarker[0] : commentIdentifier;
348
349                     if (rule.hasExceptions) {
350                         reportBegin(node, "expectedExceptionAfter", hasMarker, marker);
351                     } else {
352                         reportBegin(node, "expectedSpaceAfter", hasMarker, marker);
353                     }
354                 }
355
356                 if (balanced && type === "block" && !endMatch) {
357                     reportEnd(node, "expectedSpaceBefore");
358                 }
359             } else {
360                 if (beginMatch) {
361                     if (!beginMatch[1]) {
362                         reportBegin(node, "unexpectedSpaceAfter", beginMatch, commentIdentifier);
363                     } else {
364                         reportBegin(node, "unexpectedSpaceAfterMarker", beginMatch, beginMatch[1]);
365                     }
366                 }
367
368                 if (balanced && type === "block" && endMatch) {
369                     reportEnd(node, "unexpectedSpaceBefore", endMatch);
370                 }
371             }
372         }
373
374         return {
375             Program() {
376                 const comments = sourceCode.getAllComments();
377
378                 comments.filter(token => token.type !== "Shebang").forEach(checkCommentForSpace);
379             }
380         };
381     }
382 };