2 * @fileoverview Enforces empty lines around comments.
3 * @author Jamund Ferguson
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const astUtils
= require("./utils/ast-utils");
13 //------------------------------------------------------------------------------
15 //------------------------------------------------------------------------------
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.
22 function getEmptyLineNums(lines
) {
23 const emptyLines
= lines
.map((line
, i
) => ({
26 })).filter(line
=> !line
.code
).map(line
=> line
.num
);
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.
36 function getCommentLineNums(comments
) {
39 comments
.forEach(token
=> {
40 const start
= token
.loc
.start
.line
;
41 const end
= token
.loc
.end
.line
;
43 lines
.push(start
, end
);
48 //------------------------------------------------------------------------------
50 //------------------------------------------------------------------------------
57 description
: "require empty lines around comments",
59 url
: "https://eslint.org/docs/rules/lines-around-comment"
62 fixable
: "whitespace",
113 applyDefaultIgnorePatterns
: {
117 additionalProperties
: false
121 after
: "Expected line after comment.",
122 before
: "Expected line before comment."
128 const options
= Object
.assign({}, context
.options
[0]);
129 const ignorePattern
= options
.ignorePattern
;
130 const defaultIgnoreRegExp
= astUtils
.COMMENTS_IGNORE_PATTERN
;
131 const customIgnoreRegExp
= new RegExp(ignorePattern
, "u");
132 const applyDefaultIgnorePatterns
= options
.applyDefaultIgnorePatterns
!== false;
134 options
.beforeBlockComment
= typeof options
.beforeBlockComment
!== "undefined" ? options
.beforeBlockComment
: true;
136 const sourceCode
= context
.getSourceCode();
138 const lines
= sourceCode
.lines
,
139 numLines
= lines
.length
+ 1,
140 comments
= sourceCode
.getAllComments(),
141 commentLines
= getCommentLineNums(comments
),
142 emptyLines
= getEmptyLineNums(lines
),
143 commentAndEmptyLines
= commentLines
.concat(emptyLines
);
146 * Returns whether or not comments are on lines starting with or ending with code
147 * @param {token} token The comment token to check.
148 * @returns {boolean} True if the comment is not alone.
150 function codeAroundComment(token
) {
151 let currentToken
= token
;
154 currentToken
= sourceCode
.getTokenBefore(currentToken
, { includeComments
: true });
155 } while (currentToken
&& astUtils
.isCommentToken(currentToken
));
157 if (currentToken
&& astUtils
.isTokenOnSameLine(currentToken
, token
)) {
161 currentToken
= token
;
163 currentToken
= sourceCode
.getTokenAfter(currentToken
, { includeComments
: true });
164 } while (currentToken
&& astUtils
.isCommentToken(currentToken
));
166 if (currentToken
&& astUtils
.isTokenOnSameLine(token
, currentToken
)) {
174 * Returns whether or not comments are inside a node type or not.
175 * @param {ASTNode} parent The Comment parent node.
176 * @param {string} nodeType The parent type to check against.
177 * @returns {boolean} True if the comment is inside nodeType.
179 function isParentNodeType(parent
, nodeType
) {
180 return parent
.type
=== nodeType
||
181 (parent
.body
&& parent
.body
.type
=== nodeType
) ||
182 (parent
.consequent
&& parent
.consequent
.type
=== nodeType
);
186 * Returns the parent node that contains the given token.
187 * @param {token} token The token to check.
188 * @returns {ASTNode|null} The parent node that contains the given token.
190 function getParentNodeOfToken(token
) {
191 const node
= sourceCode
.getNodeByRangeIndex(token
.range
[0]);
194 * For the purpose of this rule, the comment token is in a `StaticBlock` node only
195 * if it's inside the braces of that `StaticBlock` node.
197 * Example where this function returns `null`:
204 * Example where this function returns `StaticBlock` node:
212 if (node
&& node
.type
=== "StaticBlock") {
213 const openingBrace
= sourceCode
.getFirstToken(node
, { skip
: 1 }); // skip the `static` token
215 return token
.range
[0] >= openingBrace
.range
[0]
224 * Returns whether or not comments are at the parent start or not.
225 * @param {token} token The Comment token.
226 * @param {string} nodeType The parent type to check against.
227 * @returns {boolean} True if the comment is at parent start.
229 function isCommentAtParentStart(token
, nodeType
) {
230 const parent
= getParentNodeOfToken(token
);
232 if (parent
&& isParentNodeType(parent
, nodeType
)) {
233 const parentStartNodeOrToken
= parent
.type
=== "StaticBlock"
234 ? sourceCode
.getFirstToken(parent
, { skip
: 1 }) // opening brace of the static block
237 return token
.loc
.start
.line
- parentStartNodeOrToken
.loc
.start
.line
=== 1;
244 * Returns whether or not comments are at the parent end or not.
245 * @param {token} token The Comment token.
246 * @param {string} nodeType The parent type to check against.
247 * @returns {boolean} True if the comment is at parent end.
249 function isCommentAtParentEnd(token
, nodeType
) {
250 const parent
= getParentNodeOfToken(token
);
252 return !!parent
&& isParentNodeType(parent
, nodeType
) &&
253 parent
.loc
.end
.line
- token
.loc
.end
.line
=== 1;
257 * Returns whether or not comments are at the block start or not.
258 * @param {token} token The Comment token.
259 * @returns {boolean} True if the comment is at block start.
261 function isCommentAtBlockStart(token
) {
263 isCommentAtParentStart(token
, "ClassBody") ||
264 isCommentAtParentStart(token
, "BlockStatement") ||
265 isCommentAtParentStart(token
, "StaticBlock") ||
266 isCommentAtParentStart(token
, "SwitchCase")
271 * Returns whether or not comments are at the block end or not.
272 * @param {token} token The Comment token.
273 * @returns {boolean} True if the comment is at block end.
275 function isCommentAtBlockEnd(token
) {
277 isCommentAtParentEnd(token
, "ClassBody") ||
278 isCommentAtParentEnd(token
, "BlockStatement") ||
279 isCommentAtParentEnd(token
, "StaticBlock") ||
280 isCommentAtParentEnd(token
, "SwitchCase") ||
281 isCommentAtParentEnd(token
, "SwitchStatement")
286 * Returns whether or not comments are at the class start or not.
287 * @param {token} token The Comment token.
288 * @returns {boolean} True if the comment is at class start.
290 function isCommentAtClassStart(token
) {
291 return isCommentAtParentStart(token
, "ClassBody");
295 * Returns whether or not comments are at the class end or not.
296 * @param {token} token The Comment token.
297 * @returns {boolean} True if the comment is at class end.
299 function isCommentAtClassEnd(token
) {
300 return isCommentAtParentEnd(token
, "ClassBody");
304 * Returns whether or not comments are at the object start or not.
305 * @param {token} token The Comment token.
306 * @returns {boolean} True if the comment is at object start.
308 function isCommentAtObjectStart(token
) {
309 return isCommentAtParentStart(token
, "ObjectExpression") || isCommentAtParentStart(token
, "ObjectPattern");
313 * Returns whether or not comments are at the object end or not.
314 * @param {token} token The Comment token.
315 * @returns {boolean} True if the comment is at object end.
317 function isCommentAtObjectEnd(token
) {
318 return isCommentAtParentEnd(token
, "ObjectExpression") || isCommentAtParentEnd(token
, "ObjectPattern");
322 * Returns whether or not comments are at the array start or not.
323 * @param {token} token The Comment token.
324 * @returns {boolean} True if the comment is at array start.
326 function isCommentAtArrayStart(token
) {
327 return isCommentAtParentStart(token
, "ArrayExpression") || isCommentAtParentStart(token
, "ArrayPattern");
331 * Returns whether or not comments are at the array end or not.
332 * @param {token} token The Comment token.
333 * @returns {boolean} True if the comment is at array end.
335 function isCommentAtArrayEnd(token
) {
336 return isCommentAtParentEnd(token
, "ArrayExpression") || isCommentAtParentEnd(token
, "ArrayPattern");
340 * Checks if a comment token has lines around it (ignores inline comments)
341 * @param {token} token The Comment token.
342 * @param {Object} opts Options to determine the newline.
343 * @param {boolean} opts.after Should have a newline after this line.
344 * @param {boolean} opts.before Should have a newline before this line.
347 function checkForEmptyLine(token
, opts
) {
348 if (applyDefaultIgnorePatterns
&& defaultIgnoreRegExp
.test(token
.value
)) {
352 if (ignorePattern
&& customIgnoreRegExp
.test(token
.value
)) {
356 let after
= opts
.after
,
357 before
= opts
.before
;
359 const prevLineNum
= token
.loc
.start
.line
- 1,
360 nextLineNum
= token
.loc
.end
.line
+ 1,
361 commentIsNotAlone
= codeAroundComment(token
);
363 const blockStartAllowed
= options
.allowBlockStart
&&
364 isCommentAtBlockStart(token
) &&
365 !(options
.allowClassStart
=== false &&
366 isCommentAtClassStart(token
)),
367 blockEndAllowed
= options
.allowBlockEnd
&& isCommentAtBlockEnd(token
) && !(options
.allowClassEnd
=== false && isCommentAtClassEnd(token
)),
368 classStartAllowed
= options
.allowClassStart
&& isCommentAtClassStart(token
),
369 classEndAllowed
= options
.allowClassEnd
&& isCommentAtClassEnd(token
),
370 objectStartAllowed
= options
.allowObjectStart
&& isCommentAtObjectStart(token
),
371 objectEndAllowed
= options
.allowObjectEnd
&& isCommentAtObjectEnd(token
),
372 arrayStartAllowed
= options
.allowArrayStart
&& isCommentAtArrayStart(token
),
373 arrayEndAllowed
= options
.allowArrayEnd
&& isCommentAtArrayEnd(token
);
375 const exceptionStartAllowed
= blockStartAllowed
|| classStartAllowed
|| objectStartAllowed
|| arrayStartAllowed
;
376 const exceptionEndAllowed
= blockEndAllowed
|| classEndAllowed
|| objectEndAllowed
|| arrayEndAllowed
;
378 // ignore top of the file and bottom of the file
379 if (prevLineNum
< 1) {
382 if (nextLineNum
>= numLines
) {
386 // we ignore all inline comments
387 if (commentIsNotAlone
) {
391 const previousTokenOrComment
= sourceCode
.getTokenBefore(token
, { includeComments
: true });
392 const nextTokenOrComment
= sourceCode
.getTokenAfter(token
, { includeComments
: true });
394 // check for newline before
395 if (!exceptionStartAllowed
&& before
&& !commentAndEmptyLines
.includes(prevLineNum
) &&
396 !(astUtils
.isCommentToken(previousTokenOrComment
) && astUtils
.isTokenOnSameLine(previousTokenOrComment
, token
))) {
397 const lineStart
= token
.range
[0] - token
.loc
.start
.column
;
398 const range
= [lineStart
, lineStart
];
404 return fixer
.insertTextBeforeRange(range
, "\n");
409 // check for newline after
410 if (!exceptionEndAllowed
&& after
&& !commentAndEmptyLines
.includes(nextLineNum
) &&
411 !(astUtils
.isCommentToken(nextTokenOrComment
) && astUtils
.isTokenOnSameLine(token
, nextTokenOrComment
))) {
416 return fixer
.insertTextAfter(token
, "\n");
423 //--------------------------------------------------------------------------
425 //--------------------------------------------------------------------------
429 comments
.forEach(token
=> {
430 if (token
.type
=== "Line") {
431 if (options
.beforeLineComment
|| options
.afterLineComment
) {
432 checkForEmptyLine(token
, {
433 after
: options
.afterLineComment
,
434 before
: options
.beforeLineComment
437 } else if (token
.type
=== "Block") {
438 if (options
.beforeBlockComment
|| options
.afterBlockComment
) {
439 checkForEmptyLine(token
, {
440 after
: options
.afterBlockComment
,
441 before
: options
.beforeBlockComment