001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2015 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.indentation; 021 022import java.util.Locale; 023import java.util.Stack; 024 025import org.apache.commons.lang3.ArrayUtils; 026 027import com.puppycrawl.tools.checkstyle.api.Check; 028import com.puppycrawl.tools.checkstyle.api.DetailAST; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.CommonUtils; 031 032/** 033 * This Check controls the indentation between comments and surrounding code. 034 * Comments are indented at the same level as the surrounding code. 035 * Detailed info about such convention can be found 036 * <a href= 037 * "http://checkstyle.sourceforge.net/reports/google-java-style.html#s4.8.6.1-block-comment-style"> 038 * here</a> 039 * <p> 040 * Examples: 041 * </p> 042 * <p> 043 * To configure the Check: 044 * </p> 045 * 046 * <pre> 047 * {@code 048 * <module name="CommentsIndentation"/> 049 * } 050 * {@code 051 * /* 052 * * comment 053 * * some comment 054 * */ 055 * boolean bool = true; - such comment indentation is ok 056 * /* 057 * * comment 058 * * some comment 059 * */ 060 * double d = 3.14; - Block Comment has incorrect indentation level 7, expected 4. 061 * // some comment - comment is ok 062 * String str = ""; 063 * // some comment Comment has incorrect indentation level 8, expected 4. 064 * String str1 = ""; 065 * } 066 * </pre> 067 * 068 * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a> 069 * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a> 070 */ 071public class CommentsIndentationCheck extends Check { 072 073 /** 074 * A key is pointing to the warning message text in "messages.properties" file. 075 */ 076 public static final String MSG_KEY_SINGLE = "comments.indentation.single"; 077 078 /** 079 * A key is pointing to the warning message text in "messages.properties" file. 080 */ 081 public static final String MSG_KEY_BLOCK = "comments.indentation.block"; 082 083 @Override 084 public int[] getDefaultTokens() { 085 return new int[] { 086 TokenTypes.SINGLE_LINE_COMMENT, 087 TokenTypes.BLOCK_COMMENT_BEGIN, 088 }; 089 } 090 091 @Override 092 public int[] getAcceptableTokens() { 093 return new int[] { 094 TokenTypes.SINGLE_LINE_COMMENT, 095 TokenTypes.BLOCK_COMMENT_BEGIN, 096 }; 097 } 098 099 @Override 100 public int[] getRequiredTokens() { 101 return ArrayUtils.EMPTY_INT_ARRAY; 102 } 103 104 @Override 105 public boolean isCommentNodesRequired() { 106 return true; 107 } 108 109 @Override 110 public void visitToken(DetailAST commentAst) { 111 switch (commentAst.getType()) { 112 case TokenTypes.SINGLE_LINE_COMMENT: 113 visitSingleLineComment(commentAst); 114 break; 115 case TokenTypes.BLOCK_COMMENT_BEGIN: 116 visitBlockComment(commentAst); 117 break; 118 default: 119 final String exceptionMsg = "Unexpected token type: " + commentAst.getText(); 120 throw new IllegalArgumentException(exceptionMsg); 121 } 122 } 123 124 /** 125 * Checks single line comment indentations over surrounding code, e.g.: 126 * <p> 127 * {@code 128 * // some comment - this is ok 129 * double d = 3.14; 130 * // some comment - this is <b>not</b> ok. 131 * double d1 = 5.0; 132 * } 133 * </p> 134 * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}. 135 */ 136 private void visitSingleLineComment(DetailAST singleLineComment) { 137 final DetailAST prevStmt = getPreviousStatementOfSingleLineComment(singleLineComment); 138 final DetailAST nextStmt = singleLineComment.getNextSibling(); 139 140 if (!isTrailingSingleLineComment(singleLineComment)) { 141 if (isInEmptyCaseBlock(prevStmt, nextStmt)) { 142 handleSingleLineCommentInEmptyCaseBlock(prevStmt, singleLineComment, 143 nextStmt); 144 } 145 else if (isFallThroughSingleLineComment(prevStmt, nextStmt)) { 146 handleFallThroughtSingleLineComment(prevStmt, singleLineComment, 147 nextStmt); 148 } 149 else if (isInEmptyCodeBlock(prevStmt, nextStmt)) { 150 handleSingleLineCommentInEmptyCodeBlock(singleLineComment, nextStmt); 151 } 152 else if (isSingleLineCommentAtTheEndOfTheCodeBlock(nextStmt)) { 153 handleSIngleLineCommentAtTheEndOfTheCodeBlock(prevStmt, singleLineComment, 154 nextStmt); 155 } 156 else if (nextStmt != null 157 && !areSameLevelIndented(singleLineComment, nextStmt, nextStmt)) { 158 log(singleLineComment.getLineNo(), MSG_KEY_SINGLE, nextStmt.getLineNo(), 159 singleLineComment.getColumnNo(), nextStmt.getColumnNo()); 160 } 161 } 162 } 163 164 /** 165 * Returns the previous statement of a single line comment. 166 * @param comment single line comment. 167 * @return the previous statement of a single line comment. 168 */ 169 private static DetailAST getPreviousStatementOfSingleLineComment(DetailAST comment) { 170 final DetailAST prevStatement; 171 if (isDistributedPreviousStatement(comment)) { 172 prevStatement = getDistributedPreviousStatementOfSingleLineComment(comment); 173 } 174 else { 175 prevStatement = getOneLinePreviousStatementOfSingleLineComment(comment); 176 } 177 return prevStatement; 178 } 179 180 /** 181 * Checks whether the previous statement of a single line comment is distributed over two or 182 * more lines. 183 * @param singleLineComment single line comment. 184 * @return true if the previous statement of a single line comment is distributed over two or 185 * more lines. 186 */ 187 private static boolean isDistributedPreviousStatement(DetailAST singleLineComment) { 188 final DetailAST previousSibling = singleLineComment.getPreviousSibling(); 189 return isDistributedMethodChainOrConcatenationStatement(singleLineComment, previousSibling) 190 || isDistributedReturnStatement(previousSibling) 191 || isDistributedThrowStatement(previousSibling); 192 } 193 194 /** 195 * Checks whether the previous statement of a single line comment is a method call chain or 196 * string concatenation statemen distributed over two ore more lines. 197 * @param comment single line comment. 198 * @param commentPreviousSibling previous sibling of the sinle line comment. 199 * @return if the previous statement of a single line comment is a method call chain or 200 * string concatenation statemen distributed over two ore more lines. 201 */ 202 private static boolean isDistributedMethodChainOrConcatenationStatement( 203 DetailAST comment, DetailAST commentPreviousSibling) { 204 boolean destributed = false; 205 if (commentPreviousSibling != null 206 && commentPreviousSibling.getType() == TokenTypes.SEMI 207 && comment.getLineNo() - commentPreviousSibling.getLineNo() == 1) { 208 DetailAST currentToken = commentPreviousSibling.getPreviousSibling(); 209 while (currentToken.getFirstChild() != null) { 210 currentToken = currentToken.getFirstChild(); 211 } 212 if (currentToken.getType() != TokenTypes.COMMENT_CONTENT 213 && commentPreviousSibling.getLineNo() != currentToken.getLineNo()) { 214 destributed = true; 215 } 216 } 217 return destributed; 218 } 219 220 /** 221 * Checks whether the previous statement of a single line comment is a destributed return 222 * statement. 223 * @param commentPreviousSibling previous sibling of the single line comment. 224 * @return true if the previous statement of a single line comment is a destributed return 225 * statement. 226 */ 227 private static boolean isDistributedReturnStatement(DetailAST commentPreviousSibling) { 228 boolean destributed = false; 229 if (commentPreviousSibling != null 230 && commentPreviousSibling.getType() == TokenTypes.LITERAL_RETURN) { 231 final DetailAST firstChild = commentPreviousSibling.getFirstChild(); 232 final DetailAST nextSibling = firstChild.getNextSibling(); 233 if (nextSibling != null) { 234 destributed = true; 235 } 236 } 237 return destributed; 238 } 239 240 /** 241 * Checks whether the previous statement of a single line comment is a destributed throw 242 * statement. 243 * @param commentPreviousSibling previous sibling of the single line comment. 244 * @return true if the previous statement of a single line comment is a destributed throw 245 * statement. 246 */ 247 private static boolean isDistributedThrowStatement(DetailAST commentPreviousSibling) { 248 boolean destributed = false; 249 if (commentPreviousSibling != null 250 && commentPreviousSibling.getType() == TokenTypes.LITERAL_THROW) { 251 final DetailAST firstChild = commentPreviousSibling.getFirstChild(); 252 final DetailAST nextSibling = firstChild.getNextSibling(); 253 if (nextSibling.getLineNo() != commentPreviousSibling.getLineNo()) { 254 destributed = true; 255 } 256 } 257 return destributed; 258 } 259 260 /** 261 * Returns the first token of the destributed previous statement of single line comment. 262 * @param comment single line comment. 263 * @return the first token of the destributed previous statement of single line comment. 264 */ 265 private static DetailAST getDistributedPreviousStatementOfSingleLineComment(DetailAST comment) { 266 final DetailAST previousStatement; 267 DetailAST currentToken = comment.getPreviousSibling(); 268 if (currentToken.getType() == TokenTypes.LITERAL_RETURN 269 || currentToken.getType() == TokenTypes.LITERAL_THROW) { 270 previousStatement = currentToken; 271 } 272 else { 273 currentToken = currentToken.getPreviousSibling(); 274 while (currentToken.getFirstChild() != null) { 275 currentToken = currentToken.getFirstChild(); 276 } 277 previousStatement = currentToken; 278 } 279 return previousStatement; 280 } 281 282 /** 283 * Checks whether case block is empty. 284 * @param nextStmt previous statement. 285 * @param prevStmt next statement. 286 * @return true if case block is empty. 287 */ 288 private static boolean isInEmptyCaseBlock(DetailAST prevStmt, DetailAST nextStmt) { 289 return prevStmt != null 290 && nextStmt != null 291 && (prevStmt.getType() == TokenTypes.LITERAL_CASE 292 || prevStmt.getType() == TokenTypes.CASE_GROUP) 293 && (nextStmt.getType() == TokenTypes.LITERAL_CASE 294 || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT); 295 } 296 297 /** 298 * Checks whether single line comment is a 'fall through' comment. 299 * For example: 300 * <p> 301 * {@code 302 * ... 303 * case OPTION_ONE: 304 * int someVariable = 1; 305 * // fall through 306 * case OPTION_TWO: 307 * int a = 5; 308 * break; 309 * ... 310 * } 311 * </p> 312 * @param prevStmt previous statement. 313 * @param nextStmt next statement. 314 * @return true if a single line comment is a 'fall through' comment. 315 */ 316 private static boolean isFallThroughSingleLineComment(DetailAST prevStmt, DetailAST nextStmt) { 317 return prevStmt != null 318 && prevStmt.getType() != TokenTypes.LITERAL_CASE 319 && nextStmt != null 320 && (nextStmt.getType() == TokenTypes.LITERAL_CASE 321 || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT); 322 } 323 324 /** 325 * Checks whether a single line comment is placed at the end of the code block. 326 * @param nextStmt next statement. 327 * @return true if a single line comment is placed at the end of the block. 328 */ 329 private static boolean isSingleLineCommentAtTheEndOfTheCodeBlock(DetailAST nextStmt) { 330 return nextStmt != null 331 && nextStmt.getType() == TokenTypes.RCURLY; 332 } 333 334 /** 335 * Checks whether comment is placed in the empty code block. 336 * For example: 337 * <p> 338 * ... 339 * {@code 340 * // empty code block 341 * } 342 * ... 343 * </p> 344 * Note, the method does not treat empty case blocks. 345 * @param prevStmt previous statement. 346 * @param nextStmt next statement. 347 * @return true if comment is placed in the empty code block. 348 */ 349 private static boolean isInEmptyCodeBlock(DetailAST prevStmt, DetailAST nextStmt) { 350 return prevStmt != null 351 && nextStmt != null 352 && (prevStmt.getType() == TokenTypes.SLIST 353 || prevStmt.getType() == TokenTypes.OBJBLOCK) 354 && nextStmt.getType() == TokenTypes.RCURLY; 355 } 356 357 /** 358 * Handles a single line comment which is plased within empty case block. 359 * Note, if comment is placed at the end of the empty case block, we have Checkstyle's 360 * limitations to clearly detect user intention of explanation target - above or below. The 361 * only case we can assume as a violation is when a single line comment within the empty case 362 * block has indentation level that is lower than the indentation level of the next case 363 * token. For example: 364 * <p> 365 * {@code 366 * ... 367 * case OPTION_ONE: 368 * // violation 369 * case OPTION_TWO: 370 * ... 371 * } 372 * </p> 373 * @param prevStmt previous statement. 374 * @param comment single line comment. 375 * @param nextStmt next statement. 376 */ 377 private void handleSingleLineCommentInEmptyCaseBlock(DetailAST prevStmt, DetailAST comment, 378 DetailAST nextStmt) { 379 380 if (comment.getColumnNo() < prevStmt.getColumnNo() 381 || comment.getColumnNo() < nextStmt.getColumnNo()) { 382 logMultilineIndentation(prevStmt, comment, nextStmt); 383 } 384 } 385 386 /** 387 * Handles 'fall through' single line comment. 388 * Note, 'fall through' and similar comments can have indentation level as next or previous 389 * statement. 390 * For example: 391 * <p> 392 * {@code 393 * ... 394 * case OPTION_ONE: 395 * int someVariable = 1; 396 * // fall through - OK 397 * case OPTION_TWO: 398 * int a = 5; 399 * break; 400 * ... 401 * } 402 * </p> 403 * <p> 404 * {@code 405 * ... 406 * case OPTION_ONE: 407 * int someVariable = 1; 408 * // than init variable a - OK 409 * case OPTION_TWO: 410 * int a = 5; 411 * break; 412 * ... 413 * } 414 * </p> 415 * @param prevStmt previous statement. 416 * @param comment single line comment. 417 * @param nextStmt next statement. 418 */ 419 private void handleFallThroughtSingleLineComment(DetailAST prevStmt, DetailAST comment, 420 DetailAST nextStmt) { 421 422 if (!areSameLevelIndented(comment, prevStmt, nextStmt)) { 423 logMultilineIndentation(prevStmt, comment, nextStmt); 424 } 425 426 } 427 428 /** 429 * Hendles a single line comment which is placed at the end of non empty code block. 430 * Note, if single line comment is plcaed at the end of non empty block the comment should have 431 * the same indentation level as the previous statement. For example: 432 * <p> 433 * {@code 434 * if (a == true) { 435 * int b = 1; 436 * // comment 437 * } 438 * } 439 * </p> 440 * @param prevStmt previous statement. 441 * @param comment single line statement. 442 * @param nextStmt next statement. 443 */ 444 private void handleSIngleLineCommentAtTheEndOfTheCodeBlock(DetailAST prevStmt, 445 DetailAST comment, 446 DetailAST nextStmt) { 447 if (prevStmt != null) { 448 if (prevStmt.getType() == TokenTypes.LITERAL_CASE 449 || prevStmt.getType() == TokenTypes.CASE_GROUP 450 || prevStmt.getType() == TokenTypes.LITERAL_DEFAULT 451 || prevStmt.getType() == TokenTypes.SINGLE_LINE_COMMENT) { 452 if (comment.getColumnNo() < nextStmt.getColumnNo()) { 453 log(comment.getLineNo(), MSG_KEY_SINGLE, nextStmt.getLineNo(), 454 comment.getColumnNo(), nextStmt.getColumnNo()); 455 } 456 } 457 else if (!areSameLevelIndented(comment, prevStmt, prevStmt)) { 458 log(comment.getLineNo(), MSG_KEY_SINGLE, prevStmt.getLineNo(), 459 comment.getColumnNo(), prevStmt.getColumnNo()); 460 } 461 } 462 463 } 464 465 /** 466 * Handles a single line comment which is placed within the empty code block. 467 * Note, if comment is placed at the end of the empty code block, we have Checkstyle's 468 * limitations to clearly detect user intention of explanation target - above or below. The 469 * only case we can assume as a violation is when a single line comment within the empty 470 * code block has indentation level that is lower than the indentation level of the closing 471 * right curly brace. For example: 472 * <p> 473 * {@code 474 * if (a == true) { 475 * // violation 476 * } 477 * } 478 * </p> 479 * 480 * @param comment single line comment. 481 * @param nextStmt next statement. 482 */ 483 private void handleSingleLineCommentInEmptyCodeBlock(DetailAST comment, DetailAST nextStmt) { 484 if (comment.getColumnNo() < nextStmt.getColumnNo()) { 485 log(comment.getLineNo(), MSG_KEY_SINGLE, nextStmt.getLineNo(), 486 comment.getColumnNo(), nextStmt.getColumnNo()); 487 } 488 } 489 490 /** 491 * Does pre-order traverse of abstract syntax tree to find the previous statement of the 492 * single line comment. If previous statement of the comment is found, then the traverse will 493 * be finished. 494 * @param comment current statement. 495 * @return previous statement of the comment or null if the comment does not have previous 496 * statement. 497 */ 498 private static DetailAST getOneLinePreviousStatementOfSingleLineComment(DetailAST comment) { 499 DetailAST previousStatement = null; 500 final Stack<DetailAST> stack = new Stack<>(); 501 DetailAST root = comment.getParent(); 502 503 while (root != null || !stack.empty()) { 504 if (!stack.empty()) { 505 root = stack.pop(); 506 } 507 while (root != null) { 508 previousStatement = findPreviousStatementOfSingleLineComment(comment, root); 509 if (previousStatement != null) { 510 root = null; 511 stack.clear(); 512 break; 513 } 514 if (root.getNextSibling() != null) { 515 stack.push(root.getNextSibling()); 516 } 517 root = root.getFirstChild(); 518 } 519 } 520 return previousStatement; 521 } 522 523 /** 524 * Finds a previous statement of the single line comment. 525 * Uses root token of the line while searching. 526 * @param comment single line comment. 527 * @param root root token of the line. 528 * @return previous statement of the single line comment or null if previous statement was not 529 * found. 530 */ 531 private static DetailAST findPreviousStatementOfSingleLineComment(DetailAST comment, 532 DetailAST root) { 533 DetailAST previousStatement = null; 534 if (root.getLineNo() >= comment.getLineNo()) { 535 // ATTENTION: parent of the comment is below the comment in case block 536 // See https://github.com/checkstyle/checkstyle/issues/851 537 previousStatement = getPrevStatementFromSwitchBlock(comment); 538 } 539 final DetailAST tokenWhichBeginsTheLine; 540 if (root.getType() == TokenTypes.EXPR 541 && root.getFirstChild().getFirstChild() != null) { 542 if (root.getFirstChild().getType() == TokenTypes.LITERAL_NEW) { 543 tokenWhichBeginsTheLine = root.getFirstChild(); 544 } 545 else { 546 tokenWhichBeginsTheLine = findTokenWhichBeginsTheLine(root); 547 } 548 } 549 else if (root.getType() == TokenTypes.PLUS) { 550 tokenWhichBeginsTheLine = root.getFirstChild(); 551 } 552 else { 553 tokenWhichBeginsTheLine = root; 554 } 555 if (tokenWhichBeginsTheLine != null 556 && isOnPreviousLine(comment, tokenWhichBeginsTheLine)) { 557 previousStatement = tokenWhichBeginsTheLine; 558 } 559 return previousStatement; 560 } 561 562 /** 563 * Finds a token which begins the line. 564 * @param root root token of the line. 565 * @return token which begins the line. 566 */ 567 private static DetailAST findTokenWhichBeginsTheLine(DetailAST root) { 568 DetailAST tokenWhichBeginsTheLine; 569 if (isUsingOfObjectReferenceToInvokeMethod(root)) { 570 tokenWhichBeginsTheLine = findStartTokenOfMethodCallChain(root); 571 } 572 else { 573 tokenWhichBeginsTheLine = root.getFirstChild().findFirstToken(TokenTypes.IDENT); 574 } 575 return tokenWhichBeginsTheLine; 576 } 577 578 /** 579 * Checks whether there is a use of an object reference to invoke an object's method on line. 580 * @param root root token of the line. 581 * @return true if there is a use of an object reference to invoke an object's method on line. 582 */ 583 private static boolean isUsingOfObjectReferenceToInvokeMethod(DetailAST root) { 584 return root.getFirstChild().getFirstChild().getFirstChild() != null 585 && root.getFirstChild().getFirstChild().getFirstChild().getNextSibling() != null; 586 } 587 588 /** 589 * Finds the start token of method call chain. 590 * @param root root token of the line. 591 * @return the start token of method call chain. 592 */ 593 private static DetailAST findStartTokenOfMethodCallChain(DetailAST root) { 594 DetailAST startOfMethodCallChain = root; 595 while (startOfMethodCallChain.getFirstChild() != null 596 && startOfMethodCallChain.getFirstChild().getLineNo() == root.getLineNo()) { 597 startOfMethodCallChain = startOfMethodCallChain.getFirstChild(); 598 } 599 if (startOfMethodCallChain.getFirstChild() != null) { 600 startOfMethodCallChain = startOfMethodCallChain.getFirstChild().getNextSibling(); 601 } 602 return startOfMethodCallChain; 603 } 604 605 /** 606 * Checks whether the checked statement is on previous line. 607 * @param currentStatement current statement. 608 * @param checkedStatement checked statement. 609 * @return true if checked statement is on the line which is previous to current statement. 610 */ 611 private static boolean isOnPreviousLine(DetailAST currentStatement, 612 DetailAST checkedStatement) { 613 return currentStatement.getLineNo() - checkedStatement.getLineNo() == 1; 614 } 615 616 /** 617 * Logs comment which can have the same indentation level as next or previous statement. 618 * @param comment comment. 619 * @param nextStmt previous statement. 620 * @param prevStmt next statement. 621 */ 622 private void logMultilineIndentation(DetailAST prevStmt, DetailAST comment, 623 DetailAST nextStmt) { 624 final String multilineNoTemplate = "%d, %d"; 625 log(comment.getLineNo(), MSG_KEY_SINGLE, 626 String.format(Locale.getDefault(), multilineNoTemplate, prevStmt.getLineNo(), 627 nextStmt.getLineNo()), comment.getColumnNo(), 628 String.format(Locale.getDefault(), multilineNoTemplate, prevStmt.getColumnNo(), 629 nextStmt.getColumnNo())); 630 } 631 632 /** 633 * Gets comment's previous statement from switch block. 634 * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single-line comment}. 635 * @return comment's previous statement or null if previous statement is absent. 636 */ 637 private static DetailAST getPrevStatementFromSwitchBlock(DetailAST comment) { 638 DetailAST prevStmt = null; 639 final DetailAST parentStatement = comment.getParent(); 640 if (parentStatement != null) { 641 if (parentStatement.getType() == TokenTypes.CASE_GROUP) { 642 prevStmt = getPrevStatementWhenCommentIsUnderCase(parentStatement); 643 } 644 else { 645 prevStmt = getPrevCaseToken(parentStatement); 646 } 647 } 648 return prevStmt; 649 } 650 651 /** 652 * Gets previous statement for comment which is placed immediately under case. 653 * @param parentStatement comment's parent statement. 654 * @return comment's previous statement or null if previous statement is absent. 655 */ 656 private static DetailAST getPrevStatementWhenCommentIsUnderCase(DetailAST parentStatement) { 657 DetailAST prevStmt = null; 658 final DetailAST prevBlock = parentStatement.getPreviousSibling(); 659 if (prevBlock.getLastChild() != null) { 660 DetailAST blockBody = prevBlock.getLastChild().getLastChild(); 661 if (blockBody.getPreviousSibling() != null) { 662 blockBody = blockBody.getPreviousSibling(); 663 } 664 if (blockBody.getType() == TokenTypes.EXPR) { 665 if (isUsingOfObjectReferenceToInvokeMethod(blockBody)) { 666 prevStmt = findStartTokenOfMethodCallChain(blockBody); 667 } 668 else { 669 prevStmt = blockBody.getFirstChild().getFirstChild(); 670 } 671 } 672 else { 673 if (blockBody.getType() == TokenTypes.SLIST) { 674 prevStmt = blockBody.getParent().getParent(); 675 } 676 else { 677 prevStmt = blockBody; 678 } 679 } 680 } 681 return prevStmt; 682 } 683 684 /** 685 * Gets previous case-token for comment. 686 * @param parentStatement comment's parent statement. 687 * @return previous case-token or null if previous case-token is absent. 688 */ 689 private static DetailAST getPrevCaseToken(DetailAST parentStatement) { 690 final DetailAST prevCaseToken; 691 final DetailAST parentBlock = parentStatement.getParent(); 692 if (parentBlock != null && parentBlock.getParent() != null 693 && parentBlock.getParent().getPreviousSibling() != null 694 && parentBlock.getParent().getPreviousSibling().getType() 695 == TokenTypes.LITERAL_CASE) { 696 prevCaseToken = parentBlock.getParent().getPreviousSibling(); 697 } 698 else { 699 prevCaseToken = null; 700 } 701 return prevCaseToken; 702 } 703 704 /** 705 * Checks if comment and next code statement 706 * (or previous code stmt like <b>case</b> in switch block) are indented at the same level, 707 * e.g.: 708 * <p> 709 * <pre> 710 * {@code 711 * // some comment - same indentation level 712 * int x = 10; 713 * // some comment - different indentation level 714 * int x1 = 5; 715 * /* 716 * * 717 * */ 718 * boolean bool = true; - same indentation level 719 * } 720 * </pre> 721 * </p> 722 * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}. 723 * @param prevStmt previous code statement. 724 * @param nextStmt next code statement. 725 * @return true if comment and next code statement are indented at the same level. 726 */ 727 private static boolean areSameLevelIndented(DetailAST comment, DetailAST prevStmt, 728 DetailAST nextStmt) { 729 boolean result; 730 if (prevStmt == null) { 731 result = comment.getColumnNo() == nextStmt.getColumnNo(); 732 } 733 else { 734 result = comment.getColumnNo() == nextStmt.getColumnNo() 735 || comment.getColumnNo() == prevStmt.getColumnNo(); 736 } 737 return result; 738 } 739 740 /** 741 * Checks if current single line comment is trailing comment, e.g.: 742 * <p> 743 * {@code 744 * double d = 3.14; // some comment 745 * } 746 * </p> 747 * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}. 748 * @return true if current single line comment is trailing comment. 749 */ 750 private boolean isTrailingSingleLineComment(DetailAST singleLineComment) { 751 final String targetSourceLine = getLine(singleLineComment.getLineNo() - 1); 752 final int commentColumnNo = singleLineComment.getColumnNo(); 753 return !CommonUtils.hasWhitespaceBefore(commentColumnNo, targetSourceLine); 754 } 755 756 /** 757 * Checks comment block indentations over surrounding code, e.g.: 758 * <p> 759 * {@code 760 * /* some comment */ - this is ok 761 * double d = 3.14; 762 * /* some comment */ - this is <b>not</b> ok. 763 * double d1 = 5.0; 764 * } 765 * </p> 766 * @param blockComment {@link TokenTypes#BLOCK_COMMENT_BEGIN block comment begin}. 767 */ 768 private void visitBlockComment(DetailAST blockComment) { 769 final DetailAST nextStatement = blockComment.getNextSibling(); 770 final DetailAST prevStatement = getPrevStatementFromSwitchBlock(blockComment); 771 772 if (nextStatement != null 773 && nextStatement.getType() != TokenTypes.RCURLY 774 && !isTrailingBlockComment(blockComment) 775 && !areSameLevelIndented(blockComment, prevStatement, nextStatement)) { 776 log(blockComment.getLineNo(), MSG_KEY_BLOCK, nextStatement.getLineNo(), 777 blockComment.getColumnNo(), nextStatement.getColumnNo()); 778 } 779 } 780 781 /** 782 * Checks if current comment block is trailing comment, e.g.: 783 * <p> 784 * {@code 785 * double d = 3.14; /* some comment */ 786 * /* some comment */ double d = 18.5; 787 * } 788 * </p> 789 * @param blockComment {@link TokenTypes#BLOCK_COMMENT_BEGIN block comment begin}. 790 * @return true if current comment block is trailing comment. 791 */ 792 private boolean isTrailingBlockComment(DetailAST blockComment) { 793 final String commentLine = getLine(blockComment.getLineNo() - 1); 794 final int commentColumnNo = blockComment.getColumnNo(); 795 return !CommonUtils.hasWhitespaceBefore(commentColumnNo, commentLine) 796 || blockComment.getNextSibling().getLineNo() == blockComment.getLineNo(); 797 } 798}