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