001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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.ArrayDeque; 023import java.util.Deque; 024import java.util.List; 025import java.util.Locale; 026import java.util.Set; 027import java.util.regex.Pattern; 028 029import com.puppycrawl.tools.checkstyle.JavadocDetailNodeParser; 030import com.puppycrawl.tools.checkstyle.StatelessCheck; 031import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 032import com.puppycrawl.tools.checkstyle.api.DetailAST; 033import com.puppycrawl.tools.checkstyle.api.FileContents; 034import com.puppycrawl.tools.checkstyle.api.Scope; 035import com.puppycrawl.tools.checkstyle.api.TextBlock; 036import com.puppycrawl.tools.checkstyle.api.TokenTypes; 037import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 038import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 039import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 040 041/** 042 * <p> 043 * Validates Javadoc comments to help ensure they are well formed. 044 * </p> 045 * <p> 046 * The following checks are performed: 047 * </p> 048 * <ul> 049 * <li> 050 * Ensures the first sentence ends with proper punctuation 051 * (That is a period, question mark, or exclamation mark, by default). 052 * Javadoc automatically places the first sentence in the method summary 053 * table and index. Without proper punctuation the Javadoc may be malformed. 054 * All items eligible for the {@code {@inheritDoc}} tag are exempt from this 055 * requirement. 056 * </li> 057 * <li> 058 * Check text for Javadoc statements that do not have any description. 059 * This includes both completely empty Javadoc, and Javadoc with only tags 060 * such as {@code @param} and {@code @return}. 061 * </li> 062 * <li> 063 * Check text for incomplete HTML tags. Verifies that HTML tags have 064 * corresponding end tags and issues an "Unclosed HTML tag found:" error if not. 065 * An "Extra HTML tag found:" error is issued if an end tag is found without 066 * a previous open tag. 067 * </li> 068 * <li> 069 * Check that a package Javadoc comment is well-formed (as described above). 070 * </li> 071 * <li> 072 * Check for allowed HTML tags. The list of allowed HTML tags is 073 * "a", "abbr", "acronym", "address", "area", "b", "bdo", "big", "blockquote", 074 * "br", "caption", "cite", "code", "colgroup", "dd", "del", "dfn", "div", "dl", 075 * "dt", "em", "fieldset", "font", "h1", "h2", "h3", "h4", "h5", "h6", "hr", 076 * "i", "img", "ins", "kbd", "li", "ol", "p", "pre", "q", "samp", "small", 077 * "span", "strong", "sub", "sup", "table", "tbody", "td", "tfoot", "th", 078 * "thead", "tr", "tt", "u", "ul", "var". 079 * </li> 080 * </ul> 081 * <p> 082 * These checks were patterned after the checks made by the 083 * <a href="https://maven-doccheck.sourceforge.net">DocCheck</a> doclet 084 * available from Sun. Note: Original Sun's DocCheck tool does not exist anymore. 085 * </p> 086 * <ul> 087 * <li> 088 * Property {@code scope} - Specify the visibility scope where Javadoc comments are checked. 089 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}. 090 * Default value is {@code private}. 091 * </li> 092 * <li> 093 * Property {@code excludeScope} - Specify the visibility scope where 094 * Javadoc comments are not checked. 095 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}. 096 * Default value is {@code null}. 097 * </li> 098 * <li> 099 * 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 endOfSentenceFormat} - Specify the format for matching 106 * the end of a sentence. 107 * Type is {@code java.util.regex.Pattern}. 108 * Default value is {@code "([.?!][ \t\n\r\f<])|([.?!]$)"}. 109 * </li> 110 * <li> 111 * Property {@code checkEmptyJavadoc} - Control whether to check if the Javadoc 112 * is missing a describing text. 113 * Type is {@code boolean}. 114 * Default value is {@code false}. 115 * </li> 116 * <li> 117 * Property {@code checkHtml} - Control whether to check for incomplete HTML tags. 118 * Type is {@code boolean}. 119 * Default value is {@code true}. 120 * </li> 121 * <li> 122 * Property {@code tokens} - tokens to check 123 * Type is {@code java.lang.String[]}. 124 * Validation type is {@code tokenSet}. 125 * Default value is: 126 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF"> 127 * ANNOTATION_DEF</a>, 128 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF"> 129 * ANNOTATION_FIELD_DEF</a>, 130 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 131 * CLASS_DEF</a>, 132 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 133 * CTOR_DEF</a>, 134 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 135 * ENUM_CONSTANT_DEF</a>, 136 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 137 * ENUM_DEF</a>, 138 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 139 * INTERFACE_DEF</a>, 140 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 141 * METHOD_DEF</a>, 142 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF"> 143 * PACKAGE_DEF</a>, 144 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 145 * VARIABLE_DEF</a>, 146 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF"> 147 * RECORD_DEF</a>, 148 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF"> 149 * COMPACT_CTOR_DEF</a>. 150 * </li> 151 * </ul> 152 * <p> 153 * To configure the default check: 154 * </p> 155 * <pre> 156 * <module name="JavadocStyle"/> 157 * </pre> 158 * <p>Example:</p> 159 * <pre> 160 * public class Test { 161 * /** 162 * * Some description here. // OK 163 * */ 164 * private void methodWithValidCommentStyle() {} 165 * 166 * /** 167 * * Some description here // violation, the sentence must end with a proper punctuation 168 * */ 169 * private void methodWithInvalidCommentStyle() {} 170 * } 171 * </pre> 172 * <p> 173 * To configure the check for {@code public} scope: 174 * </p> 175 * <pre> 176 * <module name="JavadocStyle"> 177 * <property name="scope" value="public"/> 178 * </module> 179 * </pre> 180 * <p>Example:</p> 181 * <pre> 182 * public class Test { 183 * /** 184 * * Some description here // violation, the sentence must end with a proper punctuation 185 * */ 186 * public void test1() {} 187 * 188 * /** 189 * * Some description here // OK 190 * */ 191 * private void test2() {} 192 * } 193 * </pre> 194 * <p> 195 * To configure the check for javadoc which is in {@code private}, but not in {@code package} scope: 196 * </p> 197 * <pre> 198 * <module name="JavadocStyle"> 199 * <property name="scope" value="private"/> 200 * <property name="excludeScope" value="package"/> 201 * </module> 202 * </pre> 203 * <p>Example:</p> 204 * <pre> 205 * public class Test { 206 * /** 207 * * Some description here // violation, the sentence must end with a proper punctuation 208 * */ 209 * private void test1() {} 210 * 211 * /** 212 * * Some description here // OK 213 * */ 214 * void test2() {} 215 * } 216 * </pre> 217 * <p> 218 * To configure the check to turn off first sentence checking: 219 * </p> 220 * <pre> 221 * <module name="JavadocStyle"> 222 * <property name="checkFirstSentence" value="false"/> 223 * </module> 224 * </pre> 225 * <p>Example:</p> 226 * <pre> 227 * public class Test { 228 * /** 229 * * Some description here // OK 230 * * Second line of description // violation, the sentence must end with a proper punctuation 231 * */ 232 * private void test1() {} 233 * } 234 * </pre> 235 * <p> 236 * To configure the check to turn off validation of incomplete html tags: 237 * </p> 238 * <pre> 239 * <module name="JavadocStyle"> 240 * <property name="checkHtml" value="false"/> 241 * </module> 242 * </pre> 243 * <p>Example:</p> 244 * <pre> 245 * public class Test { 246 * /** 247 * * Some description here // violation, the sentence must end with a proper punctuation 248 * * <p // OK 249 * */ 250 * private void test1() {} 251 * } 252 * </pre> 253 * <p> 254 * To configure the check for only class definitions: 255 * </p> 256 * <pre> 257 * <module name="JavadocStyle"> 258 * <property name="tokens" value="CLASS_DEF"/> 259 * </module> 260 * </pre> 261 * <p>Example:</p> 262 * <pre> 263 * /** 264 * * Some description here // violation, the sentence must end with a proper punctuation 265 * */ 266 * public class Test { 267 * /** 268 * * Some description here // OK 269 * */ 270 * private void test1() {} 271 * } 272 * </pre> 273 * <p> 274 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 275 * </p> 276 * <p> 277 * Violation Message Keys: 278 * </p> 279 * <ul> 280 * <li> 281 * {@code javadoc.empty} 282 * </li> 283 * <li> 284 * {@code javadoc.extraHtml} 285 * </li> 286 * <li> 287 * {@code javadoc.incompleteTag} 288 * </li> 289 * <li> 290 * {@code javadoc.noPeriod} 291 * </li> 292 * <li> 293 * {@code javadoc.unclosedHtml} 294 * </li> 295 * </ul> 296 * 297 * @since 3.2 298 */ 299@StatelessCheck 300public class JavadocStyleCheck 301 extends AbstractCheck { 302 303 /** Message property key for the Empty Javadoc message. */ 304 public static final String MSG_EMPTY = "javadoc.empty"; 305 306 /** Message property key for the No Javadoc end of Sentence Period message. */ 307 public static final String MSG_NO_PERIOD = "javadoc.noPeriod"; 308 309 /** Message property key for the Incomplete Tag message. */ 310 public static final String MSG_INCOMPLETE_TAG = "javadoc.incompleteTag"; 311 312 /** Message property key for the Unclosed HTML message. */ 313 public static final String MSG_UNCLOSED_HTML = JavadocDetailNodeParser.MSG_UNCLOSED_HTML_TAG; 314 315 /** Message property key for the Extra HTML message. */ 316 public static final String MSG_EXTRA_HTML = "javadoc.extraHtml"; 317 318 /** HTML tags that do not require a close tag. */ 319 private static final Set<String> SINGLE_TAGS = Set.of( 320 "br", "li", "dt", "dd", "hr", "img", "p", "td", "tr", "th" 321 ); 322 323 /** 324 * HTML tags that are allowed in java docs. 325 * From <a href="https://www.w3schools.com/tags/default.asp">w3schools</a>: 326 * <br> 327 * The forms and structure tags are not allowed 328 */ 329 private static final Set<String> ALLOWED_TAGS = Set.of( 330 "a", "abbr", "acronym", "address", "area", "b", "bdo", "big", 331 "blockquote", "br", "caption", "cite", "code", "colgroup", "dd", 332 "del", "dfn", "div", "dl", "dt", "em", "fieldset", "font", "h1", 333 "h2", "h3", "h4", "h5", "h6", "hr", "i", "img", "ins", "kbd", 334 "li", "ol", "p", "pre", "q", "samp", "small", "span", "strong", 335 "sub", "sup", "table", "tbody", "td", "tfoot", "th", "thead", 336 "tr", "tt", "u", "ul", "var" 337 ); 338 339 /** Specify the visibility scope where Javadoc comments are checked. */ 340 private Scope scope = Scope.PRIVATE; 341 342 /** Specify the visibility scope where Javadoc comments are not checked. */ 343 private Scope excludeScope; 344 345 /** Specify the format for matching the end of a sentence. */ 346 private Pattern endOfSentenceFormat = Pattern.compile("([.?!][ \t\n\r\f<])|([.?!]$)"); 347 348 /** 349 * Control whether to check the first sentence for proper end of sentence. 350 */ 351 private boolean checkFirstSentence = true; 352 353 /** 354 * Control whether to check for incomplete HTML tags. 355 */ 356 private boolean checkHtml = true; 357 358 /** 359 * Control whether to check if the Javadoc is missing a describing text. 360 */ 361 private boolean checkEmptyJavadoc; 362 363 @Override 364 public int[] getDefaultTokens() { 365 return getAcceptableTokens(); 366 } 367 368 @Override 369 public int[] getAcceptableTokens() { 370 return new int[] { 371 TokenTypes.ANNOTATION_DEF, 372 TokenTypes.ANNOTATION_FIELD_DEF, 373 TokenTypes.CLASS_DEF, 374 TokenTypes.CTOR_DEF, 375 TokenTypes.ENUM_CONSTANT_DEF, 376 TokenTypes.ENUM_DEF, 377 TokenTypes.INTERFACE_DEF, 378 TokenTypes.METHOD_DEF, 379 TokenTypes.PACKAGE_DEF, 380 TokenTypes.VARIABLE_DEF, 381 TokenTypes.RECORD_DEF, 382 TokenTypes.COMPACT_CTOR_DEF, 383 }; 384 } 385 386 @Override 387 public int[] getRequiredTokens() { 388 return CommonUtil.EMPTY_INT_ARRAY; 389 } 390 391 // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166 392 @SuppressWarnings("deprecation") 393 @Override 394 public void visitToken(DetailAST ast) { 395 if (shouldCheck(ast)) { 396 final FileContents contents = getFileContents(); 397 // Need to start searching for the comment before the annotations 398 // that may exist. Even if annotations are not defined on the 399 // package, the ANNOTATIONS AST is defined. 400 final TextBlock textBlock = 401 contents.getJavadocBefore(ast.getFirstChild().getLineNo()); 402 403 checkComment(ast, textBlock); 404 } 405 } 406 407 /** 408 * Whether we should check this node. 409 * 410 * @param ast a given node. 411 * @return whether we should check a given node. 412 */ 413 private boolean shouldCheck(final DetailAST ast) { 414 boolean check = false; 415 416 if (ast.getType() == TokenTypes.PACKAGE_DEF) { 417 check = CheckUtil.isPackageInfo(getFilePath()); 418 } 419 else if (!ScopeUtil.isInCodeBlock(ast)) { 420 final Scope customScope = ScopeUtil.getScope(ast); 421 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast); 422 423 check = customScope.isIn(scope) 424 && (surroundingScope == null || surroundingScope.isIn(scope)) 425 && (excludeScope == null 426 || !customScope.isIn(excludeScope) 427 || surroundingScope != null 428 && !surroundingScope.isIn(excludeScope)); 429 } 430 return check; 431 } 432 433 /** 434 * Performs the various checks against the Javadoc comment. 435 * 436 * @param ast the AST of the element being documented 437 * @param comment the source lines that make up the Javadoc comment. 438 * 439 * @see #checkFirstSentenceEnding(DetailAST, TextBlock) 440 * @see #checkHtmlTags(DetailAST, TextBlock) 441 */ 442 private void checkComment(final DetailAST ast, final TextBlock comment) { 443 if (comment != null) { 444 if (checkFirstSentence) { 445 checkFirstSentenceEnding(ast, comment); 446 } 447 448 if (checkHtml) { 449 checkHtmlTags(ast, comment); 450 } 451 452 if (checkEmptyJavadoc) { 453 checkJavadocIsNotEmpty(comment); 454 } 455 } 456 } 457 458 /** 459 * Checks that the first sentence ends with proper punctuation. This method 460 * uses a regular expression that checks for the presence of a period, 461 * question mark, or exclamation mark followed either by whitespace, an 462 * HTML element, or the end of string. This method ignores {_AT_inheritDoc} 463 * comments for TokenTypes that are valid for {_AT_inheritDoc}. 464 * 465 * @param ast the current node 466 * @param comment the source lines that make up the Javadoc comment. 467 */ 468 private void checkFirstSentenceEnding(final DetailAST ast, TextBlock comment) { 469 final String commentText = getCommentText(comment.getText()); 470 471 if (!commentText.isEmpty() 472 && !endOfSentenceFormat.matcher(commentText).find() 473 && !(commentText.startsWith("{@inheritDoc}") 474 && JavadocTagInfo.INHERIT_DOC.isValidOn(ast))) { 475 log(comment.getStartLineNo(), MSG_NO_PERIOD); 476 } 477 } 478 479 /** 480 * Checks that the Javadoc is not empty. 481 * 482 * @param comment the source lines that make up the Javadoc comment. 483 */ 484 private void checkJavadocIsNotEmpty(TextBlock comment) { 485 final String commentText = getCommentText(comment.getText()); 486 487 if (commentText.isEmpty()) { 488 log(comment.getStartLineNo(), MSG_EMPTY); 489 } 490 } 491 492 /** 493 * Returns the comment text from the Javadoc. 494 * 495 * @param comments the lines of Javadoc. 496 * @return a comment text String. 497 */ 498 private static String getCommentText(String... comments) { 499 final StringBuilder builder = new StringBuilder(1024); 500 for (final String line : comments) { 501 final int textStart = findTextStart(line); 502 503 if (textStart != -1) { 504 if (line.charAt(textStart) == '@') { 505 // we have found the tag section 506 break; 507 } 508 builder.append(line.substring(textStart)); 509 trimTail(builder); 510 builder.append('\n'); 511 } 512 } 513 514 return builder.toString().trim(); 515 } 516 517 /** 518 * Finds the index of the first non-whitespace character ignoring the 519 * Javadoc comment start and end strings (/** and */) as well as any 520 * leading asterisk. 521 * 522 * @param line the Javadoc comment line of text to scan. 523 * @return the int index relative to 0 for the start of text 524 * or -1 if not found. 525 */ 526 private static int findTextStart(String line) { 527 int textStart = -1; 528 int index = 0; 529 while (index < line.length()) { 530 if (!Character.isWhitespace(line.charAt(index))) { 531 if (line.regionMatches(index, "/**", 0, "/**".length())) { 532 index += 2; 533 } 534 else if (line.regionMatches(index, "*/", 0, 2)) { 535 index++; 536 } 537 else if (line.charAt(index) != '*') { 538 textStart = index; 539 break; 540 } 541 } 542 index++; 543 } 544 return textStart; 545 } 546 547 /** 548 * Trims any trailing whitespace or the end of Javadoc comment string. 549 * 550 * @param builder the StringBuilder to trim. 551 */ 552 private static void trimTail(StringBuilder builder) { 553 int index = builder.length() - 1; 554 while (true) { 555 if (Character.isWhitespace(builder.charAt(index))) { 556 builder.deleteCharAt(index); 557 } 558 else if (index > 0 && builder.charAt(index) == '/' 559 && builder.charAt(index - 1) == '*') { 560 builder.deleteCharAt(index); 561 builder.deleteCharAt(index - 1); 562 index--; 563 while (builder.charAt(index - 1) == '*') { 564 builder.deleteCharAt(index - 1); 565 index--; 566 } 567 } 568 else { 569 break; 570 } 571 index--; 572 } 573 } 574 575 /** 576 * Checks the comment for HTML tags that do not have a corresponding close 577 * tag or a close tag that has no previous open tag. This code was 578 * primarily copied from the DocCheck checkHtml method. 579 * 580 * @param ast the node with the Javadoc 581 * @param comment the {@code TextBlock} which represents 582 * the Javadoc comment. 583 * @noinspection MethodWithMultipleReturnPoints 584 * @noinspectionreason MethodWithMultipleReturnPoints - check and method are 585 * too complex to break apart 586 */ 587 // -@cs[ReturnCount] Too complex to break apart. 588 private void checkHtmlTags(final DetailAST ast, final TextBlock comment) { 589 final int lineNo = comment.getStartLineNo(); 590 final Deque<HtmlTag> htmlStack = new ArrayDeque<>(); 591 final String[] text = comment.getText(); 592 593 final TagParser parser = new TagParser(text, lineNo); 594 595 while (parser.hasNextTag()) { 596 final HtmlTag tag = parser.nextTag(); 597 598 if (tag.isIncompleteTag()) { 599 log(tag.getLineNo(), MSG_INCOMPLETE_TAG, 600 text[tag.getLineNo() - lineNo]); 601 return; 602 } 603 if (tag.isClosedTag()) { 604 // do nothing 605 continue; 606 } 607 if (tag.isCloseTag()) { 608 // We have found a close tag. 609 if (isExtraHtml(tag.getId(), htmlStack)) { 610 // No corresponding open tag was found on the stack. 611 log(tag.getLineNo(), 612 tag.getPosition(), 613 MSG_EXTRA_HTML, 614 tag.getText()); 615 } 616 else { 617 // See if there are any unclosed tags that were opened 618 // after this one. 619 checkUnclosedTags(htmlStack, tag.getId()); 620 } 621 } 622 else { 623 // We only push html tags that are allowed 624 if (isAllowedTag(tag)) { 625 htmlStack.push(tag); 626 } 627 } 628 } 629 630 // Identify any tags left on the stack. 631 // Skip multiples, like <b>...<b> 632 String lastFound = ""; 633 final List<String> typeParameters = CheckUtil.getTypeParameterNames(ast); 634 for (final HtmlTag htmlTag : htmlStack) { 635 if (!isSingleTag(htmlTag) 636 && !htmlTag.getId().equals(lastFound) 637 && !typeParameters.contains(htmlTag.getId())) { 638 log(htmlTag.getLineNo(), htmlTag.getPosition(), 639 MSG_UNCLOSED_HTML, htmlTag.getText()); 640 lastFound = htmlTag.getId(); 641 } 642 } 643 } 644 645 /** 646 * Checks to see if there are any unclosed tags on the stack. The token 647 * represents a html tag that has been closed and has a corresponding open 648 * tag on the stack. Any tags, except single tags, that were opened 649 * (pushed on the stack) after the token are missing a close. 650 * 651 * @param htmlStack the stack of opened HTML tags. 652 * @param token the current HTML tag name that has been closed. 653 */ 654 private void checkUnclosedTags(Deque<HtmlTag> htmlStack, String token) { 655 final Deque<HtmlTag> unclosedTags = new ArrayDeque<>(); 656 HtmlTag lastOpenTag = htmlStack.pop(); 657 while (!token.equalsIgnoreCase(lastOpenTag.getId())) { 658 // Find unclosed elements. Put them on a stack so the 659 // output order won't be back-to-front. 660 if (isSingleTag(lastOpenTag)) { 661 lastOpenTag = htmlStack.pop(); 662 } 663 else { 664 unclosedTags.push(lastOpenTag); 665 lastOpenTag = htmlStack.pop(); 666 } 667 } 668 669 // Output the unterminated tags, if any 670 // Skip multiples, like <b>..<b> 671 String lastFound = ""; 672 for (final HtmlTag htag : unclosedTags) { 673 lastOpenTag = htag; 674 if (lastOpenTag.getId().equals(lastFound)) { 675 continue; 676 } 677 lastFound = lastOpenTag.getId(); 678 log(lastOpenTag.getLineNo(), 679 lastOpenTag.getPosition(), 680 MSG_UNCLOSED_HTML, 681 lastOpenTag.getText()); 682 } 683 } 684 685 /** 686 * Determines if the HtmlTag is one which does not require a close tag. 687 * 688 * @param tag the HtmlTag to check. 689 * @return {@code true} if the HtmlTag is a single tag. 690 */ 691 private static boolean isSingleTag(HtmlTag tag) { 692 // If it's a singleton tag (<p>, <br>, etc.), ignore it 693 // Can't simply not put them on the stack, since singletons 694 // like <dt> and <dd> (unhappily) may either be terminated 695 // or not terminated. Both options are legal. 696 return SINGLE_TAGS.contains(tag.getId().toLowerCase(Locale.ENGLISH)); 697 } 698 699 /** 700 * Determines if the HtmlTag is one which is allowed in a javadoc. 701 * 702 * @param tag the HtmlTag to check. 703 * @return {@code true} if the HtmlTag is an allowed html tag. 704 */ 705 private static boolean isAllowedTag(HtmlTag tag) { 706 return ALLOWED_TAGS.contains(tag.getId().toLowerCase(Locale.ENGLISH)); 707 } 708 709 /** 710 * Determines if the given token is an extra HTML tag. This indicates that 711 * a close tag was found that does not have a corresponding open tag. 712 * 713 * @param token an HTML tag id for which a close was found. 714 * @param htmlStack a Stack of previous open HTML tags. 715 * @return {@code false} if a previous open tag was found 716 * for the token. 717 */ 718 private static boolean isExtraHtml(String token, Deque<HtmlTag> htmlStack) { 719 boolean isExtra = true; 720 for (final HtmlTag tag : htmlStack) { 721 // Loop, looking for tags that are closed. 722 // The loop is needed in case there are unclosed 723 // tags on the stack. In that case, the stack would 724 // not be empty, but this tag would still be extra. 725 if (token.equalsIgnoreCase(tag.getId())) { 726 isExtra = false; 727 break; 728 } 729 } 730 731 return isExtra; 732 } 733 734 /** 735 * Setter to specify the visibility scope where Javadoc comments are checked. 736 * 737 * @param scope a scope. 738 */ 739 public void setScope(Scope scope) { 740 this.scope = scope; 741 } 742 743 /** 744 * Setter to specify the visibility scope where Javadoc comments are not checked. 745 * 746 * @param excludeScope a scope. 747 */ 748 public void setExcludeScope(Scope excludeScope) { 749 this.excludeScope = excludeScope; 750 } 751 752 /** 753 * Setter to specify the format for matching the end of a sentence. 754 * 755 * @param pattern a pattern. 756 */ 757 public void setEndOfSentenceFormat(Pattern pattern) { 758 endOfSentenceFormat = pattern; 759 } 760 761 /** 762 * Setter to control whether to check the first sentence for proper end of sentence. 763 * 764 * @param flag {@code true} if the first sentence is to be checked 765 */ 766 public void setCheckFirstSentence(boolean flag) { 767 checkFirstSentence = flag; 768 } 769 770 /** 771 * Setter to control whether to check for incomplete HTML tags. 772 * 773 * @param flag {@code true} if HTML checking is to be performed. 774 */ 775 public void setCheckHtml(boolean flag) { 776 checkHtml = flag; 777 } 778 779 /** 780 * Setter to control whether to check if the Javadoc is missing a describing text. 781 * 782 * @param flag {@code true} if empty Javadoc checking should be done. 783 */ 784 public void setCheckEmptyJavadoc(boolean flag) { 785 checkEmptyJavadoc = flag; 786 } 787 788}