Coverage Report - com.puppycrawl.tools.checkstyle.checks.indentation.CommentsIndentationCheck
 
Classes in this File Line Coverage Branch Coverage Complexity
CommentsIndentationCheck
100%
283/283
100%
292/292
4.023
 
 1  
 ////////////////////////////////////////////////////////////////////////////////
 2  
 // checkstyle: Checks Java source code for adherence to a set of rules.
 3  
 // Copyright (C) 2001-2017 the original author or authors.
 4  
 //
 5  
 // This library is free software; you can redistribute it and/or
 6  
 // modify it under the terms of the GNU Lesser General Public
 7  
 // License as published by the Free Software Foundation; either
 8  
 // version 2.1 of the License, or (at your option) any later version.
 9  
 //
 10  
 // This library is distributed in the hope that it will be useful,
 11  
 // but WITHOUT ANY WARRANTY; without even the implied warranty of
 12  
 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 13  
 // Lesser General Public License for more details.
 14  
 //
 15  
 // You should have received a copy of the GNU Lesser General Public
 16  
 // License along with this library; if not, write to the Free Software
 17  
 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 18  
 ////////////////////////////////////////////////////////////////////////////////
 19  
 
 20  
 package com.puppycrawl.tools.checkstyle.checks.indentation;
 21  
 
 22  
 import java.util.ArrayDeque;
 23  
 import java.util.Deque;
 24  
 import java.util.Locale;
 25  
 
 26  
 import com.puppycrawl.tools.checkstyle.StatelessCheck;
 27  
 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
 28  
 import com.puppycrawl.tools.checkstyle.api.DetailAST;
 29  
 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
 30  
 import com.puppycrawl.tools.checkstyle.utils.CommonUtils;
 31  
 
 32  
 /**
 33  
  * This Check controls the indentation between comments and surrounding code.
 34  
  * Comments are indented at the same level as the surrounding code.
 35  
  * Detailed info about such convention can be found
 36  
  * <a href=
 37  
  * "http://checkstyle.sourceforge.net/reports/google-java-style-20170228.html#s4.8.6.1-block-comment-style">
 38  
  * here</a>
 39  
  * <p>
 40  
  * Examples:
 41  
  * </p>
 42  
  * <p>
 43  
  * To configure the Check:
 44  
  * </p>
 45  
  *
 46  
  * <pre>
 47  
  * {@code
 48  
  * &lt;module name=&quot;CommentsIndentation&quot;/&gt;
 49  
  * }
 50  
  * {@code
 51  
  * /*
 52  
  *  * comment
 53  
  *  * some comment
 54  
  *  *&#47;
 55  
  * boolean bool = true; - such comment indentation is ok
 56  
  *    /*
 57  
  *    * comment
 58  
  *    * some comment
 59  
  *     *&#47;
 60  
  * double d = 3.14; - Block Comment has incorrect indentation level 7, expected 4.
 61  
  * // some comment - comment is ok
 62  
  * String str = "";
 63  
  *     // some comment Comment has incorrect indentation level 8, expected 4.
 64  
  * String str1 = "";
 65  
  * }
 66  
  * </pre>
 67  
  *
 68  
  * @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
 69  
  * @author <a href="mailto:andreyselkin@gmail.com">Andrei Selkin</a>
 70  
  */
 71  
 @StatelessCheck
 72  22
 public class CommentsIndentationCheck extends AbstractCheck {
 73  
 
 74  
     /**
 75  
      * A key is pointing to the warning message text in "messages.properties" file.
 76  
      */
 77  
     public static final String MSG_KEY_SINGLE = "comments.indentation.single";
 78  
 
 79  
     /**
 80  
      * A key is pointing to the warning message text in "messages.properties" file.
 81  
      */
 82  
     public static final String MSG_KEY_BLOCK = "comments.indentation.block";
 83  
 
 84  
     @Override
 85  
     public int[] getDefaultTokens() {
 86  19
         return new int[] {
 87  
             TokenTypes.SINGLE_LINE_COMMENT,
 88  
             TokenTypes.BLOCK_COMMENT_BEGIN,
 89  
         };
 90  
     }
 91  
 
 92  
     @Override
 93  
     public int[] getAcceptableTokens() {
 94  8
         return new int[] {
 95  
             TokenTypes.SINGLE_LINE_COMMENT,
 96  
             TokenTypes.BLOCK_COMMENT_BEGIN,
 97  
         };
 98  
     }
 99  
 
 100  
     @Override
 101  
     public int[] getRequiredTokens() {
 102  24
         return CommonUtils.EMPTY_INT_ARRAY;
 103  
     }
 104  
 
 105  
     @Override
 106  
     public boolean isCommentNodesRequired() {
 107  43
         return true;
 108  
     }
 109  
 
 110  
     @Override
 111  
     public void visitToken(DetailAST commentAst) {
 112  359
         switch (commentAst.getType()) {
 113  
             case TokenTypes.SINGLE_LINE_COMMENT:
 114  
             case TokenTypes.BLOCK_COMMENT_BEGIN:
 115  358
                 visitComment(commentAst);
 116  358
                 break;
 117  
             default:
 118  1
                 final String exceptionMsg = "Unexpected token type: " + commentAst.getText();
 119  1
                 throw new IllegalArgumentException(exceptionMsg);
 120  
         }
 121  358
     }
 122  
 
 123  
     /**
 124  
      * Checks comment indentations over surrounding code, e.g.:
 125  
      * <p>
 126  
      * {@code
 127  
      * // some comment - this is ok
 128  
      * double d = 3.14;
 129  
      *     // some comment - this is <b>not</b> ok.
 130  
      * double d1 = 5.0;
 131  
      * }
 132  
      * </p>
 133  
      * @param comment comment to check.
 134  
      */
 135  
     private void visitComment(DetailAST comment) {
 136  358
         if (!isTrailingComment(comment)) {
 137  325
             final DetailAST prevStmt = getPreviousStatement(comment);
 138  325
             final DetailAST nextStmt = getNextStmt(comment);
 139  
 
 140  325
             if (isInEmptyCaseBlock(prevStmt, nextStmt)) {
 141  8
                 handleCommentInEmptyCaseBlock(prevStmt, comment, nextStmt);
 142  
             }
 143  317
             else if (isFallThroughComment(prevStmt, nextStmt)) {
 144  22
                 handleFallThroughComment(prevStmt, comment, nextStmt);
 145  
             }
 146  295
             else if (isInEmptyCodeBlock(prevStmt, nextStmt)) {
 147  20
                 handleCommentInEmptyCodeBlock(comment, nextStmt);
 148  
             }
 149  275
             else if (isCommentAtTheEndOfTheCodeBlock(nextStmt)) {
 150  120
                 handleCommentAtTheEndOfTheCodeBlock(prevStmt, comment, nextStmt);
 151  
             }
 152  155
             else if (nextStmt != null && !areSameLevelIndented(comment, nextStmt, nextStmt)) {
 153  82
                 log(comment.getLineNo(), getMessageKey(comment), nextStmt.getLineNo(),
 154  41
                     comment.getColumnNo(), nextStmt.getColumnNo());
 155  
             }
 156  
         }
 157  358
     }
 158  
 
 159  
     /**
 160  
      * Returns the next statement of a comment.
 161  
      * @param comment comment.
 162  
      * @return the next statement of a comment.
 163  
      */
 164  
     private static DetailAST getNextStmt(DetailAST comment) {
 165  325
         DetailAST nextStmt = comment.getNextSibling();
 166  334
         while (nextStmt != null
 167  330
                 && isComment(nextStmt)
 168  69
                 && comment.getColumnNo() != nextStmt.getColumnNo()) {
 169  9
             nextStmt = nextStmt.getNextSibling();
 170  
         }
 171  325
         return nextStmt;
 172  
     }
 173  
 
 174  
     /**
 175  
      * Returns the previous statement of a comment.
 176  
      * @param comment comment.
 177  
      * @return the previous statement of a comment.
 178  
      */
 179  
     private DetailAST getPreviousStatement(DetailAST comment) {
 180  
         final DetailAST prevStatement;
 181  325
         if (isDistributedPreviousStatement(comment)) {
 182  47
             prevStatement = getDistributedPreviousStatement(comment);
 183  
         }
 184  
         else {
 185  278
             prevStatement = getOneLinePreviousStatement(comment);
 186  
         }
 187  325
         return prevStatement;
 188  
     }
 189  
 
 190  
     /**
 191  
      * Checks whether the previous statement of a comment is distributed over two or more lines.
 192  
      * @param comment comment to check.
 193  
      * @return true if the previous statement of a comment is distributed over two or more lines.
 194  
      */
 195  
     private boolean isDistributedPreviousStatement(DetailAST comment) {
 196  325
         final DetailAST previousSibling = comment.getPreviousSibling();
 197  650
         return isDistributedExpression(comment)
 198  288
             || isDistributedReturnStatement(previousSibling)
 199  282
             || isDistributedThrowStatement(previousSibling);
 200  
     }
 201  
 
 202  
     /**
 203  
      * Checks whether the previous statement of a comment is a method call chain or
 204  
      * string concatenation statement distributed over two ore more lines.
 205  
      * @param comment comment to check.
 206  
      * @return true if the previous statement is a distributed expression.
 207  
      */
 208  
     private boolean isDistributedExpression(DetailAST comment) {
 209  325
         DetailAST previousSibling = comment.getPreviousSibling();
 210  452
         while (previousSibling != null && isComment(previousSibling)) {
 211  127
             previousSibling = previousSibling.getPreviousSibling();
 212  
         }
 213  325
         boolean isDistributed = false;
 214  325
         if (previousSibling != null) {
 215  163
             if (previousSibling.getType() == TokenTypes.SEMI
 216  66
                     && isOnPreviousLineIgnoringComments(comment, previousSibling)) {
 217  64
                 DetailAST currentToken = previousSibling.getPreviousSibling();
 218  224
                 while (currentToken.getFirstChild() != null) {
 219  160
                     currentToken = currentToken.getFirstChild();
 220  
                 }
 221  64
                 if (currentToken.getType() == TokenTypes.COMMENT_CONTENT) {
 222  10
                     currentToken = currentToken.getParent();
 223  23
                     while (isComment(currentToken)) {
 224  13
                         currentToken = currentToken.getNextSibling();
 225  
                     }
 226  
                 }
 227  64
                 if (previousSibling.getLineNo() != currentToken.getLineNo()) {
 228  17
                     isDistributed = true;
 229  
                 }
 230  64
             }
 231  
             else {
 232  99
                 isDistributed = isStatementWithPossibleCurlies(previousSibling);
 233  
             }
 234  
         }
 235  325
         return isDistributed;
 236  
     }
 237  
 
 238  
     /**
 239  
      * Whether the statement can have or always have curly brackets.
 240  
      * @param previousSibling the statement to check.
 241  
      * @return true if the statement can have or always have curly brackets.
 242  
      */
 243  
     private static boolean isStatementWithPossibleCurlies(DetailAST previousSibling) {
 244  198
         return previousSibling.getType() == TokenTypes.LITERAL_IF
 245  93
             || previousSibling.getType() == TokenTypes.LITERAL_TRY
 246  90
             || previousSibling.getType() == TokenTypes.LITERAL_FOR
 247  89
             || previousSibling.getType() == TokenTypes.LITERAL_DO
 248  88
             || previousSibling.getType() == TokenTypes.LITERAL_WHILE
 249  87
             || previousSibling.getType() == TokenTypes.LITERAL_SWITCH
 250  86
             || isDefinition(previousSibling);
 251  
     }
 252  
 
 253  
     /**
 254  
      * Whether the statement is a kind of definition (method, class etc.).
 255  
      * @param previousSibling the statement to check.
 256  
      * @return true if the statement is a kind of definition.
 257  
      */
 258  
     private static boolean isDefinition(DetailAST previousSibling) {
 259  172
         return previousSibling.getType() == TokenTypes.METHOD_DEF
 260  84
             || previousSibling.getType() == TokenTypes.CLASS_DEF
 261  82
             || previousSibling.getType() == TokenTypes.INTERFACE_DEF
 262  81
             || previousSibling.getType() == TokenTypes.ENUM_DEF
 263  80
             || previousSibling.getType() == TokenTypes.ANNOTATION_DEF;
 264  
     }
 265  
 
 266  
     /**
 267  
      * Checks whether the previous statement of a comment is a distributed return statement.
 268  
      * @param commentPreviousSibling previous sibling of the comment.
 269  
      * @return true if the previous statement of a comment is a distributed return statement.
 270  
      */
 271  
     private static boolean isDistributedReturnStatement(DetailAST commentPreviousSibling) {
 272  288
         boolean isDistributed = false;
 273  288
         if (commentPreviousSibling != null
 274  178
                 && commentPreviousSibling.getType() == TokenTypes.LITERAL_RETURN) {
 275  7
             final DetailAST firstChild = commentPreviousSibling.getFirstChild();
 276  7
             final DetailAST nextSibling = firstChild.getNextSibling();
 277  7
             if (nextSibling != null) {
 278  6
                 isDistributed = true;
 279  
             }
 280  
         }
 281  288
         return isDistributed;
 282  
     }
 283  
 
 284  
     /**
 285  
      * Checks whether the previous statement of a comment is a distributed throw statement.
 286  
      * @param commentPreviousSibling previous sibling of the comment.
 287  
      * @return true if the previous statement of a comment is a distributed throw statement.
 288  
      */
 289  
     private static boolean isDistributedThrowStatement(DetailAST commentPreviousSibling) {
 290  282
         boolean isDistributed = false;
 291  282
         if (commentPreviousSibling != null
 292  172
                 && commentPreviousSibling.getType() == TokenTypes.LITERAL_THROW) {
 293  8
             final DetailAST firstChild = commentPreviousSibling.getFirstChild();
 294  8
             final DetailAST nextSibling = firstChild.getNextSibling();
 295  8
             if (nextSibling.getLineNo() != commentPreviousSibling.getLineNo()) {
 296  4
                 isDistributed = true;
 297  
             }
 298  
         }
 299  282
         return isDistributed;
 300  
     }
 301  
 
 302  
     /**
 303  
      * Returns the first token of the distributed previous statement of comment.
 304  
      * @param comment comment to check.
 305  
      * @return the first token of the distributed previous statement of comment.
 306  
      */
 307  
     private static DetailAST getDistributedPreviousStatement(DetailAST comment) {
 308  47
         DetailAST currentToken = comment.getPreviousSibling();
 309  51
         while (isComment(currentToken)) {
 310  4
             currentToken = currentToken.getPreviousSibling();
 311  
         }
 312  
         final DetailAST previousStatement;
 313  47
         if (currentToken.getType() == TokenTypes.SEMI) {
 314  17
             currentToken = currentToken.getPreviousSibling();
 315  75
             while (currentToken.getFirstChild() != null) {
 316  58
                 currentToken = currentToken.getFirstChild();
 317  
             }
 318  17
             previousStatement = currentToken;
 319  
         }
 320  
         else {
 321  30
             previousStatement = currentToken;
 322  
         }
 323  47
         return previousStatement;
 324  
     }
 325  
 
 326  
     /**
 327  
      * Checks whether case block is empty.
 328  
      * @param nextStmt previous statement.
 329  
      * @param prevStmt next statement.
 330  
      * @return true if case block is empty.
 331  
      */
 332  
     private static boolean isInEmptyCaseBlock(DetailAST prevStmt, DetailAST nextStmt) {
 333  650
         return prevStmt != null
 334  
             && nextStmt != null
 335  250
             && (prevStmt.getType() == TokenTypes.LITERAL_CASE
 336  232
                 || prevStmt.getType() == TokenTypes.CASE_GROUP)
 337  24
             && (nextStmt.getType() == TokenTypes.LITERAL_CASE
 338  17
                 || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
 339  
     }
 340  
 
 341  
     /**
 342  
      * Checks whether comment is a 'fall through' comment.
 343  
      * For example:
 344  
      * <p>
 345  
      * {@code
 346  
      *    ...
 347  
      *    case OPTION_ONE:
 348  
      *        int someVariable = 1;
 349  
      *        // fall through
 350  
      *    case OPTION_TWO:
 351  
      *        int a = 5;
 352  
      *        break;
 353  
      *    ...
 354  
      * }
 355  
      * </p>
 356  
      * @param prevStmt previous statement.
 357  
      * @param nextStmt next statement.
 358  
      * @return true if a comment is a 'fall through' comment.
 359  
      */
 360  
     private static boolean isFallThroughComment(DetailAST prevStmt, DetailAST nextStmt) {
 361  634
         return prevStmt != null
 362  
             && nextStmt != null
 363  242
             && prevStmt.getType() != TokenTypes.LITERAL_CASE
 364  228
             && (nextStmt.getType() == TokenTypes.LITERAL_CASE
 365  212
                 || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
 366  
     }
 367  
 
 368  
     /**
 369  
      * Checks whether a comment is placed at the end of the code block.
 370  
      * @param nextStmt next statement.
 371  
      * @return true if a comment is placed at the end of the block.
 372  
      */
 373  
     private static boolean isCommentAtTheEndOfTheCodeBlock(DetailAST nextStmt) {
 374  550
         return nextStmt != null
 375  271
             && nextStmt.getType() == TokenTypes.RCURLY;
 376  
     }
 377  
 
 378  
     /**
 379  
      * Checks whether comment is placed in the empty code block.
 380  
      * For example:
 381  
      * <p>
 382  
      * ...
 383  
      * {@code
 384  
      *  // empty code block
 385  
      * }
 386  
      * ...
 387  
      * </p>
 388  
      * Note, the method does not treat empty case blocks.
 389  
      * @param prevStmt previous statement.
 390  
      * @param nextStmt next statement.
 391  
      * @return true if comment is placed in the empty code block.
 392  
      */
 393  
     private static boolean isInEmptyCodeBlock(DetailAST prevStmt, DetailAST nextStmt) {
 394  590
         return prevStmt != null
 395  
             && nextStmt != null
 396  220
             && (prevStmt.getType() == TokenTypes.SLIST
 397  183
                 || prevStmt.getType() == TokenTypes.LCURLY
 398  182
                 || prevStmt.getType() == TokenTypes.ARRAY_INIT
 399  175
                 || prevStmt.getType() == TokenTypes.OBJBLOCK)
 400  47
             && nextStmt.getType() == TokenTypes.RCURLY;
 401  
     }
 402  
 
 403  
     /**
 404  
      * Handles a comment which is placed within empty case block.
 405  
      * Note, if comment is placed at the end of the empty case block, we have Checkstyle's
 406  
      * limitations to clearly detect user intention of explanation target - above or below. The
 407  
      * only case we can assume as a violation is when a single line comment within the empty case
 408  
      * block has indentation level that is lower than the indentation level of the next case
 409  
      * token. For example:
 410  
      * <p>
 411  
      * {@code
 412  
      *    ...
 413  
      *    case OPTION_ONE:
 414  
      * // violation
 415  
      *    case OPTION_TWO:
 416  
      *    ...
 417  
      * }
 418  
      * </p>
 419  
      * @param prevStmt previous statement.
 420  
      * @param comment single line comment.
 421  
      * @param nextStmt next statement.
 422  
      */
 423  
     private void handleCommentInEmptyCaseBlock(DetailAST prevStmt, DetailAST comment,
 424  
                                                DetailAST nextStmt) {
 425  
 
 426  8
         if (comment.getColumnNo() < prevStmt.getColumnNo()
 427  6
                 || comment.getColumnNo() < nextStmt.getColumnNo()) {
 428  3
             logMultilineIndentation(prevStmt, comment, nextStmt);
 429  
         }
 430  8
     }
 431  
 
 432  
     /**
 433  
      * Handles 'fall through' single line comment.
 434  
      * Note, 'fall through' and similar comments can have indentation level as next or previous
 435  
      * statement.
 436  
      * For example:
 437  
      * <p>
 438  
      * {@code
 439  
      *    ...
 440  
      *    case OPTION_ONE:
 441  
      *        int someVariable = 1;
 442  
      *        // fall through - OK
 443  
      *    case OPTION_TWO:
 444  
      *        int a = 5;
 445  
      *        break;
 446  
      *    ...
 447  
      * }
 448  
      * </p>
 449  
      * <p>
 450  
      * {@code
 451  
      *    ...
 452  
      *    case OPTION_ONE:
 453  
      *        int someVariable = 1;
 454  
      *    // than init variable a - OK
 455  
      *    case OPTION_TWO:
 456  
      *        int a = 5;
 457  
      *        break;
 458  
      *    ...
 459  
      * }
 460  
      * </p>
 461  
      * @param prevStmt previous statement.
 462  
      * @param comment single line comment.
 463  
      * @param nextStmt next statement.
 464  
      */
 465  
     private void handleFallThroughComment(DetailAST prevStmt, DetailAST comment,
 466  
                                           DetailAST nextStmt) {
 467  
 
 468  22
         if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
 469  9
             logMultilineIndentation(prevStmt, comment, nextStmt);
 470  
         }
 471  22
     }
 472  
 
 473  
     /**
 474  
      * Handles a comment which is placed at the end of non empty code block.
 475  
      * Note, if single line comment is placed at the end of non empty block the comment should have
 476  
      * the same indentation level as the previous statement. For example:
 477  
      * <p>
 478  
      * {@code
 479  
      *    if (a == true) {
 480  
      *        int b = 1;
 481  
      *        // comment
 482  
      *    }
 483  
      * }
 484  
      * </p>
 485  
      * @param prevStmt previous statement.
 486  
      * @param comment comment to check.
 487  
      * @param nextStmt next statement.
 488  
      */
 489  
     private void handleCommentAtTheEndOfTheCodeBlock(DetailAST prevStmt, DetailAST comment,
 490  
                                                      DetailAST nextStmt) {
 491  120
         if (prevStmt != null) {
 492  115
             if (prevStmt.getType() == TokenTypes.LITERAL_CASE
 493  112
                     || prevStmt.getType() == TokenTypes.CASE_GROUP
 494  111
                     || prevStmt.getType() == TokenTypes.LITERAL_DEFAULT) {
 495  6
                 if (comment.getColumnNo() < nextStmt.getColumnNo()) {
 496  2
                     log(comment.getLineNo(), getMessageKey(comment), nextStmt.getLineNo(),
 497  1
                         comment.getColumnNo(), nextStmt.getColumnNo());
 498  
                 }
 499  
             }
 500  109
             else if (isCommentForMultiblock(nextStmt)) {
 501  20
                 if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
 502  12
                     logMultilineIndentation(prevStmt, comment, nextStmt);
 503  
                 }
 504  
             }
 505  89
             else if (!areSameLevelIndented(comment, prevStmt, prevStmt)) {
 506  48
                 final int prevStmtLineNo = prevStmt.getLineNo();
 507  96
                 log(comment.getLineNo(), getMessageKey(comment), prevStmtLineNo,
 508  48
                         comment.getColumnNo(), getLineStart(prevStmtLineNo));
 509  
             }
 510  
         }
 511  
 
 512  120
     }
 513  
 
 514  
     /**
 515  
      * Whether the comment might have been used for the next block in a multi-block structure.
 516  
      * @param endBlockStmt the end of the current block.
 517  
      * @return true, if the comment might have been used for the next
 518  
      *     block in a multi-block structure.
 519  
      */
 520  
     private static boolean isCommentForMultiblock(DetailAST endBlockStmt) {
 521  109
         final DetailAST nextBlock = endBlockStmt.getParent().getNextSibling();
 522  109
         final int endBlockLineNo = endBlockStmt.getLineNo();
 523  109
         final DetailAST catchAst = endBlockStmt.getParent().getParent();
 524  109
         final DetailAST finallyAst = catchAst.getNextSibling();
 525  218
         return nextBlock != null && nextBlock.getLineNo() == endBlockLineNo
 526  
                 || finallyAst != null
 527  90
                     && catchAst.getType() == TokenTypes.LITERAL_CATCH
 528  8
                     && finallyAst.getLineNo() == endBlockLineNo;
 529  
     }
 530  
 
 531  
     /**
 532  
      * Handles a comment which is placed within the empty code block.
 533  
      * Note, if comment is placed at the end of the empty code block, we have Checkstyle's
 534  
      * limitations to clearly detect user intention of explanation target - above or below. The
 535  
      * only case we can assume as a violation is when a single line comment within the empty
 536  
      * code block has indentation level that is lower than the indentation level of the closing
 537  
      * right curly brace. For example:
 538  
      * <p>
 539  
      * {@code
 540  
      *    if (a == true) {
 541  
      * // violation
 542  
      *    }
 543  
      * }
 544  
      * </p>
 545  
      *
 546  
      * @param comment comment to check.
 547  
      * @param nextStmt next statement.
 548  
      */
 549  
     private void handleCommentInEmptyCodeBlock(DetailAST comment, DetailAST nextStmt) {
 550  20
         if (comment.getColumnNo() < nextStmt.getColumnNo()) {
 551  14
             log(comment.getLineNo(), getMessageKey(comment), nextStmt.getLineNo(),
 552  7
                 comment.getColumnNo(), nextStmt.getColumnNo());
 553  
         }
 554  20
     }
 555  
 
 556  
     /**
 557  
      * Does pre-order traverse of abstract syntax tree to find the previous statement of the
 558  
      * comment. If previous statement of the comment is found, then the traverse will
 559  
      * be finished.
 560  
      * @param comment current statement.
 561  
      * @return previous statement of the comment or null if the comment does not have previous
 562  
      *         statement.
 563  
      */
 564  
     private DetailAST getOneLinePreviousStatement(DetailAST comment) {
 565  278
         DetailAST root = comment.getParent();
 566  544
         while (root != null && !isBlockStart(root)) {
 567  266
             root = root.getParent();
 568  
         }
 569  
 
 570  278
         final Deque<DetailAST> stack = new ArrayDeque<>();
 571  278
         DetailAST previousStatement = null;
 572  13175
         while (root != null || !stack.isEmpty()) {
 573  12897
             if (!stack.isEmpty()) {
 574  12632
                 root = stack.pop();
 575  
             }
 576  34555
             while (root != null) {
 577  21865
                 previousStatement = findPreviousStatement(comment, root);
 578  21865
                 if (previousStatement != null) {
 579  207
                     root = null;
 580  207
                     stack.clear();
 581  207
                     break;
 582  
                 }
 583  21658
                 if (root.getNextSibling() != null) {
 584  12688
                     stack.push(root.getNextSibling());
 585  
                 }
 586  21658
                 root = root.getFirstChild();
 587  
             }
 588  
         }
 589  278
         return previousStatement;
 590  
     }
 591  
 
 592  
     /**
 593  
      * Whether the ast is a comment.
 594  
      * @param ast the ast to check.
 595  
      * @return true if the ast is a comment.
 596  
      */
 597  
     private static boolean isComment(DetailAST ast) {
 598  59492
         final int astType = ast.getType();
 599  59492
         return astType == TokenTypes.SINGLE_LINE_COMMENT
 600  
             || astType == TokenTypes.BLOCK_COMMENT_BEGIN
 601  
             || astType == TokenTypes.COMMENT_CONTENT
 602  
             || astType == TokenTypes.BLOCK_COMMENT_END;
 603  
     }
 604  
 
 605  
     /**
 606  
      * Whether the AST node starts a block.
 607  
      * @param root the AST node to check.
 608  
      * @return true if the AST node starts a block.
 609  
      */
 610  
     private static boolean isBlockStart(DetailAST root) {
 611  1062
         return root.getType() == TokenTypes.SLIST
 612  339
                 || root.getType() == TokenTypes.OBJBLOCK
 613  314
                 || root.getType() == TokenTypes.ARRAY_INIT
 614  305
                 || root.getType() == TokenTypes.CASE_GROUP;
 615  
     }
 616  
 
 617  
     /**
 618  
      * Finds a previous statement of the comment.
 619  
      * Uses root token of the line while searching.
 620  
      * @param comment comment.
 621  
      * @param root root token of the line.
 622  
      * @return previous statement of the comment or null if previous statement was not found.
 623  
      */
 624  
     private DetailAST findPreviousStatement(DetailAST comment, DetailAST root) {
 625  21865
         DetailAST previousStatement = null;
 626  21865
         if (root.getLineNo() >= comment.getLineNo()) {
 627  
             // ATTENTION: parent of the comment is below the comment in case block
 628  
             // See https://github.com/checkstyle/checkstyle/issues/851
 629  12907
             previousStatement = getPrevStatementFromSwitchBlock(comment);
 630  
         }
 631  
         final DetailAST tokenWhichBeginsTheLine;
 632  21865
         if (root.getType() == TokenTypes.EXPR
 633  1454
                 && root.getFirstChild().getFirstChild() != null) {
 634  828
             if (root.getFirstChild().getType() == TokenTypes.LITERAL_NEW) {
 635  262
                 tokenWhichBeginsTheLine = root.getFirstChild();
 636  
             }
 637  
             else {
 638  566
                 tokenWhichBeginsTheLine = findTokenWhichBeginsTheLine(root);
 639  
             }
 640  
         }
 641  21037
         else if (root.getType() == TokenTypes.PLUS) {
 642  58
             tokenWhichBeginsTheLine = root.getFirstChild();
 643  
         }
 644  
         else {
 645  20979
             tokenWhichBeginsTheLine = root;
 646  
         }
 647  21865
         if (tokenWhichBeginsTheLine != null
 648  21823
                 && !isComment(tokenWhichBeginsTheLine)
 649  18955
                 && isOnPreviousLineIgnoringComments(comment, tokenWhichBeginsTheLine)) {
 650  173
             previousStatement = tokenWhichBeginsTheLine;
 651  
         }
 652  21865
         return previousStatement;
 653  
     }
 654  
 
 655  
     /**
 656  
      * Finds a token which begins the line.
 657  
      * @param root root token of the line.
 658  
      * @return token which begins the line.
 659  
      */
 660  
     private static DetailAST findTokenWhichBeginsTheLine(DetailAST root) {
 661  
         final DetailAST tokenWhichBeginsTheLine;
 662  566
         if (isUsingOfObjectReferenceToInvokeMethod(root)) {
 663  298
             tokenWhichBeginsTheLine = findStartTokenOfMethodCallChain(root);
 664  
         }
 665  
         else {
 666  268
             tokenWhichBeginsTheLine = root.getFirstChild().findFirstToken(TokenTypes.IDENT);
 667  
         }
 668  566
         return tokenWhichBeginsTheLine;
 669  
     }
 670  
 
 671  
     /**
 672  
      * Checks whether there is a use of an object reference to invoke an object's method on line.
 673  
      * @param root root token of the line.
 674  
      * @return true if there is a use of an object reference to invoke an object's method on line.
 675  
      */
 676  
     private static boolean isUsingOfObjectReferenceToInvokeMethod(DetailAST root) {
 677  1160
         return root.getFirstChild().getFirstChild().getFirstChild() != null
 678  354
             && root.getFirstChild().getFirstChild().getFirstChild().getNextSibling() != null;
 679  
     }
 680  
 
 681  
     /**
 682  
      * Finds the start token of method call chain.
 683  
      * @param root root token of the line.
 684  
      * @return the start token of method call chain.
 685  
      */
 686  
     private static DetailAST findStartTokenOfMethodCallChain(DetailAST root) {
 687  304
         DetailAST startOfMethodCallChain = root;
 688  1175
         while (startOfMethodCallChain.getFirstChild() != null
 689  1028
                 && startOfMethodCallChain.getFirstChild().getLineNo() == root.getLineNo()) {
 690  871
             startOfMethodCallChain = startOfMethodCallChain.getFirstChild();
 691  
         }
 692  304
         if (startOfMethodCallChain.getFirstChild() != null) {
 693  157
             startOfMethodCallChain = startOfMethodCallChain.getFirstChild().getNextSibling();
 694  
         }
 695  304
         return startOfMethodCallChain;
 696  
     }
 697  
 
 698  
     /**
 699  
      * Checks whether the checked statement is on the previous line ignoring empty lines
 700  
      * and lines which contain only comments.
 701  
      * @param currentStatement current statement.
 702  
      * @param checkedStatement checked statement.
 703  
      * @return true if checked statement is on the line which is previous to current statement
 704  
      *     ignoring empty lines and lines which contain only comments.
 705  
      */
 706  
     private boolean isOnPreviousLineIgnoringComments(DetailAST currentStatement,
 707  
                                                      DetailAST checkedStatement) {
 708  19021
         DetailAST nextToken = getNextToken(checkedStatement);
 709  19021
         int distanceAim = 1;
 710  19021
         if (nextToken != null && isComment(nextToken)) {
 711  752
             distanceAim += countEmptyLines(checkedStatement, currentStatement);
 712  
         }
 713  
 
 714  19773
         while (nextToken != null && nextToken != currentStatement && isComment(nextToken)) {
 715  752
             if (nextToken.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
 716  122
                 distanceAim += nextToken.getLastChild().getLineNo() - nextToken.getLineNo();
 717  
             }
 718  752
             distanceAim++;
 719  752
             nextToken = nextToken.getNextSibling();
 720  
         }
 721  19021
         return currentStatement.getLineNo() - checkedStatement.getLineNo() == distanceAim;
 722  
     }
 723  
 
 724  
     /**
 725  
      * Get the token to start counting the number of lines to add to the distance aim from.
 726  
      * @param checkedStatement the checked statement.
 727  
      * @return the token to start counting the number of lines to add to the distance aim from.
 728  
      */
 729  
     private DetailAST getNextToken(DetailAST checkedStatement) {
 730  
         DetailAST nextToken;
 731  19021
         if (checkedStatement.getType() == TokenTypes.SLIST
 732  18094
                 || checkedStatement.getType() == TokenTypes.ARRAY_INIT
 733  18029
                 || checkedStatement.getType() == TokenTypes.CASE_GROUP) {
 734  1082
             nextToken = checkedStatement.getFirstChild();
 735  
         }
 736  
         else {
 737  17939
             nextToken = checkedStatement.getNextSibling();
 738  
         }
 739  19021
         if (nextToken != null && isComment(nextToken) && isTrailingComment(nextToken)) {
 740  49
             nextToken = nextToken.getNextSibling();
 741  
         }
 742  19021
         return nextToken;
 743  
     }
 744  
 
 745  
     /**
 746  
      * Count the number of empty lines between statements.
 747  
      * @param startStatement start statement.
 748  
      * @param endStatement end statement.
 749  
      * @return the number of empty lines between statements.
 750  
      */
 751  
     private int countEmptyLines(DetailAST startStatement, DetailAST endStatement) {
 752  752
         int emptyLinesNumber = 0;
 753  752
         final String[] lines = getLines();
 754  752
         final int endLineNo = endStatement.getLineNo();
 755  24977
         for (int lineNo = startStatement.getLineNo(); lineNo < endLineNo; lineNo++) {
 756  24225
             if (CommonUtils.isBlank(lines[lineNo])) {
 757  4562
                 emptyLinesNumber++;
 758  
             }
 759  
         }
 760  752
         return emptyLinesNumber;
 761  
     }
 762  
 
 763  
     /**
 764  
      * Logs comment which can have the same indentation level as next or previous statement.
 765  
      * @param comment comment.
 766  
      * @param nextStmt next statement.
 767  
      * @param prevStmt previous statement.
 768  
      */
 769  
     private void logMultilineIndentation(DetailAST prevStmt, DetailAST comment,
 770  
                                          DetailAST nextStmt) {
 771  24
         final String multilineNoTemplate = "%d, %d";
 772  48
         log(comment.getLineNo(), getMessageKey(comment),
 773  48
             String.format(Locale.getDefault(), multilineNoTemplate, prevStmt.getLineNo(),
 774  48
                 nextStmt.getLineNo()), comment.getColumnNo(),
 775  48
             String.format(Locale.getDefault(), multilineNoTemplate,
 776  24
                     getLineStart(prevStmt.getLineNo()), getLineStart(nextStmt.getLineNo())));
 777  24
     }
 778  
 
 779  
     /**
 780  
      * Get a message key depending on a comment type.
 781  
      * @param comment the comment to process.
 782  
      * @return a message key.
 783  
      */
 784  
     private static String getMessageKey(DetailAST comment) {
 785  
         final String msgKey;
 786  121
         if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
 787  94
             msgKey = MSG_KEY_SINGLE;
 788  
         }
 789  
         else {
 790  27
             msgKey = MSG_KEY_BLOCK;
 791  
         }
 792  121
         return msgKey;
 793  
     }
 794  
 
 795  
     /**
 796  
      * Gets comment's previous statement from switch block.
 797  
      * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single-line comment}.
 798  
      * @return comment's previous statement or null if previous statement is absent.
 799  
      */
 800  
     private static DetailAST getPrevStatementFromSwitchBlock(DetailAST comment) {
 801  
         final DetailAST prevStmt;
 802  12907
         final DetailAST parentStatement = comment.getParent();
 803  12907
         if (parentStatement.getType() == TokenTypes.CASE_GROUP) {
 804  458
             prevStmt = getPrevStatementWhenCommentIsUnderCase(parentStatement);
 805  
         }
 806  
         else {
 807  12449
             prevStmt = getPrevCaseToken(parentStatement);
 808  
         }
 809  12907
         return prevStmt;
 810  
     }
 811  
 
 812  
     /**
 813  
      * Gets previous statement for comment which is placed immediately under case.
 814  
      * @param parentStatement comment's parent statement.
 815  
      * @return comment's previous statement or null if previous statement is absent.
 816  
      */
 817  
     private static DetailAST getPrevStatementWhenCommentIsUnderCase(DetailAST parentStatement) {
 818  458
         DetailAST prevStmt = null;
 819  458
         final DetailAST prevBlock = parentStatement.getPreviousSibling();
 820  458
         if (prevBlock.getLastChild() != null) {
 821  24
             DetailAST blockBody = prevBlock.getLastChild().getLastChild();
 822  24
             if (blockBody.getType() == TokenTypes.SEMI) {
 823  19
                 blockBody = blockBody.getPreviousSibling();
 824  
             }
 825  24
             if (blockBody.getType() == TokenTypes.EXPR) {
 826  14
                 if (isUsingOfObjectReferenceToInvokeMethod(blockBody)) {
 827  6
                     prevStmt = findStartTokenOfMethodCallChain(blockBody);
 828  
                 }
 829  
                 else {
 830  8
                     prevStmt = blockBody.getFirstChild().getFirstChild();
 831  
                 }
 832  
             }
 833  
             else {
 834  10
                 if (blockBody.getType() == TokenTypes.SLIST) {
 835  2
                     prevStmt = blockBody.getParent().getParent();
 836  
                 }
 837  
                 else {
 838  8
                     prevStmt = blockBody;
 839  
                 }
 840  
             }
 841  24
             if (isComment(prevStmt)) {
 842  5
                 prevStmt = prevStmt.getNextSibling();
 843  
             }
 844  
         }
 845  458
         return prevStmt;
 846  
     }
 847  
 
 848  
     /**
 849  
      * Gets previous case-token for comment.
 850  
      * @param parentStatement comment's parent statement.
 851  
      * @return previous case-token or null if previous case-token is absent.
 852  
      */
 853  
     private static DetailAST getPrevCaseToken(DetailAST parentStatement) {
 854  
         final DetailAST prevCaseToken;
 855  12449
         final DetailAST parentBlock = parentStatement.getParent();
 856  12449
         if (parentBlock.getParent() != null
 857  12435
                 && parentBlock.getParent().getPreviousSibling() != null
 858  12360
                 && parentBlock.getParent().getPreviousSibling().getType()
 859  
                     == TokenTypes.LITERAL_CASE) {
 860  10
             prevCaseToken = parentBlock.getParent().getPreviousSibling();
 861  
         }
 862  
         else {
 863  12439
             prevCaseToken = null;
 864  
         }
 865  12449
         return prevCaseToken;
 866  
     }
 867  
 
 868  
     /**
 869  
      * Checks if comment and next code statement
 870  
      * (or previous code stmt like <b>case</b> in switch block) are indented at the same level,
 871  
      * e.g.:
 872  
      * <p>
 873  
      * <pre>
 874  
      * {@code
 875  
      * // some comment - same indentation level
 876  
      * int x = 10;
 877  
      *     // some comment - different indentation level
 878  
      * int x1 = 5;
 879  
      * /*
 880  
      *  *
 881  
      *  *&#47;
 882  
      *  boolean bool = true; - same indentation level
 883  
      * }
 884  
      * </pre>
 885  
      * </p>
 886  
      * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
 887  
      * @param prevStmt previous code statement.
 888  
      * @param nextStmt next code statement.
 889  
      * @return true if comment and next code statement are indented at the same level.
 890  
      */
 891  
     private boolean areSameLevelIndented(DetailAST comment, DetailAST prevStmt,
 892  
                                                 DetailAST nextStmt) {
 893  
 
 894  564
         return comment.getColumnNo() == getLineStart(nextStmt.getLineNo())
 895  124
             || comment.getColumnNo() == getLineStart(prevStmt.getLineNo());
 896  
     }
 897  
 
 898  
     /**
 899  
      * Get a column number where a code starts.
 900  
      * @param lineNo the line number to get column number in.
 901  
      * @return the column number where a code starts.
 902  
      */
 903  
     private int getLineStart(int lineNo) {
 904  502
         final char[] line = getLines()[lineNo - 1].toCharArray();
 905  502
         int lineStart = 0;
 906  5781
         while (Character.isWhitespace(line[lineStart])) {
 907  5279
             lineStart++;
 908  
         }
 909  502
         return lineStart;
 910  
     }
 911  
 
 912  
     /**
 913  
      * Checks if current comment is a trailing comment.
 914  
      * @param comment comment to check.
 915  
      * @return true if current comment is a trailing comment.
 916  
      */
 917  
     private boolean isTrailingComment(DetailAST comment) {
 918  
         final boolean isTrailingComment;
 919  1146
         if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
 920  952
             isTrailingComment = isTrailingSingleLineComment(comment);
 921  
         }
 922  
         else {
 923  194
             isTrailingComment = isTrailingBlockComment(comment);
 924  
         }
 925  1146
         return isTrailingComment;
 926  
     }
 927  
 
 928  
     /**
 929  
      * Checks if current single line comment is trailing comment, e.g.:
 930  
      * <p>
 931  
      * {@code
 932  
      * double d = 3.14; // some comment
 933  
      * }
 934  
      * </p>
 935  
      * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
 936  
      * @return true if current single line comment is trailing comment.
 937  
      */
 938  
     private boolean isTrailingSingleLineComment(DetailAST singleLineComment) {
 939  952
         final String targetSourceLine = getLine(singleLineComment.getLineNo() - 1);
 940  952
         final int commentColumnNo = singleLineComment.getColumnNo();
 941  952
         return !CommonUtils.hasWhitespaceBefore(commentColumnNo, targetSourceLine);
 942  
     }
 943  
 
 944  
     /**
 945  
      * Checks if current comment block is trailing comment, e.g.:
 946  
      * <p>
 947  
      * {@code
 948  
      * double d = 3.14; /* some comment *&#47;
 949  
      * /* some comment *&#47; double d = 18.5;
 950  
      * }
 951  
      * </p>
 952  
      * @param blockComment {@link TokenTypes#BLOCK_COMMENT_BEGIN block comment begin}.
 953  
      * @return true if current comment block is trailing comment.
 954  
      */
 955  
     private boolean isTrailingBlockComment(DetailAST blockComment) {
 956  194
         final String commentLine = getLine(blockComment.getLineNo() - 1);
 957  194
         final int commentColumnNo = blockComment.getColumnNo();
 958  194
         final DetailAST nextSibling = blockComment.getNextSibling();
 959  388
         return !CommonUtils.hasWhitespaceBefore(commentColumnNo, commentLine)
 960  190
             || nextSibling != null && nextSibling.getLineNo() == blockComment.getLineNo();
 961  
     }
 962  
 }