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