1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  package com.puppycrawl.tools.checkstyle.checks.indentation;
21  
22  import java.util.ArrayDeque;
23  import java.util.Deque;
24  import java.util.Locale;
25  
26  import com.puppycrawl.tools.checkstyle.StatelessCheck;
27  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28  import com.puppycrawl.tools.checkstyle.api.DetailAST;
29  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  @StatelessCheck
45  public class CommentsIndentationCheck extends AbstractCheck {
46  
47      
48  
49  
50      public static final String MSG_KEY_SINGLE = "comments.indentation.single";
51  
52      
53  
54  
55      public static final String MSG_KEY_BLOCK = "comments.indentation.block";
56  
57      @Override
58      public int[] getDefaultTokens() {
59          return new int[] {
60              TokenTypes.SINGLE_LINE_COMMENT,
61              TokenTypes.BLOCK_COMMENT_BEGIN,
62          };
63      }
64  
65      @Override
66      public int[] getAcceptableTokens() {
67          return new int[] {
68              TokenTypes.SINGLE_LINE_COMMENT,
69              TokenTypes.BLOCK_COMMENT_BEGIN,
70          };
71      }
72  
73      @Override
74      public int[] getRequiredTokens() {
75          return CommonUtil.EMPTY_INT_ARRAY;
76      }
77  
78      @Override
79      public boolean isCommentNodesRequired() {
80          return true;
81      }
82  
83      @Override
84      public void visitToken(DetailAST commentAst) {
85          switch (commentAst.getType()) {
86              case TokenTypes.SINGLE_LINE_COMMENT, TokenTypes.BLOCK_COMMENT_BEGIN ->
87                  visitComment(commentAst);
88  
89              default -> {
90                  final String exceptionMsg = "Unexpected token type: " + commentAst.getText();
91                  throw new IllegalArgumentException(exceptionMsg);
92              }
93          }
94      }
95  
96      
97  
98  
99  
100 
101 
102 
103 
104 
105 
106 
107 
108 
109 
110     private void visitComment(DetailAST comment) {
111         if (!isTrailingComment(comment)) {
112             final DetailAST prevStmt = getPreviousStatement(comment);
113             final DetailAST nextStmt = getNextStmt(comment);
114 
115             if (isInEmptyCaseBlock(prevStmt, nextStmt)) {
116                 handleCommentInEmptyCaseBlock(prevStmt, comment, nextStmt);
117             }
118             else if (isFallThroughComment(prevStmt, nextStmt)) {
119                 handleFallThroughComment(prevStmt, comment, nextStmt);
120             }
121             else if (isInEmptyCodeBlock(prevStmt, nextStmt)) {
122                 handleCommentInEmptyCodeBlock(comment, nextStmt);
123             }
124             else if (isCommentAtTheEndOfTheCodeBlock(nextStmt)) {
125                 handleCommentAtTheEndOfTheCodeBlock(prevStmt, comment, nextStmt);
126             }
127             else if (nextStmt != null && !areSameLevelIndented(comment, nextStmt, nextStmt)
128                     && !areInSameMethodCallWithSameIndent(comment)) {
129                 log(comment, getMessageKey(comment), nextStmt.getLineNo(),
130                     comment.getColumnNo(), nextStmt.getColumnNo());
131             }
132         }
133     }
134 
135     
136 
137 
138 
139 
140 
141     private static DetailAST getNextStmt(DetailAST comment) {
142         DetailAST nextStmt = comment.getNextSibling();
143         while (nextStmt != null
144                 && isComment(nextStmt)
145                 && comment.getColumnNo() != nextStmt.getColumnNo()) {
146             nextStmt = nextStmt.getNextSibling();
147         }
148         return nextStmt;
149     }
150 
151     
152 
153 
154 
155 
156 
157     private DetailAST getPreviousStatement(DetailAST comment) {
158         final DetailAST prevStatement;
159         if (isDistributedPreviousStatement(comment)) {
160             prevStatement = getDistributedPreviousStatement(comment);
161         }
162         else {
163             prevStatement = getOneLinePreviousStatement(comment);
164         }
165         return prevStatement;
166     }
167 
168     
169 
170 
171 
172 
173 
174     private boolean isDistributedPreviousStatement(DetailAST comment) {
175         final DetailAST previousSibling = comment.getPreviousSibling();
176         return isDistributedExpression(comment)
177             || isDistributedReturnStatement(previousSibling)
178             || isDistributedThrowStatement(previousSibling);
179     }
180 
181     
182 
183 
184 
185 
186 
187 
188     private boolean isDistributedExpression(DetailAST comment) {
189         DetailAST previousSibling = comment.getPreviousSibling();
190         while (previousSibling != null && isComment(previousSibling)) {
191             previousSibling = previousSibling.getPreviousSibling();
192         }
193         boolean isDistributed = false;
194         if (previousSibling != null) {
195             if (previousSibling.getType() == TokenTypes.SEMI
196                     && isOnPreviousLineIgnoringComments(comment, previousSibling)) {
197                 DetailAST currentToken = previousSibling.getPreviousSibling();
198                 while (currentToken.getFirstChild() != null) {
199                     currentToken = currentToken.getFirstChild();
200                 }
201                 if (!TokenUtil.areOnSameLine(previousSibling, currentToken)) {
202                     isDistributed = true;
203                 }
204             }
205             else {
206                 isDistributed = isStatementWithPossibleCurlies(previousSibling);
207             }
208         }
209         return isDistributed;
210     }
211 
212     
213 
214 
215 
216 
217 
218     private static boolean isStatementWithPossibleCurlies(DetailAST previousSibling) {
219         return previousSibling.getType() == TokenTypes.LITERAL_IF
220             || previousSibling.getType() == TokenTypes.LITERAL_TRY
221             || previousSibling.getType() == TokenTypes.LITERAL_FOR
222             || previousSibling.getType() == TokenTypes.LITERAL_DO
223             || previousSibling.getType() == TokenTypes.LITERAL_WHILE
224             || previousSibling.getType() == TokenTypes.LITERAL_SWITCH
225             || isDefinition(previousSibling);
226     }
227 
228     
229 
230 
231 
232 
233 
234     private static boolean isDefinition(DetailAST previousSibling) {
235         return TokenUtil.isTypeDeclaration(previousSibling.getType())
236             || previousSibling.getType() == TokenTypes.METHOD_DEF;
237     }
238 
239     
240 
241 
242 
243 
244 
245     private static boolean isDistributedReturnStatement(DetailAST commentPreviousSibling) {
246         boolean isDistributed = false;
247         if (commentPreviousSibling != null
248                 && commentPreviousSibling.getType() == TokenTypes.LITERAL_RETURN) {
249             final DetailAST firstChild = commentPreviousSibling.getFirstChild();
250             final DetailAST nextSibling = firstChild.getNextSibling();
251             if (nextSibling != null) {
252                 isDistributed = true;
253             }
254         }
255         return isDistributed;
256     }
257 
258     
259 
260 
261 
262 
263 
264     private static boolean isDistributedThrowStatement(DetailAST commentPreviousSibling) {
265         boolean isDistributed = false;
266         if (commentPreviousSibling != null
267                 && commentPreviousSibling.getType() == TokenTypes.LITERAL_THROW) {
268             final DetailAST firstChild = commentPreviousSibling.getFirstChild();
269             final DetailAST nextSibling = firstChild.getNextSibling();
270             if (!TokenUtil.areOnSameLine(nextSibling, commentPreviousSibling)) {
271                 isDistributed = true;
272             }
273         }
274         return isDistributed;
275     }
276 
277     
278 
279 
280 
281 
282 
283     private static DetailAST getDistributedPreviousStatement(DetailAST comment) {
284         DetailAST currentToken = comment.getPreviousSibling();
285         while (isComment(currentToken)) {
286             currentToken = currentToken.getPreviousSibling();
287         }
288         final DetailAST previousStatement;
289         if (currentToken.getType() == TokenTypes.SEMI) {
290             currentToken = currentToken.getPreviousSibling();
291             while (currentToken.getFirstChild() != null) {
292                 if (isComment(currentToken)) {
293                     currentToken = currentToken.getNextSibling();
294                 }
295                 else {
296                     currentToken = currentToken.getFirstChild();
297                 }
298             }
299             previousStatement = currentToken;
300         }
301         else {
302             previousStatement = currentToken;
303         }
304         return previousStatement;
305     }
306 
307     
308 
309 
310 
311 
312 
313 
314     private static boolean isInEmptyCaseBlock(DetailAST prevStmt, DetailAST nextStmt) {
315         return prevStmt != null
316             && nextStmt != null
317             && (prevStmt.getType() == TokenTypes.LITERAL_CASE
318                 || prevStmt.getType() == TokenTypes.CASE_GROUP)
319             && (nextStmt.getType() == TokenTypes.LITERAL_CASE
320                 || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
321     }
322 
323     
324 
325 
326 
327 
328 
329 
330 
331 
332 
333 
334 
335 
336 
337 
338 
339 
340 
341 
342 
343 
344     private static boolean isFallThroughComment(DetailAST prevStmt, DetailAST nextStmt) {
345         return prevStmt != null
346             && nextStmt != null
347             && prevStmt.getType() != TokenTypes.LITERAL_CASE
348             && (nextStmt.getType() == TokenTypes.LITERAL_CASE
349                 || nextStmt.getType() == TokenTypes.LITERAL_DEFAULT);
350     }
351 
352     
353 
354 
355 
356 
357 
358     private static boolean isCommentAtTheEndOfTheCodeBlock(DetailAST nextStmt) {
359         return nextStmt != null
360             && nextStmt.getType() == TokenTypes.RCURLY;
361     }
362 
363     
364 
365 
366 
367 
368 
369 
370 
371 
372 
373 
374 
375 
376 
377 
378 
379 
380     private static boolean isInEmptyCodeBlock(DetailAST prevStmt, DetailAST nextStmt) {
381         return prevStmt != null
382             && nextStmt != null
383             && (prevStmt.getType() == TokenTypes.SLIST
384                 || prevStmt.getType() == TokenTypes.LCURLY
385                 || prevStmt.getType() == TokenTypes.ARRAY_INIT
386                 || prevStmt.getType() == TokenTypes.OBJBLOCK)
387             && nextStmt.getType() == TokenTypes.RCURLY;
388     }
389 
390     
391 
392 
393 
394 
395 
396 
397 
398 
399 
400 
401 
402 
403 
404 
405 
406 
407 
408 
409 
410 
411 
412     private void handleCommentInEmptyCaseBlock(DetailAST prevStmt, DetailAST comment,
413                                                DetailAST nextStmt) {
414         if (comment.getColumnNo() < prevStmt.getColumnNo()
415                 || comment.getColumnNo() < nextStmt.getColumnNo()) {
416             logMultilineIndentation(prevStmt, comment, nextStmt);
417         }
418     }
419 
420     
421 
422 
423 
424 
425 
426 
427 
428 
429 
430 
431 
432 
433 
434 
435 
436 
437 
438 
439 
440 
441 
442 
443 
444 
445 
446 
447 
448 
449 
450 
451 
452 
453 
454 
455 
456     private void handleFallThroughComment(DetailAST prevStmt, DetailAST comment,
457                                           DetailAST nextStmt) {
458         if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
459             logMultilineIndentation(prevStmt, comment, nextStmt);
460         }
461     }
462 
463     
464 
465 
466 
467 
468 
469 
470 
471 
472 
473 
474 
475 
476 
477 
478 
479 
480 
481     private void handleCommentAtTheEndOfTheCodeBlock(DetailAST prevStmt, DetailAST comment,
482                                                      DetailAST nextStmt) {
483         if (prevStmt != null) {
484             if (prevStmt.getType() == TokenTypes.LITERAL_CASE
485                     || prevStmt.getType() == TokenTypes.CASE_GROUP
486                     || prevStmt.getType() == TokenTypes.LITERAL_DEFAULT) {
487                 if (comment.getColumnNo() < nextStmt.getColumnNo()) {
488                     log(comment, getMessageKey(comment), nextStmt.getLineNo(),
489                         comment.getColumnNo(), nextStmt.getColumnNo());
490                 }
491             }
492             else if (isCommentForMultiblock(nextStmt)) {
493                 if (!areSameLevelIndented(comment, prevStmt, nextStmt)) {
494                     logMultilineIndentation(prevStmt, comment, nextStmt);
495                 }
496             }
497             else if (!areSameLevelIndented(comment, prevStmt, prevStmt)) {
498                 final int prevStmtLineNo = prevStmt.getLineNo();
499                 log(comment, getMessageKey(comment), prevStmtLineNo,
500                         comment.getColumnNo(), getLineStart(prevStmtLineNo));
501             }
502         }
503     }
504 
505     
506 
507 
508 
509 
510 
511 
512     private static boolean isCommentForMultiblock(DetailAST endBlockStmt) {
513         final DetailAST nextBlock = endBlockStmt.getParent().getNextSibling();
514         final int endBlockLineNo = endBlockStmt.getLineNo();
515         final DetailAST catchAst = endBlockStmt.getParent().getParent();
516         final DetailAST finallyAst = catchAst.getNextSibling();
517         return nextBlock != null && nextBlock.getLineNo() == endBlockLineNo
518                 || finallyAst != null
519                     && catchAst.getType() == TokenTypes.LITERAL_CATCH
520                     && finallyAst.getLineNo() == endBlockLineNo;
521     }
522 
523     
524 
525 
526 
527 
528 
529 
530 
531 
532 
533 
534 
535 
536 
537 
538 
539 
540 
541 
542     private void handleCommentInEmptyCodeBlock(DetailAST comment, DetailAST nextStmt) {
543         if (comment.getColumnNo() < nextStmt.getColumnNo()) {
544             log(comment, getMessageKey(comment), nextStmt.getLineNo(),
545                 comment.getColumnNo(), nextStmt.getColumnNo());
546         }
547     }
548 
549     
550 
551 
552 
553 
554 
555 
556 
557 
558     private DetailAST getOneLinePreviousStatement(DetailAST comment) {
559         DetailAST root = comment.getParent();
560         while (root != null && !isBlockStart(root)) {
561             root = root.getParent();
562         }
563 
564         final Deque<DetailAST> stack = new ArrayDeque<>();
565         DetailAST previousStatement = null;
566         while (root != null || !stack.isEmpty()) {
567             if (!stack.isEmpty()) {
568                 root = stack.pop();
569             }
570             while (root != null) {
571                 previousStatement = findPreviousStatement(comment, root);
572                 if (previousStatement != null) {
573                     root = null;
574                     stack.clear();
575                     break;
576                 }
577                 if (root.getNextSibling() != null) {
578                     stack.push(root.getNextSibling());
579                 }
580                 root = root.getFirstChild();
581             }
582         }
583         return previousStatement;
584     }
585 
586     
587 
588 
589 
590 
591 
592     private static boolean isComment(DetailAST ast) {
593         final int astType = ast.getType();
594         return astType == TokenTypes.SINGLE_LINE_COMMENT
595             || astType == TokenTypes.BLOCK_COMMENT_BEGIN
596             || astType == TokenTypes.COMMENT_CONTENT
597             || astType == TokenTypes.BLOCK_COMMENT_END;
598     }
599 
600     
601 
602 
603 
604 
605 
606     private static boolean isBlockStart(DetailAST root) {
607         return root.getType() == TokenTypes.SLIST
608                 || root.getType() == TokenTypes.OBJBLOCK
609                 || root.getType() == TokenTypes.ARRAY_INIT
610                 || root.getType() == TokenTypes.CASE_GROUP;
611     }
612 
613     
614 
615 
616 
617 
618 
619 
620 
621     private DetailAST findPreviousStatement(DetailAST comment, DetailAST root) {
622         DetailAST previousStatement = null;
623         if (root.getLineNo() >= comment.getLineNo()) {
624             
625             
626             previousStatement = getPrevStatementFromSwitchBlock(comment);
627         }
628         final DetailAST tokenWhichBeginsTheLine;
629         if (root.getType() == TokenTypes.EXPR
630                 && root.getFirstChild().getFirstChild() != null) {
631             if (root.getFirstChild().getType() == TokenTypes.LITERAL_NEW) {
632                 tokenWhichBeginsTheLine = root.getFirstChild();
633             }
634             else {
635                 tokenWhichBeginsTheLine = findTokenWhichBeginsTheLine(root);
636             }
637         }
638         else if (root.getType() == TokenTypes.PLUS) {
639             tokenWhichBeginsTheLine = root.getFirstChild();
640         }
641         else {
642             tokenWhichBeginsTheLine = root;
643         }
644         if (tokenWhichBeginsTheLine != null
645                 && !isComment(tokenWhichBeginsTheLine)
646                 && isOnPreviousLineIgnoringComments(comment, tokenWhichBeginsTheLine)) {
647             previousStatement = tokenWhichBeginsTheLine;
648         }
649         return previousStatement;
650     }
651 
652     
653 
654 
655 
656 
657 
658     private static DetailAST findTokenWhichBeginsTheLine(DetailAST root) {
659         final DetailAST tokenWhichBeginsTheLine;
660         if (isUsingOfObjectReferenceToInvokeMethod(root)) {
661             tokenWhichBeginsTheLine = findStartTokenOfMethodCallChain(root);
662         }
663         else {
664             tokenWhichBeginsTheLine = root.getFirstChild().findFirstToken(TokenTypes.IDENT);
665         }
666         return tokenWhichBeginsTheLine;
667     }
668 
669     
670 
671 
672 
673 
674 
675     private static boolean isUsingOfObjectReferenceToInvokeMethod(DetailAST root) {
676         return root.getFirstChild().getFirstChild().getFirstChild() != null
677             && root.getFirstChild().getFirstChild().getFirstChild().getNextSibling() != null;
678     }
679 
680     
681 
682 
683 
684 
685 
686     private static DetailAST findStartTokenOfMethodCallChain(DetailAST root) {
687         DetailAST startOfMethodCallChain = root;
688         while (startOfMethodCallChain.getFirstChild() != null
689                 && TokenUtil.areOnSameLine(startOfMethodCallChain.getFirstChild(), root)) {
690             startOfMethodCallChain = startOfMethodCallChain.getFirstChild();
691         }
692         if (startOfMethodCallChain.getFirstChild() != null) {
693             startOfMethodCallChain = startOfMethodCallChain.getFirstChild().getNextSibling();
694         }
695         return startOfMethodCallChain;
696     }
697 
698     
699 
700 
701 
702 
703 
704 
705 
706 
707     private boolean isOnPreviousLineIgnoringComments(DetailAST currentStatement,
708                                                      DetailAST checkedStatement) {
709         DetailAST nextToken = getNextToken(checkedStatement);
710         int distanceAim = 1;
711         if (nextToken != null && isComment(nextToken)) {
712             distanceAim += countEmptyLines(checkedStatement, currentStatement);
713         }
714 
715         while (nextToken != null && nextToken != currentStatement && isComment(nextToken)) {
716             if (nextToken.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) {
717                 distanceAim += nextToken.getLastChild().getLineNo() - nextToken.getLineNo();
718             }
719             distanceAim++;
720             nextToken = nextToken.getNextSibling();
721         }
722         return currentStatement.getLineNo() - checkedStatement.getLineNo() == distanceAim;
723     }
724 
725     
726 
727 
728 
729 
730 
731     private DetailAST getNextToken(DetailAST checkedStatement) {
732         DetailAST nextToken;
733         if (checkedStatement.getType() == TokenTypes.SLIST
734                 || checkedStatement.getType() == TokenTypes.ARRAY_INIT
735                 || checkedStatement.getType() == TokenTypes.CASE_GROUP) {
736             nextToken = checkedStatement.getFirstChild();
737         }
738         else {
739             nextToken = checkedStatement.getNextSibling();
740         }
741         if (nextToken != null && isComment(nextToken) && isTrailingComment(nextToken)) {
742             nextToken = nextToken.getNextSibling();
743         }
744         return nextToken;
745     }
746 
747     
748 
749 
750 
751 
752 
753 
754     private int countEmptyLines(DetailAST startStatement, DetailAST endStatement) {
755         int emptyLinesNumber = 0;
756         final String[] lines = getLines();
757         final int endLineNo = endStatement.getLineNo();
758         for (int lineNo = startStatement.getLineNo(); lineNo < endLineNo; lineNo++) {
759             if (CommonUtil.isBlank(lines[lineNo])) {
760                 emptyLinesNumber++;
761             }
762         }
763         return emptyLinesNumber;
764     }
765 
766     
767 
768 
769 
770 
771 
772 
773     private void logMultilineIndentation(DetailAST prevStmt, DetailAST comment,
774                                          DetailAST nextStmt) {
775         final String multilineNoTemplate = "%d, %d";
776         log(comment, getMessageKey(comment),
777             String.format(Locale.getDefault(), multilineNoTemplate, prevStmt.getLineNo(),
778                 nextStmt.getLineNo()), comment.getColumnNo(),
779             String.format(Locale.getDefault(), multilineNoTemplate,
780                     getLineStart(prevStmt.getLineNo()), getLineStart(nextStmt.getLineNo())));
781     }
782 
783     
784 
785 
786 
787 
788 
789     private static String getMessageKey(DetailAST comment) {
790         final String msgKey;
791         if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
792             msgKey = MSG_KEY_SINGLE;
793         }
794         else {
795             msgKey = MSG_KEY_BLOCK;
796         }
797         return msgKey;
798     }
799 
800     
801 
802 
803 
804 
805 
806     private static DetailAST getPrevStatementFromSwitchBlock(DetailAST comment) {
807         final DetailAST prevStmt;
808         final DetailAST parentStatement = comment.getParent();
809         if (parentStatement.getType() == TokenTypes.CASE_GROUP) {
810             prevStmt = getPrevStatementWhenCommentIsUnderCase(parentStatement);
811         }
812         else {
813             prevStmt = getPrevCaseToken(parentStatement);
814         }
815         return prevStmt;
816     }
817 
818     
819 
820 
821 
822 
823 
824     private static DetailAST getPrevStatementWhenCommentIsUnderCase(DetailAST parentStatement) {
825         DetailAST prevStmt = null;
826         final DetailAST prevBlock = parentStatement.getPreviousSibling();
827         if (prevBlock.getLastChild() != null) {
828             DetailAST blockBody = prevBlock.getLastChild().getLastChild();
829             if (blockBody.getType() == TokenTypes.SEMI) {
830                 blockBody = blockBody.getPreviousSibling();
831             }
832             if (blockBody.getType() == TokenTypes.EXPR) {
833                 if (isUsingOfObjectReferenceToInvokeMethod(blockBody)) {
834                     prevStmt = findStartTokenOfMethodCallChain(blockBody);
835                 }
836                 else {
837                     prevStmt = blockBody.getFirstChild().getFirstChild();
838                 }
839             }
840             else {
841                 if (blockBody.getType() == TokenTypes.SLIST) {
842                     prevStmt = blockBody.getParent().getParent();
843                 }
844                 else {
845                     prevStmt = blockBody;
846                 }
847             }
848             if (isComment(prevStmt)) {
849                 prevStmt = prevStmt.getNextSibling();
850             }
851         }
852         return prevStmt;
853     }
854 
855     
856 
857 
858 
859 
860 
861     private static DetailAST getPrevCaseToken(DetailAST parentStatement) {
862         final DetailAST prevCaseToken;
863         final DetailAST parentBlock = parentStatement.getParent();
864         if (parentBlock.getParent().getPreviousSibling() != null
865                 && parentBlock.getParent().getPreviousSibling().getType()
866                     == TokenTypes.LITERAL_CASE) {
867             prevCaseToken = parentBlock.getParent().getPreviousSibling();
868         }
869         else {
870             prevCaseToken = null;
871         }
872         return prevCaseToken;
873     }
874 
875     
876 
877 
878 
879 
880 
881 
882 
883 
884 
885 
886 
887 
888 
889 
890 
891 
892 
893 
894 
895 
896 
897     private boolean areSameLevelIndented(DetailAST comment, DetailAST prevStmt,
898                                                 DetailAST nextStmt) {
899         return comment.getColumnNo() == getLineStart(nextStmt.getLineNo())
900             || comment.getColumnNo() == getLineStart(prevStmt.getLineNo());
901     }
902 
903     
904 
905 
906 
907 
908 
909     private int getLineStart(int lineNo) {
910         final char[] line = getLines()[lineNo - 1].toCharArray();
911         int lineStart = 0;
912         while (Character.isWhitespace(line[lineStart])) {
913             lineStart++;
914         }
915         return lineStart;
916     }
917 
918     
919 
920 
921 
922 
923 
924     private boolean isTrailingComment(DetailAST comment) {
925         final boolean isTrailingComment;
926         if (comment.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
927             isTrailingComment = isTrailingSingleLineComment(comment);
928         }
929         else {
930             isTrailingComment = isTrailingBlockComment(comment);
931         }
932         return isTrailingComment;
933     }
934 
935     
936 
937 
938 
939 
940 
941 
942 
943 
944 
945 
946 
947     private boolean isTrailingSingleLineComment(DetailAST singleLineComment) {
948         final String targetSourceLine = getLine(singleLineComment.getLineNo() - 1);
949         final int commentColumnNo = singleLineComment.getColumnNo();
950         return !CommonUtil.hasWhitespaceBefore(commentColumnNo, targetSourceLine);
951     }
952 
953     
954 
955 
956 
957 
958 
959 
960 
961 
962 
963 
964 
965 
966     private boolean isTrailingBlockComment(DetailAST blockComment) {
967         final String commentLine = getLine(blockComment.getLineNo() - 1);
968         final int commentColumnNo = blockComment.getColumnNo();
969         final DetailAST nextSibling = blockComment.getNextSibling();
970         return !CommonUtil.hasWhitespaceBefore(commentColumnNo, commentLine)
971             || nextSibling != null && TokenUtil.areOnSameLine(nextSibling, blockComment);
972     }
973 
974     
975 
976 
977 
978 
979 
980 
981 
982 
983 
984 
985 
986 
987 
988 
989 
990 
991 
992 
993     private static boolean areInSameMethodCallWithSameIndent(DetailAST comment) {
994         return comment.getParent().getType() == TokenTypes.METHOD_CALL
995                 && comment.getColumnNo()
996                      == getFirstExpressionNodeFromMethodCall(comment.getParent()).getColumnNo();
997     }
998 
999     
1000 
1001 
1002 
1003 
1004 
1005     private static DetailAST getFirstExpressionNodeFromMethodCall(DetailAST methodCall) {
1006         
1007         return methodCall.findFirstToken(TokenTypes.ELIST);
1008     }
1009 
1010 }