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",
58 category
: "Stylistic Issues",
60 url
: "https://eslint.org/docs/rules/lines-around-comment"
63 fixable
: "whitespace",
114 applyDefaultIgnorePatterns
: {
118 additionalProperties
: false
122 after
: "Expected line after comment.",
123 before
: "Expected line before comment."
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;
135 options
.beforeBlockComment
= typeof options
.beforeBlockComment
!== "undefined" ? options
.beforeBlockComment
: true;
137 const sourceCode
= context
.getSourceCode();
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
);
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.
151 function codeAroundComment(token
) {
152 let currentToken
= token
;
155 currentToken
= sourceCode
.getTokenBefore(currentToken
, { includeComments
: true });
156 } while (currentToken
&& astUtils
.isCommentToken(currentToken
));
158 if (currentToken
&& astUtils
.isTokenOnSameLine(currentToken
, token
)) {
162 currentToken
= token
;
164 currentToken
= sourceCode
.getTokenAfter(currentToken
, { includeComments
: true });
165 } while (currentToken
&& astUtils
.isCommentToken(currentToken
));
167 if (currentToken
&& astUtils
.isTokenOnSameLine(token
, currentToken
)) {
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.
180 function isParentNodeType(parent
, nodeType
) {
181 return parent
.type
=== nodeType
||
182 (parent
.body
&& parent
.body
.type
=== nodeType
) ||
183 (parent
.consequent
&& parent
.consequent
.type
=== nodeType
);
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.
191 function getParentNodeOfToken(token
) {
192 return sourceCode
.getNodeByRangeIndex(token
.range
[0]);
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.
201 function isCommentAtParentStart(token
, nodeType
) {
202 const parent
= getParentNodeOfToken(token
);
204 return parent
&& isParentNodeType(parent
, nodeType
) &&
205 token
.loc
.start
.line
- parent
.loc
.start
.line
=== 1;
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.
214 function isCommentAtParentEnd(token
, nodeType
) {
215 const parent
= getParentNodeOfToken(token
);
217 return parent
&& isParentNodeType(parent
, nodeType
) &&
218 parent
.loc
.end
.line
- token
.loc
.end
.line
=== 1;
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.
226 function isCommentAtBlockStart(token
) {
227 return isCommentAtParentStart(token
, "ClassBody") || isCommentAtParentStart(token
, "BlockStatement") || isCommentAtParentStart(token
, "SwitchCase");
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.
235 function isCommentAtBlockEnd(token
) {
236 return isCommentAtParentEnd(token
, "ClassBody") || isCommentAtParentEnd(token
, "BlockStatement") || isCommentAtParentEnd(token
, "SwitchCase") || isCommentAtParentEnd(token
, "SwitchStatement");
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.
244 function isCommentAtClassStart(token
) {
245 return isCommentAtParentStart(token
, "ClassBody");
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.
253 function isCommentAtClassEnd(token
) {
254 return isCommentAtParentEnd(token
, "ClassBody");
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.
262 function isCommentAtObjectStart(token
) {
263 return isCommentAtParentStart(token
, "ObjectExpression") || isCommentAtParentStart(token
, "ObjectPattern");
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.
271 function isCommentAtObjectEnd(token
) {
272 return isCommentAtParentEnd(token
, "ObjectExpression") || isCommentAtParentEnd(token
, "ObjectPattern");
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.
280 function isCommentAtArrayStart(token
) {
281 return isCommentAtParentStart(token
, "ArrayExpression") || isCommentAtParentStart(token
, "ArrayPattern");
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.
289 function isCommentAtArrayEnd(token
) {
290 return isCommentAtParentEnd(token
, "ArrayExpression") || isCommentAtParentEnd(token
, "ArrayPattern");
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.
301 function checkForEmptyLine(token
, opts
) {
302 if (applyDefaultIgnorePatterns
&& defaultIgnoreRegExp
.test(token
.value
)) {
306 if (ignorePattern
&& customIgnoreRegExp
.test(token
.value
)) {
310 let after
= opts
.after
,
311 before
= opts
.before
;
313 const prevLineNum
= token
.loc
.start
.line
- 1,
314 nextLineNum
= token
.loc
.end
.line
+ 1,
315 commentIsNotAlone
= codeAroundComment(token
);
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
);
329 const exceptionStartAllowed
= blockStartAllowed
|| classStartAllowed
|| objectStartAllowed
|| arrayStartAllowed
;
330 const exceptionEndAllowed
= blockEndAllowed
|| classEndAllowed
|| objectEndAllowed
|| arrayEndAllowed
;
332 // ignore top of the file and bottom of the file
333 if (prevLineNum
< 1) {
336 if (nextLineNum
>= numLines
) {
340 // we ignore all inline comments
341 if (commentIsNotAlone
) {
345 const previousTokenOrComment
= sourceCode
.getTokenBefore(token
, { includeComments
: true });
346 const nextTokenOrComment
= sourceCode
.getTokenAfter(token
, { includeComments
: true });
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
];
358 return fixer
.insertTextBeforeRange(range
, "\n");
363 // check for newline after
364 if (!exceptionEndAllowed
&& after
&& !commentAndEmptyLines
.includes(nextLineNum
) &&
365 !(astUtils
.isCommentToken(nextTokenOrComment
) && astUtils
.isTokenOnSameLine(token
, nextTokenOrComment
))) {
370 return fixer
.insertTextAfter(token
, "\n");
377 //--------------------------------------------------------------------------
379 //--------------------------------------------------------------------------
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
391 } else if (token
.type
=== "Block") {
392 if (options
.beforeBlockComment
|| options
.afterBlockComment
) {
393 checkForEmptyLine(token
, {
394 after
: options
.afterBlockComment
,
395 before
: options
.beforeBlockComment