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 (!TokenUtil.areOnSameLine(previousSibling, currentToken)) {
410                    isDistributed = true;
411                }
412            }
413            else {
414                isDistributed = isStatementWithPossibleCurlies(previousSibling);
415            }
416        }
417        return isDistributed;
418    }
419
420    /**
421     * Whether the statement can have or always have curly brackets.
422     *
423     * @param previousSibling the statement to check.
424     * @return true if the statement can have or always have curly brackets.
425     */
426    private static boolean isStatementWithPossibleCurlies(DetailAST previousSibling) {
427        return previousSibling.getType() == TokenTypes.LITERAL_IF
428            || previousSibling.getType() == TokenTypes.LITERAL_TRY
429            || previousSibling.getType() == TokenTypes.LITERAL_FOR
430            || previousSibling.getType() == TokenTypes.LITERAL_DO
431            || previousSibling.getType() == TokenTypes.LITERAL_WHILE
432            || previousSibling.getType() == TokenTypes.LITERAL_SWITCH
433            || isDefinition(previousSibling);
434    }
435
436    /**
437     * Whether the statement is a kind of definition (method, class etc.).
438     *
439     * @param previousSibling the statement to check.
440     * @return true if the statement is a kind of definition.
441     */
442    private static boolean isDefinition(DetailAST previousSibling) {
443        return TokenUtil.isTypeDeclaration(previousSibling.getType())
444            || previousSibling.getType() == TokenTypes.METHOD_DEF;
445    }
446
447    /**
448     * Checks whether the previous statement of a comment is a distributed return statement.
449     *
450     * @param commentPreviousSibling previous sibling of the comment.
451     * @return true if the previous statement of a comment is a distributed return statement.
452     */
453    private static boolean isDistributedReturnStatement(DetailAST commentPreviousSibling) {
454        boolean isDistributed = false;
455        if (commentPreviousSibling != null
456                && commentPreviousSibling.getType() == TokenTypes.LITERAL_RETURN) {
457            final DetailAST firstChild = commentPreviousSibling.getFirstChild();
458            final DetailAST nextSibling = firstChild.getNextSibling();
459            if (nextSibling != null) {
460                isDistributed = true;
461            }
462        }
463        return isDistributed;
464    }
465
466    /**
467     * Checks whether the previous statement of a comment is a distributed throw statement.
468     *
469     * @param commentPreviousSibling previous sibling of the comment.
470     * @return true if the previous statement of a comment is a distributed throw statement.
471     */
472    private static boolean isDistributedThrowStatement(DetailAST commentPreviousSibling) {
473        boolean isDistributed = false;
474        if (commentPreviousSibling != null
475                && commentPreviousSibling.getType() == TokenTypes.LITERAL_THROW) {
476            final DetailAST firstChild = commentPreviousSibling.getFirstChild();
477            final DetailAST nextSibling = firstChild.getNextSibling();
478            if (!TokenUtil.areOnSameLine(nextSibling, commentPreviousSibling)) {
479                isDistributed = true;
480            }
481        }
482        return isDistributed;
483    }
484
485    /**
486     * Returns the first token of the distributed previous statement of comment.
487     *
488     * @param comment comment to check.
489     * @return the first token of the distributed previous statement of comment.
490     */
491    private static DetailAST getDistributedPreviousStatement(DetailAST comment) {
492        DetailAST currentToken = comment.getPreviousSibling();
493        while (isComment(currentToken)) {
494            currentToken = currentToken.getPreviousSibling();
495        }
496        final DetailAST previousStatement;
497        if (currentToken.getType() == TokenTypes.SEMI) {
498            currentToken = currentToken.getPreviousSibling();
499            while (currentToken.getFirstChild() != null) {
500                if (isComment(currentToken)) {
501                    currentToken = currentToken.getNextSibling();
502                }
503                else {
504                    currentToken = currentToken.getFirstChild();
505                }
506            }
507            previousStatement = currentToken;
508        }
509        else {
510            previousStatement = currentToken;
511        }
512        return previousStatement;
513    }
514
515    /**
516     * Checks whether case block is empty.
517     *
518     * @param prevStmt next statement.
519     * @param nextStmt previous statement.
520     * @return true if case block is empty.
521     */
522    private static boolean isInEmptyCaseBlock(DetailAST prevStmt, DetailAST nextStmt) {
523        return prevStmt != null
524            && nextStmt != null
525            && (prevStmt.getType() == TokenTypes.LITERAL_CASE
526                || prevStmt.getType() == TokenTypes.CASE_GROUP)
527            && (nextStmt.getType() == TokenTypes.LITERAL_CASE
528                || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
529    }
530
531    /**
532     * Checks whether comment is a 'fall through' comment.
533     * For example:
534     * <p>
535     * {@code
536     *    ...
537     *    case OPTION_ONE:
538     *        int someVariable = 1;
539     *        // fall through
540     *    case OPTION_TWO:
541     *        int a = 5;
542     *        break;
543     *    ...
544     * }
545     * </p>
546     *
547     * @param prevStmt previous statement.
548     * @param nextStmt next statement.
549     * @return true if a comment is a 'fall through' comment.
550     */
551    private static boolean isFallThroughComment(DetailAST prevStmt, DetailAST nextStmt) {
552        return prevStmt != null
553            && nextStmt != null
554            && prevStmt.getType() != TokenTypes.LITERAL_CASE
555            && (nextStmt.getType() == TokenTypes.LITERAL_CASE
556                || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
557    }
558
559    /**
560     * Checks whether a comment is placed at the end of the code block.
561     *
562     * @param nextStmt next statement.
563     * @return true if a comment is placed at the end of the block.
564     */
565    private static boolean isCommentAtTheEndOfTheCodeBlock(DetailAST nextStmt) {
566        return nextStmt != null
567            && nextStmt.getType() == TokenTypes.RCURLY;
568    }
569
570    /**
571     * Checks whether comment is placed in the empty code block.
572     * For example:
573     * <p>
574     * ...
575     * {@code
576     *  // empty code block
577     * }
578     * ...
579     * </p>
580     * Note, the method does not treat empty case blocks.
581     *
582     * @param prevStmt previous statement.
583     * @param nextStmt next statement.
584     * @return true if comment is placed in the empty code block.
585     */
586    private static boolean isInEmptyCodeBlock(DetailAST prevStmt, DetailAST nextStmt) {
587        return prevStmt != null
588            && nextStmt != null
589            && (prevStmt.getType() == TokenTypes.SLIST
590                || prevStmt.getType() == TokenTypes.LCURLY
591                || prevStmt.getType() == TokenTypes.ARRAY_INIT
592                || prevStmt.getType() == TokenTypes.OBJBLOCK)
593            && nextStmt.getType() == TokenTypes.RCURLY;
594    }
595
596    /**
597     * Handles a comment which is placed within empty case block.
598     * Note, if comment is placed at the end of the empty case block, we have Checkstyle's
599     * limitations to clearly detect user intention of explanation target - above or below. The
600     * only case we can assume as a violation is when a single line comment within the empty case
601     * block has indentation level that is lower than the indentation level of the next case
602     * token. For example:
603     * <p>
604     * {@code
605     *    ...
606     *    case OPTION_ONE:
607     * // violation
608     *    case OPTION_TWO:
609     *    ...
610     * }
611     * </p>
612     *
613     * @param prevStmt previous statement.
614     * @param comment single line comment.
615     * @param nextStmt next statement.
616     */
617    private void handleCommentInEmptyCaseBlock(DetailAST prevStmt, DetailAST comment,
618                                               DetailAST nextStmt) {
619        if (comment.getColumnNo() < prevStmt.getColumnNo()
620                || comment.getColumnNo() < nextStmt.getColumnNo()) {
621            logMultilineIndentation(prevStmt, comment, nextStmt);
622        }
623    }
624
625    /**
626     * Handles 'fall through' single line comment.
627     * Note, 'fall through' and similar comments can have indentation level as next or previous
628     * statement.
629     * For example:
630     * <p>
631     * {@code
632     *    ...
633     *    case OPTION_ONE:
634     *        int someVariable = 1;
635     *        // fall through - OK
636     *    case OPTION_TWO:
637     *        int a = 5;
638     *        break;
639     *    ...
640     * }
641     * </p>
642     * <p>
643     * {@code
644     *    ...
645     *    case OPTION_ONE:
646     *        int someVariable = 1;
647     *    // then init variable a - OK
648     *    case OPTION_TWO:
649     *        int a = 5;
650     *        break;
651     *    ...
652     * }
653     * </p>
654     *
655     * @param prevStmt previous statement.
656     * @param comment single line comment.
657     * @param nextStmt next statement.
658     */
659    private void handleFallThroughComment(DetailAST prevStmt, DetailAST comment,
660                                          DetailAST nextStmt) {
661        if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
662            logMultilineIndentation(prevStmt, comment, nextStmt);
663        }
664    }
665
666    /**
667     * Handles a comment which is placed at the end of non empty code block.
668     * Note, if single line comment is placed at the end of non empty block the comment should have
669     * the same indentation level as the previous statement. For example:
670     * <p>
671     * {@code
672     *    if (a == true) {
673     *        int b = 1;
674     *        // comment
675     *    }
676     * }
677     * </p>
678     *
679     * @param prevStmt previous statement.
680     * @param comment comment to check.
681     * @param nextStmt next statement.
682     */
683    private void handleCommentAtTheEndOfTheCodeBlock(DetailAST prevStmt, DetailAST comment,
684                                                     DetailAST nextStmt) {
685        if (prevStmt != null) {
686            if (prevStmt.getType() == TokenTypes.LITERAL_CASE
687                    || prevStmt.getType() == TokenTypes.CASE_GROUP
688                    || prevStmt.getType() == TokenTypes.LITERAL_DEFAULT) {
689                if (comment.getColumnNo() < nextStmt.getColumnNo()) {
690                    log(comment, getMessageKey(comment), nextStmt.getLineNo(),
691                        comment.getColumnNo(), nextStmt.getColumnNo());
692                }
693            }
694            else if (isCommentForMultiblock(nextStmt)) {
695                if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
696                    logMultilineIndentation(prevStmt, comment, nextStmt);
697                }
698            }
699            else if (!areSameLevelIndented(comment, prevStmt, prevStmt)) {
700                final int prevStmtLineNo = prevStmt.getLineNo();
701                log(comment, getMessageKey(comment), prevStmtLineNo,
702                        comment.getColumnNo(), getLineStart(prevStmtLineNo));
703            }
704        }
705    }
706
707    /**
708     * Whether the comment might have been used for the next block in a multi-block structure.
709     *
710     * @param endBlockStmt the end of the current block.
711     * @return true, if the comment might have been used for the next
712     *     block in a multi-block structure.
713     */
714    private static boolean isCommentForMultiblock(DetailAST endBlockStmt) {
715        final DetailAST nextBlock = endBlockStmt.getParent().getNextSibling();
716        final int endBlockLineNo = endBlockStmt.getLineNo();
717        final DetailAST catchAst = endBlockStmt.getParent().getParent();
718        final DetailAST finallyAst = catchAst.getNextSibling();
719        return nextBlock != null && nextBlock.getLineNo() == endBlockLineNo
720                || finallyAst != null
721                    && catchAst.getType() == TokenTypes.LITERAL_CATCH
722                    && finallyAst.getLineNo() == endBlockLineNo;
723    }
724
725    /**
726     * Handles a comment which is placed within the empty code block.
727     * Note, if comment is placed at the end of the empty code block, we have Checkstyle's
728     * limitations to clearly detect user intention of explanation target - above or below. The
729     * only case we can assume as a violation is when a single line comment within the empty
730     * code block has indentation level that is lower than the indentation level of the closing
731     * right curly brace. For example:
732     * <p>
733     * {@code
734     *    if (a == true) {
735     * // violation
736     *    }
737     * }
738     * </p>
739     *
740     * @param comment comment to check.
741     * @param nextStmt next statement.
742     */
743    private void handleCommentInEmptyCodeBlock(DetailAST comment, DetailAST nextStmt) {
744        if (comment.getColumnNo() < nextStmt.getColumnNo()) {
745            log(comment, getMessageKey(comment), nextStmt.getLineNo(),
746                comment.getColumnNo(), nextStmt.getColumnNo());
747        }
748    }
749
750    /**
751     * Does pre-order traverse of abstract syntax tree to find the previous statement of the
752     * comment. If previous statement of the comment is found, then the traverse will
753     * be finished.
754     *
755     * @param comment current statement.
756     * @return previous statement of the comment or null if the comment does not have previous
757     *         statement.
758     */
759    private DetailAST getOneLinePreviousStatement(DetailAST comment) {
760        DetailAST root = comment.getParent();
761        while (root != null && !isBlockStart(root)) {
762            root = root.getParent();
763        }
764
765        final Deque<DetailAST> stack = new ArrayDeque<>();
766        DetailAST previousStatement = null;
767        while (root != null || !stack.isEmpty()) {
768            if (!stack.isEmpty()) {
769                root = stack.pop();
770            }
771            while (root != null) {
772                previousStatement = findPreviousStatement(comment, root);
773                if (previousStatement != null) {
774                    root = null;
775                    stack.clear();
776                    break;
777                }
778                if (root.getNextSibling() != null) {
779                    stack.push(root.getNextSibling());
780                }
781                root = root.getFirstChild();
782            }
783        }
784        return previousStatement;
785    }
786
787    /**
788     * Whether the ast is a comment.
789     *
790     * @param ast the ast to check.
791     * @return true if the ast is a comment.
792     */
793    private static boolean isComment(DetailAST ast) {
794        final int astType = ast.getType();
795        return astType == TokenTypes.SINGLE_LINE_COMMENT
796            || astType == TokenTypes.BLOCK_COMMENT_BEGIN
797            || astType == TokenTypes.COMMENT_CONTENT
798            || astType == TokenTypes.BLOCK_COMMENT_END;
799    }
800
801    /**
802     * Whether the AST node starts a block.
803     *
804     * @param root the AST node to check.
805     * @return true if the AST node starts a block.
806     */
807    private static boolean isBlockStart(DetailAST root) {
808        return root.getType() == TokenTypes.SLIST
809                || root.getType() == TokenTypes.OBJBLOCK
810                || root.getType() == TokenTypes.ARRAY_INIT
811                || root.getType() == TokenTypes.CASE_GROUP;
812    }
813
814    /**
815     * Finds a previous statement of the comment.
816     * Uses root token of the line while searching.
817     *
818     * @param comment comment.
819     * @param root root token of the line.
820     * @return previous statement of the comment or null if previous statement was not found.
821     */
822    private DetailAST findPreviousStatement(DetailAST comment, DetailAST root) {
823        DetailAST previousStatement = null;
824        if (root.getLineNo() >= comment.getLineNo()) {
825            // ATTENTION: parent of the comment is below the comment in case block
826            // See https://github.com/checkstyle/checkstyle/issues/851
827            previousStatement = getPrevStatementFromSwitchBlock(comment);
828        }
829        final DetailAST tokenWhichBeginsTheLine;
830        if (root.getType() == TokenTypes.EXPR
831                && root.getFirstChild().getFirstChild() != null) {
832            if (root.getFirstChild().getType() == TokenTypes.LITERAL_NEW) {
833                tokenWhichBeginsTheLine = root.getFirstChild();
834            }
835            else {
836                tokenWhichBeginsTheLine = findTokenWhichBeginsTheLine(root);
837            }
838        }
839        else if (root.getType() == TokenTypes.PLUS) {
840            tokenWhichBeginsTheLine = root.getFirstChild();
841        }
842        else {
843            tokenWhichBeginsTheLine = root;
844        }
845        if (tokenWhichBeginsTheLine != null
846                && !isComment(tokenWhichBeginsTheLine)
847                && isOnPreviousLineIgnoringComments(comment, tokenWhichBeginsTheLine)) {
848            previousStatement = tokenWhichBeginsTheLine;
849        }
850        return previousStatement;
851    }
852
853    /**
854     * Finds a token which begins the line.
855     *
856     * @param root root token of the line.
857     * @return token which begins the line.
858     */
859    private static DetailAST findTokenWhichBeginsTheLine(DetailAST root) {
860        final DetailAST tokenWhichBeginsTheLine;
861        if (isUsingOfObjectReferenceToInvokeMethod(root)) {
862            tokenWhichBeginsTheLine = findStartTokenOfMethodCallChain(root);
863        }
864        else {
865            tokenWhichBeginsTheLine = root.getFirstChild().findFirstToken(TokenTypes.IDENT);
866        }
867        return tokenWhichBeginsTheLine;
868    }
869
870    /**
871     * Checks whether there is a use of an object reference to invoke an object's method on line.
872     *
873     * @param root root token of the line.
874     * @return true if there is a use of an object reference to invoke an object's method on line.
875     */
876    private static boolean isUsingOfObjectReferenceToInvokeMethod(DetailAST root) {
877        return root.getFirstChild().getFirstChild().getFirstChild() != null
878            && root.getFirstChild().getFirstChild().getFirstChild().getNextSibling() != null;
879    }
880
881    /**
882     * Finds the start token of method call chain.
883     *
884     * @param root root token of the line.
885     * @return the start token of method call chain.
886     */
887    private static DetailAST findStartTokenOfMethodCallChain(DetailAST root) {
888        DetailAST startOfMethodCallChain = root;
889        while (startOfMethodCallChain.getFirstChild() != null
890                && TokenUtil.areOnSameLine(startOfMethodCallChain.getFirstChild(), root)) {
891            startOfMethodCallChain = startOfMethodCallChain.getFirstChild();
892        }
893        if (startOfMethodCallChain.getFirstChild() != null) {
894            startOfMethodCallChain = startOfMethodCallChain.getFirstChild().getNextSibling();
895        }
896        return startOfMethodCallChain;
897    }
898
899    /**
900     * Checks whether the checked statement is on the previous line ignoring empty lines
901     * and lines which contain only comments.
902     *
903     * @param currentStatement current statement.
904     * @param checkedStatement checked statement.
905     * @return true if checked statement is on the line which is previous to current statement
906     *     ignoring empty lines and lines which contain only comments.
907     */
908    private boolean isOnPreviousLineIgnoringComments(DetailAST currentStatement,
909                                                     DetailAST checkedStatement) {
910        DetailAST nextToken = getNextToken(checkedStatement);
911        int distanceAim = 1;
912        if (nextToken != null && isComment(nextToken)) {
913            distanceAim += countEmptyLines(checkedStatement, currentStatement);
914        }
915
916        while (nextToken != null && nextToken != currentStatement && isComment(nextToken)) {
917            if (nextToken.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
918                distanceAim += nextToken.getLastChild().getLineNo() - nextToken.getLineNo();
919            }
920            distanceAim++;
921            nextToken = nextToken.getNextSibling();
922        }
923        return currentStatement.getLineNo() - checkedStatement.getLineNo() == distanceAim;
924    }
925
926    /**
927     * Get the token to start counting the number of lines to add to the distance aim from.
928     *
929     * @param checkedStatement the checked statement.
930     * @return the token to start counting the number of lines to add to the distance aim from.
931     */
932    private DetailAST getNextToken(DetailAST checkedStatement) {
933        DetailAST nextToken;
934        if (checkedStatement.getType() == TokenTypes.SLIST
935                || checkedStatement.getType() == TokenTypes.ARRAY_INIT
936                || checkedStatement.getType() == TokenTypes.CASE_GROUP) {
937            nextToken = checkedStatement.getFirstChild();
938        }
939        else {
940            nextToken = checkedStatement.getNextSibling();
941        }
942        if (nextToken != null && isComment(nextToken) && isTrailingComment(nextToken)) {
943            nextToken = nextToken.getNextSibling();
944        }
945        return nextToken;
946    }
947
948    /**
949     * Count the number of empty lines between statements.
950     *
951     * @param startStatement start statement.
952     * @param endStatement end statement.
953     * @return the number of empty lines between statements.
954     */
955    private int countEmptyLines(DetailAST startStatement, DetailAST endStatement) {
956        int emptyLinesNumber = 0;
957        final String[] lines = getLines();
958        final int endLineNo = endStatement.getLineNo();
959        for (int lineNo = startStatement.getLineNo(); lineNo < endLineNo; lineNo++) {
960            if (CommonUtil.isBlank(lines[lineNo])) {
961                emptyLinesNumber++;
962            }
963        }
964        return emptyLinesNumber;
965    }
966
967    /**
968     * Logs comment which can have the same indentation level as next or previous statement.
969     *
970     * @param prevStmt previous statement.
971     * @param comment comment.
972     * @param nextStmt next statement.
973     */
974    private void logMultilineIndentation(DetailAST prevStmt, DetailAST comment,
975                                         DetailAST nextStmt) {
976        final String multilineNoTemplate = "%d, %d";
977        log(comment, getMessageKey(comment),
978            String.format(Locale.getDefault(), multilineNoTemplate, prevStmt.getLineNo(),
979                nextStmt.getLineNo()), comment.getColumnNo(),
980            String.format(Locale.getDefault(), multilineNoTemplate,
981                    getLineStart(prevStmt.getLineNo()), getLineStart(nextStmt.getLineNo())));
982    }
983
984    /**
985     * Get a message key depending on a comment type.
986     *
987     * @param comment the comment to process.
988     * @return a message key.
989     */
990    private static String getMessageKey(DetailAST comment) {
991        final String msgKey;
992        if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
993            msgKey = MSG_KEY_SINGLE;
994        }
995        else {
996            msgKey = MSG_KEY_BLOCK;
997        }
998        return msgKey;
999    }
1000
1001    /**
1002     * Gets comment's previous statement from switch block.
1003     *
1004     * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single-line comment}.
1005     * @return comment's previous statement or null if previous statement is absent.
1006     */
1007    private static DetailAST getPrevStatementFromSwitchBlock(DetailAST comment) {
1008        final DetailAST prevStmt;
1009        final DetailAST parentStatement = comment.getParent();
1010        if (parentStatement.getType() == TokenTypes.CASE_GROUP) {
1011            prevStmt = getPrevStatementWhenCommentIsUnderCase(parentStatement);
1012        }
1013        else {
1014            prevStmt = getPrevCaseToken(parentStatement);
1015        }
1016        return prevStmt;
1017    }
1018
1019    /**
1020     * Gets previous statement for comment which is placed immediately under case.
1021     *
1022     * @param parentStatement comment's parent statement.
1023     * @return comment's previous statement or null if previous statement is absent.
1024     */
1025    private static DetailAST getPrevStatementWhenCommentIsUnderCase(DetailAST parentStatement) {
1026        DetailAST prevStmt = null;
1027        final DetailAST prevBlock = parentStatement.getPreviousSibling();
1028        if (prevBlock.getLastChild() != null) {
1029            DetailAST blockBody = prevBlock.getLastChild().getLastChild();
1030            if (blockBody.getType() == TokenTypes.SEMI) {
1031                blockBody = blockBody.getPreviousSibling();
1032            }
1033            if (blockBody.getType() == TokenTypes.EXPR) {
1034                if (isUsingOfObjectReferenceToInvokeMethod(blockBody)) {
1035                    prevStmt = findStartTokenOfMethodCallChain(blockBody);
1036                }
1037                else {
1038                    prevStmt = blockBody.getFirstChild().getFirstChild();
1039                }
1040            }
1041            else {
1042                if (blockBody.getType() == TokenTypes.SLIST) {
1043                    prevStmt = blockBody.getParent().getParent();
1044                }
1045                else {
1046                    prevStmt = blockBody;
1047                }
1048            }
1049            if (isComment(prevStmt)) {
1050                prevStmt = prevStmt.getNextSibling();
1051            }
1052        }
1053        return prevStmt;
1054    }
1055
1056    /**
1057     * Gets previous case-token for comment.
1058     *
1059     * @param parentStatement comment's parent statement.
1060     * @return previous case-token or null if previous case-token is absent.
1061     */
1062    private static DetailAST getPrevCaseToken(DetailAST parentStatement) {
1063        final DetailAST prevCaseToken;
1064        final DetailAST parentBlock = parentStatement.getParent();
1065        if (parentBlock.getParent().getPreviousSibling() != null
1066                && parentBlock.getParent().getPreviousSibling().getType()
1067                    == TokenTypes.LITERAL_CASE) {
1068            prevCaseToken = parentBlock.getParent().getPreviousSibling();
1069        }
1070        else {
1071            prevCaseToken = null;
1072        }
1073        return prevCaseToken;
1074    }
1075
1076    /**
1077     * Checks if comment and next code statement
1078     * (or previous code stmt like <b>case</b> in switch block) are indented at the same level,
1079     * e.g.:
1080     * <p>
1081     * <pre>
1082     * {@code
1083     * // some comment - same indentation level
1084     * int x = 10;
1085     *     // some comment - different indentation level
1086     * int x1 = 5;
1087     * /*
1088     *  *
1089     *  *&#47;
1090     *  boolean bool = true; - same indentation level
1091     * }
1092     * </pre>
1093     * </p>
1094     *
1095     * @param comment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
1096     * @param prevStmt previous code statement.
1097     * @param nextStmt next code statement.
1098     * @return true if comment and next code statement are indented at the same level.
1099     */
1100    private boolean areSameLevelIndented(DetailAST comment, DetailAST prevStmt,
1101                                                DetailAST nextStmt) {
1102        return comment.getColumnNo() == getLineStart(nextStmt.getLineNo())
1103            || comment.getColumnNo() == getLineStart(prevStmt.getLineNo());
1104    }
1105
1106    /**
1107     * Get a column number where a code starts.
1108     *
1109     * @param lineNo the line number to get column number in.
1110     * @return the column number where a code starts.
1111     */
1112    private int getLineStart(int lineNo) {
1113        final char[] line = getLines()[lineNo - 1].toCharArray();
1114        int lineStart = 0;
1115        while (Character.isWhitespace(line[lineStart])) {
1116            lineStart++;
1117        }
1118        return lineStart;
1119    }
1120
1121    /**
1122     * Checks if current comment is a trailing comment.
1123     *
1124     * @param comment comment to check.
1125     * @return true if current comment is a trailing comment.
1126     */
1127    private boolean isTrailingComment(DetailAST comment) {
1128        final boolean isTrailingComment;
1129        if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
1130            isTrailingComment = isTrailingSingleLineComment(comment);
1131        }
1132        else {
1133            isTrailingComment = isTrailingBlockComment(comment);
1134        }
1135        return isTrailingComment;
1136    }
1137
1138    /**
1139     * Checks if current single line comment is trailing comment, e.g.:
1140     * <p>
1141     * {@code
1142     * double d = 3.14; // some comment
1143     * }
1144     * </p>
1145     *
1146     * @param singleLineComment {@link TokenTypes#SINGLE_LINE_COMMENT single line comment}.
1147     * @return true if current single line comment is trailing comment.
1148     */
1149    private boolean isTrailingSingleLineComment(DetailAST singleLineComment) {
1150        final String targetSourceLine = getLine(singleLineComment.getLineNo() - 1);
1151        final int commentColumnNo = singleLineComment.getColumnNo();
1152        return !CommonUtil.hasWhitespaceBefore(commentColumnNo, targetSourceLine);
1153    }
1154
1155    /**
1156     * Checks if current comment block is trailing comment, e.g.:
1157     * <p>
1158     * {@code
1159     * double d = 3.14; /* some comment *&#47;
1160     * /* some comment *&#47; double d = 18.5;
1161     * }
1162     * </p>
1163     *
1164     * @param blockComment {@link TokenTypes#BLOCK_COMMENT_BEGIN block comment begin}.
1165     * @return true if current comment block is trailing comment.
1166     */
1167    private boolean isTrailingBlockComment(DetailAST blockComment) {
1168        final String commentLine = getLine(blockComment.getLineNo() - 1);
1169        final int commentColumnNo = blockComment.getColumnNo();
1170        final DetailAST nextSibling = blockComment.getNextSibling();
1171        return !CommonUtil.hasWhitespaceBefore(commentColumnNo, commentLine)
1172            || nextSibling != null && TokenUtil.areOnSameLine(nextSibling, blockComment);
1173    }
1174
1175    /**
1176     * Checks if the comment is inside a method call with same indentation of
1177     * first expression. e.g:
1178     * <p>
1179     * {@code
1180     * private final boolean myList = someMethod(
1181     *     // Some comment here
1182     *     s1,
1183     *     s2,
1184     *     s3
1185     *     // ok
1186     * );
1187     * }
1188     * </p>
1189     *
1190     * @param comment comment to check.
1191     * @return true, if comment is inside inside a method call with same indentation.
1192     */
1193    private static boolean areInSameMethodCallWithSameIndent(DetailAST comment) {
1194        return comment.getParent().getType() == TokenTypes.METHOD_CALL
1195                && comment.getColumnNo()
1196                     == getFirstExpressionNodeFromMethodCall(comment.getParent()).getColumnNo();
1197    }
1198
1199    /**
1200     * Returns the first EXPR DetailAST child from parent of comment.
1201     *
1202     * @param methodCall methodCall DetailAst from which node to be extracted.
1203     * @return first EXPR DetailAST child from parent of comment.
1204     */
1205    private static DetailAST getFirstExpressionNodeFromMethodCall(DetailAST methodCall) {
1206        // Method call always has ELIST
1207        return methodCall.findFirstToken(TokenTypes.ELIST);
1208    }
1209
1210}