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