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