Actualizacion maquina principal
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / lines-around-comment.js
1 /**
2  * @fileoverview Enforces empty lines around comments.
3  * @author Jamund Ferguson
4  */
5 "use strict";
6
7 //------------------------------------------------------------------------------
8 // Requirements
9 //------------------------------------------------------------------------------
10
11 const lodash = require("lodash"),
12     astUtils = require("./utils/ast-utils");
13
14 //------------------------------------------------------------------------------
15 // Helpers
16 //------------------------------------------------------------------------------
17
18 /**
19  * Return an array with with any line numbers that are empty.
20  * @param {Array} lines An array of each line of the file.
21  * @returns {Array} An array of line numbers.
22  */
23 function getEmptyLineNums(lines) {
24     const emptyLines = lines.map((line, i) => ({
25         code: line.trim(),
26         num: i + 1
27     })).filter(line => !line.code).map(line => line.num);
28
29     return emptyLines;
30 }
31
32 /**
33  * Return an array with with any line numbers that contain comments.
34  * @param {Array} comments An array of comment tokens.
35  * @returns {Array} An array of line numbers.
36  */
37 function getCommentLineNums(comments) {
38     const lines = [];
39
40     comments.forEach(token => {
41         const start = token.loc.start.line;
42         const end = token.loc.end.line;
43
44         lines.push(start, end);
45     });
46     return lines;
47 }
48
49 //------------------------------------------------------------------------------
50 // Rule Definition
51 //------------------------------------------------------------------------------
52
53 module.exports = {
54     meta: {
55         type: "layout",
56
57         docs: {
58             description: "require empty lines around comments",
59             category: "Stylistic Issues",
60             recommended: false,
61             url: "https://eslint.org/docs/rules/lines-around-comment"
62         },
63
64         fixable: "whitespace",
65
66         schema: [
67             {
68                 type: "object",
69                 properties: {
70                     beforeBlockComment: {
71                         type: "boolean",
72                         default: true
73                     },
74                     afterBlockComment: {
75                         type: "boolean",
76                         default: false
77                     },
78                     beforeLineComment: {
79                         type: "boolean",
80                         default: false
81                     },
82                     afterLineComment: {
83                         type: "boolean",
84                         default: false
85                     },
86                     allowBlockStart: {
87                         type: "boolean",
88                         default: false
89                     },
90                     allowBlockEnd: {
91                         type: "boolean",
92                         default: false
93                     },
94                     allowClassStart: {
95                         type: "boolean"
96                     },
97                     allowClassEnd: {
98                         type: "boolean"
99                     },
100                     allowObjectStart: {
101                         type: "boolean"
102                     },
103                     allowObjectEnd: {
104                         type: "boolean"
105                     },
106                     allowArrayStart: {
107                         type: "boolean"
108                     },
109                     allowArrayEnd: {
110                         type: "boolean"
111                     },
112                     ignorePattern: {
113                         type: "string"
114                     },
115                     applyDefaultIgnorePatterns: {
116                         type: "boolean"
117                     }
118                 },
119                 additionalProperties: false
120             }
121         ],
122         messages: {
123             after: "Expected line after comment.",
124             before: "Expected line before comment."
125         }
126     },
127
128     create(context) {
129
130         const options = Object.assign({}, context.options[0]);
131         const ignorePattern = options.ignorePattern;
132         const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN;
133         const customIgnoreRegExp = new RegExp(ignorePattern, "u");
134         const applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false;
135
136         options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true;
137
138         const sourceCode = context.getSourceCode();
139
140         const lines = sourceCode.lines,
141             numLines = lines.length + 1,
142             comments = sourceCode.getAllComments(),
143             commentLines = getCommentLineNums(comments),
144             emptyLines = getEmptyLineNums(lines),
145             commentAndEmptyLines = commentLines.concat(emptyLines);
146
147         /**
148          * Returns whether or not comments are on lines starting with or ending with code
149          * @param {token} token The comment token to check.
150          * @returns {boolean} True if the comment is not alone.
151          */
152         function codeAroundComment(token) {
153             let currentToken = token;
154
155             do {
156                 currentToken = sourceCode.getTokenBefore(currentToken, { includeComments: true });
157             } while (currentToken && astUtils.isCommentToken(currentToken));
158
159             if (currentToken && astUtils.isTokenOnSameLine(currentToken, token)) {
160                 return true;
161             }
162
163             currentToken = token;
164             do {
165                 currentToken = sourceCode.getTokenAfter(currentToken, { includeComments: true });
166             } while (currentToken && astUtils.isCommentToken(currentToken));
167
168             if (currentToken && astUtils.isTokenOnSameLine(token, currentToken)) {
169                 return true;
170             }
171
172             return false;
173         }
174
175         /**
176          * Returns whether or not comments are inside a node type or not.
177          * @param {ASTNode} parent The Comment parent node.
178          * @param {string} nodeType The parent type to check against.
179          * @returns {boolean} True if the comment is inside nodeType.
180          */
181         function isParentNodeType(parent, nodeType) {
182             return parent.type === nodeType ||
183                 (parent.body && parent.body.type === nodeType) ||
184                 (parent.consequent && parent.consequent.type === nodeType);
185         }
186
187         /**
188          * Returns the parent node that contains the given token.
189          * @param {token} token The token to check.
190          * @returns {ASTNode} The parent node that contains the given token.
191          */
192         function getParentNodeOfToken(token) {
193             return sourceCode.getNodeByRangeIndex(token.range[0]);
194         }
195
196         /**
197          * Returns whether or not comments are at the parent start or not.
198          * @param {token} token The Comment token.
199          * @param {string} nodeType The parent type to check against.
200          * @returns {boolean} True if the comment is at parent start.
201          */
202         function isCommentAtParentStart(token, nodeType) {
203             const parent = getParentNodeOfToken(token);
204
205             return parent && isParentNodeType(parent, nodeType) &&
206                     token.loc.start.line - parent.loc.start.line === 1;
207         }
208
209         /**
210          * Returns whether or not comments are at the parent end or not.
211          * @param {token} token The Comment token.
212          * @param {string} nodeType The parent type to check against.
213          * @returns {boolean} True if the comment is at parent end.
214          */
215         function isCommentAtParentEnd(token, nodeType) {
216             const parent = getParentNodeOfToken(token);
217
218             return parent && isParentNodeType(parent, nodeType) &&
219                     parent.loc.end.line - token.loc.end.line === 1;
220         }
221
222         /**
223          * Returns whether or not comments are at the block start or not.
224          * @param {token} token The Comment token.
225          * @returns {boolean} True if the comment is at block start.
226          */
227         function isCommentAtBlockStart(token) {
228             return isCommentAtParentStart(token, "ClassBody") || isCommentAtParentStart(token, "BlockStatement") || isCommentAtParentStart(token, "SwitchCase");
229         }
230
231         /**
232          * Returns whether or not comments are at the block end or not.
233          * @param {token} token The Comment token.
234          * @returns {boolean} True if the comment is at block end.
235          */
236         function isCommentAtBlockEnd(token) {
237             return isCommentAtParentEnd(token, "ClassBody") || isCommentAtParentEnd(token, "BlockStatement") || isCommentAtParentEnd(token, "SwitchCase") || isCommentAtParentEnd(token, "SwitchStatement");
238         }
239
240         /**
241          * Returns whether or not comments are at the class start or not.
242          * @param {token} token The Comment token.
243          * @returns {boolean} True if the comment is at class start.
244          */
245         function isCommentAtClassStart(token) {
246             return isCommentAtParentStart(token, "ClassBody");
247         }
248
249         /**
250          * Returns whether or not comments are at the class end or not.
251          * @param {token} token The Comment token.
252          * @returns {boolean} True if the comment is at class end.
253          */
254         function isCommentAtClassEnd(token) {
255             return isCommentAtParentEnd(token, "ClassBody");
256         }
257
258         /**
259          * Returns whether or not comments are at the object start or not.
260          * @param {token} token The Comment token.
261          * @returns {boolean} True if the comment is at object start.
262          */
263         function isCommentAtObjectStart(token) {
264             return isCommentAtParentStart(token, "ObjectExpression") || isCommentAtParentStart(token, "ObjectPattern");
265         }
266
267         /**
268          * Returns whether or not comments are at the object end or not.
269          * @param {token} token The Comment token.
270          * @returns {boolean} True if the comment is at object end.
271          */
272         function isCommentAtObjectEnd(token) {
273             return isCommentAtParentEnd(token, "ObjectExpression") || isCommentAtParentEnd(token, "ObjectPattern");
274         }
275
276         /**
277          * Returns whether or not comments are at the array start or not.
278          * @param {token} token The Comment token.
279          * @returns {boolean} True if the comment is at array start.
280          */
281         function isCommentAtArrayStart(token) {
282             return isCommentAtParentStart(token, "ArrayExpression") || isCommentAtParentStart(token, "ArrayPattern");
283         }
284
285         /**
286          * Returns whether or not comments are at the array end or not.
287          * @param {token} token The Comment token.
288          * @returns {boolean} True if the comment is at array end.
289          */
290         function isCommentAtArrayEnd(token) {
291             return isCommentAtParentEnd(token, "ArrayExpression") || isCommentAtParentEnd(token, "ArrayPattern");
292         }
293
294         /**
295          * Checks if a comment token has lines around it (ignores inline comments)
296          * @param {token} token The Comment token.
297          * @param {Object} opts Options to determine the newline.
298          * @param {boolean} opts.after Should have a newline after this line.
299          * @param {boolean} opts.before Should have a newline before this line.
300          * @returns {void}
301          */
302         function checkForEmptyLine(token, opts) {
303             if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(token.value)) {
304                 return;
305             }
306
307             if (ignorePattern && customIgnoreRegExp.test(token.value)) {
308                 return;
309             }
310
311             let after = opts.after,
312                 before = opts.before;
313
314             const prevLineNum = token.loc.start.line - 1,
315                 nextLineNum = token.loc.end.line + 1,
316                 commentIsNotAlone = codeAroundComment(token);
317
318             const blockStartAllowed = options.allowBlockStart &&
319                     isCommentAtBlockStart(token) &&
320                     !(options.allowClassStart === false &&
321                     isCommentAtClassStart(token)),
322                 blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token) && !(options.allowClassEnd === false && isCommentAtClassEnd(token)),
323                 classStartAllowed = options.allowClassStart && isCommentAtClassStart(token),
324                 classEndAllowed = options.allowClassEnd && isCommentAtClassEnd(token),
325                 objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(token),
326                 objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(token),
327                 arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(token),
328                 arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(token);
329
330             const exceptionStartAllowed = blockStartAllowed || classStartAllowed || objectStartAllowed || arrayStartAllowed;
331             const exceptionEndAllowed = blockEndAllowed || classEndAllowed || objectEndAllowed || arrayEndAllowed;
332
333             // ignore top of the file and bottom of the file
334             if (prevLineNum < 1) {
335                 before = false;
336             }
337             if (nextLineNum >= numLines) {
338                 after = false;
339             }
340
341             // we ignore all inline comments
342             if (commentIsNotAlone) {
343                 return;
344             }
345
346             const previousTokenOrComment = sourceCode.getTokenBefore(token, { includeComments: true });
347             const nextTokenOrComment = sourceCode.getTokenAfter(token, { includeComments: true });
348
349             // check for newline before
350             if (!exceptionStartAllowed && before && !lodash.includes(commentAndEmptyLines, prevLineNum) &&
351                     !(astUtils.isCommentToken(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, token))) {
352                 const lineStart = token.range[0] - token.loc.start.column;
353                 const range = [lineStart, lineStart];
354
355                 context.report({
356                     node: token,
357                     messageId: "before",
358                     fix(fixer) {
359                         return fixer.insertTextBeforeRange(range, "\n");
360                     }
361                 });
362             }
363
364             // check for newline after
365             if (!exceptionEndAllowed && after && !lodash.includes(commentAndEmptyLines, nextLineNum) &&
366                     !(astUtils.isCommentToken(nextTokenOrComment) && astUtils.isTokenOnSameLine(token, nextTokenOrComment))) {
367                 context.report({
368                     node: token,
369                     messageId: "after",
370                     fix(fixer) {
371                         return fixer.insertTextAfter(token, "\n");
372                     }
373                 });
374             }
375
376         }
377
378         //--------------------------------------------------------------------------
379         // Public
380         //--------------------------------------------------------------------------
381
382         return {
383             Program() {
384                 comments.forEach(token => {
385                     if (token.type === "Line") {
386                         if (options.beforeLineComment || options.afterLineComment) {
387                             checkForEmptyLine(token, {
388                                 after: options.afterLineComment,
389                                 before: options.beforeLineComment
390                             });
391                         }
392                     } else if (token.type === "Block") {
393                         if (options.beforeBlockComment || options.afterBlockComment) {
394                             checkForEmptyLine(token, {
395                                 after: options.afterBlockComment,
396                                 before: options.beforeBlockComment
397                             });
398                         }
399                     }
400                 });
401             }
402         };
403     }
404 };