2 * @fileoverview Enforces empty lines around comments.
3 * @author Jamund Ferguson
7 //------------------------------------------------------------------------------
9 //------------------------------------------------------------------------------
11 const lodash
= require("lodash"),
12 astUtils
= require("./utils/ast-utils");
14 //------------------------------------------------------------------------------
16 //------------------------------------------------------------------------------
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.
23 function getEmptyLineNums(lines
) {
24 const emptyLines
= lines
.map((line
, i
) => ({
27 })).filter(line
=> !line
.code
).map(line
=> line
.num
);
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.
37 function getCommentLineNums(comments
) {
40 comments
.forEach(token
=> {
41 const start
= token
.loc
.start
.line
;
42 const end
= token
.loc
.end
.line
;
44 lines
.push(start
, end
);
49 //------------------------------------------------------------------------------
51 //------------------------------------------------------------------------------
58 description
: "require empty lines around comments",
59 category
: "Stylistic Issues",
61 url
: "https://eslint.org/docs/rules/lines-around-comment"
64 fixable
: "whitespace",
115 applyDefaultIgnorePatterns
: {
119 additionalProperties
: false
123 after
: "Expected line after comment.",
124 before
: "Expected line before comment."
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;
136 options
.beforeBlockComment
= typeof options
.beforeBlockComment
!== "undefined" ? options
.beforeBlockComment
: true;
138 const sourceCode
= context
.getSourceCode();
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
);
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.
152 function codeAroundComment(token
) {
153 let currentToken
= token
;
156 currentToken
= sourceCode
.getTokenBefore(currentToken
, { includeComments
: true });
157 } while (currentToken
&& astUtils
.isCommentToken(currentToken
));
159 if (currentToken
&& astUtils
.isTokenOnSameLine(currentToken
, token
)) {
163 currentToken
= token
;
165 currentToken
= sourceCode
.getTokenAfter(currentToken
, { includeComments
: true });
166 } while (currentToken
&& astUtils
.isCommentToken(currentToken
));
168 if (currentToken
&& astUtils
.isTokenOnSameLine(token
, currentToken
)) {
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.
181 function isParentNodeType(parent
, nodeType
) {
182 return parent
.type
=== nodeType
||
183 (parent
.body
&& parent
.body
.type
=== nodeType
) ||
184 (parent
.consequent
&& parent
.consequent
.type
=== nodeType
);
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.
192 function getParentNodeOfToken(token
) {
193 return sourceCode
.getNodeByRangeIndex(token
.range
[0]);
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.
202 function isCommentAtParentStart(token
, nodeType
) {
203 const parent
= getParentNodeOfToken(token
);
205 return parent
&& isParentNodeType(parent
, nodeType
) &&
206 token
.loc
.start
.line
- parent
.loc
.start
.line
=== 1;
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.
215 function isCommentAtParentEnd(token
, nodeType
) {
216 const parent
= getParentNodeOfToken(token
);
218 return parent
&& isParentNodeType(parent
, nodeType
) &&
219 parent
.loc
.end
.line
- token
.loc
.end
.line
=== 1;
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.
227 function isCommentAtBlockStart(token
) {
228 return isCommentAtParentStart(token
, "ClassBody") || isCommentAtParentStart(token
, "BlockStatement") || isCommentAtParentStart(token
, "SwitchCase");
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.
236 function isCommentAtBlockEnd(token
) {
237 return isCommentAtParentEnd(token
, "ClassBody") || isCommentAtParentEnd(token
, "BlockStatement") || isCommentAtParentEnd(token
, "SwitchCase") || isCommentAtParentEnd(token
, "SwitchStatement");
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.
245 function isCommentAtClassStart(token
) {
246 return isCommentAtParentStart(token
, "ClassBody");
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.
254 function isCommentAtClassEnd(token
) {
255 return isCommentAtParentEnd(token
, "ClassBody");
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.
263 function isCommentAtObjectStart(token
) {
264 return isCommentAtParentStart(token
, "ObjectExpression") || isCommentAtParentStart(token
, "ObjectPattern");
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.
272 function isCommentAtObjectEnd(token
) {
273 return isCommentAtParentEnd(token
, "ObjectExpression") || isCommentAtParentEnd(token
, "ObjectPattern");
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.
281 function isCommentAtArrayStart(token
) {
282 return isCommentAtParentStart(token
, "ArrayExpression") || isCommentAtParentStart(token
, "ArrayPattern");
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.
290 function isCommentAtArrayEnd(token
) {
291 return isCommentAtParentEnd(token
, "ArrayExpression") || isCommentAtParentEnd(token
, "ArrayPattern");
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.
302 function checkForEmptyLine(token
, opts
) {
303 if (applyDefaultIgnorePatterns
&& defaultIgnoreRegExp
.test(token
.value
)) {
307 if (ignorePattern
&& customIgnoreRegExp
.test(token
.value
)) {
311 let after
= opts
.after
,
312 before
= opts
.before
;
314 const prevLineNum
= token
.loc
.start
.line
- 1,
315 nextLineNum
= token
.loc
.end
.line
+ 1,
316 commentIsNotAlone
= codeAroundComment(token
);
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
);
330 const exceptionStartAllowed
= blockStartAllowed
|| classStartAllowed
|| objectStartAllowed
|| arrayStartAllowed
;
331 const exceptionEndAllowed
= blockEndAllowed
|| classEndAllowed
|| objectEndAllowed
|| arrayEndAllowed
;
333 // ignore top of the file and bottom of the file
334 if (prevLineNum
< 1) {
337 if (nextLineNum
>= numLines
) {
341 // we ignore all inline comments
342 if (commentIsNotAlone
) {
346 const previousTokenOrComment
= sourceCode
.getTokenBefore(token
, { includeComments
: true });
347 const nextTokenOrComment
= sourceCode
.getTokenAfter(token
, { includeComments
: true });
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
];
359 return fixer
.insertTextBeforeRange(range
, "\n");
364 // check for newline after
365 if (!exceptionEndAllowed
&& after
&& !lodash
.includes(commentAndEmptyLines
, nextLineNum
) &&
366 !(astUtils
.isCommentToken(nextTokenOrComment
) && astUtils
.isTokenOnSameLine(token
, nextTokenOrComment
))) {
371 return fixer
.insertTextAfter(token
, "\n");
378 //--------------------------------------------------------------------------
380 //--------------------------------------------------------------------------
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
392 } else if (token
.type
=== "Block") {
393 if (options
.beforeBlockComment
|| options
.afterBlockComment
) {
394 checkForEmptyLine(token
, {
395 after
: options
.afterBlockComment
,
396 before
: options
.beforeBlockComment