1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.checks.javadoc; 21 22 import java.util.ArrayDeque; 23 import java.util.Arrays; 24 import java.util.Deque; 25 import java.util.List; 26 import java.util.Locale; 27 import java.util.Set; 28 import java.util.regex.Matcher; 29 import java.util.regex.Pattern; 30 31 import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser; 32 import com.puppycrawl.tools.checkstyle.StatelessCheck; 33 import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 34 import com.puppycrawl.tools.checkstyle.api.DetailAST; 35 import com.puppycrawl.tools.checkstyle.api.FileContents; 36 import com.puppycrawl.tools.checkstyle.api.Scope; 37 import com.puppycrawl.tools.checkstyle.api.TextBlock; 38 import com.puppycrawl.tools.checkstyle.api.TokenTypes; 39 import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 40 import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 41 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 42 43 /** 44 * <div> 45 * Validates Javadoc comments to help ensure they are well formed. 46 * </div> 47 * 48 * <p> 49 * The following checks are performed: 50 * </p> 51 * <ul> 52 * <li> 53 * Ensures the first sentence ends with proper punctuation 54 * (That is a period, question mark, or exclamation mark, by default). 55 * Note that this check is not applied to inline {@code @return} tags, 56 * because the Javadoc tools automatically appends a period to the end of the tag 57 * content. 58 * Javadoc automatically places the first sentence in the method summary 59 * table and index. Without proper punctuation the Javadoc may be malformed. 60 * All items eligible for the {@code {@inheritDoc}} tag are exempt from this 61 * requirement. 62 * </li> 63 * <li> 64 * Check text for Javadoc statements that do not have any description. 65 * This includes both completely empty Javadoc, and Javadoc with only tags 66 * such as {@code @param} and {@code @return}. 67 * </li> 68 * <li> 69 * Check text for incomplete HTML tags. Verifies that HTML tags have 70 * corresponding end tags and issues an "Unclosed HTML tag found:" error if not. 71 * An "Extra HTML tag found:" error is issued if an end tag is found without 72 * a previous open tag. 73 * </li> 74 * <li> 75 * Check that a package Javadoc comment is well-formed (as described above). 76 * </li> 77 * <li> 78 * Check for allowed HTML tags. The list of allowed HTML tags is 79 * "a", "abbr", "acronym", "address", "area", "b", "bdo", "big", "blockquote", 80 * "br", "caption", "cite", "code", "colgroup", "dd", "del", "dfn", "div", "dl", 81 * "dt", "em", "fieldset", "font", "h1", "h2", "h3", "h4", "h5", "h6", "hr", 82 * "i", "img", "ins", "kbd", "li", "ol", "p", "pre", "q", "samp", "small", 83 * "span", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", 84 * "thead", "tr", "tt", "u", "ul", "var". 85 * </li> 86 * </ul> 87 * 88 * <p> 89 * These checks were patterned after the checks made by the 90 * <a href="https://maven-doccheck.sourceforge.net">DocCheck</a> doclet 91 * available from Sun. Note: Original Sun's DocCheck tool does not exist anymore. 92 * </p> 93 * <ul> 94 * <li> 95 * Property {@code checkEmptyJavadoc} - Control whether to check if the Javadoc 96 * is missing a describing text. 97 * Type is {@code boolean}. 98 * Default value is {@code false}. 99 * </li> 100 * <li> 101 * Property {@code checkFirstSentence} - Control whether to check the first 102 * sentence for proper end of sentence. 103 * Type is {@code boolean}. 104 * Default value is {@code true}. 105 * </li> 106 * <li> 107 * Property {@code checkHtml} - Control whether to check for incomplete HTML tags. 108 * Type is {@code boolean}. 109 * Default value is {@code true}. 110 * </li> 111 * <li> 112 * Property {@code endOfSentenceFormat} - Specify the format for matching 113 * the end of a sentence. 114 * Type is {@code java.util.regex.Pattern}. 115 * Default value is {@code "([.?!][ \t\n\r\f<])|([.?!]$)"}. 116 * </li> 117 * <li> 118 * Property {@code excludeScope} - Specify the visibility scope where 119 * Javadoc comments are not checked. 120 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}. 121 * Default value is {@code null}. 122 * </li> 123 * <li> 124 * Property {@code scope} - Specify the visibility scope where Javadoc comments are checked. 125 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}. 126 * Default value is {@code private}. 127 * </li> 128 * <li> 129 * Property {@code tokens} - tokens to check 130 * Type is {@code java.lang.String[]}. 131 * Validation type is {@code tokenSet}. 132 * Default value is: 133 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF"> 134 * ANNOTATION_DEF</a>, 135 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF"> 136 * ANNOTATION_FIELD_DEF</a>, 137 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 138 * CLASS_DEF</a>, 139 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 140 * CTOR_DEF</a>, 141 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 142 * ENUM_CONSTANT_DEF</a>, 143 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 144 * ENUM_DEF</a>, 145 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 146 * INTERFACE_DEF</a>, 147 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 148 * METHOD_DEF</a>, 149 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF"> 150 * PACKAGE_DEF</a>, 151 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 152 * VARIABLE_DEF</a>, 153 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 154 * RECORD_DEF</a>, 155 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 156 * COMPACT_CTOR_DEF</a>. 157 * </li> 158 * </ul> 159 * 160 * <p> 161 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 162 * </p> 163 * 164 * <p> 165 * Violation Message Keys: 166 * </p> 167 * <ul> 168 * <li> 169 * {@code javadoc.empty} 170 * </li> 171 * <li> 172 * {@code javadoc.extraHtml} 173 * </li> 174 * <li> 175 * {@code javadoc.incompleteTag} 176 * </li> 177 * <li> 178 * {@code javadoc.noPeriod} 179 * </li> 180 * <li> 181 * {@code javadoc.unclosedHtml} 182 * </li> 183 * </ul> 184 * 185 * @since 3.2 186 */ 187 @StatelessCheck 188 public class JavadocStyleCheck 189 extends AbstractCheck { 190 191 /** Message property key for the Empty Javadoc message. */ 192 public static final String MSG_EMPTY = "javadoc.empty"; 193 194 /** Message property key for the No Javadoc end of Sentence Period message. */ 195 public static final String MSG_NO_PERIOD = "javadoc.noPeriod"; 196 197 /** Message property key for the Incomplete Tag message. */ 198 public static final String MSG_INCOMPLETE_TAG = "javadoc.incompleteTag"; 199 200 /** Message property key for the Unclosed HTML message. */ 201 public static final String MSG_UNCLOSED_HTML = JavadocDetailNodeParser.MSG_UNCLOSED_HTML_TAG; 202 203 /** Message property key for the Extra HTML message. */ 204 public static final String MSG_EXTRA_HTML = "javadoc.extraHtml"; 205 206 /** HTML tags that do not require a close tag. */ 207 private static final Set<String> SINGLE_TAGS = Set.of( 208 "br", "li", "dt", "dd", "hr", "img", "p", "td", "tr", "th" 209 ); 210 211 /** 212 * HTML tags that are allowed in java docs. 213 * From <a href="https://www.w3schools.com/tags/default.asp">w3schools</a>: 214 * <br> 215 * The forms and structure tags are not allowed 216 */ 217 private static final Set<String> ALLOWED_TAGS = Set.of( 218 "a", "abbr", "acronym", "address", "area", "b", "bdo", "big", 219 "blockquote", "br", "caption", "cite", "code", "colgroup", "dd", 220 "del", "dfn", "div", "dl", "dt", "em", "fieldset", "font", "h1", 221 "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "ins", "kbd", 222 "li", "ol", "p", "pre", "q", "samp", "small", "span", "strong", 223 "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", 224 "tr", "tt", "u", "ul", "var" 225 ); 226 227 /** Specify the format for inline return Javadoc. */ 228 private static final Pattern INLINE_RETURN_TAG_PATTERN = 229 Pattern.compile("\\{@return.*?}\\s*"); 230 231 /** Specify the format for first word in javadoc. */ 232 private static final Pattern SENTENCE_SEPARATOR = Pattern.compile("\\.(?=\\s|$)"); 233 234 /** Specify the visibility scope where Javadoc comments are checked. */ 235 private Scope scope = Scope.PRIVATE; 236 237 /** Specify the visibility scope where Javadoc comments are not checked. */ 238 private Scope excludeScope; 239 240 /** Specify the format for matching the end of a sentence. */ 241 private Pattern endOfSentenceFormat = Pattern.compile("([.?!][ \t\n\r\f<])|([.?!]$)"); 242 243 /** 244 * Control whether to check the first sentence for proper end of sentence. 245 */ 246 private boolean checkFirstSentence = true; 247 248 /** 249 * Control whether to check for incomplete HTML tags. 250 */ 251 private boolean checkHtml = true; 252 253 /** 254 * Control whether to check if the Javadoc is missing a describing text. 255 */ 256 private boolean checkEmptyJavadoc; 257 258 @Override 259 public int[] getDefaultTokens() { 260 return getAcceptableTokens(); 261 } 262 263 @Override 264 public int[] getAcceptableTokens() { 265 return new int[] { 266 TokenTypes.ANNOTATION_DEF, 267 TokenTypes.ANNOTATION_FIELD_DEF, 268 TokenTypes.CLASS_DEF, 269 TokenTypes.CTOR_DEF, 270 TokenTypes.ENUM_CONSTANT_DEF, 271 TokenTypes.ENUM_DEF, 272 TokenTypes.INTERFACE_DEF, 273 TokenTypes.METHOD_DEF, 274 TokenTypes.PACKAGE_DEF, 275 TokenTypes.VARIABLE_DEF, 276 TokenTypes.RECORD_DEF, 277 TokenTypes.COMPACT_CTOR_DEF, 278 }; 279 } 280 281 @Override 282 public int[] getRequiredTokens() { 283 return CommonUtil.EMPTY_INT_ARRAY; 284 } 285 286 // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166 287 @SuppressWarnings("deprecation") 288 @Override 289 public void visitToken(DetailAST ast) { 290 if (shouldCheck(ast)) { 291 final FileContents contents = getFileContents(); 292 // Need to start searching for the comment before the annotations 293 // that may exist. Even if annotations are not defined on the 294 // package, the ANNOTATIONS AST is defined. 295 final TextBlock textBlock = 296 contents.getJavadocBefore(ast.getFirstChild().getLineNo()); 297 298 checkComment(ast, textBlock); 299 } 300 } 301 302 /** 303 * Whether we should check this node. 304 * 305 * @param ast a given node. 306 * @return whether we should check a given node. 307 */ 308 private boolean shouldCheck(final DetailAST ast) { 309 boolean check = false; 310 311 if (ast.getType() == TokenTypes.PACKAGE_DEF) { 312 check = CheckUtil.isPackageInfo(getFilePath()); 313 } 314 else if (!ScopeUtil.isInCodeBlock(ast)) { 315 final Scope customScope = ScopeUtil.getScope(ast); 316 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast); 317 318 check = customScope.isIn(scope) 319 && surroundingScope.isIn(scope) 320 && (excludeScope == null || !customScope.isIn(excludeScope) 321 || !surroundingScope.isIn(excludeScope)); 322 } 323 return check; 324 } 325 326 /** 327 * Performs the various checks against the Javadoc comment. 328 * 329 * @param ast the AST of the element being documented 330 * @param comment the source lines that make up the Javadoc comment. 331 * 332 * @see #checkFirstSentenceEnding(DetailAST, TextBlock) 333 * @see #checkHtmlTags(DetailAST, TextBlock) 334 */ 335 private void checkComment(final DetailAST ast, final TextBlock comment) { 336 if (comment != null) { 337 if (checkFirstSentence) { 338 checkFirstSentenceEnding(ast, comment); 339 } 340 341 if (checkHtml) { 342 checkHtmlTags(ast, comment); 343 } 344 345 if (checkEmptyJavadoc) { 346 checkJavadocIsNotEmpty(comment); 347 } 348 } 349 } 350 351 /** 352 * Checks that the first sentence ends with proper punctuation. This method 353 * uses a regular expression that checks for the presence of a period, 354 * question mark, or exclamation mark followed either by whitespace, an 355 * HTML element, or the end of string. This method ignores {_AT_inheritDoc} 356 * comments for TokenTypes that are valid for {_AT_inheritDoc}. 357 * 358 * @param ast the current node 359 * @param comment the source lines that make up the Javadoc comment. 360 */ 361 private void checkFirstSentenceEnding(final DetailAST ast, TextBlock comment) { 362 final String commentText = getCommentText(comment.getText()); 363 final boolean hasInLineReturnTag = Arrays.stream(SENTENCE_SEPARATOR.split(commentText)) 364 .findFirst() 365 .map(INLINE_RETURN_TAG_PATTERN::matcher) 366 .filter(Matcher::find) 367 .isPresent(); 368 369 if (!hasInLineReturnTag 370 && !commentText.isEmpty() 371 && !endOfSentenceFormat.matcher(commentText).find() 372 && !(commentText.startsWith("{@inheritDoc}") 373 && JavadocTagInfo.INHERIT_DOC.isValidOn(ast))) { 374 log(comment.getStartLineNo(), MSG_NO_PERIOD); 375 } 376 } 377 378 /** 379 * Checks that the Javadoc is not empty. 380 * 381 * @param comment the source lines that make up the Javadoc comment. 382 */ 383 private void checkJavadocIsNotEmpty(TextBlock comment) { 384 final String commentText = getCommentText(comment.getText()); 385 386 if (commentText.isEmpty()) { 387 log(comment.getStartLineNo(), MSG_EMPTY); 388 } 389 } 390 391 /** 392 * Returns the comment text from the Javadoc. 393 * 394 * @param comments the lines of Javadoc. 395 * @return a comment text String. 396 */ 397 private static String getCommentText(String... comments) { 398 final StringBuilder builder = new StringBuilder(1024); 399 for (final String line : comments) { 400 final int textStart = findTextStart(line); 401 402 if (textStart != -1) { 403 if (line.charAt(textStart) == '@') { 404 // we have found the tag section 405 break; 406 } 407 builder.append(line.substring(textStart)); 408 trimTail(builder); 409 builder.append('\n'); 410 } 411 } 412 413 return builder.toString().trim(); 414 } 415 416 /** 417 * Finds the index of the first non-whitespace character ignoring the 418 * Javadoc comment start and end strings (/** and */) as well as any 419 * leading asterisk. 420 * 421 * @param line the Javadoc comment line of text to scan. 422 * @return the int index relative to 0 for the start of text 423 * or -1 if not found. 424 */ 425 private static int findTextStart(String line) { 426 int textStart = -1; 427 int index = 0; 428 while (index < line.length()) { 429 if (!Character.isWhitespace(line.charAt(index))) { 430 if (line.regionMatches(index, "/**", 0, "/**".length()) 431 || line.regionMatches(index, "*/", 0, 2)) { 432 index++; 433 } 434 else if (line.charAt(index) != '*') { 435 textStart = index; 436 break; 437 } 438 } 439 index++; 440 } 441 return textStart; 442 } 443 444 /** 445 * Trims any trailing whitespace or the end of Javadoc comment string. 446 * 447 * @param builder the StringBuilder to trim. 448 */ 449 private static void trimTail(StringBuilder builder) { 450 int index = builder.length() - 1; 451 while (true) { 452 if (Character.isWhitespace(builder.charAt(index))) { 453 builder.deleteCharAt(index); 454 } 455 else if (index > 0 && builder.charAt(index) == '/' 456 && builder.charAt(index - 1) == '*') { 457 builder.deleteCharAt(index); 458 builder.deleteCharAt(index - 1); 459 index--; 460 while (builder.charAt(index - 1) == '*') { 461 builder.deleteCharAt(index - 1); 462 index--; 463 } 464 } 465 else { 466 break; 467 } 468 index--; 469 } 470 } 471 472 /** 473 * Checks the comment for HTML tags that do not have a corresponding close 474 * tag or a close tag that has no previous open tag. This code was 475 * primarily copied from the DocCheck checkHtml method. 476 * 477 * @param ast the node with the Javadoc 478 * @param comment the {@code TextBlock} which represents 479 * the Javadoc comment. 480 * @noinspection MethodWithMultipleReturnPoints 481 * @noinspectionreason MethodWithMultipleReturnPoints - check and method are 482 * too complex to break apart 483 */ 484 // -@cs[ReturnCount] Too complex to break apart. 485 private void checkHtmlTags(final DetailAST ast, final TextBlock comment) { 486 final int lineNo = comment.getStartLineNo(); 487 final Deque<HtmlTag> htmlStack = new ArrayDeque<>(); 488 final String[] text = comment.getText(); 489 490 final TagParser parser = new TagParser(text, lineNo); 491 492 while (parser.hasNextTag()) { 493 final HtmlTag tag = parser.nextTag(); 494 495 if (tag.isIncompleteTag()) { 496 log(tag.getLineNo(), MSG_INCOMPLETE_TAG, 497 text[tag.getLineNo() - lineNo]); 498 return; 499 } 500 if (tag.isClosedTag()) { 501 // do nothing 502 continue; 503 } 504 if (tag.isCloseTag()) { 505 // We have found a close tag. 506 if (isExtraHtml(tag.getId(), htmlStack)) { 507 // No corresponding open tag was found on the stack. 508 log(tag.getLineNo(), 509 tag.getPosition(), 510 MSG_EXTRA_HTML, 511 tag.getText()); 512 } 513 else { 514 // See if there are any unclosed tags that were opened 515 // after this one. 516 checkUnclosedTags(htmlStack, tag.getId()); 517 } 518 } 519 else { 520 // We only push html tags that are allowed 521 if (isAllowedTag(tag)) { 522 htmlStack.push(tag); 523 } 524 } 525 } 526 527 // Identify any tags left on the stack. 528 // Skip multiples, like <b>...<b> 529 String lastFound = ""; 530 final List<String> typeParameters = CheckUtil.getTypeParameterNames(ast); 531 for (final HtmlTag htmlTag : htmlStack) { 532 if (!isSingleTag(htmlTag) 533 && !htmlTag.getId().equals(lastFound) 534 && !typeParameters.contains(htmlTag.getId())) { 535 log(htmlTag.getLineNo(), htmlTag.getPosition(), 536 MSG_UNCLOSED_HTML, htmlTag.getText()); 537 lastFound = htmlTag.getId(); 538 } 539 } 540 } 541 542 /** 543 * Checks to see if there are any unclosed tags on the stack. The token 544 * represents a html tag that has been closed and has a corresponding open 545 * tag on the stack. Any tags, except single tags, that were opened 546 * (pushed on the stack) after the token are missing a close. 547 * 548 * @param htmlStack the stack of opened HTML tags. 549 * @param token the current HTML tag name that has been closed. 550 */ 551 private void checkUnclosedTags(Deque<HtmlTag> htmlStack, String token) { 552 final Deque<HtmlTag> unclosedTags = new ArrayDeque<>(); 553 HtmlTag lastOpenTag = htmlStack.pop(); 554 while (!token.equalsIgnoreCase(lastOpenTag.getId())) { 555 // Find unclosed elements. Put them on a stack so the 556 // output order won't be back-to-front. 557 if (isSingleTag(lastOpenTag)) { 558 lastOpenTag = htmlStack.pop(); 559 } 560 else { 561 unclosedTags.push(lastOpenTag); 562 lastOpenTag = htmlStack.pop(); 563 } 564 } 565 566 // Output the unterminated tags, if any 567 // Skip multiples, like <b>..<b> 568 String lastFound = ""; 569 for (final HtmlTag htag : unclosedTags) { 570 lastOpenTag = htag; 571 if (lastOpenTag.getId().equals(lastFound)) { 572 continue; 573 } 574 lastFound = lastOpenTag.getId(); 575 log(lastOpenTag.getLineNo(), 576 lastOpenTag.getPosition(), 577 MSG_UNCLOSED_HTML, 578 lastOpenTag.getText()); 579 } 580 } 581 582 /** 583 * Determines if the HtmlTag is one which does not require a close tag. 584 * 585 * @param tag the HtmlTag to check. 586 * @return {@code true} if the HtmlTag is a single tag. 587 */ 588 private static boolean isSingleTag(HtmlTag tag) { 589 // If it's a singleton tag (<p>, <br>, etc.), ignore it 590 // Can't simply not put them on the stack, since singletons 591 // like <dt> and <dd> (unhappily) may either be terminated 592 // or not terminated. Both options are legal. 593 return SINGLE_TAGS.contains(tag.getId().toLowerCase(Locale.ENGLISH)); 594 } 595 596 /** 597 * Determines if the HtmlTag is one which is allowed in a javadoc. 598 * 599 * @param tag the HtmlTag to check. 600 * @return {@code true} if the HtmlTag is an allowed html tag. 601 */ 602 private static boolean isAllowedTag(HtmlTag tag) { 603 return ALLOWED_TAGS.contains(tag.getId().toLowerCase(Locale.ENGLISH)); 604 } 605 606 /** 607 * Determines if the given token is an extra HTML tag. This indicates that 608 * a close tag was found that does not have a corresponding open tag. 609 * 610 * @param token an HTML tag id for which a close was found. 611 * @param htmlStack a Stack of previous open HTML tags. 612 * @return {@code false} if a previous open tag was found 613 * for the token. 614 */ 615 private static boolean isExtraHtml(String token, Deque<HtmlTag> htmlStack) { 616 boolean isExtra = true; 617 for (final HtmlTag tag : htmlStack) { 618 // Loop, looking for tags that are closed. 619 // The loop is needed in case there are unclosed 620 // tags on the stack. In that case, the stack would 621 // not be empty, but this tag would still be extra. 622 if (token.equalsIgnoreCase(tag.getId())) { 623 isExtra = false; 624 break; 625 } 626 } 627 628 return isExtra; 629 } 630 631 /** 632 * Setter to specify the visibility scope where Javadoc comments are checked. 633 * 634 * @param scope a scope. 635 * @since 3.2 636 */ 637 public void setScope(Scope scope) { 638 this.scope = scope; 639 } 640 641 /** 642 * Setter to specify the visibility scope where Javadoc comments are not checked. 643 * 644 * @param excludeScope a scope. 645 * @since 3.4 646 */ 647 public void setExcludeScope(Scope excludeScope) { 648 this.excludeScope = excludeScope; 649 } 650 651 /** 652 * Setter to specify the format for matching the end of a sentence. 653 * 654 * @param pattern a pattern. 655 * @since 5.0 656 */ 657 public void setEndOfSentenceFormat(Pattern pattern) { 658 endOfSentenceFormat = pattern; 659 } 660 661 /** 662 * Setter to control whether to check the first sentence for proper end of sentence. 663 * 664 * @param flag {@code true} if the first sentence is to be checked 665 * @since 3.2 666 */ 667 public void setCheckFirstSentence(boolean flag) { 668 checkFirstSentence = flag; 669 } 670 671 /** 672 * Setter to control whether to check for incomplete HTML tags. 673 * 674 * @param flag {@code true} if HTML checking is to be performed. 675 * @since 3.2 676 */ 677 public void setCheckHtml(boolean flag) { 678 checkHtml = flag; 679 } 680 681 /** 682 * Setter to control whether to check if the Javadoc is missing a describing text. 683 * 684 * @param flag {@code true} if empty Javadoc checking should be done. 685 * @since 3.4 686 */ 687 public void setCheckEmptyJavadoc(boolean flag) { 688 checkEmptyJavadoc = flag; 689 } 690 691 }