001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2021 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 com.puppycrawl.tools.checkstyle.StatelessCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.TokenTypes;
030import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
031import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
032
033/**
034 * <p>
035 * Controls the indentation between comments and surrounding code.
036 * Comments are indented at the same level as the surrounding code.
037 * Detailed info about such convention can be found
038 * <a href="https://checkstyle.org/styleguides/google-java-style-20180523/javaguide.html#s4.8.6.1-block-comment-style">
039 * here</a>
040 * </p>
041 * <p>
042 * Please take a look at the following examples to understand how the check works:
043 * </p>
044 * <p>
045 * Example #1: Block comments.
046 * </p>
047 * <pre>
048 * 1   &#47;*
049 * 2    * it is Ok
050 * 3    *&#47;
051 * 4   boolean bool = true;
052 * 5
053 * 6     &#47;* violation
054 * 7      * (block comment should have the same indentation level as line 9)
055 * 8      *&#47;
056 * 9   double d = 3.14;
057 * </pre>
058 * <p>
059 * Example #2: Comment is placed at the end of the block and has previous statement.
060 * </p>
061 * <pre>
062 * 1   public void foo1() {
063 * 2     foo2();
064 * 3     // it is OK
065 * 4   }
066 * 5
067 * 6   public void foo2() {
068 * 7     foo3();
069 * 8       // violation (comment should have the same indentation level as line 7)
070 * 9   }
071 * </pre>
072 * <p>
073 * Example #3: Comment is used as a single line border to separate groups of methods.
074 * </p>
075 * <pre>
076 * 1   /////////////////////////////// it is OK
077 * 2
078 * 3   public void foo7() {
079 * 4     int a = 0;
080 * 5   }
081 * 6
082 * 7     ///////////////////////////// violation (should have the same indentation level as line 9)
083 * 8
084 * 9   public void foo8() {}
085 * </pre>
086 * <p>
087 * Example #4: Comment has distributed previous statement.
088 * </p>
089 * <pre>
090 * 1   public void foo11() {
091 * 2     CheckUtil
092 * 3       .getFirstNode(new DetailAST())
093 * 4       .getFirstChild()
094 * 5       .getNextSibling();
095 * 6     // it is OK
096 * 7   }
097 * 8
098 * 9   public void foo12() {
099 * 10    CheckUtil
100 * 11      .getFirstNode(new DetailAST())
101 * 12      .getFirstChild()
102 * 13      .getNextSibling();
103 * 14              // violation (should have the same indentation level as line 10)
104 * 15  }
105 * </pre>
106 * <p>
107 * Example #5: Single line block comment is placed within an empty code block.
108 * Note, if comment is placed at the end of the empty code block, we have
109 * Checkstyle's limitations to clearly detect user intention of explanation
110 * target - above or below. The only case we can assume as a violation is when
111 * a single line comment within the empty code block has indentation level that
112 * is lower than the indentation level of the closing right curly brace.
113 * </p>
114 * <pre>
115 * 1   public void foo46() {
116 * 2     // comment
117 * 3     // block
118 * 4     // it is OK (we cannot clearly detect user intention of explanation target)
119 * 5   }
120 * 6
121 * 7   public void foo46() {
122 * 8  // comment
123 * 9  // block
124 * 10 // violation (comment should have the same indentation level as line 11)
125 * 11  }
126 * </pre>
127 * <p>
128 * Example #6: 'fallthrough' comments and similar.
129 * </p>
130 * <pre>
131 * 0   switch(a) {
132 * 1     case "1":
133 * 2       int k = 7;
134 * 3       // it is OK
135 * 4     case "2":
136 * 5       int k = 7;
137 * 6     // it is OK
138 * 7     case "3":
139 * 8       if (true) {}
140 * 9           // violation (should have the same indentation level as line 8 or 10)
141 * 10    case "4":
142 * 11    case "5": {
143 * 12      int a;
144 * 13    }
145 * 14    // fall through (it is OK)
146 * 15    case "12": {
147 * 16      int a;
148 * 17    }
149 * 18    default:
150 * 19      // it is OK
151 * 20  }
152 * </pre>
153 * <p>
154 * Example #7: Comment is placed within a distributed statement.
155 * </p>
156 * <pre>
157 * 1   String breaks = "J"
158 * 2   // violation (comment should have the same indentation level as line 3)
159 * 3       + "A"
160 * 4       // it is OK
161 * 5       + "V"
162 * 6       + "A"
163 * 7   // it is OK
164 * 8   ;
165 * </pre>
166 * <p>
167 * Example #8: Comment is placed within an empty case block.
168 * Note, if comment is placed at the end of the empty case block, we have
169 * Checkstyle's limitations to clearly detect user intention of explanation
170 * target - above or below. The only case we can assume as a violation is when
171 * a single line comment within the empty case block has indentation level that
172 * is lower than the indentation level of the next case token.
173 * </p>
174 * <pre>
175 * 1   case 4:
176 * 2     // it is OK
177 * 3   case 5:
178 * 4  // violation (should have the same indentation level as line 3 or 5)
179 * 5   case 6:
180 * </pre>
181 * <p>
182 * Example #9: Single line block comment has previous and next statement.
183 * </p>
184 * <pre>
185 * 1   String s1 = "Clean code!";
186 * 2      s.toString().toString().toString();
187 * 3   // single line
188 * 4   // block
189 * 5   // comment (it is OK)
190 * 6   int a = 5;
191 * 7
192 * 8   String s2 = "Code complete!";
193 * 9    s.toString().toString().toString();
194 * 10            // violation (should have the same indentation level as line 11)
195 * 11       // violation (should have the same indentation level as line 12)
196 * 12     // violation (should have the same indentation level as line 13)
197 * 13  int b = 18;
198 * </pre>
199 * <p>
200 * Example #10: Comment within the block tries to describe the next code block.
201 * </p>
202 * <pre>
203 * 1   public void foo42() {
204 * 2     int a = 5;
205 * 3     if (a == 5) {
206 * 4       int b;
207 * 5       // it is OK
208 * 6      } else if (a ==6) { ... }
209 * 7   }
210 * 8
211 * 9   public void foo43() {
212 * 10    try {
213 * 11      int a;
214 * 12     // Why do we catch exception here? - violation (not the same indentation as line 11)
215 * 13     } catch (Exception e) { ... }
216 * 14  }
217 * </pre>
218 * <ul>
219 * <li>
220 * Property {@code tokens} - tokens to check
221 * Type is {@code java.lang.String[]}.
222 * Validation type is {@code tokenSet}.
223 * Default value is:
224 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#SINGLE_LINE_COMMENT">
225 * SINGLE_LINE_COMMENT</a>,
226 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#BLOCK_COMMENT_BEGIN">
227 * BLOCK_COMMENT_BEGIN</a>.
228 * </li>
229 * </ul>
230 * <p>
231 * To configure the Check:
232 * </p>
233 * <pre>
234 * &lt;module name=&quot;CommentsIndentation&quot;/&gt;
235 * </pre>
236 * <p>
237 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
238 * </p>
239 * <p>
240 * Violation Message Keys:
241 * </p>
242 * <ul>
243 * <li>
244 * {@code comments.indentation.block}
245 * </li>
246 * <li>
247 * {@code comments.indentation.single}
248 * </li>
249 * </ul>
250 *
251 * @since 6.10
252 */
253@StatelessCheck
254public class CommentsIndentationCheck extends AbstractCheck {
255
256    /**
257     * A key is pointing to the warning message text in "messages.properties" file.
258     */
259    public static final String MSG_KEY_SINGLE = "comments.indentation.single";
260
261    /**
262     * A key is pointing to the warning message text in "messages.properties" file.
263     */
264    public static final String MSG_KEY_BLOCK = "comments.indentation.block";
265
266    @Override
267    public int[] getDefaultTokens() {
268        return new int[] {
269            TokenTypes.SINGLE_LINE_COMMENT,
270            TokenTypes.BLOCK_COMMENT_BEGIN,
271        };
272    }
273
274    @Override
275    public int[] getAcceptableTokens() {
276        return new int[] {
277            TokenTypes.SINGLE_LINE_COMMENT,
278            TokenTypes.BLOCK_COMMENT_BEGIN,
279        };
280    }
281
282    @Override
283    public int[] getRequiredTokens() {
284        return CommonUtil.EMPTY_INT_ARRAY;
285    }
286
287    @Override
288    public boolean isCommentNodesRequired() {
289        return true;
290    }
291
292    @Override
293    public void visitToken(DetailAST commentAst) {
294        switch (commentAst.getType()) {
295            case TokenTypes.SINGLE_LINE_COMMENT:
296            case TokenTypes.BLOCK_COMMENT_BEGIN:
297                visitComment(commentAst);
298                break;
299            default:
300                final String exceptionMsg = "Unexpected token type: " + commentAst.getText();
301                throw new IllegalArgumentException(exceptionMsg);
302        }
303    }
304
305    /**
306     * Checks comment indentations over surrounding code, e.g.:
307     * <p>
308     * {@code
309     * // some comment - this is ok
310     * double d = 3.14;
311     *     // some comment - this is <b>not</b> ok.
312     * double d1 = 5.0;
313     * }
314     * </p>
315     *
316     * @param comment comment to check.
317     */
318    private void visitComment(DetailAST comment) {
319        if (!isTrailingComment(comment)) {
320            final DetailAST prevStmt = getPreviousStatement(comment);
321            final DetailAST nextStmt = getNextStmt(comment);
322
323            if (isInEmptyCaseBlock(prevStmt, nextStmt)) {
324                handleCommentInEmptyCaseBlock(prevStmt, comment, nextStmt);
325            }
326            else if (isFallThroughComment(prevStmt, nextStmt)) {
327                handleFallThroughComment(prevStmt, comment, nextStmt);
328            }
329            else if (isInEmptyCodeBlock(prevStmt, nextStmt)) {
330                handleCommentInEmptyCodeBlock(comment, nextStmt);
331            }
332            else if (isCommentAtTheEndOfTheCodeBlock(nextStmt)) {
333                handleCommentAtTheEndOfTheCodeBlock(prevStmt, comment, nextStmt);
334            }
335            else if (nextStmt != null && !areSameLevelIndented(comment, nextStmt, nextStmt)
336                    && !areInSameMethodCallWithSameIndent(comment)) {
337                log(comment, getMessageKey(comment), nextStmt.getLineNo(),
338                    comment.getColumnNo(), nextStmt.getColumnNo());
339            }
340        }
341    }
342
343    /**
344     * Returns the next statement of a comment.
345     *
346     * @param comment comment.
347     * @return the next statement of a comment.
348     */
349    private static DetailAST getNextStmt(DetailAST comment) {
350        DetailAST nextStmt = comment.getNextSibling();
351        while (nextStmt != null
352                && isComment(nextStmt)
353                && comment.getColumnNo() != nextStmt.getColumnNo()) {
354            nextStmt = nextStmt.getNextSibling();
355        }
356        return nextStmt;
357    }
358
359    /**
360     * Returns the previous statement of a comment.
361     *
362     * @param comment comment.
363     * @return the previous statement of a comment.
364     */
365    private DetailAST getPreviousStatement(DetailAST comment) {
366        final DetailAST prevStatement;
367        if (isDistributedPreviousStatement(comment)) {
368            prevStatement = getDistributedPreviousStatement(comment);
369        }
370        else {
371            prevStatement = getOneLinePreviousStatement(comment);
372        }
373        return prevStatement;
374    }
375
376    /**
377     * Checks whether the previous statement of a comment is distributed over two or more lines.
378     *
379     * @param comment comment to check.
380     * @return true if the previous statement of a comment is distributed over two or more lines.
381     */
382    private boolean isDistributedPreviousStatement(DetailAST comment) {
383        final DetailAST previousSibling = comment.getPreviousSibling();
384        return isDistributedExpression(comment)
385            || isDistributedReturnStatement(previousSibling)
386            || isDistributedThrowStatement(previousSibling);
387    }
388
389    /**
390     * Checks whether the previous statement of a comment is a method call chain or
391     * string concatenation statement distributed over two ore more lines.
392     *
393     * @param comment comment to check.
394     * @return true if the previous statement is a distributed expression.
395     */
396    private boolean isDistributedExpression(DetailAST comment) {
397        DetailAST previousSibling = comment.getPreviousSibling();
398        while (previousSibling != null && isComment(previousSibling)) {
399            previousSibling = previousSibling.getPreviousSibling();
400        }
401        boolean isDistributed = false;
402        if (previousSibling != null) {
403            if (previousSibling.getType() == TokenTypes.SEMI
404                    && isOnPreviousLineIgnoringComments(comment, previousSibling)) {
405                DetailAST currentToken = previousSibling.getPreviousSibling();
406                while (currentToken.getFirstChild() != null) {
407                    currentToken = currentToken.getFirstChild();
408                }
409                if (currentToken.getType() == TokenTypes.COMMENT_CONTENT) {
410                    currentToken = currentToken.getParent();
411                    while (isComment(currentToken)) {
412                        currentToken = currentToken.getNextSibling();
413                    }
414                }
415                if (!TokenUtil.areOnSameLine(previousSibling, currentToken)) {
416                    isDistributed = true;
417                }
418            }
419            else {
420                isDistributed = isStatementWithPossibleCurlies(previousSibling);
421            }
422        }
423        return isDistributed;
424    }
425
426    /**
427     * Whether the statement can have or always have curly brackets.
428     *
429     * @param previousSibling the statement to check.
430     * @return true if the statement can have or always have curly brackets.
431     */
432    private static boolean isStatementWithPossibleCurlies(DetailAST previousSibling) {
433        return previousSibling.getType() == TokenTypes.LITERAL_IF
434            || previousSibling.getType() == TokenTypes.LITERAL_TRY
435            || previousSibling.getType() == TokenTypes.LITERAL_FOR
436            || previousSibling.getType() == TokenTypes.LITERAL_DO
437            || previousSibling.getType() == TokenTypes.LITERAL_WHILE
438            || previousSibling.getType() == TokenTypes.LITERAL_SWITCH
439            || isDefinition(previousSibling);
440    }
441
442    /**
443     * Whether the statement is a kind of definition (method, class etc.).
444     *
445     * @param previousSibling the statement to check.
446     * @return true if the statement is a kind of definition.
447     */
448    private static boolean isDefinition(DetailAST previousSibling) {
449        return TokenUtil.isTypeDeclaration(previousSibling.getType())
450            || previousSibling.getType() == TokenTypes.METHOD_DEF;
451    }
452
453    /**
454     * Checks whether the previous statement of a comment is a distributed return statement.
455     *
456     * @param commentPreviousSibling previous sibling of the comment.
457     * @return true if the previous statement of a comment is a distributed return statement.
458     */
459    private static boolean isDistributedReturnStatement(DetailAST commentPreviousSibling) {
460        boolean isDistributed = false;
461        if (commentPreviousSibling != null
462                && commentPreviousSibling.getType() == TokenTypes.LITERAL_RETURN) {
463            final DetailAST firstChild = commentPreviousSibling.getFirstChild();
464            final DetailAST nextSibling = firstChild.getNextSibling();
465            if (nextSibling != null) {
466                isDistributed = true;
467            }
468        }
469        return isDistributed;
470    }
471
472    /**
473     * Checks whether the previous statement of a comment is a distributed throw statement.
474     *
475     * @param commentPreviousSibling previous sibling of the comment.
476     * @return true if the previous statement of a comment is a distributed throw statement.
477     */
478    private static boolean isDistributedThrowStatement(DetailAST commentPreviousSibling) {
479        boolean isDistributed = false;
480        if (commentPreviousSibling != null
481                && commentPreviousSibling.getType() == TokenTypes.LITERAL_THROW) {
482            final DetailAST firstChild = commentPreviousSibling.getFirstChild();
483            final DetailAST nextSibling = firstChild.getNextSibling();
484            if (!TokenUtil.areOnSameLine(nextSibling, commentPreviousSibling)) {
485                isDistributed = true;
486            }
487        }
488        return isDistributed;
489    }
490
491    /**
492     * Returns the first token of the distributed previous statement of comment.
493     *
494     * @param comment comment to check.
495     * @return the first token of the distributed previous statement of comment.
496     */
497    private static DetailAST getDistributedPreviousStatement(DetailAST comment) {
498        DetailAST currentToken = comment.getPreviousSibling();
499        while (isComment(currentToken)) {
500            currentToken = currentToken.getPreviousSibling();
501        }
502        final DetailAST previousStatement;
503        if (currentToken.getType() == TokenTypes.SEMI) {
504            currentToken = currentToken.getPreviousSibling();
505            while (currentToken.getFirstChild() != null) {
506                currentToken = currentToken.getFirstChild();
507            }
508            previousStatement = currentToken;
509        }
510        else {
511            previousStatement = currentToken;
512        }
513        return previousStatement;
514    }
515
516    /**
517     * Checks whether case block is empty.
518     *
519     * @param nextStmt previous statement.
520     * @param prevStmt next statement.
521     * @return true if case block is empty.
522     */
523    private static boolean isInEmptyCaseBlock(DetailAST prevStmt, DetailAST nextStmt) {
524        return prevStmt != null
525            && nextStmt != null
526            && (prevStmt.getType() == TokenTypes.LITERAL_CASE
527                || prevStmt.getType() == TokenTypes.CASE_GROUP)
528            && (nextStmt.getType() == TokenTypes.LITERAL_CASE
529                || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
530    }
531
532    /**
533     * Checks whether comment is a 'fall through' comment.
534     * For example:
535     * <p>
536     * {@code
537     *    ...
538     *    case OPTION_ONE:
539     *        int someVariable = 1;
540     *        // fall through
541     *    case OPTION_TWO:
542     *        int a = 5;
543     *        break;
544     *    ...
545     * }
546     * </p>
547     *
548     * @param prevStmt previous statement.
549     * @param nextStmt next statement.
550     * @return true if a comment is a 'fall through' comment.
551     */
552    private static boolean isFallThroughComment(DetailAST prevStmt, DetailAST nextStmt) {
553        return prevStmt != null
554            && nextStmt != null
555            && prevStmt.getType() != TokenTypes.LITERAL_CASE
556            && (nextStmt.getType() == TokenTypes.LITERAL_CASE
557                || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
558    }
559
560    /**
561     * Checks whether a comment is placed at the end of the code block.
562     *
563     * @param nextStmt next statement.
564     * @return true if a comment is placed at the end of the block.
565     */
566    private static boolean isCommentAtTheEndOfTheCodeBlock(DetailAST nextStmt) {
567        return nextStmt != null
568            && nextStmt.getType() == TokenTypes.RCURLY;
569    }
570
571    /**
572     * Checks whether comment is placed in the empty code block.
573     * For example:
574     * <p>
575     * ...
576     * {@code
577     *  // empty code block
578     * }
579     * ...
580     * </p>
581     * Note, the method does not treat empty case blocks.
582     *
583     * @param prevStmt previous statement.
584     * @param nextStmt next statement.
585     * @return true if comment is placed in the empty code block.
586     */
587    private static boolean isInEmptyCodeBlock(DetailAST prevStmt, DetailAST nextStmt) {
588        return prevStmt != null
589            && nextStmt != null
590            && (prevStmt.getType() == TokenTypes.SLIST
591                || prevStmt.getType() == TokenTypes.LCURLY
592                || prevStmt.getType() == TokenTypes.ARRAY_INIT
593                || prevStmt.getType() == TokenTypes.OBJBLOCK)
594            && nextStmt.getType() == TokenTypes.RCURLY;
595    }
596
597    /**
598     * Handles a comment which is placed within empty case block.
599     * Note, if comment is placed at the end of the empty case block, we have Checkstyle's
600     * limitations to clearly detect user intention of explanation target - above or below. The
601     * only case we can assume as a violation is when a single line comment within the empty case
602     * block has indentation level that is lower than the indentation level of the next case
603     * token. For example:
604     * <p>
605     * {@code
606     *    ...
607     *    case OPTION_ONE:
608     * // violation
609     *    case OPTION_TWO:
610     *    ...
611     * }
612     * </p>
613     *
614     * @param prevStmt previous statement.
615     * @param comment single line comment.
616     * @param nextStmt next statement.
617     */
618    private void handleCommentInEmptyCaseBlock(DetailAST prevStmt, DetailAST comment,
619                                               DetailAST nextStmt) {
620        if (comment.getColumnNo() < prevStmt.getColumnNo()
621                || comment.getColumnNo() < nextStmt.getColumnNo()) {
622            logMultilineIndentation(prevStmt, comment, nextStmt);
623        }
624    }
625
626    /**
627     * Handles 'fall through' single line comment.
628     * Note, 'fall through' and similar comments can have indentation level as next or previous
629     * statement.
630     * For example:
631     * <p>
632     * {@code
633     *    ...
634     *    case OPTION_ONE:
635     *        int someVariable = 1;
636     *        // fall through - OK
637     *    case OPTION_TWO:
638     *        int a = 5;
639     *        break;
640     *    ...
641     * }
642     * </p>
643     * <p>
644     * {@code
645     *    ...
646     *    case OPTION_ONE:
647     *        int someVariable = 1;
648     *    // then init variable a - OK
649     *    case OPTION_TWO:
650     *        int a = 5;
651     *        break;
652     *    ...
653     * }
654     * </p>
655     *
656     * @param prevStmt previous statement.
657     * @param comment single line comment.
658     * @param nextStmt next statement.
659     */
660    private void handleFallThroughComment(DetailAST prevStmt, DetailAST comment,
661                                          DetailAST nextStmt) {
662        if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
663            logMultilineIndentation(prevStmt, comment, nextStmt);
664        }
665    }
666
667    /**
668     * Handles a comment which is placed at the end of non empty code block.
669     * Note, if single line comment is placed at the end of non empty block the comment should have
670     * the same indentation level as the previous statement. For example:
671     * <p>
672     * {@code
673     *    if (a == true) {
674     *        int b = 1;
675     *        // comment
676     *    }
677     * }
678     * </p>
679     *
680     * @param prevStmt previous statement.
681     * @param comment comment to check.
682     * @param nextStmt next statement.
683     */
684    private void handleCommentAtTheEndOfTheCodeBlock(DetailAST prevStmt, DetailAST comment,
685                                                     DetailAST nextStmt) {
686        if (prevStmt != null) {
687            if (prevStmt.getType() == TokenTypes.LITERAL_CASE
688                    || prevStmt.getType() == TokenTypes.CASE_GROUP
689                    || prevStmt.getType() == TokenTypes.LITERAL_DEFAULT) {
690                if (comment.getColumnNo() < nextStmt.getColumnNo()) {
691                    log(comment, getMessageKey(comment), nextStmt.getLineNo(),
692                        comment.getColumnNo(), nextStmt.getColumnNo());
693                }
694            }
695            else if (isCommentForMultiblock(nextStmt)) {
696                if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
697                    logMultilineIndentation(prevStmt, comment, nextStmt);
698                }
699            }
700            else if (!areSameLevelIndented(comment, prevStmt, prevStmt)) {
701                final int prevStmtLineNo = prevStmt.getLineNo();
702                log(comment, getMessageKey(comment), prevStmtLineNo,
703                        comment.getColumnNo(), getLineStart(prevStmtLineNo));
704            }
705        }
706    }
707
708    /**
709     * Whether the comment might have been used for the next block in a multi-block structure.
710     *
711     * @param endBlockStmt the end of the current block.
712     * @return true, if the comment might have been used for the next
713     *     block in a multi-block structure.
714     */
715    private static boolean isCommentForMultiblock(DetailAST endBlockStmt) {
716        final DetailAST nextBlock = endBlockStmt.getParent().getNextSibling();
717        final int endBlockLineNo = endBlockStmt.getLineNo();
718        final DetailAST catchAst = endBlockStmt.getParent().getParent();
719        final DetailAST finallyAst = catchAst.getNextSibling();
720        return nextBlock != null && nextBlock.getLineNo() == endBlockLineNo
721                || finallyAst != null
722                    && catchAst.getType() == TokenTypes.LITERAL_CATCH
723                    && finallyAst.getLineNo() == endBlockLineNo;
724    }
725
726    /**
727     * Handles a comment which is placed within the empty code block.
728     * Note, if comment is placed at the end of the empty code block, we have Checkstyle's
729     * limitations to clearly detect user intention of explanation target - above or below. The
730     * only case we can assume as a violation is when a single line comment within the empty
731     * code block has indentation level that is lower than the indentation level of the closing
732     * right curly brace. For example:
733     * <p>
734     * {@code
735     *    if (a == true) {
736     * // violation
737     *    }
738     * }
739     * </p>
740     *
741     * @param comment comment to check.
742     * @param nextStmt next statement.
743     */
744    private void handleCommentInEmptyCodeBlock(DetailAST comment, DetailAST nextStmt) {
745        if (comment.getColumnNo() < nextStmt.getColumnNo()) {
746            log(comment, getMessageKey(comment), nextStmt.getLineNo(),
747                comment.getColumnNo(), nextStmt.getColumnNo());
748        }
749    }
750
751    /**
752     * Does pre-order traverse of abstract syntax tree to find the previous statement of the
753     * comment. If previous statement of the comment is found, then the traverse will
754     * be finished.
755     *
756     * @param comment current statement.
757     * @return previous statement of the comment or null if the comment does not have previous
758     *         statement.
759     */
760    private DetailAST getOneLinePreviousStatement(DetailAST comment) {
761        DetailAST root = comment.getParent();
762        while (root != null && !isBlockStart(root)) {
763            root = root.getParent();
764        }
765
766        final Deque<DetailAST> stack = new ArrayDeque<>();
767        DetailAST previousStatement = null;
768        while (root != null || !stack.isEmpty()) {
769            if (!stack.isEmpty()) {
770                root = stack.pop();
771            }
772            while (root != null) {
773                previousStatement = findPreviousStatement(comment, root);
774                if (previousStatement != null) {
775                    root = null;
776                    stack.clear();
777                    break;
778                }
779                if (root.getNextSibling() != null) {
780                    stack.push(root.getNextSibling());
781                }
782                root = root.getFirstChild();
783            }
784        }
785        return previousStatement;
786    }
787
788    /**
789     * Whether the ast is a comment.
790     *
791     * @param ast the ast to check.
792     * @return true if the ast is a comment.
793     */
794    private static boolean isComment(DetailAST ast) {
795        final int astType = ast.getType();
796        return astType == TokenTypes.SINGLE_LINE_COMMENT
797            || astType == TokenTypes.BLOCK_COMMENT_BEGIN
798            || astType == TokenTypes.COMMENT_CONTENT
799            || astType == TokenTypes.BLOCK_COMMENT_END;
800    }
801
802    /**
803     * Whether the AST node starts a block.
804     *
805     * @param root the AST node to check.
806     * @return true if the AST node starts a block.
807     */
808    private static boolean isBlockStart(DetailAST root) {
809        return root.getType() == TokenTypes.SLIST
810                || root.getType() == TokenTypes.OBJBLOCK
811                || root.getType() == TokenTypes.ARRAY_INIT
812                || root.getType() == TokenTypes.CASE_GROUP;
813    }
814
815    /**
816     * Finds a previous statement of the comment.
817     * Uses root token of the line while searching.
818     *
819     * @param comment comment.
820     * @param root root token of the line.
821     * @return previous statement of the comment or null if previous statement was not found.
822     */
823    private DetailAST findPreviousStatement(DetailAST comment, DetailAST root) {
824        DetailAST previousStatement = null;
825        if (root.getLineNo() >= comment.getLineNo()) {
826            // ATTENTION: parent of the comment is below the comment in case block
827            // See https://github.com/checkstyle/checkstyle/issues/851
828            previousStatement = getPrevStatementFromSwitchBlock(comment);
829        }
830        final DetailAST tokenWhichBeginsTheLine;
831        if (root.getType() == TokenTypes.EXPR
832                && root.getFirstChild().getFirstChild() != null) {
833            if (root.getFirstChild().getType() == TokenTypes.LITERAL_NEW) {
834                tokenWhichBeginsTheLine = root.getFirstChild();
835            }
836            else {
837                tokenWhichBeginsTheLine = findTokenWhichBeginsTheLine(root);
838            }
839        }
840        else if (root.getType() == TokenTypes.PLUS) {
841            tokenWhichBeginsTheLine = root.getFirstChild();
842        }
843        else {
844            tokenWhichBeginsTheLine = root;
845        }
846        if (tokenWhichBeginsTheLine != null
847                && !isComment(tokenWhichBeginsTheLine)
848                && isOnPreviousLineIgnoringComments(comment, tokenWhichBeginsTheLine)) {
849            previousStatement = tokenWhichBeginsTheLine;
850        }
851        return previousStatement;
852    }
853
854    /**
855     * Finds a token which begins the line.
856     *
857     * @param root root token of the line.
858     * @return token which begins the line.
859     */
860    private static DetailAST findTokenWhichBeginsTheLine(DetailAST root) {
861        final DetailAST tokenWhichBeginsTheLine;
862        if (isUsingOfObjectReferenceToInvokeMethod(root)) {
863            tokenWhichBeginsTheLine = findStartTokenOfMethodCallChain(root);
864        }
865        else {
866            tokenWhichBeginsTheLine = root.getFirstChild().findFirstToken(TokenTypes.IDENT);
867        }
868        return tokenWhichBeginsTheLine;
869    }
870
871    /**
872     * Checks whether there is a use of an object reference to invoke an object's method on line.
873     *
874     * @param root root token of the line.
875     * @return true if there is a use of an object reference to invoke an object's method on line.
876     */
877    private static boolean isUsingOfObjectReferenceToInvokeMethod(DetailAST root) {
878        return root.getFirstChild().getFirstChild().getFirstChild() != null
879            && root.getFirstChild().getFirstChild().getFirstChild().getNextSibling() != null;
880    }
881
882    /**
883     * Finds the start token of method call chain.
884     *
885     * @param root root token of the line.
886     * @return the start token of method call chain.
887     */
888    private static DetailAST findStartTokenOfMethodCallChain(DetailAST root) {
889        DetailAST startOfMethodCallChain = root;
890        while (startOfMethodCallChain.getFirstChild() != null
891                && TokenUtil.areOnSameLine(startOfMethodCallChain.getFirstChild(), root)) {
892            startOfMethodCallChain = startOfMethodCallChain.getFirstChild();
893        }
894        if (startOfMethodCallChain.getFirstChild() != null) {
895            startOfMethodCallChain = startOfMethodCallChain.getFirstChild().getNextSibling();
896        }
897        return startOfMethodCallChain;
898    }
899
900    /**
901     * Checks whether the checked statement is on the previous line ignoring empty lines
902     * and lines which contain only comments.
903     *
904     * @param currentStatement current statement.
905     * @param checkedStatement checked statement.
906     * @return true if checked statement is on the line which is previous to current statement
907     *     ignoring empty lines and lines which contain only comments.
908     */
909    private boolean isOnPreviousLineIgnoringComments(DetailAST currentStatement,
910                                                     DetailAST checkedStatement) {
911        DetailAST nextToken = getNextToken(checkedStatement);
912        int distanceAim = 1;
913        if (nextToken != null && isComment(nextToken)) {
914            distanceAim += countEmptyLines(checkedStatement, currentStatement);
915        }
916
917        while (nextToken != null && nextToken != currentStatement && isComment(nextToken)) {
918            if (nextToken.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
919                distanceAim += nextToken.getLastChild().getLineNo() - nextToken.getLineNo();
920            }
921            distanceAim++;
922            nextToken = nextToken.getNextSibling();
923        }
924        return currentStatement.getLineNo() - checkedStatement.getLineNo() == distanceAim;
925    }
926
927    /**
928     * Get the token to start counting the number of lines to add to the distance aim from.
929     *
930     * @param checkedStatement the checked statement.
931     * @return the token to start counting the number of lines to add to the distance aim from.
932     */
933    private DetailAST getNextToken(DetailAST checkedStatement) {
934        DetailAST nextToken;
935        if (checkedStatement.getType() == TokenTypes.SLIST
936                || checkedStatement.getType() == TokenTypes.ARRAY_INIT
937                || checkedStatement.getType() == TokenTypes.CASE_GROUP) {
938            nextToken = checkedStatement.getFirstChild();
939        }
940        else {
941            nextToken = checkedStatement.getNextSibling();
942        }
943        if (nextToken != null && isComment(nextToken) && isTrailingComment(nextToken)) {
944            nextToken = nextToken.getNextSibling();
945        }
946        return nextToken;
947    }
948
949    /**
950     * Count the number of empty lines between statements.
951     *
952     * @param startStatement start statement.
953     * @param endStatement end statement.
954     * @return the number of empty lines between statements.
955     */
956    private int countEmptyLines(DetailAST startStatement, DetailAST endStatement) {
957        int emptyLinesNumber = 0;
958        final String[] lines = getLines();
959        final int endLineNo = endStatement.getLineNo();
960        for (int lineNo = startStatement.getLineNo(); lineNo < endLineNo; lineNo++) {
961            if (CommonUtil.isBlank(lines[lineNo])) {
962                emptyLinesNumber++;
963            }
964        }
965        return emptyLinesNumber;
966    }
967
968    /**
969     * Logs comment which can have the same indentation level as next or previous statement.
970     *
971     * @param comment comment.
972     * @param nextStmt next statement.
973     * @param prevStmt previous statement.
974     */
975    private void logMultilineIndentation(DetailAST prevStmt, DetailAST comment,
976                                         DetailAST nextStmt) {
977        final String multilineNoTemplate = "%d, %d";
978        log(comment, getMessageKey(comment),
979            String.format(Locale.getDefault(), multilineNoTemplate, prevStmt.getLineNo(),
980                nextStmt.getLineNo()), comment.getColumnNo(),
981            String.format(Locale.getDefault(), multilineNoTemplate,
982                    getLineStart(prevStmt.getLineNo()), getLineStart(nextStmt.getLineNo())));
983    }
984
985    /**
986     * Get a message key depending on a comment type.
987     *
988     * @param comment the comment to process.
989     * @return a message key.
990     */
991    private static String getMessageKey(DetailAST comment) {
992        final String msgKey;
993        if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
994            msgKey = MSG_KEY_SINGLE;
995        }
996        else {
997            msgKey = MSG_KEY_BLOCK;
998        }
999        return msgKey;
1000    }
1001
1002    /**
1003     * Gets comment's previous statement from switch block.
1004     *
1005     * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single-line comment}.
1006     * @return comment's previous statement or null if previous statement is absent.
1007     */
1008    private static DetailAST getPrevStatementFromSwitchBlock(DetailAST comment) {
1009        final DetailAST prevStmt;
1010        final DetailAST parentStatement = comment.getParent();
1011        if (parentStatement.getType() == TokenTypes.CASE_GROUP) {
1012            prevStmt = getPrevStatementWhenCommentIsUnderCase(parentStatement);
1013        }
1014        else {
1015            prevStmt = getPrevCaseToken(parentStatement);
1016        }
1017        return prevStmt;
1018    }
1019
1020    /**
1021     * Gets previous statement for comment which is placed immediately under case.
1022     *
1023     * @param parentStatement comment's parent statement.
1024     * @return comment's previous statement or null if previous statement is absent.
1025     */
1026    private static DetailAST getPrevStatementWhenCommentIsUnderCase(DetailAST parentStatement) {
1027        DetailAST prevStmt = null;
1028        final DetailAST prevBlock = parentStatement.getPreviousSibling();
1029        if (prevBlock.getLastChild() != null) {
1030            DetailAST blockBody = prevBlock.getLastChild().getLastChild();
1031            if (blockBody.getType() == TokenTypes.SEMI) {
1032                blockBody = blockBody.getPreviousSibling();
1033            }
1034            if (blockBody.getType() == TokenTypes.EXPR) {
1035                if (isUsingOfObjectReferenceToInvokeMethod(blockBody)) {
1036                    prevStmt = findStartTokenOfMethodCallChain(blockBody);
1037                }
1038                else {
1039                    prevStmt = blockBody.getFirstChild().getFirstChild();
1040                }
1041            }
1042            else {
1043                if (blockBody.getType() == TokenTypes.SLIST) {
1044                    prevStmt = blockBody.getParent().getParent();
1045                }
1046                else {
1047                    prevStmt = blockBody;
1048                }
1049            }
1050            if (isComment(prevStmt)) {
1051                prevStmt = prevStmt.getNextSibling();
1052            }
1053        }
1054        return prevStmt;
1055    }
1056
1057    /**
1058     * Gets previous case-token for comment.
1059     *
1060     * @param parentStatement comment's parent statement.
1061     * @return previous case-token or null if previous case-token is absent.
1062     */
1063    private static DetailAST getPrevCaseToken(DetailAST parentStatement) {
1064        final DetailAST prevCaseToken;
1065        final DetailAST parentBlock = parentStatement.getParent();
1066        if (parentBlock.getParent() != null
1067                && parentBlock.getParent().getPreviousSibling() != null
1068                && parentBlock.getParent().getPreviousSibling().getType()
1069                    == TokenTypes.LITERAL_CASE) {
1070            prevCaseToken = parentBlock.getParent().getPreviousSibling();
1071        }
1072        else {
1073            prevCaseToken = null;
1074        }
1075        return prevCaseToken;
1076    }
1077
1078    /**
1079     * Checks if comment and next code statement
1080     * (or previous code stmt like <b>case</b> in switch block) are indented at the same level,
1081     * e.g.:
1082     * <p>
1083     * <pre>
1084     * {@code
1085     * // some comment - same indentation level
1086     * int x = 10;
1087     *     // some comment - different indentation level
1088     * int x1 = 5;
1089     * /*
1090     *  *
1091     *  *&#47;
1092     *  boolean bool = true; - same indentation level
1093     * }
1094     * </pre>
1095     * </p>
1096     *
1097     * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
1098     * @param prevStmt previous code statement.
1099     * @param nextStmt next code statement.
1100     * @return true if comment and next code statement are indented at the same level.
1101     */
1102    private boolean areSameLevelIndented(DetailAST comment, DetailAST prevStmt,
1103                                                DetailAST nextStmt) {
1104        return comment.getColumnNo() == getLineStart(nextStmt.getLineNo())
1105            || comment.getColumnNo() == getLineStart(prevStmt.getLineNo());
1106    }
1107
1108    /**
1109     * Get a column number where a code starts.
1110     *
1111     * @param lineNo the line number to get column number in.
1112     * @return the column number where a code starts.
1113     */
1114    private int getLineStart(int lineNo) {
1115        final char[] line = getLines()[lineNo - 1].toCharArray();
1116        int lineStart = 0;
1117        while (Character.isWhitespace(line[lineStart])) {
1118            lineStart++;
1119        }
1120        return lineStart;
1121    }
1122
1123    /**
1124     * Checks if current comment is a trailing comment.
1125     *
1126     * @param comment comment to check.
1127     * @return true if current comment is a trailing comment.
1128     */
1129    private boolean isTrailingComment(DetailAST comment) {
1130        final boolean isTrailingComment;
1131        if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
1132            isTrailingComment = isTrailingSingleLineComment(comment);
1133        }
1134        else {
1135            isTrailingComment = isTrailingBlockComment(comment);
1136        }
1137        return isTrailingComment;
1138    }
1139
1140    /**
1141     * Checks if current single line comment is trailing comment, e.g.:
1142     * <p>
1143     * {@code
1144     * double d = 3.14; // some comment
1145     * }
1146     * </p>
1147     *
1148     * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
1149     * @return true if current single line comment is trailing comment.
1150     */
1151    private boolean isTrailingSingleLineComment(DetailAST singleLineComment) {
1152        final String targetSourceLine = getLine(singleLineComment.getLineNo() - 1);
1153        final int commentColumnNo = singleLineComment.getColumnNo();
1154        return !CommonUtil.hasWhitespaceBefore(commentColumnNo, targetSourceLine);
1155    }
1156
1157    /**
1158     * Checks if current comment block is trailing comment, e.g.:
1159     * <p>
1160     * {@code
1161     * double d = 3.14; /* some comment *&#47;
1162     * /* some comment *&#47; double d = 18.5;
1163     * }
1164     * </p>
1165     *
1166     * @param blockComment {@link TokenTypes#BLOCK_COMMENT_BEGIN block comment begin}.
1167     * @return true if current comment block is trailing comment.
1168     */
1169    private boolean isTrailingBlockComment(DetailAST blockComment) {
1170        final String commentLine = getLine(blockComment.getLineNo() - 1);
1171        final int commentColumnNo = blockComment.getColumnNo();
1172        final DetailAST nextSibling = blockComment.getNextSibling();
1173        return !CommonUtil.hasWhitespaceBefore(commentColumnNo, commentLine)
1174            || nextSibling != null && TokenUtil.areOnSameLine(nextSibling, blockComment);
1175    }
1176
1177    /**
1178     * Checks if the comment is inside a method call with same indentation of
1179     * first expression. e.g:
1180     * <p>
1181     * {@code
1182     * private final boolean myList = someMethod(
1183     *     // Some comment here
1184     *     s1,
1185     *     s2,
1186     *     s3
1187     *     // ok
1188     * );
1189     * }
1190     * </p>
1191     *
1192     * @param comment comment to check.
1193     * @return true, if comment is inside inside a method call with same indentation.
1194     */
1195    private static boolean areInSameMethodCallWithSameIndent(DetailAST comment) {
1196        return comment.getParent() != null
1197                && comment.getParent().getType() == TokenTypes.METHOD_CALL
1198                && comment.getColumnNo()
1199                     == getFirstExpressionNodeFromMethodCall(comment.getParent()).getColumnNo();
1200    }
1201
1202    /**
1203     * Returns the first EXPR DetailAST child from parent of comment.
1204     *
1205     * @param methodCall methodCall DetailAst from which node to be extracted.
1206     * @return first EXPR DetailAST child from parent of comment.
1207     */
1208    private static DetailAST getFirstExpressionNodeFromMethodCall(DetailAST methodCall) {
1209        // Method call always has ELIST
1210        return methodCall.findFirstToken(TokenTypes.ELIST);
1211    }
1212
1213}