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