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