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