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