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