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