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