View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.whitespace;
21  
22  import java.util.ArrayList;
23  import java.util.List;
24  import java.util.Optional;
25  import java.util.Set;
26  
27  import com.puppycrawl.tools.checkstyle.StatelessCheck;
28  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29  import com.puppycrawl.tools.checkstyle.api.DetailAST;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
32  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
33  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
34  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
35  
36  /**
37   * <div>
38   * Checks for empty line separators after,
39   * fields, constructors, methods, nested classes,
40   * static initializers and instance initializers.
41   * </div>
42   *
43   * <p>
44   * For package declaration it checks for both before and after
45   * line separators and for import declarations it checks for
46   * empty line separator after last import declaration.
47   * </p>
48   *
49   * <p>
50   * Checks for empty line separators after not only statements but
51   * implementation and documentation comments and blocks as well.
52   * </p>
53   *
54   * <p>
55   * ATTENTION: empty line separator is required between token siblings,
56   * not after line where token is found.
57   * If token does not have a sibling of the same type, then empty line
58   * is required at its end (for example for CLASS_DEF it is after '}').
59   * Also, trailing comments are skipped.
60   * </p>
61   *
62   * @since 5.8
63   */
64  @StatelessCheck
65  public class EmptyLineSeparatorCheck extends AbstractCheck {
66  
67      /**
68       * A key is pointing to the warning message empty.line.separator in "messages.properties"
69       * file.
70       */
71      public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator";
72  
73      /**
74       * A key is pointing to the warning message empty.line.separator.multiple.lines
75       *  in "messages.properties"
76       * file.
77       */
78      public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines";
79  
80      /**
81       * A key is pointing to the warning message empty.line.separator.lines.after
82       * in "messages.properties" file.
83       */
84      public static final String MSG_MULTIPLE_LINES_AFTER =
85              "empty.line.separator.multiple.lines.after";
86  
87      /**
88       * A key is pointing to the warning message empty.line.separator.multiple.lines.inside
89       * in "messages.properties" file.
90       */
91      public static final String MSG_MULTIPLE_LINES_INSIDE =
92              "empty.line.separator.multiple.lines.inside";
93  
94      /** Tokens for which preceding comment lines (if any) are checked via previous siblings. */
95      private static final Set<Integer> TOKENS_TO_CHECK_FOR_PRECEDING_COMMENTS = Set.of(
96          TokenTypes.PACKAGE_DEF,
97          TokenTypes.IMPORT,
98          TokenTypes.STATIC_IMPORT,
99          TokenTypes.MODULE_IMPORT,
100         TokenTypes.STATIC_INIT);
101 
102     /** Allow no empty line between fields. */
103     private boolean allowNoEmptyLineBetweenFields;
104 
105     /** Allow multiple empty lines between class members. */
106     private boolean allowMultipleEmptyLines = true;
107 
108     /** Allow multiple empty lines inside class members. */
109     private boolean allowMultipleEmptyLinesInsideClassMembers = true;
110 
111     /**
112      * Setter to allow no empty line between fields.
113      *
114      * @param allow
115      *        User's value.
116      * @since 5.8
117      */
118     public final void setAllowNoEmptyLineBetweenFields(boolean allow) {
119         allowNoEmptyLineBetweenFields = allow;
120     }
121 
122     /**
123      * Setter to allow multiple empty lines between class members.
124      *
125      * @param allow User's value.
126      * @since 6.3
127      */
128     public void setAllowMultipleEmptyLines(boolean allow) {
129         allowMultipleEmptyLines = allow;
130     }
131 
132     /**
133      * Setter to allow multiple empty lines inside class members.
134      *
135      * @param allow User's value.
136      * @since 6.18
137      */
138     public void setAllowMultipleEmptyLinesInsideClassMembers(boolean allow) {
139         allowMultipleEmptyLinesInsideClassMembers = allow;
140     }
141 
142     @Override
143     public boolean isCommentNodesRequired() {
144         return true;
145     }
146 
147     @Override
148     public int[] getDefaultTokens() {
149         return getAcceptableTokens();
150     }
151 
152     @Override
153     public int[] getAcceptableTokens() {
154         return new int[] {
155             TokenTypes.PACKAGE_DEF,
156             TokenTypes.IMPORT,
157             TokenTypes.STATIC_IMPORT,
158             TokenTypes.MODULE_IMPORT,
159             TokenTypes.CLASS_DEF,
160             TokenTypes.INTERFACE_DEF,
161             TokenTypes.ENUM_DEF,
162             TokenTypes.STATIC_INIT,
163             TokenTypes.INSTANCE_INIT,
164             TokenTypes.METHOD_DEF,
165             TokenTypes.CTOR_DEF,
166             TokenTypes.VARIABLE_DEF,
167             TokenTypes.RECORD_DEF,
168             TokenTypes.COMPACT_CTOR_DEF,
169         };
170     }
171 
172     @Override
173     public int[] getRequiredTokens() {
174         return CommonUtil.EMPTY_INT_ARRAY;
175     }
176 
177     @Override
178     public void visitToken(DetailAST ast) {
179         checkComments(ast);
180         if (hasMultipleLinesBefore(ast)) {
181             log(ast, MSG_MULTIPLE_LINES, ast.getText());
182         }
183         if (!allowMultipleEmptyLinesInsideClassMembers) {
184             processMultipleLinesInside(ast);
185         }
186         if (ast.getType() == TokenTypes.PACKAGE_DEF) {
187             checkCommentInModifiers(ast);
188         }
189         DetailAST nextToken = ast.getNextSibling();
190         while (nextToken != null && TokenUtil.isCommentType(nextToken.getType())) {
191             nextToken = nextToken.getNextSibling();
192         }
193         if (ast.getType() == TokenTypes.PACKAGE_DEF) {
194             processPackage(ast, nextToken);
195         }
196         else if (nextToken != null) {
197             checkToken(ast, nextToken);
198         }
199     }
200 
201     /**
202      * Checks that token and next token are separated.
203      *
204      * @param ast token to validate
205      * @param nextToken next sibling of the token
206      */
207     private void checkToken(DetailAST ast, DetailAST nextToken) {
208         final int astType = ast.getType();
209 
210         switch (astType) {
211             case TokenTypes.VARIABLE_DEF -> processVariableDef(ast, nextToken);
212 
213             case TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT, TokenTypes.MODULE_IMPORT ->
214                 processImport(ast, nextToken);
215 
216             default -> {
217                 if (nextToken.getType() == TokenTypes.RCURLY) {
218                     if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) {
219                         final DetailAST result = getLastElementBeforeEmptyLines(
220                                 ast, nextToken.getLineNo()
221                         );
222                         log(result, MSG_MULTIPLE_LINES_AFTER, result.getText());
223                     }
224                 }
225                 else if (!hasEmptyLineAfter(ast)) {
226                     log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
227                 }
228             }
229         }
230     }
231 
232     /**
233      * Checks that packageDef token is separated from comment in modifiers.
234      *
235      * @param packageDef package def token
236      */
237     private void checkCommentInModifiers(DetailAST packageDef) {
238         final Optional<DetailAST> comment = findCommentUnder(packageDef);
239         comment.ifPresent(commentValue -> {
240             log(commentValue, MSG_SHOULD_BE_SEPARATED, commentValue.getText());
241         });
242     }
243 
244     /**
245      * Log violation in case there are multiple empty lines inside constructor,
246      * initialization block or method.
247      *
248      * @param ast the ast to check.
249      */
250     private void processMultipleLinesInside(DetailAST ast) {
251         final int astType = ast.getType();
252         if (isClassMemberBlock(astType)) {
253             final List<Integer> emptyLines = getEmptyLines(ast);
254             final List<Integer> emptyLinesToLog = getEmptyLinesToLog(emptyLines);
255             for (Integer lineNo : emptyLinesToLog) {
256                 log(getLastElementBeforeEmptyLines(ast, lineNo), MSG_MULTIPLE_LINES_INSIDE);
257             }
258         }
259     }
260 
261     /**
262      * Returns the element after which empty lines exist.
263      *
264      * @param ast the ast to check.
265      * @param line the empty line which gives violation.
266      * @return The DetailAST after which empty lines are present.
267      */
268     private static DetailAST getLastElementBeforeEmptyLines(DetailAST ast, int line) {
269         DetailAST result = ast;
270         if (ast.getFirstChild().getLineNo() <= line) {
271             result = ast.getFirstChild();
272             while (result.getNextSibling() != null
273                     && result.getNextSibling().getLineNo() <= line) {
274                 result = result.getNextSibling();
275             }
276             if (result.hasChildren()) {
277                 result = getLastElementBeforeEmptyLines(result, line);
278             }
279         }
280 
281         return positionToPotentialPostFixNode(result, line);
282     }
283 
284     /**
285      * A post fix AST will always have a sibling METHOD CALL
286      * METHOD CALL will at least have two children
287      * The first child is DOT in case of POSTFIX which have at least 2 children
288      * First child of DOT again puts us back to normal AST tree which will
289      * recurse down below from here.
290      *
291      * @param postFixAst the ast to check.
292      * @param line the empty line which gives violation.
293      * @return The potential post fix node after which empty lines are present.
294      */
295     private static DetailAST positionToPotentialPostFixNode(DetailAST postFixAst, int line) {
296         DetailAST result = postFixAst;
297         if (result.getNextSibling() != null) {
298             final Optional<DetailAST> postFixNode = getPostFixNode(result.getNextSibling());
299             if (postFixNode.isPresent()) {
300                 final DetailAST firstChildAfterPostFix = postFixNode.orElseThrow();
301                 result = getLastElementBeforeEmptyLines(firstChildAfterPostFix, line);
302             }
303         }
304 
305         if (result.getLineNo() > line) {
306             result = postFixAst;
307         }
308 
309         return result;
310     }
311 
312     /**
313      * Gets postfix Node from AST if present.
314      *
315      * @param ast the AST used to get postfix Node.
316      * @return Optional postfix node.
317      */
318     private static Optional<DetailAST> getPostFixNode(DetailAST ast) {
319         Optional<DetailAST> result = Optional.empty();
320         if (ast.getType() == TokenTypes.EXPR
321             // EXPR always has at least one child
322             && ast.getFirstChild().getType() == TokenTypes.METHOD_CALL) {
323             // METHOD CALL always has at two least child
324             final DetailAST node = ast.getFirstChild().getFirstChild();
325             if (node.getType() == TokenTypes.DOT) {
326                 result = Optional.of(node);
327             }
328         }
329         return result;
330     }
331 
332     /**
333      * Whether the AST is a class member block.
334      *
335      * @param astType the AST to check.
336      * @return true if the AST is a class member block.
337      */
338     private static boolean isClassMemberBlock(int astType) {
339         return TokenUtil.isOfType(astType,
340             TokenTypes.STATIC_INIT, TokenTypes.INSTANCE_INIT, TokenTypes.METHOD_DEF,
341             TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF);
342     }
343 
344     /**
345      * Get list of empty lines.
346      *
347      * @param ast the ast to check.
348      * @return list of line numbers for empty lines.
349      */
350     private List<Integer> getEmptyLines(DetailAST ast) {
351         final DetailAST lastToken = ast.getLastChild().getLastChild();
352         int lastTokenLineNo = 0;
353         if (lastToken != null) {
354             // -1 as count starts from 0
355             // -2 as last token line cannot be empty, because it is a RCURLY
356             lastTokenLineNo = lastToken.getLineNo() - 2;
357         }
358         final List<Integer> emptyLines = new ArrayList<>();
359 
360         for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) {
361             if (CommonUtil.isBlank(getLine(lineNo))) {
362                 emptyLines.add(lineNo);
363             }
364         }
365         return emptyLines;
366     }
367 
368     /**
369      * Get list of empty lines to log.
370      *
371      * @param emptyLines list of empty lines.
372      * @return list of empty lines to log.
373      */
374     private static List<Integer> getEmptyLinesToLog(Iterable<Integer> emptyLines) {
375         final List<Integer> emptyLinesToLog = new ArrayList<>();
376         int previousEmptyLineNo = -1;
377         for (int emptyLineNo : emptyLines) {
378             if (previousEmptyLineNo + 1 == emptyLineNo) {
379                 emptyLinesToLog.add(previousEmptyLineNo);
380             }
381             previousEmptyLineNo = emptyLineNo;
382         }
383         return emptyLinesToLog;
384     }
385 
386     /**
387      * Whether the token has not allowed multiple empty lines before.
388      *
389      * @param ast the ast to check.
390      * @return true if the token has not allowed multiple empty lines before.
391      */
392     private boolean hasMultipleLinesBefore(DetailAST ast) {
393         return (ast.getType() != TokenTypes.VARIABLE_DEF || isTypeField(ast))
394                 && hasNotAllowedTwoEmptyLinesBefore(ast);
395     }
396 
397     /**
398      * Process Package.
399      *
400      * @param ast token
401      * @param nextToken next token
402      */
403     private void processPackage(DetailAST ast, DetailAST nextToken) {
404         if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) {
405             if (CheckUtil.isPackageInfo(getFilePath())) {
406                 if (!ast.getFirstChild().hasChildren() && !isPrecededByJavadoc(ast)) {
407                     log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText());
408                 }
409             }
410             else {
411                 log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText());
412             }
413         }
414         if (isLineEmptyAfterPackage(ast)) {
415             final DetailAST elementAst = getViolationAstForPackage(ast);
416             log(elementAst, MSG_SHOULD_BE_SEPARATED, elementAst.getText());
417         }
418         else if (nextToken != null && !hasEmptyLineAfter(ast)) {
419             log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
420         }
421     }
422 
423     /**
424      * Checks if there is another element at next line of package declaration.
425      *
426      * @param ast Package ast.
427      * @return true, if there is an element.
428      */
429     private static boolean isLineEmptyAfterPackage(DetailAST ast) {
430         DetailAST nextElement = ast;
431         final int lastChildLineNo = ast.getLastChild().getLineNo();
432         while (nextElement.getLineNo() < lastChildLineNo + 1
433                 && nextElement.getNextSibling() != null) {
434             nextElement = nextElement.getNextSibling();
435         }
436         return nextElement.getLineNo() == lastChildLineNo + 1;
437     }
438 
439     /**
440      * Gets the Ast on which violation is to be given for package declaration.
441      *
442      * @param ast Package ast.
443      * @return Violation ast.
444      */
445     private static DetailAST getViolationAstForPackage(DetailAST ast) {
446         DetailAST nextElement = ast;
447         final int lastChildLineNo = ast.getLastChild().getLineNo();
448         while (nextElement.getLineNo() < lastChildLineNo + 1) {
449             nextElement = nextElement.getNextSibling();
450         }
451         return nextElement;
452     }
453 
454     /**
455      * Process Import.
456      *
457      * @param ast token
458      * @param nextToken next token
459      */
460     private void processImport(DetailAST ast, DetailAST nextToken) {
461         if (!TokenUtil.isOfType(nextToken, TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT,
462                 TokenTypes.MODULE_IMPORT)
463             && !hasEmptyLineAfter(ast)) {
464             log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText());
465         }
466     }
467 
468     /**
469      * Process Variable.
470      *
471      * @param ast token
472      * @param nextToken next Token
473      */
474     private void processVariableDef(DetailAST ast, DetailAST nextToken) {
475         if (isTypeField(ast) && !hasEmptyLineAfter(ast)
476                 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) {
477             log(nextToken, MSG_SHOULD_BE_SEPARATED,
478                     nextToken.getText());
479         }
480     }
481 
482     /**
483      * Checks whether token placement violates policy of empty line between fields.
484      *
485      * @param detailAST token to be analyzed
486      * @return true if policy is violated and warning should be raised; false otherwise
487      */
488     private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) {
489         return detailAST.getType() != TokenTypes.RCURLY
490                 && (!allowNoEmptyLineBetweenFields
491                     || !TokenUtil.isOfType(detailAST, TokenTypes.COMMA, TokenTypes.VARIABLE_DEF));
492     }
493 
494     /**
495      * Checks if a token has empty two previous lines and multiple empty lines is not allowed.
496      *
497      * @param token DetailAST token
498      * @return true, if token has empty two lines before and allowMultipleEmptyLines is false
499      */
500     private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) {
501         return !allowMultipleEmptyLines
502                 && (hasEmptyLineBefore(token) || token.findFirstToken(TokenTypes.TYPE) != null)
503                 && isPrePreviousLineEmpty(token);
504     }
505 
506     /**
507      * Check if group of comments located right before token has more than one previous empty line.
508      *
509      * @param token DetailAST token
510      */
511     private void checkComments(DetailAST token) {
512         if (!allowMultipleEmptyLines) {
513             if (TokenUtil.isOfType(token.getType(), TOKENS_TO_CHECK_FOR_PRECEDING_COMMENTS)) {
514                 DetailAST previousNode = token.getPreviousSibling();
515                 while (isCommentInBeginningOfLine(previousNode)) {
516                     if (hasEmptyLineBefore(previousNode) && isPrePreviousLineEmpty(previousNode)) {
517                         log(previousNode, MSG_MULTIPLE_LINES, previousNode.getText());
518                     }
519                     previousNode = previousNode.getPreviousSibling();
520                 }
521             }
522             else {
523                 checkCommentsInsideToken(token);
524             }
525         }
526     }
527 
528     /**
529      * Check if group of comments located at the start of token has more than one previous empty
530      * line.
531      *
532      * @param token DetailAST token
533      */
534     private void checkCommentsInsideToken(DetailAST token) {
535         final List<DetailAST> childNodes = new ArrayList<>();
536         DetailAST childNode = token.getLastChild();
537         while (childNode != null) {
538             if (childNode.getType() == TokenTypes.MODIFIERS) {
539                 for (DetailAST node = token.getFirstChild().getLastChild();
540                          node != null;
541                          node = node.getPreviousSibling()) {
542                     if (isCommentInBeginningOfLine(node)) {
543                         childNodes.add(node);
544                     }
545                 }
546             }
547             else if (isCommentInBeginningOfLine(childNode)) {
548                 childNodes.add(childNode);
549             }
550             childNode = childNode.getPreviousSibling();
551         }
552         for (DetailAST node : childNodes) {
553             if (hasEmptyLineBefore(node) && isPrePreviousLineEmpty(node)) {
554                 log(node, MSG_MULTIPLE_LINES, node.getText());
555             }
556         }
557     }
558 
559     /**
560      * Checks if a token has empty pre-previous line.
561      *
562      * @param token DetailAST token.
563      * @return true, if token has empty lines before.
564      */
565     private boolean isPrePreviousLineEmpty(DetailAST token) {
566         boolean result = false;
567         final int lineNo = token.getLineNo();
568         // 3 is the number of the pre-previous line because the numbering starts from zero.
569         final int number = 3;
570         if (lineNo >= number) {
571             final String prePreviousLine = getLine(lineNo - number);
572 
573             result = CommonUtil.isBlank(prePreviousLine);
574             final boolean previousLineIsEmpty = CommonUtil.isBlank(getLine(lineNo - 2));
575 
576             if (previousLineIsEmpty && result) {
577                 result = true;
578             }
579             else if (token.findFirstToken(TokenTypes.TYPE) != null) {
580                 result = isTwoPrecedingPreviousLinesFromCommentEmpty(token);
581             }
582         }
583         return result;
584 
585     }
586 
587     /**
588      * Checks if token has two preceding lines empty, starting from its describing comment.
589      *
590      * @param token token checked.
591      * @return true, if both previous and pre-previous lines from dependent comment are empty
592      */
593     private boolean isTwoPrecedingPreviousLinesFromCommentEmpty(DetailAST token) {
594         boolean upToPrePreviousLinesEmpty = false;
595 
596         for (DetailAST typeChild = token.findFirstToken(TokenTypes.TYPE).getLastChild();
597              typeChild != null; typeChild = typeChild.getPreviousSibling()) {
598 
599             if (typeChild.getLineNo() > 2
600                 && isTokenNotOnPreviousSiblingLines(typeChild, token)) {
601 
602                 final String commentBeginningPreviousLine =
603                     getLine(typeChild.getLineNo() - 2);
604                 final String commentBeginningPrePreviousLine =
605                     getLine(typeChild.getLineNo() - 3);
606 
607                 if (CommonUtil.isBlank(commentBeginningPreviousLine)
608                     && CommonUtil.isBlank(commentBeginningPrePreviousLine)) {
609                     upToPrePreviousLinesEmpty = true;
610                     break;
611                 }
612 
613             }
614 
615         }
616 
617         return upToPrePreviousLinesEmpty;
618     }
619 
620     /**
621      * Checks if token is not placed on the realm of previous sibling of token's parent.
622      *
623      * @param token token checked.
624      * @param parentToken parent token.
625      * @return true, if child token doesn't occupy parent token's previous sibling's realm.
626      */
627     private static boolean isTokenNotOnPreviousSiblingLines(DetailAST token,
628                                                             DetailAST parentToken) {
629         DetailAST previousSibling = parentToken.getPreviousSibling();
630         for (DetailAST astNode = previousSibling; astNode != null;
631              astNode = astNode.getLastChild()) {
632             previousSibling = astNode;
633         }
634 
635         return previousSibling == null
636                 || token.getLineNo() != previousSibling.getLineNo();
637     }
638 
639     /**
640      * Checks if token have empty line after.
641      *
642      * @param token token.
643      * @return true if token have empty line after.
644      */
645     private boolean hasEmptyLineAfter(DetailAST token) {
646         DetailAST lastToken = token.getLastChild().getLastChild();
647         if (lastToken == null) {
648             lastToken = token.getLastChild();
649         }
650         DetailAST nextToken = token.getNextSibling();
651         if (TokenUtil.isCommentType(nextToken.getType())) {
652             nextToken = nextToken.getNextSibling();
653         }
654         // Start of the next token
655         final int nextBegin = nextToken.getLineNo();
656         // End of current token.
657         final int currentEnd = lastToken.getLineNo();
658         return hasEmptyLine(currentEnd + 1, nextBegin - 1);
659     }
660 
661     /**
662      * Finds comment in next sibling of given packageDef.
663      *
664      * @param packageDef token to check
665      * @return comment under the token
666      */
667     private static Optional<DetailAST> findCommentUnder(DetailAST packageDef) {
668         return Optional.ofNullable(packageDef.getNextSibling())
669             .map(sibling -> sibling.findFirstToken(TokenTypes.MODIFIERS))
670             .map(DetailAST::getFirstChild)
671             .filter(token -> TokenUtil.isCommentType(token.getType()))
672             .filter(comment -> comment.getLineNo() == packageDef.getLineNo() + 1);
673     }
674 
675     /**
676      * Checks, whether there are empty lines within the specified line range. Line numbering is
677      * started from 1 for parameter values
678      *
679      * @param startLine number of the first line in the range
680      * @param endLine number of the second line in the range
681      * @return {@code true} if found any blank line within the range, {@code false}
682      *         otherwise
683      */
684     private boolean hasEmptyLine(int startLine, int endLine) {
685         // Initial value is false - blank line not found
686         boolean result = false;
687         for (int line = startLine; line <= endLine; line++) {
688             // Check, if the line is blank. Lines are numbered from 0, so subtract 1
689             if (CommonUtil.isBlank(getLine(line - 1))) {
690                 result = true;
691                 break;
692             }
693         }
694         return result;
695     }
696 
697     /**
698      * Checks if a token has an empty line before.
699      *
700      * @param token token.
701      * @return true, if token have empty line before.
702      */
703     private boolean hasEmptyLineBefore(DetailAST token) {
704         boolean result = false;
705         final int lineNo = token.getLineNo();
706         if (lineNo != 1) {
707             // [lineNo - 2] is the number of the previous line as the numbering starts from zero.
708             final String lineBefore = getLine(lineNo - 2);
709 
710             result = CommonUtil.isBlank(lineBefore);
711         }
712         return result;
713     }
714 
715     /**
716      * Check if token is comment, which starting in beginning of line.
717      *
718      * @param comment comment token for check.
719      * @return true, if token is comment, which starting in beginning of line.
720      */
721     private boolean isCommentInBeginningOfLine(DetailAST comment) {
722         // comment.getLineNo() - 1 is the number of the previous line as the numbering starts
723         // from zero.
724         boolean result = false;
725         if (comment != null) {
726             final String lineWithComment = getLine(comment.getLineNo() - 1).trim();
727             result = lineWithComment.startsWith("//") || lineWithComment.startsWith("/*");
728         }
729         return result;
730     }
731 
732     /**
733      * Check if token is preceded by javadoc comment.
734      *
735      * @param token token for check.
736      * @return true, if token is preceded by javadoc comment.
737      */
738     private static boolean isPrecededByJavadoc(DetailAST token) {
739         boolean result = false;
740         final DetailAST previous = token.getPreviousSibling();
741         if (previous.getType() == TokenTypes.BLOCK_COMMENT_BEGIN
742                 && JavadocUtil.isJavadocComment(previous.getFirstChild().getText())) {
743             result = true;
744         }
745         return result;
746     }
747 
748     /**
749      * If variable definition is a type field.
750      *
751      * @param variableDef variable definition.
752      * @return true variable definition is a type field.
753      */
754     private static boolean isTypeField(DetailAST variableDef) {
755         final DetailAST parent = variableDef.getParent();
756 
757         return parent.getType() == TokenTypes.COMPACT_COMPILATION_UNIT
758                 || TokenUtil.isTypeDeclaration(parent.getParent().getType());
759     }
760 
761 }