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