001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.List; 024import java.util.Optional; 025 026import com.puppycrawl.tools.checkstyle.StatelessCheck; 027import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 031import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 032import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 033import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 034 035/** 036 * <div> 037 * Checks for empty line separators after, 038 * fields, constructors, methods, nested classes, 039 * static initializers and instance initializers. 040 * </div> 041 * 042 * <p> 043 * For package declaration it checks for both before and after 044 * line separators and for import declarations it checks for 045 * empty line separator after last import declaration. 046 * </p> 047 * 048 * <p> 049 * Checks for empty line separators after not only statements but 050 * implementation and documentation comments and blocks as well. 051 * </p> 052 * 053 * <p> 054 * ATTENTION: empty line separator is required between token siblings, 055 * not after line where token is found. 056 * If token does not have a sibling of the same type, then empty line 057 * is required at its end (for example for CLASS_DEF it is after '}'). 058 * Also, trailing comments are skipped. 059 * </p> 060 * 061 * @since 5.8 062 */ 063@StatelessCheck 064public class EmptyLineSeparatorCheck extends AbstractCheck { 065 066 /** 067 * A key is pointing to the warning message empty.line.separator in "messages.properties" 068 * file. 069 */ 070 public static final String MSG_SHOULD_BE_SEPARATED = "empty.line.separator"; 071 072 /** 073 * A key is pointing to the warning message empty.line.separator.multiple.lines 074 * in "messages.properties" 075 * file. 076 */ 077 public static final String MSG_MULTIPLE_LINES = "empty.line.separator.multiple.lines"; 078 079 /** 080 * A key is pointing to the warning message empty.line.separator.lines.after 081 * in "messages.properties" file. 082 */ 083 public static final String MSG_MULTIPLE_LINES_AFTER = 084 "empty.line.separator.multiple.lines.after"; 085 086 /** 087 * A key is pointing to the warning message empty.line.separator.multiple.lines.inside 088 * in "messages.properties" file. 089 */ 090 public static final String MSG_MULTIPLE_LINES_INSIDE = 091 "empty.line.separator.multiple.lines.inside"; 092 093 /** Allow no empty line between fields. */ 094 private boolean allowNoEmptyLineBetweenFields; 095 096 /** Allow multiple empty lines between class members. */ 097 private boolean allowMultipleEmptyLines = true; 098 099 /** Allow multiple empty lines inside class members. */ 100 private boolean allowMultipleEmptyLinesInsideClassMembers = true; 101 102 /** 103 * Setter to allow no empty line between fields. 104 * 105 * @param allow 106 * User's value. 107 * @since 5.8 108 */ 109 public final void setAllowNoEmptyLineBetweenFields(boolean allow) { 110 allowNoEmptyLineBetweenFields = allow; 111 } 112 113 /** 114 * Setter to allow multiple empty lines between class members. 115 * 116 * @param allow User's value. 117 * @since 6.3 118 */ 119 public void setAllowMultipleEmptyLines(boolean allow) { 120 allowMultipleEmptyLines = allow; 121 } 122 123 /** 124 * Setter to allow multiple empty lines inside class members. 125 * 126 * @param allow User's value. 127 * @since 6.18 128 */ 129 public void setAllowMultipleEmptyLinesInsideClassMembers(boolean allow) { 130 allowMultipleEmptyLinesInsideClassMembers = allow; 131 } 132 133 @Override 134 public boolean isCommentNodesRequired() { 135 return true; 136 } 137 138 @Override 139 public int[] getDefaultTokens() { 140 return getAcceptableTokens(); 141 } 142 143 @Override 144 public int[] getAcceptableTokens() { 145 return new int[] { 146 TokenTypes.PACKAGE_DEF, 147 TokenTypes.IMPORT, 148 TokenTypes.STATIC_IMPORT, 149 TokenTypes.CLASS_DEF, 150 TokenTypes.INTERFACE_DEF, 151 TokenTypes.ENUM_DEF, 152 TokenTypes.ENUM_CONSTANT_DEF, 153 TokenTypes.STATIC_INIT, 154 TokenTypes.INSTANCE_INIT, 155 TokenTypes.METHOD_DEF, 156 TokenTypes.CTOR_DEF, 157 TokenTypes.VARIABLE_DEF, 158 TokenTypes.RECORD_DEF, 159 TokenTypes.COMPACT_CTOR_DEF, 160 }; 161 } 162 163 @Override 164 public int[] getRequiredTokens() { 165 return CommonUtil.EMPTY_INT_ARRAY; 166 } 167 168 @Override 169 public void visitToken(DetailAST ast) { 170 171 if (ast.getType() != TokenTypes.ENUM_CONSTANT_DEF) { 172 checkComments(ast); 173 } 174 175 if (hasMultipleLinesBefore(ast)) { 176 log(ast, MSG_MULTIPLE_LINES, ast.getText()); 177 } 178 if (!allowMultipleEmptyLinesInsideClassMembers) { 179 processMultipleLinesInside(ast); 180 } 181 if (ast.getType() == TokenTypes.PACKAGE_DEF) { 182 checkCommentInModifiers(ast); 183 } 184 DetailAST nextToken = ast.getNextSibling(); 185 while (nextToken != null && TokenUtil.isCommentType(nextToken.getType())) { 186 nextToken = nextToken.getNextSibling(); 187 } 188 if (ast.getType() == TokenTypes.PACKAGE_DEF) { 189 processPackage(ast, nextToken); 190 } 191 else if (nextToken != null) { 192 checkToken(ast, nextToken); 193 } 194 } 195 196 /** 197 * Checks that token and next token are separated. 198 * 199 * @param ast token to validate 200 * @param nextToken next sibling of the token 201 */ 202 private void checkToken(DetailAST ast, DetailAST nextToken) { 203 final int astType = ast.getType(); 204 205 switch (astType) { 206 case TokenTypes.VARIABLE_DEF -> processVariableDef(ast, nextToken); 207 208 case TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT -> processImport(ast, nextToken); 209 210 case TokenTypes.ENUM_CONSTANT_DEF -> processEnumConstant(ast); 211 212 default -> { 213 if (nextToken.getType() == TokenTypes.RCURLY) { 214 if (hasNotAllowedTwoEmptyLinesBefore(nextToken)) { 215 final DetailAST result = getLastElementBeforeEmptyLines( 216 ast, nextToken.getLineNo() 217 ); 218 log(result, MSG_MULTIPLE_LINES_AFTER, result.getText()); 219 } 220 } 221 else if (!hasEmptyLineAfter(ast)) { 222 log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 223 } 224 } 225 } 226 } 227 228 /** 229 * Checks that packageDef token is separated from comment in modifiers. 230 * 231 * @param packageDef package def token 232 */ 233 private void checkCommentInModifiers(DetailAST packageDef) { 234 final Optional<DetailAST> comment = findCommentUnder(packageDef); 235 comment.ifPresent(commentValue -> { 236 log(commentValue, MSG_SHOULD_BE_SEPARATED, commentValue.getText()); 237 }); 238 } 239 240 /** 241 * Log violation in case there are multiple empty lines inside constructor, 242 * initialization block or method. 243 * 244 * @param ast the ast to check. 245 */ 246 private void processMultipleLinesInside(DetailAST ast) { 247 final int astType = ast.getType(); 248 if (isClassMemberBlock(astType)) { 249 final List<Integer> emptyLines = getEmptyLines(ast); 250 final List<Integer> emptyLinesToLog = getEmptyLinesToLog(emptyLines); 251 for (Integer lineNo : emptyLinesToLog) { 252 log(getLastElementBeforeEmptyLines(ast, lineNo), MSG_MULTIPLE_LINES_INSIDE); 253 } 254 } 255 } 256 257 /** 258 * Returns the element after which empty lines exist. 259 * 260 * @param ast the ast to check. 261 * @param line the empty line which gives violation. 262 * @return The DetailAST after which empty lines are present. 263 */ 264 private static DetailAST getLastElementBeforeEmptyLines(DetailAST ast, int line) { 265 DetailAST result = ast; 266 if (ast.getFirstChild().getLineNo() <= line) { 267 result = ast.getFirstChild(); 268 while (result.getNextSibling() != null 269 && result.getNextSibling().getLineNo() <= line) { 270 result = result.getNextSibling(); 271 } 272 if (result.hasChildren()) { 273 result = getLastElementBeforeEmptyLines(result, line); 274 } 275 } 276 277 if (result.getNextSibling() != null) { 278 final Optional<DetailAST> postFixNode = getPostFixNode(result.getNextSibling()); 279 if (postFixNode.isPresent()) { 280 // A post fix AST will always have a sibling METHOD CALL 281 // METHOD CALL will at least have two children 282 // The first child is DOT in case of POSTFIX which have at least 2 children 283 // First child of DOT again puts us back to normal AST tree which will 284 // recurse down below from here 285 final DetailAST firstChildAfterPostFix = postFixNode.orElseThrow(); 286 result = getLastElementBeforeEmptyLines(firstChildAfterPostFix, line); 287 } 288 } 289 return result; 290 } 291 292 /** 293 * Gets postfix Node from AST if present. 294 * 295 * @param ast the AST used to get postfix Node. 296 * @return Optional postfix node. 297 */ 298 private static Optional<DetailAST> getPostFixNode(DetailAST ast) { 299 Optional<DetailAST> result = Optional.empty(); 300 if (ast.getType() == TokenTypes.EXPR 301 // EXPR always has at least one child 302 && ast.getFirstChild().getType() == TokenTypes.METHOD_CALL) { 303 // METHOD CALL always has at two least child 304 final DetailAST node = ast.getFirstChild().getFirstChild(); 305 if (node.getType() == TokenTypes.DOT) { 306 result = Optional.of(node); 307 } 308 } 309 return result; 310 } 311 312 /** 313 * Whether the AST is a class member block. 314 * 315 * @param astType the AST to check. 316 * @return true if the AST is a class member block. 317 */ 318 private static boolean isClassMemberBlock(int astType) { 319 return TokenUtil.isOfType(astType, 320 TokenTypes.STATIC_INIT, TokenTypes.INSTANCE_INIT, TokenTypes.METHOD_DEF, 321 TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF); 322 } 323 324 /** 325 * Get list of empty lines. 326 * 327 * @param ast the ast to check. 328 * @return list of line numbers for empty lines. 329 */ 330 private List<Integer> getEmptyLines(DetailAST ast) { 331 final DetailAST lastToken = ast.getLastChild().getLastChild(); 332 int lastTokenLineNo = 0; 333 if (lastToken != null) { 334 // -1 as count starts from 0 335 // -2 as last token line cannot be empty, because it is a RCURLY 336 lastTokenLineNo = lastToken.getLineNo() - 2; 337 } 338 final List<Integer> emptyLines = new ArrayList<>(); 339 340 for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) { 341 if (CommonUtil.isBlank(getLine(lineNo))) { 342 emptyLines.add(lineNo); 343 } 344 } 345 return emptyLines; 346 } 347 348 /** 349 * Get list of empty lines to log. 350 * 351 * @param emptyLines list of empty lines. 352 * @return list of empty lines to log. 353 */ 354 private static List<Integer> getEmptyLinesToLog(Iterable<Integer> emptyLines) { 355 final List<Integer> emptyLinesToLog = new ArrayList<>(); 356 int previousEmptyLineNo = -1; 357 for (int emptyLineNo : emptyLines) { 358 if (previousEmptyLineNo + 1 == emptyLineNo) { 359 emptyLinesToLog.add(previousEmptyLineNo); 360 } 361 previousEmptyLineNo = emptyLineNo; 362 } 363 return emptyLinesToLog; 364 } 365 366 /** 367 * Whether the token has not allowed multiple empty lines before. 368 * 369 * @param ast the ast to check. 370 * @return true if the token has not allowed multiple empty lines before. 371 */ 372 private boolean hasMultipleLinesBefore(DetailAST ast) { 373 return (ast.getType() != TokenTypes.VARIABLE_DEF || isTypeField(ast)) 374 && hasNotAllowedTwoEmptyLinesBefore(ast) 375 && ast.getType() != TokenTypes.ENUM_CONSTANT_DEF; 376 } 377 378 /** 379 * Process Package. 380 * 381 * @param ast token 382 * @param nextToken next token 383 */ 384 private void processPackage(DetailAST ast, DetailAST nextToken) { 385 if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) { 386 if (CheckUtil.isPackageInfo(getFilePath())) { 387 if (!ast.getFirstChild().hasChildren() && !isPrecededByJavadoc(ast)) { 388 log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText()); 389 } 390 } 391 else { 392 log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText()); 393 } 394 } 395 if (isLineEmptyAfterPackage(ast)) { 396 final DetailAST elementAst = getViolationAstForPackage(ast); 397 log(elementAst, MSG_SHOULD_BE_SEPARATED, elementAst.getText()); 398 } 399 else if (nextToken != null && !hasEmptyLineAfter(ast)) { 400 log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 401 } 402 } 403 404 /** 405 * Checks if there is another element at next line of package declaration. 406 * 407 * @param ast Package ast. 408 * @return true, if there is an element. 409 */ 410 private static boolean isLineEmptyAfterPackage(DetailAST ast) { 411 DetailAST nextElement = ast; 412 final int lastChildLineNo = ast.getLastChild().getLineNo(); 413 while (nextElement.getLineNo() < lastChildLineNo + 1 414 && nextElement.getNextSibling() != null) { 415 nextElement = nextElement.getNextSibling(); 416 } 417 return nextElement.getLineNo() == lastChildLineNo + 1; 418 } 419 420 /** 421 * Gets the Ast on which violation is to be given for package declaration. 422 * 423 * @param ast Package ast. 424 * @return Violation ast. 425 */ 426 private static DetailAST getViolationAstForPackage(DetailAST ast) { 427 DetailAST nextElement = ast; 428 final int lastChildLineNo = ast.getLastChild().getLineNo(); 429 while (nextElement.getLineNo() < lastChildLineNo + 1) { 430 nextElement = nextElement.getNextSibling(); 431 } 432 return nextElement; 433 } 434 435 /** 436 * Process Import. 437 * 438 * @param ast token 439 * @param nextToken next token 440 */ 441 private void processImport(DetailAST ast, DetailAST nextToken) { 442 if (!TokenUtil.isOfType(nextToken, TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT) 443 && !hasEmptyLineAfter(ast)) { 444 log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 445 } 446 } 447 448 /** 449 * Process Variable. 450 * 451 * @param ast token 452 * @param nextToken next Token 453 */ 454 private void processVariableDef(DetailAST ast, DetailAST nextToken) { 455 if (isTypeField(ast) && !hasEmptyLineAfter(ast) 456 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) { 457 log(nextToken, MSG_SHOULD_BE_SEPARATED, 458 nextToken.getText()); 459 } 460 } 461 462 /** 463 * Process Enum Constant. 464 * 465 * @param ast token 466 */ 467 public void processEnumConstant(DetailAST ast) { 468 469 // Check empty line before enum Constant (comment skipped) 470 final DetailAST secondLastChild = ast.getLastChild().getPreviousSibling(); 471 472 final boolean hasMultipleEmptyLinesBeforeConstant = 473 !isEnumConstantOnSharedLine(ast) 474 && hasNotAllowedTwoEmptyLinesBefore(ast); 475 476 final boolean hasMultipleEmptyLinesBeforeConstantComment = 477 secondLastChild.getLineNo() > ast.getPreviousSibling().getLineNo() 478 && hasMultipleLinesBefore(secondLastChild); 479 480 if (hasMultipleEmptyLinesBeforeConstant 481 || hasMultipleEmptyLinesBeforeConstantComment) { 482 log(ast, MSG_MULTIPLE_LINES, ast.getText()); 483 } 484 485 if (!allowMultipleEmptyLines) { 486 checkMultipleEmptyLinesAfterLastEnumConstant(ast); 487 } 488 } 489 490 /** 491 * Checks whether the enum constant is placed on a line that also contains 492 * another enum constant. 493 * 494 * @param ast the ast to check. 495 * @return true if the enum constant shares its line with another enum constant 496 */ 497 private static boolean isEnumConstantOnSharedLine(DetailAST ast) { 498 boolean result = false; 499 final DetailAST objectBlock = ast.getParent(); 500 // true if token is placed in single line enum 501 if (objectBlock.getParent().getLineNo() == ast.getLineNo()) { 502 result = true; 503 } 504 else { 505 // For multiline enum, true if token is placed in group of single line 506 // except first enum constant 507 final DetailAST firstConstant = 508 objectBlock.findFirstToken(TokenTypes.ENUM_CONSTANT_DEF); 509 if (firstConstant.getLineNo() == ast.getLineNo() 510 && firstConstant.getColumnNo() < ast.getColumnNo()) { 511 result = true; 512 } 513 } 514 515 return result; 516 } 517 518 /** 519 * Checks whether there are more than one empty lines after the last enum 520 * constant before the closing brace. 521 * 522 * @param ast token 523 */ 524 private void checkMultipleEmptyLinesAfterLastEnumConstant(DetailAST ast) { 525 // skip next token is comma or comment 526 DetailAST nextToken = ast.getNextSibling(); 527 while (nextToken.getType() == TokenTypes.COMMA 528 || TokenUtil.isCommentType(nextToken.getType())) { 529 nextToken = nextToken.getNextSibling(); 530 } 531 532 // Check consecutive empty line after last 533 // enum constant (skipped comment after token) 534 if (nextToken.getType() == TokenTypes.RCURLY 535 && hasConsecutiveEmptyLines(ast.getLineNo(), nextToken.getLineNo())) { 536 log(ast, MSG_MULTIPLE_LINES_AFTER, ast.getText()); 537 } 538 } 539 540 /** 541 * Checks, whether there is a consecutive empty lines within the specified line range. 542 * 543 * @param startLine first line in the range 544 * @param endLine second line in the range 545 * @return true, if found any consecutive empty lines within the range 546 */ 547 private boolean hasConsecutiveEmptyLines(int startLine, int endLine) { 548 int emptyLines = 0; 549 boolean hasConsecutiveEmptyLines = false; 550 for (int lineNo = startLine; lineNo < endLine; ++lineNo) { 551 if (CommonUtil.isBlank(getLine(lineNo))) { 552 emptyLines++; 553 if (emptyLines > 1) { 554 hasConsecutiveEmptyLines = true; 555 break; 556 } 557 } 558 else { 559 emptyLines = 0; 560 } 561 } 562 563 return hasConsecutiveEmptyLines; 564 } 565 566 /** 567 * Checks whether token placement violates policy of empty line between fields. 568 * 569 * @param detailAST token to be analyzed 570 * @return true if policy is violated and warning should be raised; false otherwise 571 */ 572 private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) { 573 return detailAST.getType() != TokenTypes.RCURLY 574 && (!allowNoEmptyLineBetweenFields 575 || !TokenUtil.isOfType(detailAST, TokenTypes.COMMA, TokenTypes.VARIABLE_DEF)); 576 } 577 578 /** 579 * Checks if a token has empty two previous lines and multiple empty lines is not allowed. 580 * 581 * @param token DetailAST token 582 * @return true, if token has empty two lines before and allowMultipleEmptyLines is false 583 */ 584 private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) { 585 return !allowMultipleEmptyLines && hasEmptyLineBefore(token) 586 && isPrePreviousLineEmpty(token); 587 } 588 589 /** 590 * Check if group of comments located right before token has more than one previous empty line. 591 * 592 * @param token DetailAST token 593 */ 594 private void checkComments(DetailAST token) { 595 if (!allowMultipleEmptyLines) { 596 if (TokenUtil.isOfType(token, 597 TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT, 598 TokenTypes.STATIC_IMPORT, TokenTypes.STATIC_INIT)) { 599 DetailAST previousNode = token.getPreviousSibling(); 600 while (isCommentInBeginningOfLine(previousNode)) { 601 if (hasEmptyLineBefore(previousNode) && isPrePreviousLineEmpty(previousNode)) { 602 log(previousNode, MSG_MULTIPLE_LINES, previousNode.getText()); 603 } 604 previousNode = previousNode.getPreviousSibling(); 605 } 606 } 607 else { 608 checkCommentsInsideToken(token); 609 } 610 } 611 } 612 613 /** 614 * Check if group of comments located at the start of token has more than one previous empty 615 * line. 616 * 617 * @param token DetailAST token 618 */ 619 private void checkCommentsInsideToken(DetailAST token) { 620 final List<DetailAST> childNodes = new ArrayList<>(); 621 DetailAST childNode = token.getLastChild(); 622 while (childNode != null) { 623 if (childNode.getType() == TokenTypes.MODIFIERS) { 624 for (DetailAST node = token.getFirstChild().getLastChild(); 625 node != null; 626 node = node.getPreviousSibling()) { 627 if (isCommentInBeginningOfLine(node)) { 628 childNodes.add(node); 629 } 630 } 631 } 632 else if (isCommentInBeginningOfLine(childNode)) { 633 childNodes.add(childNode); 634 } 635 childNode = childNode.getPreviousSibling(); 636 } 637 for (DetailAST node : childNodes) { 638 if (hasEmptyLineBefore(node) && isPrePreviousLineEmpty(node)) { 639 log(node, MSG_MULTIPLE_LINES, node.getText()); 640 } 641 } 642 } 643 644 /** 645 * Checks if a token has empty pre-previous line. 646 * 647 * @param token DetailAST token. 648 * @return true, if token has empty lines before. 649 */ 650 private boolean isPrePreviousLineEmpty(DetailAST token) { 651 boolean result = false; 652 final int lineNo = token.getLineNo(); 653 // 3 is the number of the pre-previous line because the numbering starts from zero. 654 final int number = 3; 655 if (lineNo >= number) { 656 final String prePreviousLine = getLine(lineNo - number); 657 658 result = CommonUtil.isBlank(prePreviousLine); 659 final boolean previousLineIsEmpty = CommonUtil.isBlank(getLine(lineNo - 2)); 660 661 if (previousLineIsEmpty && result) { 662 result = true; 663 } 664 else if (token.findFirstToken(TokenTypes.TYPE) != null) { 665 result = isTwoPrecedingPreviousLinesFromCommentEmpty(token); 666 } 667 } 668 return result; 669 670 } 671 672 /** 673 * Checks if token has two preceding lines empty, starting from its describing comment. 674 * 675 * @param token token checked. 676 * @return true, if both previous and pre-previous lines from dependent comment are empty 677 */ 678 private boolean isTwoPrecedingPreviousLinesFromCommentEmpty(DetailAST token) { 679 boolean upToPrePreviousLinesEmpty = false; 680 681 for (DetailAST typeChild = token.findFirstToken(TokenTypes.TYPE).getLastChild(); 682 typeChild != null; typeChild = typeChild.getPreviousSibling()) { 683 684 if (isTokenNotOnPreviousSiblingLines(typeChild, token)) { 685 686 final String commentBeginningPreviousLine = 687 getLine(typeChild.getLineNo() - 2); 688 final String commentBeginningPrePreviousLine = 689 getLine(typeChild.getLineNo() - 3); 690 691 if (CommonUtil.isBlank(commentBeginningPreviousLine) 692 && CommonUtil.isBlank(commentBeginningPrePreviousLine)) { 693 upToPrePreviousLinesEmpty = true; 694 break; 695 } 696 697 } 698 699 } 700 701 return upToPrePreviousLinesEmpty; 702 } 703 704 /** 705 * Checks if token is not placed on the realm of previous sibling of token's parent. 706 * 707 * @param token token checked. 708 * @param parentToken parent token. 709 * @return true, if child token doesn't occupy parent token's previous sibling's realm. 710 */ 711 private static boolean isTokenNotOnPreviousSiblingLines(DetailAST token, 712 DetailAST parentToken) { 713 DetailAST previousSibling = parentToken.getPreviousSibling(); 714 for (DetailAST astNode = previousSibling; astNode != null; 715 astNode = astNode.getLastChild()) { 716 previousSibling = astNode; 717 } 718 719 return token.getLineNo() != previousSibling.getLineNo(); 720 } 721 722 /** 723 * Checks if token have empty line after. 724 * 725 * @param token token. 726 * @return true if token have empty line after. 727 */ 728 private boolean hasEmptyLineAfter(DetailAST token) { 729 DetailAST lastToken = token.getLastChild().getLastChild(); 730 if (lastToken == null) { 731 lastToken = token.getLastChild(); 732 } 733 DetailAST nextToken = token.getNextSibling(); 734 if (TokenUtil.isCommentType(nextToken.getType())) { 735 nextToken = nextToken.getNextSibling(); 736 } 737 // Start of the next token 738 final int nextBegin = nextToken.getLineNo(); 739 // End of current token. 740 final int currentEnd = lastToken.getLineNo(); 741 return hasEmptyLine(currentEnd + 1, nextBegin - 1); 742 } 743 744 /** 745 * Finds comment in next sibling of given packageDef. 746 * 747 * @param packageDef token to check 748 * @return comment under the token 749 */ 750 private static Optional<DetailAST> findCommentUnder(DetailAST packageDef) { 751 return Optional.ofNullable(packageDef.getNextSibling()) 752 .map(sibling -> sibling.findFirstToken(TokenTypes.MODIFIERS)) 753 .map(DetailAST::getFirstChild) 754 .filter(token -> TokenUtil.isCommentType(token.getType())) 755 .filter(comment -> comment.getLineNo() == packageDef.getLineNo() + 1); 756 } 757 758 /** 759 * Checks, whether there are empty lines within the specified line range. Line numbering is 760 * started from 1 for parameter values 761 * 762 * @param startLine number of the first line in the range 763 * @param endLine number of the second line in the range 764 * @return {@code true} if found any blank line within the range, {@code false} 765 * otherwise 766 */ 767 private boolean hasEmptyLine(int startLine, int endLine) { 768 // Initial value is false - blank line not found 769 boolean result = false; 770 for (int line = startLine; line <= endLine; line++) { 771 // Check, if the line is blank. Lines are numbered from 0, so subtract 1 772 if (CommonUtil.isBlank(getLine(line - 1))) { 773 result = true; 774 break; 775 } 776 } 777 return result; 778 } 779 780 /** 781 * Checks if a token has an empty line before. 782 * 783 * @param token token. 784 * @return true, if token have empty line before. 785 */ 786 private boolean hasEmptyLineBefore(DetailAST token) { 787 boolean result = false; 788 final int lineNo = token.getLineNo(); 789 if (lineNo != 1) { 790 // [lineNo - 2] is the number of the previous line as the numbering starts from zero. 791 final String lineBefore = getLine(lineNo - 2); 792 793 if (CommonUtil.isBlank(lineBefore)) { 794 result = true; 795 } 796 else if (token.findFirstToken(TokenTypes.TYPE) != null) { 797 for (DetailAST typeChild = token.findFirstToken(TokenTypes.TYPE).getLastChild(); 798 typeChild != null && !result && typeChild.getLineNo() > 1; 799 typeChild = typeChild.getPreviousSibling()) { 800 801 final String commentBeginningPreviousLine = 802 getLine(typeChild.getLineNo() - 2); 803 result = CommonUtil.isBlank(commentBeginningPreviousLine); 804 805 } 806 } 807 } 808 return result; 809 } 810 811 /** 812 * Check if token is comment, which starting in beginning of line. 813 * 814 * @param comment comment token for check. 815 * @return true, if token is comment, which starting in beginning of line. 816 */ 817 private boolean isCommentInBeginningOfLine(DetailAST comment) { 818 // comment.getLineNo() - 1 is the number of the previous line as the numbering starts 819 // from zero. 820 boolean result = false; 821 if (comment != null) { 822 final String lineWithComment = getLine(comment.getLineNo() - 1).trim(); 823 result = lineWithComment.startsWith("//") || lineWithComment.startsWith("/*"); 824 } 825 return result; 826 } 827 828 /** 829 * Check if token is preceded by javadoc comment. 830 * 831 * @param token token for check. 832 * @return true, if token is preceded by javadoc comment. 833 */ 834 private static boolean isPrecededByJavadoc(DetailAST token) { 835 boolean result = false; 836 final DetailAST previous = token.getPreviousSibling(); 837 if (previous.getType() == TokenTypes.BLOCK_COMMENT_BEGIN 838 && JavadocUtil.isJavadocComment(previous.getFirstChild().getText())) { 839 result = true; 840 } 841 return result; 842 } 843 844 /** 845 * If variable definition is a type field. 846 * 847 * @param variableDef variable definition. 848 * @return true variable definition is a type field. 849 */ 850 private static boolean isTypeField(DetailAST variableDef) { 851 return TokenUtil.isTypeDeclaration(variableDef.getParent().getParent().getType()); 852 } 853 854}