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 * 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 int[]}. 129 * Default value is: 130 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF"> 131 * ANNOTATION_DEF</a>, 132 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF"> 133 * ANNOTATION_FIELD_DEF</a>, 134 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF"> 135 * CLASS_DEF</a>, 136 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF"> 137 * CTOR_DEF</a>, 138 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF"> 139 * ENUM_CONSTANT_DEF</a>, 140 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF"> 141 * ENUM_DEF</a>, 142 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF"> 143 * INTERFACE_DEF</a>, 144 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF"> 145 * METHOD_DEF</a>, 146 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#PACKAGE_DEF"> 147 * PACKAGE_DEF</a>, 148 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#VARIABLE_DEF"> 149 * VARIABLE_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.missing} 291 * </li> 292 * <li> 293 * {@code javadoc.noPeriod} 294 * </li> 295 * <li> 296 * {@code javadoc.unclosedHtml} 297 * </li> 298 * </ul> 299 * 300 * @since 3.2 301 */ 302@StatelessCheck 303public class JavadocStyleCheck 304 extends AbstractCheck { 305 306 /** Message property key for the Missing Javadoc message. */ 307 public static final String MSG_JAVADOC_MISSING = "javadoc.missing"; 308 309 /** Message property key for the Empty Javadoc message. */ 310 public static final String MSG_EMPTY = "javadoc.empty"; 311 312 /** Message property key for the No Javadoc end of Sentence Period message. */ 313 public static final String MSG_NO_PERIOD = "javadoc.noPeriod"; 314 315 /** Message property key for the Incomplete Tag message. */ 316 public static final String MSG_INCOMPLETE_TAG = "javadoc.incompleteTag"; 317 318 /** Message property key for the Unclosed HTML message. */ 319 public static final String MSG_UNCLOSED_HTML = JavadocDetailNodeParser.MSG_UNCLOSED_HTML_TAG; 320 321 /** Message property key for the Extra HTML message. */ 322 public static final String MSG_EXTRA_HTML = "javadoc.extraHtml"; 323 324 /** HTML tags that do not require a close tag. */ 325 private static final Set<String> SINGLE_TAGS = Collections.unmodifiableSortedSet( 326 Arrays.stream(new String[] {"br", "li", "dt", "dd", "hr", "img", "p", "td", "tr", "th", }) 327 .collect(Collectors.toCollection(TreeSet::new))); 328 329 /** 330 * HTML tags that are allowed in java docs. 331 * From https://www.w3schools.com/tags/default.asp 332 * The forms and structure tags are not allowed 333 */ 334 private static final Set<String> ALLOWED_TAGS = Collections.unmodifiableSortedSet( 335 Arrays.stream(new String[] { 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 .collect(Collectors.toCollection(TreeSet::new))); 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 }; 388 } 389 390 @Override 391 public int[] getRequiredTokens() { 392 return CommonUtil.EMPTY_INT_ARRAY; 393 } 394 395 @Override 396 public void visitToken(DetailAST ast) { 397 if (shouldCheck(ast)) { 398 final FileContents contents = getFileContents(); 399 // Need to start searching for the comment before the annotations 400 // that may exist. Even if annotations are not defined on the 401 // package, the ANNOTATIONS AST is defined. 402 final TextBlock textBlock = 403 contents.getJavadocBefore(ast.getFirstChild().getLineNo()); 404 405 checkComment(ast, textBlock); 406 } 407 } 408 409 /** 410 * Whether we should check this node. 411 * 412 * @param ast a given node. 413 * @return whether we should check a given node. 414 */ 415 private boolean shouldCheck(final DetailAST ast) { 416 boolean check = false; 417 418 if (ast.getType() == TokenTypes.PACKAGE_DEF) { 419 check = getFileContents().inPackageInfo(); 420 } 421 else if (!ScopeUtil.isInCodeBlock(ast)) { 422 final Scope customScope; 423 424 if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast) 425 || ast.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 426 customScope = Scope.PUBLIC; 427 } 428 else { 429 customScope = ScopeUtil.getScopeFromMods(ast.findFirstToken(TokenTypes.MODIFIERS)); 430 } 431 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast); 432 433 check = customScope.isIn(scope) 434 && (surroundingScope == null || surroundingScope.isIn(scope)) 435 && (excludeScope == null 436 || !customScope.isIn(excludeScope) 437 || surroundingScope != null 438 && !surroundingScope.isIn(excludeScope)); 439 } 440 return check; 441 } 442 443 /** 444 * Performs the various checks against the Javadoc comment. 445 * 446 * @param ast the AST of the element being documented 447 * @param comment the source lines that make up the Javadoc comment. 448 * 449 * @see #checkFirstSentenceEnding(DetailAST, TextBlock) 450 * @see #checkHtmlTags(DetailAST, TextBlock) 451 */ 452 private void checkComment(final DetailAST ast, final TextBlock comment) { 453 if (comment == null) { 454 // checking for missing docs in JavadocStyleCheck is not consistent 455 // with the rest of CheckStyle... Even though, I didn't think it 456 // made sense to make another check just to ensure that the 457 // package-info.java file actually contains package Javadocs. 458 if (getFileContents().inPackageInfo()) { 459 log(ast, MSG_JAVADOC_MISSING); 460 } 461 } 462 else { 463 if (checkFirstSentence) { 464 checkFirstSentenceEnding(ast, comment); 465 } 466 467 if (checkHtml) { 468 checkHtmlTags(ast, comment); 469 } 470 471 if (checkEmptyJavadoc) { 472 checkJavadocIsNotEmpty(comment); 473 } 474 } 475 } 476 477 /** 478 * Checks that the first sentence ends with proper punctuation. This method 479 * uses a regular expression that checks for the presence of a period, 480 * question mark, or exclamation mark followed either by whitespace, an 481 * HTML element, or the end of string. This method ignores {_AT_inheritDoc} 482 * comments for TokenTypes that are valid for {_AT_inheritDoc}. 483 * 484 * @param ast the current node 485 * @param comment the source lines that make up the Javadoc comment. 486 */ 487 private void checkFirstSentenceEnding(final DetailAST ast, TextBlock comment) { 488 final String commentText = getCommentText(comment.getText()); 489 490 if (!commentText.isEmpty() 491 && !endOfSentenceFormat.matcher(commentText).find() 492 && !(commentText.startsWith("{@inheritDoc}") 493 && JavadocTagInfo.INHERIT_DOC.isValidOn(ast))) { 494 log(comment.getStartLineNo(), MSG_NO_PERIOD); 495 } 496 } 497 498 /** 499 * Checks that the Javadoc is not empty. 500 * 501 * @param comment the source lines that make up the Javadoc comment. 502 */ 503 private void checkJavadocIsNotEmpty(TextBlock comment) { 504 final String commentText = getCommentText(comment.getText()); 505 506 if (commentText.isEmpty()) { 507 log(comment.getStartLineNo(), MSG_EMPTY); 508 } 509 } 510 511 /** 512 * Returns the comment text from the Javadoc. 513 * 514 * @param comments the lines of Javadoc. 515 * @return a comment text String. 516 */ 517 private static String getCommentText(String... comments) { 518 final StringBuilder builder = new StringBuilder(1024); 519 for (final String line : comments) { 520 final int textStart = findTextStart(line); 521 522 if (textStart != -1) { 523 if (line.charAt(textStart) == '@') { 524 // we have found the tag section 525 break; 526 } 527 builder.append(line.substring(textStart)); 528 trimTail(builder); 529 builder.append('\n'); 530 } 531 } 532 533 return builder.toString().trim(); 534 } 535 536 /** 537 * Finds the index of the first non-whitespace character ignoring the 538 * Javadoc comment start and end strings (/** and */) as well as any 539 * leading asterisk. 540 * 541 * @param line the Javadoc comment line of text to scan. 542 * @return the int index relative to 0 for the start of text 543 * or -1 if not found. 544 */ 545 private static int findTextStart(String line) { 546 int textStart = -1; 547 int index = 0; 548 while (index < line.length()) { 549 if (!Character.isWhitespace(line.charAt(index))) { 550 if (line.regionMatches(index, "/**", 0, "/**".length())) { 551 index += 2; 552 } 553 else if (line.regionMatches(index, "*/", 0, 2)) { 554 index++; 555 } 556 else if (line.charAt(index) != '*') { 557 textStart = index; 558 break; 559 } 560 } 561 index++; 562 } 563 return textStart; 564 } 565 566 /** 567 * Trims any trailing whitespace or the end of Javadoc comment string. 568 * 569 * @param builder the StringBuilder to trim. 570 */ 571 private static void trimTail(StringBuilder builder) { 572 int index = builder.length() - 1; 573 while (true) { 574 if (Character.isWhitespace(builder.charAt(index))) { 575 builder.deleteCharAt(index); 576 } 577 else if (index > 0 && builder.charAt(index) == '/' 578 && builder.charAt(index - 1) == '*') { 579 builder.deleteCharAt(index); 580 builder.deleteCharAt(index - 1); 581 index--; 582 while (builder.charAt(index - 1) == '*') { 583 builder.deleteCharAt(index - 1); 584 index--; 585 } 586 } 587 else { 588 break; 589 } 590 index--; 591 } 592 } 593 594 /** 595 * Checks the comment for HTML tags that do not have a corresponding close 596 * tag or a close tag that has no previous open tag. This code was 597 * primarily copied from the DocCheck checkHtml method. 598 * 599 * @param ast the node with the Javadoc 600 * @param comment the {@code TextBlock} which represents 601 * the Javadoc comment. 602 * @noinspection MethodWithMultipleReturnPoints 603 */ 604 // -@cs[ReturnCount] Too complex to break apart. 605 private void checkHtmlTags(final DetailAST ast, final TextBlock comment) { 606 final int lineNo = comment.getStartLineNo(); 607 final Deque<HtmlTag> htmlStack = new ArrayDeque<>(); 608 final String[] text = comment.getText(); 609 610 final TagParser parser = new TagParser(text, lineNo); 611 612 while (parser.hasNextTag()) { 613 final HtmlTag tag = parser.nextTag(); 614 615 if (tag.isIncompleteTag()) { 616 log(tag.getLineNo(), MSG_INCOMPLETE_TAG, 617 text[tag.getLineNo() - lineNo]); 618 return; 619 } 620 if (tag.isClosedTag()) { 621 // do nothing 622 continue; 623 } 624 if (tag.isCloseTag()) { 625 // We have found a close tag. 626 if (isExtraHtml(tag.getId(), htmlStack)) { 627 // No corresponding open tag was found on the stack. 628 log(tag.getLineNo(), 629 tag.getPosition(), 630 MSG_EXTRA_HTML, 631 tag.getText()); 632 } 633 else { 634 // See if there are any unclosed tags that were opened 635 // after this one. 636 checkUnclosedTags(htmlStack, tag.getId()); 637 } 638 } 639 else { 640 // We only push html tags that are allowed 641 if (isAllowedTag(tag)) { 642 htmlStack.push(tag); 643 } 644 } 645 } 646 647 // Identify any tags left on the stack. 648 // Skip multiples, like <b>...<b> 649 String lastFound = ""; 650 final List<String> typeParameters = CheckUtil.getTypeParameterNames(ast); 651 for (final HtmlTag htmlTag : htmlStack) { 652 if (!isSingleTag(htmlTag) 653 && !htmlTag.getId().equals(lastFound) 654 && !typeParameters.contains(htmlTag.getId())) { 655 log(htmlTag.getLineNo(), htmlTag.getPosition(), 656 MSG_UNCLOSED_HTML, htmlTag.getText()); 657 lastFound = htmlTag.getId(); 658 } 659 } 660 } 661 662 /** 663 * Checks to see if there are any unclosed tags on the stack. The token 664 * represents a html tag that has been closed and has a corresponding open 665 * tag on the stack. Any tags, except single tags, that were opened 666 * (pushed on the stack) after the token are missing a close. 667 * 668 * @param htmlStack the stack of opened HTML tags. 669 * @param token the current HTML tag name that has been closed. 670 */ 671 private void checkUnclosedTags(Deque<HtmlTag> htmlStack, String token) { 672 final Deque<HtmlTag> unclosedTags = new ArrayDeque<>(); 673 HtmlTag lastOpenTag = htmlStack.pop(); 674 while (!token.equalsIgnoreCase(lastOpenTag.getId())) { 675 // Find unclosed elements. Put them on a stack so the 676 // output order won't be back-to-front. 677 if (isSingleTag(lastOpenTag)) { 678 lastOpenTag = htmlStack.pop(); 679 } 680 else { 681 unclosedTags.push(lastOpenTag); 682 lastOpenTag = htmlStack.pop(); 683 } 684 } 685 686 // Output the unterminated tags, if any 687 // Skip multiples, like <b>..<b> 688 String lastFound = ""; 689 for (final HtmlTag htag : unclosedTags) { 690 lastOpenTag = htag; 691 if (lastOpenTag.getId().equals(lastFound)) { 692 continue; 693 } 694 lastFound = lastOpenTag.getId(); 695 log(lastOpenTag.getLineNo(), 696 lastOpenTag.getPosition(), 697 MSG_UNCLOSED_HTML, 698 lastOpenTag.getText()); 699 } 700 } 701 702 /** 703 * Determines if the HtmlTag is one which does not require a close tag. 704 * 705 * @param tag the HtmlTag to check. 706 * @return {@code true} if the HtmlTag is a single tag. 707 */ 708 private static boolean isSingleTag(HtmlTag tag) { 709 // If its a singleton tag (<p>, <br>, etc.), ignore it 710 // Can't simply not put them on the stack, since singletons 711 // like <dt> and <dd> (unhappily) may either be terminated 712 // or not terminated. Both options are legal. 713 return SINGLE_TAGS.contains(tag.getId().toLowerCase(Locale.ENGLISH)); 714 } 715 716 /** 717 * Determines if the HtmlTag is one which is allowed in a javadoc. 718 * 719 * @param tag the HtmlTag to check. 720 * @return {@code true} if the HtmlTag is an allowed html tag. 721 */ 722 private static boolean isAllowedTag(HtmlTag tag) { 723 return ALLOWED_TAGS.contains(tag.getId().toLowerCase(Locale.ENGLISH)); 724 } 725 726 /** 727 * Determines if the given token is an extra HTML tag. This indicates that 728 * a close tag was found that does not have a corresponding open tag. 729 * 730 * @param token an HTML tag id for which a close was found. 731 * @param htmlStack a Stack of previous open HTML tags. 732 * @return {@code false} if a previous open tag was found 733 * for the token. 734 */ 735 private static boolean isExtraHtml(String token, Deque<HtmlTag> htmlStack) { 736 boolean isExtra = true; 737 for (final HtmlTag tag : htmlStack) { 738 // Loop, looking for tags that are closed. 739 // The loop is needed in case there are unclosed 740 // tags on the stack. In that case, the stack would 741 // not be empty, but this tag would still be extra. 742 if (token.equalsIgnoreCase(tag.getId())) { 743 isExtra = false; 744 break; 745 } 746 } 747 748 return isExtra; 749 } 750 751 /** 752 * Setter to specify the visibility scope where Javadoc comments are checked. 753 * 754 * @param scope a scope. 755 */ 756 public void setScope(Scope scope) { 757 this.scope = scope; 758 } 759 760 /** 761 * Setter to specify the visibility scope where Javadoc comments are not checked. 762 * 763 * @param excludeScope a scope. 764 */ 765 public void setExcludeScope(Scope excludeScope) { 766 this.excludeScope = excludeScope; 767 } 768 769 /** 770 * Setter to specify the format for matching the end of a sentence. 771 * 772 * @param pattern a pattern. 773 */ 774 public void setEndOfSentenceFormat(Pattern pattern) { 775 endOfSentenceFormat = pattern; 776 } 777 778 /** 779 * Setter to control whether to check the first sentence for proper end of sentence. 780 * 781 * @param flag {@code true} if the first sentence is to be checked 782 */ 783 public void setCheckFirstSentence(boolean flag) { 784 checkFirstSentence = flag; 785 } 786 787 /** 788 * Setter to control whether to check for incomplete HTML tags. 789 * 790 * @param flag {@code true} if HTML checking is to be performed. 791 */ 792 public void setCheckHtml(boolean flag) { 793 checkHtml = flag; 794 } 795 796 /** 797 * Setter to control whether to check if the Javadoc is missing a describing text. 798 * 799 * @param flag {@code true} if empty Javadoc checking should be done. 800 */ 801 public void setCheckEmptyJavadoc(boolean flag) { 802 checkEmptyJavadoc = flag; 803 } 804 805}