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