001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 the original author or authors. 004// 005// This library is free software; you can redistribute it and/or 006// modify it under the terms of the GNU Lesser General Public 007// License as published by the Free Software Foundation; either 008// version 2.1 of the License, or (at your option) any later version. 009// 010// This library is distributed in the hope that it will be useful, 011// but WITHOUT ANY WARRANTY; without even the implied warranty of 012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013// Lesser General Public License for more details. 014// 015// You should have received a copy of the GNU Lesser General Public 016// License along with this library; if not, write to the Free Software 017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 018/////////////////////////////////////////////////////////////////////////////////////////////// 019 020package com.puppycrawl.tools.checkstyle.checks.whitespace; 021 022import java.util.ArrayList; 023import java.util.LinkedList; 024import java.util.List; 025import java.util.Optional; 026 027import com.puppycrawl.tools.checkstyle.StatelessCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.FileContents; 031import com.puppycrawl.tools.checkstyle.api.TokenTypes; 032import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 033import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 034import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 035import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 036 037/** 038 * <div> 039 * Checks for empty line separators before package, all import declarations, 040 * fields, constructors, methods, nested classes, 041 * static initializers and instance initializers. 042 * </div> 043 * 044 * <p> 045 * Checks for empty line separators before not only statements but 046 * implementation and documentation comments and blocks as well. 047 * </p> 048 * 049 * <p> 050 * ATTENTION: empty line separator is required between token siblings, 051 * not after line where token is found. 052 * If token does not have a sibling of the same type, then empty line 053 * is required at its end (for example for CLASS_DEF it is after '}'). 054 * Also, trailing comments are skipped. 055 * </p> 056 * <ul> 057 * <li> 058 * Property {@code allowMultipleEmptyLines} - Allow multiple empty lines between class members. 059 * Type is {@code boolean}. 060 * Default value is {@code true}. 061 * </li> 062 * <li> 063 * Property {@code allowMultipleEmptyLinesInsideClassMembers} - Allow multiple 064 * empty lines inside class members. 065 * Type is {@code boolean}. 066 * Default value is {@code true}. 067 * </li> 068 * <li> 069 * Property {@code allowNoEmptyLineBetweenFields} - Allow no empty line between fields. 070 * Type is {@code boolean}. 071 * Default value is {@code false}. 072 * </li> 073 * <li> 074 * Property {@code tokens} - tokens to check 075 * Type is {@code java.lang.String[]}. 076 * Validation type is {@code tokenSet}. 077 * Default value is: 078 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF"> 079 * PACKAGE_DEF</a>, 080 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#IMPORT"> 081 * IMPORT</a>, 082 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_IMPORT"> 083 * STATIC_IMPORT</a>, 084 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 085 * CLASS_DEF</a>, 086 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 087 * INTERFACE_DEF</a>, 088 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 089 * ENUM_DEF</a>, 090 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#STATIC_INIT"> 091 * STATIC_INIT</a>, 092 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INSTANCE_INIT"> 093 * INSTANCE_INIT</a>, 094 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 095 * METHOD_DEF</a>, 096 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 097 * CTOR_DEF</a>, 098 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 099 * 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 132public 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}