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