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