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