1 package com.puppycrawl.tools.checkstyle.checks.javadoc; 20 21 import java.util.ArrayList ; 22 import java.util.HashSet ; 23 import java.util.Iterator ; 24 import java.util.List ; 25 import java.util.ListIterator ; 26 import java.util.Set ; 27 28 import java.util.regex.Matcher ; 29 import java.util.regex.Pattern ; 30 31 import antlr.collections.AST; 32 33 import com.puppycrawl.tools.checkstyle.api.DetailAST; 34 import com.puppycrawl.tools.checkstyle.api.FileContents; 35 import com.puppycrawl.tools.checkstyle.api.FullIdent; 36 import com.puppycrawl.tools.checkstyle.api.Scope; 37 import com.puppycrawl.tools.checkstyle.api.ScopeUtils; 38 import com.puppycrawl.tools.checkstyle.api.TextBlock; 39 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 40 import com.puppycrawl.tools.checkstyle.api.Utils; 41 import com.puppycrawl.tools.checkstyle.checks.AbstractTypeAwareCheck; 42 import com.puppycrawl.tools.checkstyle.checks.CheckUtils; 43 44 52 public class JavadocMethodCheck extends AbstractTypeAwareCheck 53 { 54 55 private static final String MATCH_JAVADOC_ARG_PAT = 56 "@(throws|exception|param)\\s+(\\S+)\\s+\\S"; 57 58 private static final Pattern MATCH_JAVADOC_ARG = Utils 59 .createPattern(MATCH_JAVADOC_ARG_PAT); 60 61 65 private static final String MATCH_JAVADOC_ARG_MULTILINE_START_PAT = 66 "@(throws|exception|param)\\s+(\\S+)\\s*$"; 67 68 private static final Pattern MATCH_JAVADOC_ARG_MULTILINE_START = Utils 69 .createPattern(MATCH_JAVADOC_ARG_MULTILINE_START_PAT); 70 71 72 private static final String MATCH_JAVADOC_MULTILINE_CONT_PAT = 73 "(\\*/|@|[^\\s\\*])"; 74 75 private static final Pattern MATCH_JAVADOC_MULTILINE_CONT = Utils 76 .createPattern(MATCH_JAVADOC_MULTILINE_CONT_PAT); 77 78 private static final String END_JAVADOC = "*/"; 79 80 private static final String NEXT_TAG = "@"; 81 82 83 private static final String MATCH_JAVADOC_NOARG_PAT = 84 "@(return|see)\\s+\\S"; 85 86 private static final Pattern MATCH_JAVADOC_NOARG = Utils 87 .createPattern(MATCH_JAVADOC_NOARG_PAT); 88 92 private static final String MATCH_JAVADOC_NOARG_MULTILINE_START_PAT = 93 "@(return|see)\\s*$"; 94 95 private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START = Utils 96 .createPattern(MATCH_JAVADOC_NOARG_MULTILINE_START_PAT); 97 98 99 private static final String MATCH_JAVADOC_NOARG_CURLY_PAT = 100 "\\{\\s*@(inheritDoc)\\s*\\}"; 101 102 private static final Pattern MATCH_JAVADOC_NOARG_CURLY = Utils 103 .createPattern(MATCH_JAVADOC_NOARG_CURLY_PAT); 104 105 106 private static final int MAX_CHILDREN = 7; 107 108 109 private static final int BODY_SIZE = 3; 110 111 112 private Scope mScope = Scope.PRIVATE; 113 114 115 private Scope mExcludeScope; 116 117 121 private boolean mAllowUndeclaredRTE; 122 123 127 private boolean mAllowThrowsTagsForSubclasses; 128 129 133 private boolean mAllowMissingParamTags; 134 135 140 private boolean mAllowMissingThrowsTags; 141 142 146 private boolean mAllowMissingReturnTag; 147 148 152 private boolean mAllowMissingJavadoc; 153 154 158 private boolean mAllowMissingPropertyJavadoc; 159 160 165 public void setScope(String aFrom) 166 { 167 mScope = Scope.getInstance(aFrom); 168 } 169 170 175 public void setExcludeScope(String aScope) 176 { 177 mExcludeScope = Scope.getInstance(aScope); 178 } 179 180 186 public void setAllowUndeclaredRTE(boolean aFlag) 187 { 188 mAllowUndeclaredRTE = aFlag; 189 } 190 191 197 public void setAllowThrowsTagsForSubclasses(boolean aFlag) 198 { 199 mAllowThrowsTagsForSubclasses = aFlag; 200 } 201 202 208 public void setAllowMissingParamTags(boolean aFlag) 209 { 210 mAllowMissingParamTags = aFlag; 211 } 212 213 220 public void setAllowMissingThrowsTags(boolean aFlag) 221 { 222 mAllowMissingThrowsTags = aFlag; 223 } 224 225 231 public void setAllowMissingReturnTag(boolean aFlag) 232 { 233 mAllowMissingReturnTag = aFlag; 234 } 235 236 242 public void setAllowMissingJavadoc(boolean aFlag) 243 { 244 mAllowMissingJavadoc = aFlag; 245 } 246 247 253 public void setAllowMissingPropertyJavadoc(final boolean aFlag) 254 { 255 mAllowMissingPropertyJavadoc = aFlag; 256 } 257 258 259 public int[] getDefaultTokens() 260 { 261 return new int[] {TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT, 262 TokenTypes.CLASS_DEF, TokenTypes.ENUM_DEF, 263 TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF, 264 TokenTypes.ANNOTATION_FIELD_DEF, 265 }; 266 } 267 268 269 public int[] getAcceptableTokens() 270 { 271 return new int[] {TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF, 272 TokenTypes.ANNOTATION_FIELD_DEF, 273 }; 274 } 275 276 281 protected final void processAST(DetailAST aAST) 282 { 283 final Scope theScope = calculateScope(aAST); 284 if (shouldCheck(aAST, theScope)) { 285 final FileContents contents = getFileContents(); 286 final TextBlock cmt = contents.getJavadocBefore(aAST.getLineNo()); 287 288 if (cmt == null) { 289 if (!isMissingJavadocAllowed(aAST)) { 290 log(aAST, "javadoc.missing"); 291 } 292 } 293 else { 294 checkComment(aAST, cmt, theScope); 295 } 296 } 297 } 298 299 304 protected final void logLoadError(Token aIdent) 305 { 306 logLoadErrorImpl(aIdent.getLineNo(), aIdent.getColumnNo(), 307 "javadoc.classInfo", 308 new Object [] {"@throws", aIdent.getText()}); 309 } 310 311 322 protected boolean isMissingJavadocAllowed(final DetailAST aAST) 323 { 324 return mAllowMissingJavadoc 325 || (mAllowMissingPropertyJavadoc 326 && (isSetterMethod(aAST) || isGetterMethod(aAST))); 327 } 328 329 336 private boolean shouldCheck(final DetailAST aAST, final Scope aScope) 337 { 338 final Scope surroundingScope = ScopeUtils.getSurroundingScope(aAST); 339 340 return aScope.isIn(mScope) 341 && surroundingScope.isIn(mScope) 342 && ((mExcludeScope == null) || !aScope.isIn(mExcludeScope) 343 || !surroundingScope.isIn(mExcludeScope)); 344 } 345 346 353 private void checkComment(DetailAST aAST, TextBlock aComment, Scope aScope) 354 { 355 final List tags = getMethodTags(aComment); 356 357 if (hasShortCircuitTag(aAST, tags, aScope)) { 358 return; 359 } 360 361 Iterator it = tags.iterator(); 362 if (aAST.getType() != TokenTypes.ANNOTATION_FIELD_DEF) { 363 boolean hasInheritDocTag = false; 365 while (it.hasNext() && !hasInheritDocTag) { 366 hasInheritDocTag |= ((JavadocTag) it.next()).isInheritDocTag(); 367 } 368 369 checkParamTags(tags, aAST, !hasInheritDocTag); 370 checkThrowsTags(tags, getThrows(aAST), !hasInheritDocTag); 371 if (isFunction(aAST)) { 372 checkReturnTag(tags, aAST.getLineNo(), !hasInheritDocTag); 373 } 374 } 375 376 it = tags.iterator(); 378 while (it.hasNext()) { 379 final JavadocTag jt = (JavadocTag) it.next(); 380 if (!jt.isSeeOrInheritDocTag()) { 381 log(jt.getLineNo(), "javadoc.unusedTagGeneral"); 382 } 383 } 384 } 385 386 395 private boolean hasShortCircuitTag(final DetailAST aAST, final List aTags, 396 final Scope aScope) 397 { 398 if ((aTags.size() != 1) 400 || !((JavadocTag) aTags.get(0)).isInheritDocTag()) 401 { 402 return false; 403 } 404 405 if ((aAST.getType() == TokenTypes.CTOR_DEF) 407 || (aScope == Scope.PRIVATE)) 408 { 409 log(aAST, "javadoc.invalidInheritDoc"); 410 } 411 412 return true; 413 } 414 415 423 private Scope calculateScope(final DetailAST aAST) 424 { 425 final DetailAST mods = aAST.findFirstToken(TokenTypes.MODIFIERS); 426 final Scope declaredScope = ScopeUtils.getScopeFromMods(mods); 427 return ScopeUtils.inInterfaceOrAnnotationBlock(aAST) ? Scope.PUBLIC 428 : declaredScope; 429 } 430 431 438 private List getMethodTags(TextBlock aComment) 439 { 440 final String [] lines = aComment.getText(); 441 final List tags = new ArrayList (); 442 int currentLine = aComment.getStartLineNo() - 1; 443 444 for (int i = 0; i < lines.length; i++) { 445 currentLine++; 446 final Matcher javadocArgMatcher = 447 MATCH_JAVADOC_ARG.matcher(lines[i]); 448 final Matcher javadocNoargMatcher = 449 MATCH_JAVADOC_NOARG.matcher(lines[i]); 450 final Matcher noargCurlyMatcher = 451 MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]); 452 final Matcher argMultilineStart = 453 MATCH_JAVADOC_ARG_MULTILINE_START.matcher(lines[i]); 454 final Matcher noargMultilineStart = 455 MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]); 456 457 if (javadocArgMatcher.find()) { 458 int col = javadocArgMatcher.start(1) - 1; 459 if (i == 0) { 460 col += aComment.getStartColNo(); 461 } 462 tags.add(new JavadocTag(currentLine, col, javadocArgMatcher 463 .group(1), javadocArgMatcher.group(2))); 464 } 465 else if (javadocNoargMatcher.find()) { 466 int col = javadocNoargMatcher.start(1) - 1; 467 if (i == 0) { 468 col += aComment.getStartColNo(); 469 } 470 tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher 471 .group(1))); 472 } 473 else if (noargCurlyMatcher.find()) { 474 int col = noargCurlyMatcher.start(1) - 1; 475 if (i == 0) { 476 col += aComment.getStartColNo(); 477 } 478 tags.add(new JavadocTag(currentLine, col, noargCurlyMatcher 479 .group(1))); 480 } 481 else if (argMultilineStart.find()) { 482 final String p1 = argMultilineStart.group(1); 483 final String p2 = argMultilineStart.group(2); 484 int col = argMultilineStart.start(1) - 1; 485 if (i == 0) { 486 col += aComment.getStartColNo(); 487 } 488 489 int remIndex = i + 1; 494 while (remIndex < lines.length) { 495 final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT 496 .matcher(lines[remIndex]); 497 if (multilineCont.find()) { 498 remIndex = lines.length; 499 final String lFin = multilineCont.group(1); 500 if (!lFin.equals(NEXT_TAG) && !lFin.equals(END_JAVADOC)) 501 { 502 tags.add(new JavadocTag(currentLine, col, p1, p2)); 503 } 504 } 505 remIndex++; 506 } 507 } 508 else if (noargMultilineStart.find()) { 509 final String p1 = noargMultilineStart.group(1); 510 int col = noargMultilineStart.start(1) - 1; 511 if (i == 0) { 512 col += aComment.getStartColNo(); 513 } 514 515 int remIndex = i + 1; 520 while (remIndex < lines.length) { 521 final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT 522 .matcher(lines[remIndex]); 523 if (multilineCont.find()) { 524 remIndex = lines.length; 525 final String lFin = multilineCont.group(1); 526 if (!lFin.equals(NEXT_TAG) && !lFin.equals(END_JAVADOC)) 527 { 528 tags.add(new JavadocTag(currentLine, col, p1)); 529 } 530 } 531 remIndex++; 532 } 533 } 534 } 535 return tags; 536 } 537 538 544 private List getParameters(DetailAST aAST) 545 { 546 final DetailAST params = aAST.findFirstToken(TokenTypes.PARAMETERS); 547 final List retVal = new ArrayList (); 548 549 DetailAST child = (DetailAST) params.getFirstChild(); 550 while (child != null) { 551 if (child.getType() == TokenTypes.PARAMETER_DEF) { 552 final DetailAST ident = child.findFirstToken(TokenTypes.IDENT); 553 retVal.add(ident); 554 } 555 child = (DetailAST) child.getNextSibling(); 556 } 557 return retVal; 558 } 559 560 566 private List getThrows(DetailAST aAST) 567 { 568 final List retVal = new ArrayList (); 569 final DetailAST throwsAST = aAST 570 .findFirstToken(TokenTypes.LITERAL_THROWS); 571 if (throwsAST != null) { 572 DetailAST child = (DetailAST) throwsAST.getFirstChild(); 573 while (child != null) { 574 if ((child.getType() == TokenTypes.IDENT) 575 || (child.getType() == TokenTypes.DOT)) 576 { 577 final FullIdent fi = FullIdent.createFullIdent(child); 578 final ExceptionInfo ei = new ExceptionInfo(new Token(fi), 579 getCurrentClassName()); 580 retVal.add(ei); 581 } 582 child = (DetailAST) child.getNextSibling(); 583 } 584 } 585 return retVal; 586 } 587 588 596 private void checkParamTags(final List aTags, final DetailAST aParent, 597 boolean aReportExpectedTags) 598 { 599 final List params = getParameters(aParent); 600 final List typeParams = CheckUtils.getTypeParameters(aParent); 601 602 final ListIterator tagIt = aTags.listIterator(); 604 while (tagIt.hasNext()) { 605 final JavadocTag tag = (JavadocTag) tagIt.next(); 606 607 if (!tag.isParamTag()) { 608 continue; 609 } 610 611 tagIt.remove(); 612 613 boolean found = false; 614 615 final Iterator paramIt = params.iterator(); 617 while (paramIt.hasNext()) { 618 final DetailAST param = (DetailAST) paramIt.next(); 619 if (param.getText().equals(tag.getArg1())) { 620 found = true; 621 paramIt.remove(); 622 break; 623 } 624 } 625 626 if (tag.getArg1().startsWith("<") && tag.getArg1().endsWith(">")) { 627 final Iterator typeParamsIt = typeParams.iterator(); 629 while (typeParamsIt.hasNext()) { 630 final DetailAST typeParam = (DetailAST) typeParamsIt.next(); 631 if (typeParam.findFirstToken(TokenTypes.IDENT).getText() 632 .equals( 633 tag.getArg1().substring(1, 634 tag.getArg1().length() - 1))) 635 { 636 found = true; 637 typeParamsIt.remove(); 638 break; 639 } 640 } 641 642 } 643 644 if (!found) { 646 log(tag.getLineNo(), tag.getColumnNo(), "javadoc.unusedTag", 647 "@param", tag.getArg1()); 648 } 649 } 650 651 if (!mAllowMissingParamTags && aReportExpectedTags) { 654 final Iterator paramIt = params.iterator(); 655 while (paramIt.hasNext()) { 656 final DetailAST param = (DetailAST) paramIt.next(); 657 log(param, "javadoc.expectedTag", "@param", param.getText()); 658 } 659 660 final Iterator typeParamsIt = typeParams.iterator(); 661 while (typeParamsIt.hasNext()) { 662 final DetailAST typeParam = (DetailAST) typeParamsIt.next(); 663 log(typeParam, "javadoc.expectedTag", "@param", "<" 664 + typeParam.findFirstToken(TokenTypes.IDENT).getText() 665 + ">"); 666 } 667 } 668 } 669 670 676 private boolean isFunction(DetailAST aAST) 677 { 678 boolean retVal = false; 679 if (aAST.getType() == TokenTypes.METHOD_DEF) { 680 final DetailAST typeAST = aAST.findFirstToken(TokenTypes.TYPE); 681 if ((typeAST != null) 682 && (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null)) 683 { 684 retVal = true; 685 } 686 } 687 return retVal; 688 } 689 690 699 private void checkReturnTag(List aTags, int aLineNo, 700 boolean aReportExpectedTags) 701 { 702 boolean found = false; 705 final ListIterator it = aTags.listIterator(); 706 while (it.hasNext()) { 707 final JavadocTag jt = (JavadocTag) it.next(); 708 if (jt.isReturnTag()) { 709 if (found) { 710 log(jt.getLineNo(), jt.getColumnNo(), 711 "javadoc.return.duplicate"); 712 } 713 found = true; 714 it.remove(); 715 } 716 } 717 718 if (!found && !mAllowMissingReturnTag && aReportExpectedTags) { 721 log(aLineNo, "javadoc.return.expected"); 722 } 723 } 724 725 733 private void checkThrowsTags(List aTags, List aThrows, 734 boolean aReportExpectedTags) 735 { 736 final Set foundThrows = new HashSet (); final ListIterator tagIt = aTags.listIterator(); 739 while (tagIt.hasNext()) { 740 final JavadocTag tag = (JavadocTag) tagIt.next(); 741 742 if (!tag.isThrowsTag()) { 743 continue; 744 } 745 746 tagIt.remove(); 747 748 final String documentedEx = tag.getArg1(); 750 final Token token = new Token(tag.getArg1(), tag.getLineNo(), tag 751 .getColumnNo()); 752 final ClassInfo documentedCI = createClassInfo(token, 753 getCurrentClassName()); 754 boolean found = foundThrows.contains(documentedEx); 755 756 final ListIterator throwIt = aThrows.listIterator(); 757 while (!found && throwIt.hasNext()) { 758 final ExceptionInfo ei = (ExceptionInfo) throwIt.next(); 759 760 if (documentedCI.getClazz() == ei.getClazz()) { 761 found = true; 762 ei.setFound(); 763 foundThrows.add(documentedEx); 764 } 765 else if (mAllowThrowsTagsForSubclasses) { 766 found = isSubclass(documentedCI.getClazz(), ei.getClazz()); 767 } 768 } 769 770 if (!found) { 772 boolean reqd = true; 773 if (mAllowUndeclaredRTE) { 774 reqd = !isUnchecked(documentedCI.getClazz()); 775 } 776 777 if (reqd) { 778 log(tag.getLineNo(), tag.getColumnNo(), 779 "javadoc.unusedTag", "@throws", tag.getArg1()); 780 781 } 782 } 783 } 784 785 if (!mAllowMissingThrowsTags && aReportExpectedTags) { 788 final ListIterator throwIt = aThrows.listIterator(); 789 while (throwIt.hasNext()) { 790 final ExceptionInfo ei = (ExceptionInfo) throwIt.next(); 791 if (!ei.isFound()) { 792 final Token fi = ei.getName(); 793 log(fi.getLineNo(), fi.getColumnNo(), 794 "javadoc.expectedTag", "@throws", fi.getText()); 795 } 796 } 797 } 798 } 799 800 805 private boolean isSetterMethod(final DetailAST aAST) 806 { 807 if ((aAST.getType() != TokenTypes.METHOD_DEF) 811 || (aAST.getChildCount() != MAX_CHILDREN)) 812 { 813 return false; 814 } 815 816 818 final DetailAST type = aAST.findFirstToken(TokenTypes.TYPE); 820 final String name = type.getNextSibling().getText(); 821 if (!name.matches("^set[A-Z].*")) { return false; 823 } 824 825 if (type.getChildCount(TokenTypes.LITERAL_VOID) == 0) { 827 return false; 828 } 829 830 final DetailAST params = aAST.findFirstToken(TokenTypes.PARAMETERS); 832 if ((params == null) 833 || (params.getChildCount(TokenTypes.PARAMETER_DEF) != 1)) 834 { 835 return false; 836 } 837 838 final DetailAST slist = aAST.findFirstToken(TokenTypes.SLIST); 843 if ((slist == null) || (slist.getChildCount() != BODY_SIZE)) { 844 return false; 845 } 846 847 final AST expr = slist.getFirstChild(); 848 if ((expr.getType() != TokenTypes.EXPR) 849 || (expr.getFirstChild().getType() != TokenTypes.ASSIGN)) 850 { 851 return false; 852 } 853 854 return true; 855 } 856 857 862 private boolean isGetterMethod(final DetailAST aAST) 863 { 864 if ((aAST.getType() != TokenTypes.METHOD_DEF) 868 || (aAST.getChildCount() != MAX_CHILDREN)) 869 { 870 return false; 871 } 872 873 final DetailAST type = aAST.findFirstToken(TokenTypes.TYPE); 876 final String name = type.getNextSibling().getText(); 877 if (!name.matches("^(is|get)[A-Z].*")) { return false; 879 } 880 881 if (type.getChildCount(TokenTypes.LITERAL_VOID) > 0) { 883 return false; 884 } 885 886 final DetailAST params = aAST.findFirstToken(TokenTypes.PARAMETERS); 888 if ((params == null) 889 || (params.getChildCount(TokenTypes.PARAMETER_DEF) > 0)) 890 { 891 return false; 892 } 893 894 final DetailAST slist = aAST.findFirstToken(TokenTypes.SLIST); 898 if ((slist == null) || (slist.getChildCount() != 2)) { 899 return false; 900 } 901 902 final AST expr = slist.getFirstChild(); 903 if ((expr.getType() != TokenTypes.LITERAL_RETURN) 904 || (expr.getFirstChild().getType() != TokenTypes.EXPR)) 905 { 906 return false; 907 } 908 909 return true; 910 } 911 912 913 private class ExceptionInfo 914 { 915 916 private boolean mFound; 917 918 private ClassInfo mClassInfo; 919 920 926 ExceptionInfo(Token aIdent, String aCurrentClass) 927 { 928 mClassInfo = createClassInfo(aIdent, aCurrentClass); 929 } 930 931 932 final void setFound() 933 { 934 mFound = true; 935 } 936 937 938 final boolean isFound() 939 { 940 return mFound; 941 } 942 943 944 final Token getName() 945 { 946 return mClassInfo.getName(); 947 } 948 949 950 final Class getClazz() 951 { 952 return mClassInfo.getClazz(); 953 } 954 } 955 } 956 | Popular Tags |