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