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