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