001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2014 Oliver Burn 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//////////////////////////////////////////////////////////////////////////////// 019package com.puppycrawl.tools.checkstyle.checks.javadoc; 020 021import antlr.collections.AST; 022 023import com.google.common.collect.Lists; 024import com.google.common.collect.Sets; 025import com.puppycrawl.tools.checkstyle.api.DetailAST; 026import com.puppycrawl.tools.checkstyle.api.FileContents; 027import com.puppycrawl.tools.checkstyle.api.FullIdent; 028import com.puppycrawl.tools.checkstyle.api.JavadocTagInfo; 029import com.puppycrawl.tools.checkstyle.api.Scope; 030import com.puppycrawl.tools.checkstyle.api.ScopeUtils; 031import com.puppycrawl.tools.checkstyle.api.TextBlock; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033import com.puppycrawl.tools.checkstyle.api.Utils; 034import com.puppycrawl.tools.checkstyle.checks.AbstractTypeAwareCheck; 035import com.puppycrawl.tools.checkstyle.checks.CheckUtils; 036 037import java.util.ArrayList; 038import java.util.Arrays; 039import java.util.Iterator; 040import java.util.List; 041import java.util.ListIterator; 042import java.util.Set; 043import java.util.regex.Matcher; 044import java.util.regex.Pattern; 045 046/** 047 * Checks the Javadoc of a method or constructor. 048 * 049 * @author Oliver Burn 050 * @author Rick Giles 051 * @author o_sukhodoslky 052 */ 053@SuppressWarnings("deprecation") 054public class JavadocMethodCheck extends AbstractTypeAwareCheck 055{ 056 /** compiled regexp to match Javadoc tags that take an argument * */ 057 private static final Pattern MATCH_JAVADOC_ARG = 058 Utils.createPattern("@(throws|exception|param)\\s+(\\S+)\\s+\\S*"); 059 060 /** compiled regexp to match first part of multilineJavadoc tags * */ 061 private static final Pattern MATCH_JAVADOC_ARG_MULTILINE_START = 062 Utils.createPattern("@(throws|exception|param)\\s+(\\S+)\\s*$"); 063 064 /** compiled regexp to look for a continuation of the comment * */ 065 private static final Pattern MATCH_JAVADOC_MULTILINE_CONT = 066 Utils.createPattern("(\\*/|@|[^\\s\\*])"); 067 068 /** Multiline finished at end of comment * */ 069 private static final String END_JAVADOC = "*/"; 070 /** Multiline finished at next Javadoc * */ 071 private static final String NEXT_TAG = "@"; 072 073 /** compiled regexp to match Javadoc tags with no argument * */ 074 private static final Pattern MATCH_JAVADOC_NOARG = 075 Utils.createPattern("@(return|see)\\s+\\S"); 076 /** compiled regexp to match first part of multilineJavadoc tags * */ 077 private static final Pattern MATCH_JAVADOC_NOARG_MULTILINE_START = 078 Utils.createPattern("@(return|see)\\s*$"); 079 /** compiled regexp to match Javadoc tags with no argument and {} * */ 080 private static final Pattern MATCH_JAVADOC_NOARG_CURLY = 081 Utils.createPattern("\\{\\s*@(inheritDoc)\\s*\\}"); 082 083 /** Maximum children allowed * */ 084 private static final int MAX_CHILDREN = 7; 085 086 /** Maximum children allowed * */ 087 private static final int BODY_SIZE = 3; 088 089 /** Default value of minimal amount of lines in method to demand documentation presence.*/ 090 private static final int DEFAULT_MIN_LINE_COUNT = -1; 091 092 /** the visibility scope where Javadoc comments are checked * */ 093 private Scope scope = Scope.PRIVATE; 094 095 /** the visibility scope where Javadoc comments shouldn't be checked * */ 096 private Scope excludeScope; 097 098 /** Minimal amount of lines in method to demand documentation presence.*/ 099 private int minLineCount = DEFAULT_MIN_LINE_COUNT; 100 101 /** 102 * controls whether to allow documented exceptions that are not declared if 103 * they are a subclass of java.lang.RuntimeException. 104 */ 105 private boolean allowUndeclaredRTE; 106 107 /** 108 * Allows validating throws tags. 109 */ 110 private boolean validateThrows; 111 112 /** 113 * controls whether to allow documented exceptions that are subclass of one 114 * of declared exception. Defaults to false (backward compatibility). 115 */ 116 private boolean allowThrowsTagsForSubclasses; 117 118 /** 119 * controls whether to ignore errors when a method has parameters but does 120 * not have matching param tags in the javadoc. Defaults to false. 121 */ 122 private boolean allowMissingParamTags; 123 124 /** 125 * controls whether to ignore errors when a method declares that it throws 126 * exceptions but does not have matching throws tags in the javadoc. 127 * Defaults to false. 128 */ 129 private boolean allowMissingThrowsTags; 130 131 /** 132 * controls whether to ignore errors when a method returns non-void type 133 * but does not have a return tag in the javadoc. Defaults to false. 134 */ 135 private boolean allowMissingReturnTag; 136 137 /** 138 * Controls whether to ignore errors when there is no javadoc. Defaults to 139 * false. 140 */ 141 private boolean allowMissingJavadoc; 142 143 /** 144 * Controls whether to allow missing Javadoc on accessor methods for 145 * properties (setters and getters). 146 */ 147 private boolean allowMissingPropertyJavadoc; 148 149 /** List of annotations that could allow missed documentation. */ 150 private List<String> allowedAnnotations = Arrays.asList("Override"); 151 152 /** Method names that match this pattern do not require javadoc blocks. */ 153 private Pattern ignoreMethodNamesRegex; 154 155 /** 156 * Set regex for matching method names to ignore. 157 * @param regex regex for matching method names. 158 */ 159 public void setIgnoreMethodNamesRegex(String regex) 160 { 161 ignoreMethodNamesRegex = Utils.createPattern(regex); 162 } 163 164 /** 165 * Sets minimal amount of lines in method. 166 * @param value user's value. 167 */ 168 public void setMinLineCount(int value) 169 { 170 minLineCount = value; 171 } 172 173 /** 174 * Allow validating throws tag. 175 * @param value user's value. 176 */ 177 public void setValidateThrows(boolean value) 178 { 179 validateThrows = value; 180 } 181 182 /** 183 * Sets list of annotations. 184 * @param userAnnotations user's value. 185 */ 186 public void setAllowedAnnotations(String userAnnotations) 187 { 188 final List<String> annotations = new ArrayList<String>(); 189 for (String annotation : userAnnotations.split(", ")) { 190 annotations.add(annotation); 191 } 192 allowedAnnotations = annotations; 193 } 194 195 /** 196 * Set the scope. 197 * 198 * @param from a <code>String</code> value 199 */ 200 public void setScope(String from) 201 { 202 scope = Scope.getInstance(from); 203 } 204 205 /** 206 * Set the excludeScope. 207 * 208 * @param scope a <code>String</code> value 209 */ 210 public void setExcludeScope(String scope) 211 { 212 excludeScope = Scope.getInstance(scope); 213 } 214 215 /** 216 * controls whether to allow documented exceptions that are not declared if 217 * they are a subclass of java.lang.RuntimeException. 218 * 219 * @param flag a <code>Boolean</code> value 220 */ 221 public void setAllowUndeclaredRTE(boolean flag) 222 { 223 allowUndeclaredRTE = flag; 224 } 225 226 /** 227 * controls whether to allow documented exception that are subclass of one 228 * of declared exceptions. 229 * 230 * @param flag a <code>Boolean</code> value 231 */ 232 public void setAllowThrowsTagsForSubclasses(boolean flag) 233 { 234 allowThrowsTagsForSubclasses = flag; 235 } 236 237 /** 238 * controls whether to allow a method which has parameters to omit matching 239 * param tags in the javadoc. Defaults to false. 240 * 241 * @param flag a <code>Boolean</code> value 242 */ 243 public void setAllowMissingParamTags(boolean flag) 244 { 245 allowMissingParamTags = flag; 246 } 247 248 /** 249 * controls whether to allow a method which declares that it throws 250 * exceptions to omit matching throws tags in the javadoc. Defaults to 251 * false. 252 * 253 * @param flag a <code>Boolean</code> value 254 */ 255 public void setAllowMissingThrowsTags(boolean flag) 256 { 257 allowMissingThrowsTags = flag; 258 } 259 260 /** 261 * controls whether to allow a method which returns non-void type to omit 262 * the return tag in the javadoc. Defaults to false. 263 * 264 * @param flag a <code>Boolean</code> value 265 */ 266 public void setAllowMissingReturnTag(boolean flag) 267 { 268 allowMissingReturnTag = flag; 269 } 270 271 /** 272 * Controls whether to ignore errors when there is no javadoc. Defaults to 273 * false. 274 * 275 * @param flag a <code>Boolean</code> value 276 */ 277 public void setAllowMissingJavadoc(boolean flag) 278 { 279 allowMissingJavadoc = flag; 280 } 281 282 /** 283 * Controls whether to ignore errors when there is no javadoc for a 284 * property accessor (setter/getter methods). Defaults to false. 285 * 286 * @param flag a <code>Boolean</code> value 287 */ 288 public void setAllowMissingPropertyJavadoc(final boolean flag) 289 { 290 allowMissingPropertyJavadoc = flag; 291 } 292 293 @Override 294 public int[] getDefaultTokens() 295 { 296 return new int[] {TokenTypes.PACKAGE_DEF, TokenTypes.IMPORT, 297 TokenTypes.CLASS_DEF, TokenTypes.ENUM_DEF, 298 TokenTypes.INTERFACE_DEF, 299 TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF, 300 TokenTypes.ANNOTATION_FIELD_DEF, 301 }; 302 } 303 304 @Override 305 public int[] getAcceptableTokens() 306 { 307 return new int[] {TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF, 308 TokenTypes.ANNOTATION_FIELD_DEF, 309 }; 310 } 311 312 @Override 313 public boolean isCommentNodesRequired() 314 { 315 return true; 316 } 317 318 @Override 319 protected final void processAST(DetailAST ast) 320 { 321 if ((ast.getType() == TokenTypes.METHOD_DEF || ast.getType() == TokenTypes.CTOR_DEF) 322 && (getMethodsNumberOfLine(ast) <= minLineCount) 323 || hasAllowedAnnotations(ast)) 324 { 325 return; 326 } 327 final Scope theScope = calculateScope(ast); 328 if (shouldCheck(ast, theScope)) { 329 final FileContents contents = getFileContents(); 330 final TextBlock cmt = contents.getJavadocBefore(ast.getLineNo()); 331 332 if (cmt == null) { 333 if (!isMissingJavadocAllowed(ast)) { 334 log(ast, "javadoc.missing"); 335 } 336 } 337 else { 338 checkComment(ast, cmt); 339 } 340 } 341 } 342 343 /** 344 * Some javadoc. 345 * @param methodDef Some javadoc. 346 * @return Some javadoc. 347 */ 348 private boolean hasAllowedAnnotations(DetailAST methodDef) 349 { 350 final DetailAST modifiersNode = methodDef.findFirstToken(TokenTypes.MODIFIERS); 351 DetailAST annotationNode = modifiersNode.findFirstToken(TokenTypes.ANNOTATION); 352 while (annotationNode != null && annotationNode.getType() == TokenTypes.ANNOTATION) { 353 DetailAST identNode = annotationNode.findFirstToken(TokenTypes.IDENT); 354 if (identNode == null) { 355 identNode = annotationNode.findFirstToken(TokenTypes.DOT) 356 .findFirstToken(TokenTypes.IDENT); 357 } 358 if (allowedAnnotations.contains(identNode.getText())) { 359 return true; 360 } 361 annotationNode = annotationNode.getNextSibling(); 362 } 363 return false; 364 } 365 366 /** 367 * Some javadoc. 368 * @param methodDef Some javadoc. 369 * @return Some javadoc. 370 */ 371 private int getMethodsNumberOfLine(DetailAST methodDef) 372 { 373 int numberOfLines; 374 final DetailAST lcurly = methodDef.getLastChild(); 375 final DetailAST rcurly = lcurly.getLastChild(); 376 377 if (lcurly.getFirstChild() == rcurly) { 378 numberOfLines = 1; 379 } 380 else { 381 numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1; 382 } 383 return numberOfLines; 384 } 385 386 @Override 387 protected final void logLoadError(Token ident) 388 { 389 logLoadErrorImpl(ident.getLineNo(), ident.getColumnNo(), 390 "javadoc.classInfo", 391 JavadocTagInfo.THROWS.getText(), ident.getText()); 392 } 393 394 /** 395 * The JavadocMethodCheck is about to report a missing Javadoc. 396 * This hook can be used by derived classes to allow a missing javadoc 397 * in some situations. The default implementation checks 398 * <code>allowMissingJavadoc</code> and 399 * <code>allowMissingPropertyJavadoc</code> properties, do not forget 400 * to call <code>super.isMissingJavadocAllowed(ast)</code> in case 401 * you want to keep this logic. 402 * @param ast the tree node for the method or constructor. 403 * @return True if this method or constructor doesn't need Javadoc. 404 */ 405 protected boolean isMissingJavadocAllowed(final DetailAST ast) 406 { 407 return allowMissingJavadoc 408 || (allowMissingPropertyJavadoc 409 && (isSetterMethod(ast) || isGetterMethod(ast))) 410 || matchesSkipRegex(ast); 411 } 412 413 /** 414 * Checks if the given method name matches the regex. In that case 415 * we skip enforcement of javadoc for this method 416 * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF} 417 * @return true if given method name matches the regex. 418 */ 419 private boolean matchesSkipRegex(DetailAST methodDef) 420 { 421 if (ignoreMethodNamesRegex != null) { 422 final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT); 423 final String methodName = ident.getText(); 424 425 final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName); 426 if (matcher.matches()) { 427 return true; 428 } 429 } 430 return false; 431 } 432 433 /** 434 * Whether we should check this node. 435 * 436 * @param ast a given node. 437 * @param scope the scope of the node. 438 * @return whether we should check a given node. 439 */ 440 private boolean shouldCheck(final DetailAST ast, final Scope scope) 441 { 442 final Scope surroundingScope = ScopeUtils.getSurroundingScope(ast); 443 444 return scope.isIn(this.scope) 445 && surroundingScope.isIn(this.scope) 446 && ((excludeScope == null) || !scope.isIn(excludeScope) 447 || !surroundingScope.isIn(excludeScope)); 448 } 449 450 /** 451 * Checks the Javadoc for a method. 452 * 453 * @param ast the token for the method 454 * @param comment the Javadoc comment 455 */ 456 private void checkComment(DetailAST ast, TextBlock comment) 457 { 458 final List<JavadocTag> tags = getMethodTags(comment); 459 460 if (hasShortCircuitTag(ast, tags)) { 461 return; 462 } 463 464 Iterator<JavadocTag> it = tags.iterator(); 465 if (ast.getType() != TokenTypes.ANNOTATION_FIELD_DEF) { 466 // Check for inheritDoc 467 boolean hasInheritDocTag = false; 468 while (it.hasNext() && !hasInheritDocTag) { 469 hasInheritDocTag |= (it.next()).isInheritDocTag(); 470 } 471 472 checkParamTags(tags, ast, !hasInheritDocTag); 473 checkThrowsTags(tags, getThrows(ast), !hasInheritDocTag); 474 if (isFunction(ast)) { 475 checkReturnTag(tags, ast.getLineNo(), !hasInheritDocTag); 476 } 477 } 478 479 // Dump out all unused tags 480 it = tags.iterator(); 481 while (it.hasNext()) { 482 final JavadocTag jt = it.next(); 483 if (!jt.isSeeOrInheritDocTag()) { 484 log(jt.getLineNo(), "javadoc.unusedTagGeneral"); 485 } 486 } 487 } 488 489 /** 490 * Validates whether the Javadoc has a short circuit tag. Currently this is 491 * the inheritTag. Any errors are logged. 492 * 493 * @param ast the construct being checked 494 * @param tags the list of Javadoc tags associated with the construct 495 * @return true if the construct has a short circuit tag. 496 */ 497 private boolean hasShortCircuitTag(final DetailAST ast, 498 final List<JavadocTag> tags) 499 { 500 // Check if it contains {@inheritDoc} tag 501 if ((tags.size() != 1) 502 || !(tags.get(0)).isInheritDocTag()) 503 { 504 return false; 505 } 506 507 // Invalid if private, a constructor, or a static method 508 if (!JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) { 509 log(ast, "javadoc.invalidInheritDoc"); 510 } 511 512 return true; 513 } 514 515 /** 516 * Returns the scope for the method/constructor at the specified AST. If 517 * the method is in an interface or annotation block, the scope is assumed 518 * to be public. 519 * 520 * @param ast the token of the method/constructor 521 * @return the scope of the method/constructor 522 */ 523 private Scope calculateScope(final DetailAST ast) 524 { 525 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS); 526 final Scope declaredScope = ScopeUtils.getScopeFromMods(mods); 527 return ScopeUtils.inInterfaceOrAnnotationBlock(ast) ? Scope.PUBLIC 528 : declaredScope; 529 } 530 531 /** 532 * Returns the tags in a javadoc comment. Only finds throws, exception, 533 * param, return and see tags. 534 * 535 * @return the tags found 536 * @param comment the Javadoc comment 537 */ 538 private List<JavadocTag> getMethodTags(TextBlock comment) 539 { 540 final String[] lines = comment.getText(); 541 final List<JavadocTag> tags = Lists.newArrayList(); 542 int currentLine = comment.getStartLineNo() - 1; 543 544 for (int i = 0; i < lines.length; i++) { 545 currentLine++; 546 final Matcher javadocArgMatcher = 547 MATCH_JAVADOC_ARG.matcher(lines[i]); 548 final Matcher javadocNoargMatcher = 549 MATCH_JAVADOC_NOARG.matcher(lines[i]); 550 final Matcher noargCurlyMatcher = 551 MATCH_JAVADOC_NOARG_CURLY.matcher(lines[i]); 552 final Matcher argMultilineStart = 553 MATCH_JAVADOC_ARG_MULTILINE_START.matcher(lines[i]); 554 final Matcher noargMultilineStart = 555 MATCH_JAVADOC_NOARG_MULTILINE_START.matcher(lines[i]); 556 557 if (javadocArgMatcher.find()) { 558 int col = javadocArgMatcher.start(1) - 1; 559 if (i == 0) { 560 col += comment.getStartColNo(); 561 } 562 tags.add(new JavadocTag(currentLine, col, javadocArgMatcher 563 .group(1), javadocArgMatcher.group(2))); 564 } 565 else if (javadocNoargMatcher.find()) { 566 int col = javadocNoargMatcher.start(1) - 1; 567 if (i == 0) { 568 col += comment.getStartColNo(); 569 } 570 tags.add(new JavadocTag(currentLine, col, javadocNoargMatcher 571 .group(1))); 572 } 573 else if (noargCurlyMatcher.find()) { 574 int col = noargCurlyMatcher.start(1) - 1; 575 if (i == 0) { 576 col += comment.getStartColNo(); 577 } 578 tags.add(new JavadocTag(currentLine, col, noargCurlyMatcher 579 .group(1))); 580 } 581 else if (argMultilineStart.find()) { 582 final String p1 = argMultilineStart.group(1); 583 final String p2 = argMultilineStart.group(2); 584 int col = argMultilineStart.start(1) - 1; 585 if (i == 0) { 586 col += comment.getStartColNo(); 587 } 588 589 // Look for the rest of the comment if all we saw was 590 // the tag and the name. Stop when we see '*/' (end of 591 // Javadoc), '@' (start of next tag), or anything that's 592 // not whitespace or '*' characters. 593 int remIndex = i + 1; 594 while (remIndex < lines.length) { 595 final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT 596 .matcher(lines[remIndex]); 597 if (multilineCont.find()) { 598 remIndex = lines.length; 599 final String lFin = multilineCont.group(1); 600 if (!lFin.equals(NEXT_TAG) 601 && !lFin.equals(END_JAVADOC)) 602 { 603 tags.add(new JavadocTag(currentLine, col, p1, p2)); 604 } 605 } 606 remIndex++; 607 } 608 } 609 else if (noargMultilineStart.find()) { 610 final String p1 = noargMultilineStart.group(1); 611 int col = noargMultilineStart.start(1) - 1; 612 if (i == 0) { 613 col += comment.getStartColNo(); 614 } 615 616 // Look for the rest of the comment if all we saw was 617 // the tag and the name. Stop when we see '*/' (end of 618 // Javadoc), '@' (start of next tag), or anything that's 619 // not whitespace or '*' characters. 620 int remIndex = i + 1; 621 while (remIndex < lines.length) { 622 final Matcher multilineCont = MATCH_JAVADOC_MULTILINE_CONT 623 .matcher(lines[remIndex]); 624 if (multilineCont.find()) { 625 remIndex = lines.length; 626 final String lFin = multilineCont.group(1); 627 if (!lFin.equals(NEXT_TAG) 628 && !lFin.equals(END_JAVADOC)) 629 { 630 tags.add(new JavadocTag(currentLine, col, p1)); 631 } 632 } 633 remIndex++; 634 } 635 } 636 } 637 return tags; 638 } 639 640 /** 641 * Computes the parameter nodes for a method. 642 * 643 * @param ast the method node. 644 * @return the list of parameter nodes for ast. 645 */ 646 private List<DetailAST> getParameters(DetailAST ast) 647 { 648 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 649 final List<DetailAST> retVal = Lists.newArrayList(); 650 651 DetailAST child = params.getFirstChild(); 652 while (child != null) { 653 if (child.getType() == TokenTypes.PARAMETER_DEF) { 654 final DetailAST ident = child.findFirstToken(TokenTypes.IDENT); 655 retVal.add(ident); 656 } 657 child = child.getNextSibling(); 658 } 659 return retVal; 660 } 661 662 /** 663 * Computes the exception nodes for a method. 664 * 665 * @param ast the method node. 666 * @return the list of exception nodes for ast. 667 */ 668 private List<ExceptionInfo> getThrows(DetailAST ast) 669 { 670 final List<ExceptionInfo> retVal = Lists.newArrayList(); 671 final DetailAST throwsAST = ast 672 .findFirstToken(TokenTypes.LITERAL_THROWS); 673 if (throwsAST != null) { 674 DetailAST child = throwsAST.getFirstChild(); 675 while (child != null) { 676 if ((child.getType() == TokenTypes.IDENT) 677 || (child.getType() == TokenTypes.DOT)) 678 { 679 final FullIdent fi = FullIdent.createFullIdent(child); 680 final ExceptionInfo ei = new ExceptionInfo(new Token(fi), 681 getCurrentClassName()); 682 retVal.add(ei); 683 } 684 child = child.getNextSibling(); 685 } 686 } 687 return retVal; 688 } 689 690 /** 691 * Checks a set of tags for matching parameters. 692 * 693 * @param tags the tags to check 694 * @param parent the node which takes the parameters 695 * @param reportExpectedTags whether we should report if do not find 696 * expected tag 697 */ 698 private void checkParamTags(final List<JavadocTag> tags, 699 final DetailAST parent, boolean reportExpectedTags) 700 { 701 final List<DetailAST> params = getParameters(parent); 702 final List<DetailAST> typeParams = CheckUtils 703 .getTypeParameters(parent); 704 705 // Loop over the tags, checking to see they exist in the params. 706 final ListIterator<JavadocTag> tagIt = tags.listIterator(); 707 while (tagIt.hasNext()) { 708 final JavadocTag tag = tagIt.next(); 709 710 if (!tag.isParamTag()) { 711 continue; 712 } 713 714 tagIt.remove(); 715 716 boolean found = false; 717 718 // Loop looking for matching param 719 final Iterator<DetailAST> paramIt = params.iterator(); 720 while (paramIt.hasNext()) { 721 final DetailAST param = paramIt.next(); 722 if (param.getText().equals(tag.getArg1())) { 723 found = true; 724 paramIt.remove(); 725 break; 726 } 727 } 728 729 if (tag.getArg1().startsWith("<") && tag.getArg1().endsWith(">")) { 730 // Loop looking for matching type param 731 final Iterator<DetailAST> typeParamsIt = typeParams.iterator(); 732 while (typeParamsIt.hasNext()) { 733 final DetailAST typeParam = typeParamsIt.next(); 734 if (typeParam.findFirstToken(TokenTypes.IDENT).getText() 735 .equals( 736 tag.getArg1().substring(1, 737 tag.getArg1().length() - 1))) 738 { 739 found = true; 740 typeParamsIt.remove(); 741 break; 742 } 743 } 744 745 } 746 747 // Handle extra JavadocTag 748 if (!found) { 749 log(tag.getLineNo(), tag.getColumnNo(), "javadoc.unusedTag", 750 "@param", tag.getArg1()); 751 } 752 } 753 754 // Now dump out all type parameters/parameters without tags :- unless 755 // the user has chosen to suppress these problems 756 if (!allowMissingParamTags && reportExpectedTags) { 757 for (DetailAST param : params) { 758 log(param, "javadoc.expectedTag", 759 JavadocTagInfo.PARAM.getText(), param.getText()); 760 } 761 762 for (DetailAST typeParam : typeParams) { 763 log(typeParam, "javadoc.expectedTag", 764 JavadocTagInfo.PARAM.getText(), 765 "<" + typeParam.findFirstToken(TokenTypes.IDENT).getText() 766 + ">"); 767 } 768 } 769 } 770 771 /** 772 * Checks whether a method is a function. 773 * 774 * @param ast the method node. 775 * @return whether the method is a function. 776 */ 777 private boolean isFunction(DetailAST ast) 778 { 779 boolean retVal = false; 780 if (ast.getType() == TokenTypes.METHOD_DEF) { 781 final DetailAST typeAST = ast.findFirstToken(TokenTypes.TYPE); 782 if ((typeAST != null) 783 && (typeAST.findFirstToken(TokenTypes.LITERAL_VOID) == null)) 784 { 785 retVal = true; 786 } 787 } 788 return retVal; 789 } 790 791 /** 792 * Checks for only one return tag. All return tags will be removed from the 793 * supplied list. 794 * 795 * @param tags the tags to check 796 * @param lineNo the line number of the expected tag 797 * @param reportExpectedTags whether we should report if do not find 798 * expected tag 799 */ 800 private void checkReturnTag(List<JavadocTag> tags, int lineNo, 801 boolean reportExpectedTags) 802 { 803 // Loop over tags finding return tags. After the first one, report an 804 // error. 805 boolean found = false; 806 final ListIterator<JavadocTag> it = tags.listIterator(); 807 while (it.hasNext()) { 808 final JavadocTag jt = it.next(); 809 if (jt.isReturnTag()) { 810 if (found) { 811 log(jt.getLineNo(), jt.getColumnNo(), 812 "javadoc.duplicateTag", 813 JavadocTagInfo.RETURN.getText()); 814 } 815 found = true; 816 it.remove(); 817 } 818 } 819 820 // Handle there being no @return tags :- unless 821 // the user has chosen to suppress these problems 822 if (!found && !allowMissingReturnTag && reportExpectedTags) { 823 log(lineNo, "javadoc.return.expected"); 824 } 825 } 826 827 /** 828 * Checks a set of tags for matching throws. 829 * 830 * @param tags the tags to check 831 * @param throwsList the throws to check 832 * @param reportExpectedTags whether we should report if do not find 833 * expected tag 834 */ 835 private void checkThrowsTags(List<JavadocTag> tags, 836 List<ExceptionInfo> throwsList, boolean reportExpectedTags) 837 { 838 // Loop over the tags, checking to see they exist in the throws. 839 // The foundThrows used for performance only 840 final Set<String> foundThrows = Sets.newHashSet(); 841 final ListIterator<JavadocTag> tagIt = tags.listIterator(); 842 while (tagIt.hasNext()) { 843 final JavadocTag tag = tagIt.next(); 844 845 if (!tag.isThrowsTag()) { 846 continue; 847 } 848 849 tagIt.remove(); 850 851 // Loop looking for matching throw 852 final String documentedEx = tag.getArg1(); 853 final Token token = new Token(tag.getArg1(), tag.getLineNo(), tag 854 .getColumnNo()); 855 final ClassInfo documentedCI = createClassInfo(token, 856 getCurrentClassName()); 857 boolean found = foundThrows.contains(documentedEx); 858 859 // First look for matches on the exception name 860 ListIterator<ExceptionInfo> throwIt = throwsList.listIterator(); 861 while (!found && throwIt.hasNext()) { 862 final ExceptionInfo ei = throwIt.next(); 863 864 if (ei.getName().getText().equals( 865 documentedCI.getName().getText())) 866 { 867 found = true; 868 ei.setFound(); 869 foundThrows.add(documentedEx); 870 } 871 } 872 873 // Now match on the exception type 874 throwIt = throwsList.listIterator(); 875 while (!found && throwIt.hasNext()) { 876 final ExceptionInfo ei = throwIt.next(); 877 878 if (documentedCI.getClazz() == ei.getClazz()) { 879 found = true; 880 ei.setFound(); 881 foundThrows.add(documentedEx); 882 } 883 else if (allowThrowsTagsForSubclasses) { 884 found = isSubclass(documentedCI.getClazz(), ei.getClazz()); 885 } 886 } 887 888 // Handle extra JavadocTag. 889 if (!found) { 890 boolean reqd = true; 891 if (allowUndeclaredRTE) { 892 reqd = !isUnchecked(documentedCI.getClazz()); 893 } 894 895 if (reqd && validateThrows) { 896 log(tag.getLineNo(), tag.getColumnNo(), 897 "javadoc.unusedTag", 898 JavadocTagInfo.THROWS.getText(), tag.getArg1()); 899 900 } 901 } 902 } 903 904 // Now dump out all throws without tags :- unless 905 // the user has chosen to suppress these problems 906 if (!allowMissingThrowsTags && reportExpectedTags) { 907 for (ExceptionInfo ei : throwsList) { 908 if (!ei.isFound()) { 909 final Token fi = ei.getName(); 910 log(fi.getLineNo(), fi.getColumnNo(), 911 "javadoc.expectedTag", 912 JavadocTagInfo.THROWS.getText(), fi.getText()); 913 } 914 } 915 } 916 } 917 918 /** 919 * Returns whether an AST represents a setter method. 920 * @param ast the AST to check with 921 * @return whether the AST represents a setter method 922 */ 923 private boolean isSetterMethod(final DetailAST ast) 924 { 925 // Check have a method with exactly 7 children which are all that 926 // is allowed in a proper setter method which does not throw any 927 // exceptions. 928 if ((ast.getType() != TokenTypes.METHOD_DEF) 929 || (ast.getChildCount() != MAX_CHILDREN)) 930 { 931 return false; 932 } 933 934 // Should I handle only being in a class???? 935 936 // Check the name matches format setX... 937 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 938 final String name = type.getNextSibling().getText(); 939 if (!name.matches("^set[A-Z].*")) { // Depends on JDK 1.4 940 return false; 941 } 942 943 // Check the return type is void 944 if (type.getChildCount(TokenTypes.LITERAL_VOID) == 0) { 945 return false; 946 } 947 948 // Check that is had only one parameter 949 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 950 if ((params == null) 951 || (params.getChildCount(TokenTypes.PARAMETER_DEF) != 1)) 952 { 953 return false; 954 } 955 956 // Now verify that the body consists of: 957 // SLIST -> EXPR -> ASSIGN 958 // SEMI 959 // RCURLY 960 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 961 if ((slist == null) || (slist.getChildCount() != BODY_SIZE)) { 962 return false; 963 } 964 965 final AST expr = slist.getFirstChild(); 966 if ((expr.getType() != TokenTypes.EXPR) 967 || (expr.getFirstChild().getType() != TokenTypes.ASSIGN)) 968 { 969 return false; 970 } 971 972 return true; 973 } 974 975 /** 976 * Returns whether an AST represents a getter method. 977 * @param ast the AST to check with 978 * @return whether the AST represents a getter method 979 */ 980 private boolean isGetterMethod(final DetailAST ast) 981 { 982 // Check have a method with exactly 7 children which are all that 983 // is allowed in a proper getter method which does not throw any 984 // exceptions. 985 if ((ast.getType() != TokenTypes.METHOD_DEF) 986 || (ast.getChildCount() != MAX_CHILDREN)) 987 { 988 return false; 989 } 990 991 // Check the name matches format of getX or isX. Technically I should 992 // check that the format isX is only used with a boolean type. 993 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 994 final String name = type.getNextSibling().getText(); 995 if (!name.matches("^(is|get)[A-Z].*")) { // Depends on JDK 1.4 996 return false; 997 } 998 999 // Check the return type is void 1000 if (type.getChildCount(TokenTypes.LITERAL_VOID) > 0) { 1001 return false; 1002 } 1003 1004 // Check that is had only one parameter 1005 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS); 1006 if ((params == null) 1007 || (params.getChildCount(TokenTypes.PARAMETER_DEF) > 0)) 1008 { 1009 return false; 1010 } 1011 1012 // Now verify that the body consists of: 1013 // SLIST -> RETURN 1014 // RCURLY 1015 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST); 1016 if ((slist == null) || (slist.getChildCount() != 2)) { 1017 return false; 1018 } 1019 1020 final AST expr = slist.getFirstChild(); 1021 if ((expr.getType() != TokenTypes.LITERAL_RETURN) 1022 || (expr.getFirstChild().getType() != TokenTypes.EXPR)) 1023 { 1024 return false; 1025 } 1026 1027 return true; 1028 } 1029 1030 /** Stores useful information about declared exception. */ 1031 private class ExceptionInfo 1032 { 1033 /** does the exception have throws tag associated with. */ 1034 private boolean found; 1035 /** class information associated with this exception. */ 1036 private final ClassInfo classInfo; 1037 1038 /** 1039 * Creates new instance for <code>FullIdent</code>. 1040 * 1041 * @param ident the exception 1042 * @param currentClass name of current class. 1043 */ 1044 ExceptionInfo(Token ident, String currentClass) 1045 { 1046 classInfo = createClassInfo(ident, currentClass); 1047 } 1048 1049 /** Mark that the exception has associated throws tag */ 1050 final void setFound() 1051 { 1052 found = true; 1053 } 1054 1055 /** @return whether the exception has throws tag associated with */ 1056 final boolean isFound() 1057 { 1058 return found; 1059 } 1060 1061 /** @return exception's name */ 1062 final Token getName() 1063 { 1064 return classInfo.getName(); 1065 } 1066 1067 /** @return class for this exception */ 1068 final Class<?> getClazz() 1069 { 1070 return classInfo.getClazz(); 1071 } 1072 } 1073}