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