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