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