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