001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2021 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 private List<Integer> getEmptyLines(DetailAST ast) { 587 final DetailAST lastToken = ast.getLastChild().getLastChild(); 588 int lastTokenLineNo = 0; 589 if (lastToken != null) { 590 // -1 as count starts from 0 591 // -2 as last token line cannot be empty, because it is a RCURLY 592 lastTokenLineNo = lastToken.getLineNo() - 2; 593 } 594 final List<Integer> emptyLines = new ArrayList<>(); 595 final FileContents fileContents = getFileContents(); 596 597 for (int lineNo = ast.getLineNo(); lineNo <= lastTokenLineNo; lineNo++) { 598 if (fileContents.lineIsBlank(lineNo)) { 599 emptyLines.add(lineNo); 600 } 601 } 602 return emptyLines; 603 } 604 605 /** 606 * Get list of empty lines to log. 607 * 608 * @param emptyLines list of empty lines. 609 * @return list of empty lines to log. 610 */ 611 private static List<Integer> getEmptyLinesToLog(List<Integer> emptyLines) { 612 final List<Integer> emptyLinesToLog = new ArrayList<>(); 613 if (emptyLines.size() >= 2) { 614 int previousEmptyLineNo = emptyLines.get(0); 615 for (int emptyLineNo : emptyLines) { 616 if (previousEmptyLineNo + 1 == emptyLineNo) { 617 emptyLinesToLog.add(previousEmptyLineNo); 618 } 619 previousEmptyLineNo = emptyLineNo; 620 } 621 } 622 return emptyLinesToLog; 623 } 624 625 /** 626 * Whether the token has not allowed multiple empty lines before. 627 * 628 * @param ast the ast to check. 629 * @return true if the token has not allowed multiple empty lines before. 630 */ 631 private boolean hasMultipleLinesBefore(DetailAST ast) { 632 boolean result = false; 633 if ((ast.getType() != TokenTypes.VARIABLE_DEF 634 || isTypeField(ast)) 635 && hasNotAllowedTwoEmptyLinesBefore(ast)) { 636 result = true; 637 } 638 return result; 639 } 640 641 /** 642 * Process Package. 643 * 644 * @param ast token 645 * @param nextToken next token 646 */ 647 private void processPackage(DetailAST ast, DetailAST nextToken) { 648 if (ast.getLineNo() > 1 && !hasEmptyLineBefore(ast)) { 649 if (getFileContents().getFileName().endsWith("package-info.java")) { 650 if (!ast.getFirstChild().hasChildren() && !isPrecededByJavadoc(ast)) { 651 log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText()); 652 } 653 } 654 else { 655 log(ast, MSG_SHOULD_BE_SEPARATED, ast.getText()); 656 } 657 } 658 if (isLineEmptyAfterPackage(ast)) { 659 final DetailAST elementAst = getViolationAstForPackage(ast); 660 log(elementAst, MSG_SHOULD_BE_SEPARATED, elementAst.getText()); 661 } 662 else if (!hasEmptyLineAfter(ast)) { 663 log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 664 } 665 } 666 667 /** 668 * Checks if there is another element at next line of package declaration. 669 * 670 * @param ast Package ast. 671 * @return true, if there is an element. 672 */ 673 private static boolean isLineEmptyAfterPackage(DetailAST ast) { 674 DetailAST nextElement = ast.getNextSibling(); 675 final int lastChildLineNo = ast.getLastChild().getLineNo(); 676 while (nextElement.getLineNo() < lastChildLineNo + 1 677 && nextElement.getNextSibling() != null) { 678 nextElement = nextElement.getNextSibling(); 679 } 680 return nextElement.getLineNo() == lastChildLineNo + 1; 681 } 682 683 /** 684 * Gets the Ast on which violation is to be given for package declaration. 685 * 686 * @param ast Package ast. 687 * @return Violation ast. 688 */ 689 private static DetailAST getViolationAstForPackage(DetailAST ast) { 690 DetailAST nextElement = ast.getNextSibling(); 691 final int lastChildLineNo = ast.getLastChild().getLineNo(); 692 while (nextElement.getLineNo() < lastChildLineNo + 1) { 693 nextElement = nextElement.getNextSibling(); 694 } 695 return nextElement; 696 } 697 698 /** 699 * Process Import. 700 * 701 * @param ast token 702 * @param nextToken next token 703 */ 704 private void processImport(DetailAST ast, DetailAST nextToken) { 705 if (!TokenUtil.isOfType(nextToken, TokenTypes.IMPORT, TokenTypes.STATIC_IMPORT) 706 && !hasEmptyLineAfter(ast)) { 707 log(nextToken, MSG_SHOULD_BE_SEPARATED, nextToken.getText()); 708 } 709 } 710 711 /** 712 * Process Variable. 713 * 714 * @param ast token 715 * @param nextToken next Token 716 */ 717 private void processVariableDef(DetailAST ast, DetailAST nextToken) { 718 if (isTypeField(ast) && !hasEmptyLineAfter(ast) 719 && isViolatingEmptyLineBetweenFieldsPolicy(nextToken)) { 720 log(nextToken, MSG_SHOULD_BE_SEPARATED, 721 nextToken.getText()); 722 } 723 } 724 725 /** 726 * Checks whether token placement violates policy of empty line between fields. 727 * 728 * @param detailAST token to be analyzed 729 * @return true if policy is violated and warning should be raised; false otherwise 730 */ 731 private boolean isViolatingEmptyLineBetweenFieldsPolicy(DetailAST detailAST) { 732 return detailAST.getType() != TokenTypes.RCURLY 733 && (!allowNoEmptyLineBetweenFields 734 || !TokenUtil.isOfType(detailAST, TokenTypes.COMMA, TokenTypes.VARIABLE_DEF)); 735 } 736 737 /** 738 * Checks if a token has empty two previous lines and multiple empty lines is not allowed. 739 * 740 * @param token DetailAST token 741 * @return true, if token has empty two lines before and allowMultipleEmptyLines is false 742 */ 743 private boolean hasNotAllowedTwoEmptyLinesBefore(DetailAST token) { 744 return !allowMultipleEmptyLines && hasEmptyLineBefore(token) 745 && isPrePreviousLineEmpty(token); 746 } 747 748 /** 749 * Check if group of comments located right before token has more than one previous empty line. 750 * 751 * @param token DetailAST token 752 */ 753 private void checkComments(DetailAST token) { 754 if (!allowMultipleEmptyLines) { 755 if (TokenUtil.isOfType(token, 756 TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT, 757 TokenTypes.STATIC_IMPORT, TokenTypes.STATIC_INIT)) { 758 DetailAST previousNode = token.getPreviousSibling(); 759 while (isCommentInBeginningOfLine(previousNode)) { 760 if (hasEmptyLineBefore(previousNode) && isPrePreviousLineEmpty(previousNode)) { 761 log(previousNode, MSG_MULTIPLE_LINES, previousNode.getText()); 762 } 763 previousNode = previousNode.getPreviousSibling(); 764 } 765 } 766 else { 767 checkCommentsInsideToken(token); 768 } 769 } 770 } 771 772 /** 773 * Check if group of comments located at the start of token has more than one previous empty 774 * line. 775 * 776 * @param token DetailAST token 777 */ 778 private void checkCommentsInsideToken(DetailAST token) { 779 final List<DetailAST> childNodes = new LinkedList<>(); 780 DetailAST childNode = token.getLastChild(); 781 while (childNode != null) { 782 if (childNode.getType() == TokenTypes.MODIFIERS) { 783 for (DetailAST node = token.getFirstChild().getLastChild(); 784 node != null; 785 node = node.getPreviousSibling()) { 786 if (isCommentInBeginningOfLine(node)) { 787 childNodes.add(node); 788 } 789 } 790 } 791 else if (isCommentInBeginningOfLine(childNode)) { 792 childNodes.add(childNode); 793 } 794 childNode = childNode.getPreviousSibling(); 795 } 796 for (DetailAST node : childNodes) { 797 if (hasEmptyLineBefore(node) && isPrePreviousLineEmpty(node)) { 798 log(node, MSG_MULTIPLE_LINES, node.getText()); 799 } 800 } 801 } 802 803 /** 804 * Checks if a token has empty pre-previous line. 805 * 806 * @param token DetailAST token. 807 * @return true, if token has empty lines before. 808 */ 809 private boolean isPrePreviousLineEmpty(DetailAST token) { 810 boolean result = false; 811 final int lineNo = token.getLineNo(); 812 // 3 is the number of the pre-previous line because the numbering starts from zero. 813 final int number = 3; 814 if (lineNo >= number) { 815 final String prePreviousLine = getLines()[lineNo - number]; 816 result = CommonUtil.isBlank(prePreviousLine); 817 } 818 return result; 819 } 820 821 /** 822 * Checks if token have empty line after. 823 * 824 * @param token token. 825 * @return true if token have empty line after. 826 */ 827 private boolean hasEmptyLineAfter(DetailAST token) { 828 DetailAST lastToken = token.getLastChild().getLastChild(); 829 if (lastToken == null) { 830 lastToken = token.getLastChild(); 831 } 832 DetailAST nextToken = token.getNextSibling(); 833 if (TokenUtil.isCommentType(nextToken.getType())) { 834 nextToken = nextToken.getNextSibling(); 835 } 836 // Start of the next token 837 final int nextBegin = nextToken.getLineNo(); 838 // End of current token. 839 final int currentEnd = lastToken.getLineNo(); 840 return hasEmptyLine(currentEnd + 1, nextBegin - 1); 841 } 842 843 /** 844 * Finds comment in next sibling of given packageDef. 845 * 846 * @param packageDef token to check 847 * @return comment under the token 848 */ 849 private static Optional<DetailAST> findCommentUnder(DetailAST packageDef) { 850 return Optional.ofNullable(packageDef.getNextSibling()) 851 .map(sibling -> sibling.findFirstToken(TokenTypes.MODIFIERS)) 852 .map(DetailAST::getFirstChild) 853 .filter(token -> TokenUtil.isCommentType(token.getType())) 854 .filter(comment -> comment.getLineNo() == packageDef.getLineNo() + 1); 855 } 856 857 /** 858 * Checks, whether there are empty lines within the specified line range. Line numbering is 859 * started from 1 for parameter values 860 * 861 * @param startLine number of the first line in the range 862 * @param endLine number of the second line in the range 863 * @return {@code true} if found any blank line within the range, {@code false} 864 * otherwise 865 */ 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 a 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 = getLines()[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 = getLines()[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.isOfType(variableDef.getParent().getParent(), 938 TokenTypes.CLASS_DEF, TokenTypes.RECORD_DEF); 939 } 940 941}