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