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.ArrayList;
023import java.util.Collection;
024import java.util.List;
025import java.util.Set;
026import java.util.regex.Matcher;
027import java.util.regex.Pattern;
028
029import com.puppycrawl.tools.checkstyle.StatelessCheck;
030import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
031import com.puppycrawl.tools.checkstyle.api.DetailAST;
032import com.puppycrawl.tools.checkstyle.api.FileContents;
033import com.puppycrawl.tools.checkstyle.api.Scope;
034import com.puppycrawl.tools.checkstyle.api.TextBlock;
035import com.puppycrawl.tools.checkstyle.api.TokenTypes;
036import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
037import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
038import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
039import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
040import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
041import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
042
043/**
044 * <p>
045 * Checks the Javadoc comments for type definitions. By default, does
046 * not check for author or version tags. The scope to verify is specified using the {@code Scope}
047 * class and defaults to {@code Scope.PRIVATE}. To verify another scope, set property
048 * scope to one of the {@code Scope} constants. To define the format for an author
049 * tag or a version tag, set property authorFormat or versionFormat respectively to a
050 * <a href="https://docs.oracle.com/javase/7/docs/api/java/util/regex/Pattern.html">
051 * pattern</a>.
052 * </p>
053 * <p>
054 * Does not perform checks for author and version tags for inner classes,
055 * as they should be redundant because of outer class.
056 * </p>
057 * <p>
058 * Does not perform checks for type definitions that do not have any Javadoc comments.
059 * </p>
060 * <p>
061 * Error messages about type parameters and record components for which no param tags are present
062 * can be suppressed by defining property {@code allowMissingParamTags}.
063 * </p>
064 * <ul>
065 * <li>
066 * Property {@code scope} - Specify the visibility scope where Javadoc comments are checked.
067 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}.
068 * Default value is {@code private}.
069 * </li>
070 * <li>
071 * Property {@code excludeScope} - Specify the visibility scope where Javadoc
072 * comments are not checked.
073 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}.
074 * Default value is {@code null}.
075 * </li>
076 * <li>
077 * Property {@code authorFormat} - Specify the pattern for {@code @author} tag.
078 * Type is {@code java.util.regex.Pattern}.
079 * Default value is {@code null}.
080 * </li>
081 * <li>
082 * Property {@code versionFormat} - Specify the pattern for {@code @version} tag.
083 * Type is {@code java.util.regex.Pattern}.
084 * Default value is {@code null}.
085 * </li>
086 * <li>
087 * Property {@code allowMissingParamTags} - Control whether to ignore violations
088 * when a class has type parameters but does not have matching param tags in the Javadoc.
089 * Type is {@code boolean}.
090 * Default value is {@code false}.
091 * </li>
092 * <li>
093 * Property {@code allowUnknownTags} - Control whether to ignore violations when
094 * a Javadoc tag is not recognised.
095 * Type is {@code boolean}.
096 * Default value is {@code false}.
097 * </li>
098 * <li>
099 * Property {@code allowedAnnotations} - Specify annotations that allow
100 * skipping validation at all. Only short names are allowed, e.g. {@code Generated}.
101 * Type is {@code java.lang.String[]}.
102 * Default value is {@code Generated}.
103 * </li>
104 * <li>
105 * Property {@code tokens} - tokens to check
106 * Type is {@code java.lang.String[]}.
107 * Validation type is {@code tokenSet}.
108 * Default value is:
109 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#INTERFACE_DEF">
110 * INTERFACE_DEF</a>,
111 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CLASS_DEF">
112 * CLASS_DEF</a>,
113 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_DEF">
114 * ENUM_DEF</a>,
115 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_DEF">
116 * ANNOTATION_DEF</a>,
117 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#RECORD_DEF">
118 * RECORD_DEF</a>.
119 * </li>
120 * </ul>
121 * <p>
122 * To configure the default check:
123 * </p>
124 * <pre>
125 * &lt;module name="JavadocType"/&gt;
126 * </pre>
127 * <p>
128 * Example:
129 * </p>
130 * <pre>
131 * &#47;**
132 *  * &#64;author a
133 *  * &#64;version $Revision1$
134 *  *&#47;
135 * public class ClassA { // OK
136 *     &#47;** *&#47;
137 *     private class ClassB {} // OK
138 * }
139 *
140 * &#47;**
141 *  * &#64;author
142 *  * &#64;version abc
143 *  * &#64;unknownTag value // violation
144 *  *&#47;
145 * public class ClassC {} // OK
146 *
147 * &#47;** *&#47;
148 * public class ClassD&lt;T&gt; {} // violation, as param tag for &lt;T&gt; is missing
149 *
150 * &#47;** *&#47;
151 * private class ClassE&lt;T&gt; {} // violation, as param tag for &lt;T&gt; is missing
152 *
153 * &#47;** *&#47;
154 * &#64;Generated
155 * public class ClassF&lt;T&gt; {} // OK
156 * </pre>
157 * <p>
158 * To configure the check for {@code public} scope:
159 * </p>
160 * <pre>
161 * &lt;module name="JavadocType"&gt;
162 *   &lt;property name="scope" value="public"/&gt;
163 * &lt;/module&gt;
164 * </pre>
165 * <p>
166 * Example:
167 * </p>
168 * <pre>
169 * &#47;**
170 *  * &#64;author a
171 *  * &#64;version $Revision1$
172 *  *&#47;
173 * public class ClassA { // OK
174 *     &#47;** *&#47;
175 *     private class ClassB {} // OK
176 * }
177 *
178 * &#47;**
179 *  * &#64;author
180 *  * &#64;version abc
181 *  * &#64;unknownTag value // violation
182 *  *&#47;
183 * public class ClassC {} // OK
184 *
185 * &#47;** *&#47;
186 * public class ClassD&lt;T&gt; {} // violation, as param tag for &lt;T&gt; is missing
187 *
188 * &#47;** *&#47;
189 * private class ClassE&lt;T&gt; {} // OK
190 *
191 * &#47;** *&#47;
192 * &#64;Generated
193 * public class ClassF&lt;T&gt; {} // OK
194 * </pre>
195 * <p>
196 * To configure the check for an {@code @author} tag:
197 * </p>
198 * <pre>
199 * &lt;module name="JavadocType"&gt;
200 *   &lt;property name="authorFormat" value="\S"/&gt;
201 * &lt;/module&gt;
202 * </pre>
203 * <p>
204 * Example:
205 * </p>
206 * <pre>
207 * &#47;**
208 *  * &#64;author a
209 *  * &#64;version $Revision1$
210 *  *&#47;
211 * public class ClassA { // OK
212 *     &#47;** *&#47;
213 *     private class ClassB {} // OK, as author tag check is ignored for inner class
214 * }
215 *
216 * &#47;**
217 *  * &#64;author
218 *  * &#64;version abc
219 *  * &#64;unknownTag value // violation
220 *  *&#47;
221 * public class ClassC {} // violation, as author format with only whitespace or new line is invalid
222 *
223 * &#47;** *&#47;
224 * public class ClassD {} // violation, as author tag is missing
225 *
226 * &#47;** *&#47;
227 * private class ClassE {} // violation, as author tag is missing
228 *
229 * &#47;** *&#47;
230 * &#64;Generated
231 * public class ClassF&lt;T&gt; {} // OK
232 * </pre>
233 * <p>
234 * To configure the check for a CVS revision version tag:
235 * </p>
236 * <pre>
237 * &lt;module name="JavadocType"&gt;
238 *   &lt;property name="versionFormat" value="\$Revision.*\$"/&gt;
239 * &lt;/module&gt;
240 * </pre>
241 * <p>
242 * Example:
243 * </p>
244 * <pre>
245 * &#47;**
246 *  * &#64;author a
247 *  * &#64;version $Revision1$
248 *  *&#47;
249 * public class ClassA { // OK
250 *     &#47;** *&#47;
251 *     private class ClassB {} // OK, as version tag check is ignored for inner class
252 * }
253 *
254 * &#47;**
255 *  * &#64;author
256 *  * &#64;version abc
257 *  * &#64;unknownTag value // violation
258 *  *&#47;
259 * public class ClassC {} // violation, as version format is invalid
260 *
261 * &#47;** *&#47;
262 * public class ClassD {} // violation, as version tag is missing
263 *
264 * &#47;** *&#47;
265 * private class ClassE {} // violation, as version tag is missing
266 *
267 * &#47;** *&#47;
268 * &#64;Generated
269 * public class ClassF&lt;T&gt; {} // OK
270 * </pre>
271 * <p>
272 * To configure the check for {@code private} classes only:
273 * </p>
274 * <pre>
275 * &lt;module name="JavadocType"&gt;
276 *   &lt;property name="scope" value="private"/&gt;
277 *   &lt;property name="excludeScope" value="package"/&gt;
278 * &lt;/module&gt;
279 * </pre>
280 * <p>
281 * Example:
282 * </p>
283 * <pre>
284 * &#47;**
285 *  * &#64;author a
286 *  * &#64;version $Revision1$
287 *  *&#47;
288 * public class ClassA { // OK
289 *     &#47;** *&#47;
290 *     private class ClassB {} // OK
291 * }
292 *
293 * &#47;**
294 *  * &#64;author
295 *  * &#64;version abc
296 *  * &#64;unknownTag value // OK
297 *  *&#47;
298 * public class ClassC {} // OK
299 *
300 * &#47;** *&#47;
301 * public class ClassD&lt;T&gt; {} // OK
302 *
303 * &#47;** *&#47;
304 * private class ClassE&lt;T&gt; {} // violation, as param tag for &lt;T&gt; is missing
305 *
306 * &#47;** *&#47;
307 * &#64;Generated
308 * public class ClassF&lt;T&gt; {} // OK
309 * </pre>
310 * <p>
311 * To configure the check that allows missing {@code @param} tags:
312 * </p>
313 * <pre>
314 * &lt;module name="JavadocType"&gt;
315 *   &lt;property name="allowMissingParamTags" value="true"/&gt;
316 * &lt;/module&gt;
317 * </pre>
318 * <p>
319 * Example:
320 * </p>
321 * <pre>
322 * &#47;**
323 *  * &#64;author a
324 *  * &#64;version $Revision1$
325 *  *&#47;
326 * public class ClassA { // OK
327 *     &#47;** *&#47;
328 *     private class ClassB {} // OK
329 * }
330 *
331 * &#47;**
332 *  * &#64;author
333 *  * &#64;version abc
334 *  * &#64;unknownTag value // violation
335 *  *&#47;
336 * public class ClassC {} // OK
337 *
338 * &#47;** *&#47;
339 * public class ClassD&lt;T&gt; {} // OK, as missing param tag is allowed
340 *
341 * &#47;** *&#47;
342 * private class ClassE&lt;T&gt; {} // OK, as missing param tag is allowed
343 *
344 * &#47;** *&#47;
345 * &#64;Generated
346 * public class ClassF&lt;T&gt; {} // OK
347 * </pre>
348 * <p>
349 * To configure the check that allows unknown tags:
350 * </p>
351 * <pre>
352 * &lt;module name="JavadocType"&gt;
353 *   &lt;property name="allowUnknownTags" value="true"/&gt;
354 * &lt;/module&gt;
355 * </pre>
356 * <p>
357 * Example:
358 * </p>
359 * <pre>
360 * &#47;**
361 *  * &#64;author a
362 *  * &#64;version $Revision1$
363 *  *&#47;
364 * public class ClassA { // OK
365 *     &#47;** *&#47;
366 *     private class ClassB {} // OK
367 * }
368 *
369 * &#47;**
370 *  * &#64;author
371 *  * &#64;version abc
372 *  * &#64;unknownTag value // OK, as unknown tag is allowed
373 *  *&#47;
374 * public class ClassC {} // OK
375 *
376 * &#47;** *&#47;
377 * public class ClassD {} // OK
378 *
379 * &#47;** *&#47;
380 * private class ClassE {} // OK
381 *
382 * &#47;** *&#47;
383 * &#64;Generated
384 * public class ClassF&lt;T&gt; {} // OK
385 * </pre>
386 * <p>
387 * To configure a check that allows skipping validation at all for classes annotated
388 * with {@code @SpringBootApplication} and {@code @Configuration}:
389 * </p>
390 * <pre>
391 * &lt;module name="JavadocType"&gt;
392 *   &lt;property name="allowedAnnotations" value="SpringBootApplication,Configuration"/&gt;
393 * &lt;/module&gt;
394 * </pre>
395 * <p>
396 * Example:
397 * </p>
398 * <pre>
399 * &#47;** *&#47;
400 * &#64;SpringBootApplication // no violations about missing param tag on class
401 * public class Application&lt;T&gt; {}
402 *
403 * &#47;** *&#47;
404 * &#64;Configuration // no violations about missing param tag on class
405 * class DatabaseConfiguration&lt;T&gt; {}
406 * </pre>
407 * <p>
408 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
409 * </p>
410 * <p>
411 * Violation Message Keys:
412 * </p>
413 * <ul>
414 * <li>
415 * {@code javadoc.unknownTag}
416 * </li>
417 * <li>
418 * {@code javadoc.unusedTag}
419 * </li>
420 * <li>
421 * {@code javadoc.unusedTagGeneral}
422 * </li>
423 * <li>
424 * {@code type.missingTag}
425 * </li>
426 * <li>
427 * {@code type.tagFormat}
428 * </li>
429 * </ul>
430 *
431 * @since 3.0
432 *
433 */
434@StatelessCheck
435public class JavadocTypeCheck
436    extends AbstractCheck {
437
438    /**
439     * A key is pointing to the warning message text in "messages.properties"
440     * file.
441     */
442    public static final String MSG_UNKNOWN_TAG = "javadoc.unknownTag";
443
444    /**
445     * A key is pointing to the warning message text in "messages.properties"
446     * file.
447     */
448    public static final String MSG_TAG_FORMAT = "type.tagFormat";
449
450    /**
451     * A key is pointing to the warning message text in "messages.properties"
452     * file.
453     */
454    public static final String MSG_MISSING_TAG = "type.missingTag";
455
456    /**
457     * A key is pointing to the warning message text in "messages.properties"
458     * file.
459     */
460    public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
461
462    /**
463     * A key is pointing to the warning message text in "messages.properties"
464     * file.
465     */
466    public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
467
468    /** Open angle bracket literal. */
469    private static final String OPEN_ANGLE_BRACKET = "<";
470
471    /** Close angle bracket literal. */
472    private static final String CLOSE_ANGLE_BRACKET = ">";
473
474    /** Space literal. */
475    private static final String SPACE = " ";
476
477    /** Pattern to match type name within angle brackets in javadoc param tag. */
478    private static final Pattern TYPE_NAME_IN_JAVADOC_TAG =
479            Pattern.compile("\\s*<([^>]+)>.*");
480
481    /** Pattern to split type name field in javadoc param tag. */
482    private static final Pattern TYPE_NAME_IN_JAVADOC_TAG_SPLITTER =
483            Pattern.compile("\\s+");
484
485    /** Specify the visibility scope where Javadoc comments are checked. */
486    private Scope scope = Scope.PRIVATE;
487    /** Specify the visibility scope where Javadoc comments are not checked. */
488    private Scope excludeScope;
489    /** Specify the pattern for {@code @author} tag. */
490    private Pattern authorFormat;
491    /** Specify the pattern for {@code @version} tag. */
492    private Pattern versionFormat;
493    /**
494     * Control whether to ignore violations when a class has type parameters but
495     * does not have matching param tags in the Javadoc.
496     */
497    private boolean allowMissingParamTags;
498    /** Control whether to ignore violations when a Javadoc tag is not recognised. */
499    private boolean allowUnknownTags;
500
501    /**
502     * Specify annotations that allow skipping validation at all.
503     * Only short names are allowed, e.g. {@code Generated}.
504     */
505    private Set<String> allowedAnnotations = Set.of("Generated");
506
507    /**
508     * Setter to specify the visibility scope where Javadoc comments are checked.
509     *
510     * @param scope a scope.
511     */
512    public void setScope(Scope scope) {
513        this.scope = scope;
514    }
515
516    /**
517     * Setter to specify the visibility scope where Javadoc comments are not checked.
518     *
519     * @param excludeScope a scope.
520     */
521    public void setExcludeScope(Scope excludeScope) {
522        this.excludeScope = excludeScope;
523    }
524
525    /**
526     * Setter to specify the pattern for {@code @author} tag.
527     *
528     * @param pattern a pattern.
529     */
530    public void setAuthorFormat(Pattern pattern) {
531        authorFormat = pattern;
532    }
533
534    /**
535     * Setter to specify the pattern for {@code @version} tag.
536     *
537     * @param pattern a pattern.
538     */
539    public void setVersionFormat(Pattern pattern) {
540        versionFormat = pattern;
541    }
542
543    /**
544     * Setter to control whether to ignore violations when a class has type parameters but
545     * does not have matching param tags in the Javadoc.
546     *
547     * @param flag a {@code Boolean} value
548     */
549    public void setAllowMissingParamTags(boolean flag) {
550        allowMissingParamTags = flag;
551    }
552
553    /**
554     * Setter to control whether to ignore violations when a Javadoc tag is not recognised.
555     *
556     * @param flag a {@code Boolean} value
557     */
558    public void setAllowUnknownTags(boolean flag) {
559        allowUnknownTags = flag;
560    }
561
562    /**
563     * Setter to specify annotations that allow skipping validation at all.
564     * Only short names are allowed, e.g. {@code Generated}.
565     *
566     * @param userAnnotations user's value.
567     */
568    public void setAllowedAnnotations(String... userAnnotations) {
569        allowedAnnotations = Set.of(userAnnotations);
570    }
571
572    @Override
573    public int[] getDefaultTokens() {
574        return getAcceptableTokens();
575    }
576
577    @Override
578    public int[] getAcceptableTokens() {
579        return new int[] {
580            TokenTypes.INTERFACE_DEF,
581            TokenTypes.CLASS_DEF,
582            TokenTypes.ENUM_DEF,
583            TokenTypes.ANNOTATION_DEF,
584            TokenTypes.RECORD_DEF,
585        };
586    }
587
588    @Override
589    public int[] getRequiredTokens() {
590        return CommonUtil.EMPTY_INT_ARRAY;
591    }
592
593    // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
594    @SuppressWarnings("deprecation")
595    @Override
596    public void visitToken(DetailAST ast) {
597        if (shouldCheck(ast)) {
598            final FileContents contents = getFileContents();
599            final int lineNo = ast.getLineNo();
600            final TextBlock textBlock = contents.getJavadocBefore(lineNo);
601            if (textBlock != null) {
602                final List<JavadocTag> tags = getJavadocTags(textBlock);
603                if (ScopeUtil.isOuterMostType(ast)) {
604                    // don't check author/version for inner classes
605                    checkTag(ast, tags, JavadocTagInfo.AUTHOR.getName(),
606                            authorFormat);
607                    checkTag(ast, tags, JavadocTagInfo.VERSION.getName(),
608                            versionFormat);
609                }
610
611                final List<String> typeParamNames =
612                    CheckUtil.getTypeParameterNames(ast);
613                final List<String> recordComponentNames =
614                    getRecordComponentNames(ast);
615
616                if (!allowMissingParamTags) {
617
618                    typeParamNames.forEach(typeParamName -> {
619                        checkTypeParamTag(ast, tags, typeParamName);
620                    });
621
622                    recordComponentNames.forEach(componentName -> {
623                        checkComponentParamTag(ast, tags, componentName);
624                    });
625                }
626
627                checkUnusedParamTags(tags, typeParamNames, recordComponentNames);
628            }
629        }
630    }
631
632    /**
633     * Whether we should check this node.
634     *
635     * @param ast a given node.
636     * @return whether we should check a given node.
637     */
638    private boolean shouldCheck(DetailAST ast) {
639        final Scope customScope = ScopeUtil.getScope(ast);
640        final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast);
641
642        return customScope.isIn(scope)
643            && (surroundingScope == null || surroundingScope.isIn(scope))
644            && (excludeScope == null
645                || !customScope.isIn(excludeScope)
646                || surroundingScope != null
647                && !surroundingScope.isIn(excludeScope))
648            && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations);
649    }
650
651    /**
652     * Gets all standalone tags from a given javadoc.
653     *
654     * @param textBlock the Javadoc comment to process.
655     * @return all standalone tags from the given javadoc.
656     */
657    private List<JavadocTag> getJavadocTags(TextBlock textBlock) {
658        final JavadocTags tags = JavadocUtil.getJavadocTags(textBlock,
659            JavadocUtil.JavadocTagType.BLOCK);
660        if (!allowUnknownTags) {
661            for (final InvalidJavadocTag tag : tags.getInvalidTags()) {
662                log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG,
663                    tag.getName());
664            }
665        }
666        return tags.getValidTags();
667    }
668
669    /**
670     * Verifies that a type definition has a required tag.
671     *
672     * @param ast the AST node for the type definition.
673     * @param tags tags from the Javadoc comment for the type definition.
674     * @param tagName the required tag name.
675     * @param formatPattern regexp for the tag value.
676     */
677    private void checkTag(DetailAST ast, Iterable<JavadocTag> tags, String tagName,
678                          Pattern formatPattern) {
679        if (formatPattern != null) {
680            boolean hasTag = false;
681            final String tagPrefix = "@";
682
683            for (final JavadocTag tag :tags) {
684                if (tag.getTagName().equals(tagName)) {
685                    hasTag = true;
686                    if (!formatPattern.matcher(tag.getFirstArg()).find()) {
687                        log(ast, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern());
688                    }
689                }
690            }
691            if (!hasTag) {
692                log(ast, MSG_MISSING_TAG, tagPrefix + tagName);
693            }
694        }
695    }
696
697    /**
698     * Verifies that a record definition has the specified param tag for
699     * the specified record component name.
700     *
701     * @param ast the AST node for the record definition.
702     * @param tags tags from the Javadoc comment for the record definition.
703     * @param recordComponentName the name of the type parameter
704     */
705    private void checkComponentParamTag(DetailAST ast,
706                                        Collection<JavadocTag> tags,
707                                        String recordComponentName) {
708
709        final boolean found = tags
710            .stream()
711            .filter(JavadocTag::isParamTag)
712            .anyMatch(tag -> tag.getFirstArg().indexOf(recordComponentName) == 0);
713
714        if (!found) {
715            log(ast, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText()
716                + SPACE + recordComponentName);
717        }
718    }
719
720    /**
721     * Verifies that a type definition has the specified param tag for
722     * the specified type parameter name.
723     *
724     * @param ast the AST node for the type definition.
725     * @param tags tags from the Javadoc comment for the type definition.
726     * @param typeParamName the name of the type parameter
727     */
728    private void checkTypeParamTag(DetailAST ast,
729            Collection<JavadocTag> tags, String typeParamName) {
730        final String typeParamNameWithBrackets =
731            OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET;
732
733        final boolean found = tags
734            .stream()
735            .filter(JavadocTag::isParamTag)
736            .anyMatch(tag -> tag.getFirstArg().indexOf(typeParamNameWithBrackets) == 0);
737
738        if (!found) {
739            log(ast, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText()
740                + SPACE + typeParamNameWithBrackets);
741        }
742    }
743
744    /**
745     * Checks for unused param tags for type parameters and record components.
746     *
747     * @param tags tags from the Javadoc comment for the type definition
748     * @param typeParamNames names of type parameters
749     * @param recordComponentNames record component names in this definition
750     */
751    private void checkUnusedParamTags(
752        List<JavadocTag> tags,
753        List<String> typeParamNames,
754        List<String> recordComponentNames) {
755
756        for (final JavadocTag tag: tags) {
757            if (tag.isParamTag()) {
758                final String paramName = extractParamNameFromTag(tag);
759                final boolean found = typeParamNames.contains(paramName)
760                        || recordComponentNames.contains(paramName);
761
762                if (!found) {
763                    final String actualParamName =
764                        TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0];
765                    log(tag.getLineNo(), tag.getColumnNo(),
766                        MSG_UNUSED_TAG,
767                        JavadocTagInfo.PARAM.getText(), actualParamName);
768                }
769            }
770        }
771
772    }
773
774    /**
775     * Extracts parameter name from tag.
776     *
777     * @param tag javadoc tag to extract parameter name
778     * @return extracts type parameter name from tag
779     */
780    private static String extractParamNameFromTag(JavadocTag tag) {
781        final String typeParamName;
782        final Matcher matchInAngleBrackets =
783                TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg());
784        if (matchInAngleBrackets.find()) {
785            typeParamName = matchInAngleBrackets.group(1).trim();
786        }
787        else {
788            typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0];
789        }
790        return typeParamName;
791    }
792
793    /**
794     * Collects the record components in a record definition.
795     *
796     * @param node the possible record definition ast.
797     * @return the record components in this record definition.
798     */
799    private static List<String> getRecordComponentNames(DetailAST node) {
800        final DetailAST components = node.findFirstToken(TokenTypes.RECORD_COMPONENTS);
801        final List<String> componentList = new ArrayList<>();
802
803        if (components != null) {
804            TokenUtil.forEachChild(components,
805                TokenTypes.RECORD_COMPONENT_DEF, component -> {
806                    final DetailAST ident = component.findFirstToken(TokenTypes.IDENT);
807                    componentList.add(ident.getText());
808                });
809        }
810
811        return componentList;
812    }
813}