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