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