View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2026 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.utils;
21  
22  import static com.google.common.truth.Truth.assertWithMessage;
23  import static com.puppycrawl.tools.checkstyle.checks.javadoc.InappropriateJavadocBlockTagsOnPackageCheck.MSG_INAPPROPRIATE_TAG;
24  import static com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck.MSG_EXPECTED_TAG;
25  import static com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck.MSG_RETURN_EXPECTED;
26  import static com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocVariableCheck.MSG_JAVADOC_MISSING;
27  import static com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocPackageCheck.MSG_PKG_JAVADOC_MISSING;
28  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.getExpectedThrowable;
29  import static com.puppycrawl.tools.checkstyle.internal.utils.TestUtil.isUtilsClassHasPrivateConstructor;
30  
31  import java.util.ArrayList;
32  import java.util.List;
33  
34  import org.junit.jupiter.api.Test;
35  
36  import com.puppycrawl.tools.checkstyle.AbstractModuleTestSupport;
37  import com.puppycrawl.tools.checkstyle.DetailAstImpl;
38  import com.puppycrawl.tools.checkstyle.api.Comment;
39  import com.puppycrawl.tools.checkstyle.api.DetailNode;
40  import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
41  import com.puppycrawl.tools.checkstyle.api.LineColumn;
42  import com.puppycrawl.tools.checkstyle.api.TextBlock;
43  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
44  import com.puppycrawl.tools.checkstyle.checks.javadoc.InappropriateJavadocBlockTagsOnPackageCheck;
45  import com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag;
46  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocMethodCheck;
47  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocNodeImpl;
48  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTag;
49  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocVariableCheck;
50  import com.puppycrawl.tools.checkstyle.checks.javadoc.MissingJavadocPackageCheck;
51  import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.BlockTagUtil;
52  import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.InlineTagUtil;
53  import com.puppycrawl.tools.checkstyle.checks.javadoc.utils.TagInfo;
54  
55  public class JavadocUtilTest extends AbstractModuleTestSupport {
56  
57      @Override
58      public String getPackageLocation() {
59          return "com/puppycrawl/tools/checkstyle/utils/javadocutil";
60      }
61  
62      @Test
63      public void testTags() {
64          final String[] text = {
65              "/** @see elsewhere ",
66              " * {@link List }, {@link List link text }",
67              "   {@link List#add(Object) link text}",
68              " * {@link Class link text}",
69          };
70          final Comment comment = new Comment(text, 1, 4, text[3].length());
71          final JavadocTags allTags =
72              getJavadocTags(comment, JavadocTagType.ALL);
73          assertWithMessage("Invalid valid tags size")
74              .that(allTags.validTags())
75              .hasSize(5);
76      }
77  
78      @Test
79      public void testBlockTag() {
80          final String[] text = {
81              "/** @see elsewhere ",
82              " */",
83          };
84          final Comment comment = new Comment(text, 1, 4, text[1].length());
85          final JavadocTags allTags =
86              getJavadocTags(comment, JavadocTagType.ALL);
87          assertWithMessage("Invalid valid tags size")
88              .that(allTags.validTags())
89              .hasSize(1);
90      }
91  
92      @Test
93      public void testTagType() {
94          final String[] text = {
95              "/** @see block",
96              " * {@link List inline}, {@link List#add(Object)}",
97          };
98          final Comment comment = new Comment(text, 1, 2, text[1].length());
99          final JavadocTags blockTags =
100             getJavadocTags(comment, JavadocTagType.BLOCK);
101         final JavadocTags inlineTags =
102             getJavadocTags(comment, JavadocTagType.INLINE);
103         assertWithMessage("Invalid valid tags size")
104             .that(blockTags.validTags())
105             .hasSize(1);
106         assertWithMessage("Invalid valid tags size")
107             .that(inlineTags.validTags())
108             .hasSize(2);
109     }
110 
111     @Test
112     public void testInlineTagLinkText() {
113         final String[] text = {
114             "/** {@link List link text }",
115         };
116         final Comment comment = new Comment(text, 1, 1, text[0].length());
117         final List<JavadocTag> tags =
118             getJavadocTags(comment, JavadocTagType.ALL).validTags();
119         assertWithMessage("Invalid first arg")
120             .that(tags.getFirst().getFirstArg())
121             .isEqualTo("List link text");
122     }
123 
124     @Test
125     public void testInlineTagMethodRef() {
126         final String[] text = {
127             "/** {@link List#add(Object)}",
128         };
129         final Comment comment = new Comment(text, 1, 1, text[0].length());
130         final List<JavadocTag> tags =
131             getJavadocTags(comment, JavadocTagType.ALL).validTags();
132         assertWithMessage("Invalid first arg")
133             .that(tags.getFirst().getFirstArg())
134             .isEqualTo("List#add(Object)");
135     }
136 
137     @Test
138     public void testTagPositions() {
139         final String[] text = {
140             "/** @see elsewhere",
141             "    also {@link Name value} */",
142         };
143         final Comment comment = new Comment(text, 1, 2, text[1].length());
144 
145         final List<JavadocTag> tags =
146             getJavadocTags(comment, JavadocTagType.ALL).validTags();
147 
148         assertWithMessage("Invalid tags size")
149             .that(tags)
150             .hasSize(2);
151 
152         final JavadocTag seeTag = tags.getFirst();
153         assertWithMessage("Invalid first argument")
154             .that(seeTag.getFirstArg())
155             .isEqualTo("elsewhere");
156         assertWithMessage("Invalid line number")
157             .that(seeTag.getLineNo())
158             .isEqualTo(1);
159         assertWithMessage("Invalid column number")
160             .that(seeTag.getColumnNo())
161             .isEqualTo(4);
162 
163         final JavadocTag linkTag = tags.get(1);
164         assertWithMessage("Invalid first argument")
165             .that(linkTag.getFirstArg())
166             .isEqualTo("Name value");
167         assertWithMessage("Invalid line number")
168             .that(linkTag.getLineNo())
169             .isEqualTo(2);
170         assertWithMessage("Invalid column number")
171             .that(linkTag.getColumnNo())
172             .isEqualTo(10);
173     }
174 
175     @Test
176     public void testInlineTagPositions() {
177         final String[] text = {"/** Also {@link Name value} */"};
178         final Comment comment = new Comment(text, 1, 0, text[0].length());
179 
180         final List<JavadocTag> tags =
181             getJavadocTags(comment, JavadocTagType.INLINE).validTags();
182 
183         assertWithMessage("Invalid tags size")
184             .that(tags)
185             .hasSize(1);
186         final int lineNo = tags.getFirst().getLineNo();
187         assertWithMessage("Unexpected line number")
188             .that(lineNo)
189             .isEqualTo(0);
190         final int columnNo = tags.getFirst().getColumnNo();
191         assertWithMessage("Unexpected column number")
192             .that(columnNo)
193             .isEqualTo(10);
194     }
195 
196     @Test
197     public void testInvalidTags() {
198         final String[] text = {
199             "/** @fake block",
200             " * {@bogus inline}",
201             " * {@link List valid}",
202         };
203         final Comment comment = new Comment(text, 1, 3, text[2].length());
204         final JavadocTags allTags =
205             getJavadocTags(comment, JavadocTagType.ALL);
206         assertWithMessage("Unexpected invalid tags size")
207             .that(allTags.invalidTags())
208             .hasSize(2);
209         assertTag("Unexpected invalid tag", new InvalidJavadocTag(1, 4, "fake"),
210                 allTags.invalidTags().getFirst());
211         assertTag("Unexpected invalid tag", new InvalidJavadocTag(2, 4, "bogus"),
212                 allTags.invalidTags().get(1));
213         assertWithMessage("Unexpected valid tags size")
214             .that(allTags.validTags())
215             .hasSize(1);
216         assertTag("Unexpected valid tag", new JavadocTag(3, 4, "link", "List valid"),
217                 allTags.validTags().getFirst());
218     }
219 
220     @Test
221     public void testEmptyBlockComment() {
222         final String emptyComment = "";
223         assertWithMessage("Should return false when empty string is passed")
224                 .that(JavadocUtil.isJavadocComment(emptyComment))
225                 .isFalse();
226     }
227 
228     @Test
229     public void testEmptyBlockCommentAst() {
230         final DetailAstImpl commentBegin = new DetailAstImpl();
231         commentBegin.setType(TokenTypes.BLOCK_COMMENT_BEGIN);
232         commentBegin.setText("/*");
233 
234         final DetailAstImpl commentContent = new DetailAstImpl();
235         commentContent.setType(TokenTypes.COMMENT_CONTENT);
236         commentContent.setText("");
237 
238         final DetailAstImpl commentEnd = new DetailAstImpl();
239         commentEnd.setType(TokenTypes.BLOCK_COMMENT_END);
240         commentEnd.setText("*/");
241 
242         commentBegin.setFirstChild(commentContent);
243         commentContent.setNextSibling(commentEnd);
244 
245         assertWithMessage("Should return false when empty block comment is passed")
246                 .that(JavadocUtil.isJavadocComment(commentBegin))
247                 .isFalse();
248     }
249 
250     @Test
251     public void testEmptyJavadocComment() {
252         final String emptyJavadocComment = "*";
253         assertWithMessage("Should return true when empty javadoc comment is passed")
254                 .that(JavadocUtil.isJavadocComment(emptyJavadocComment))
255                 .isTrue();
256     }
257 
258     @Test
259     public void testEmptyJavadocCommentAst() {
260         final DetailAstImpl commentBegin = new DetailAstImpl();
261         commentBegin.setType(TokenTypes.BLOCK_COMMENT_BEGIN);
262         commentBegin.setText("/*");
263 
264         final DetailAstImpl javadocCommentContent = new DetailAstImpl();
265         javadocCommentContent.setType(TokenTypes.COMMENT_CONTENT);
266         javadocCommentContent.setText("*");
267 
268         final DetailAstImpl commentEnd = new DetailAstImpl();
269         commentEnd.setType(TokenTypes.BLOCK_COMMENT_END);
270         commentEnd.setText("*/");
271 
272         commentBegin.setFirstChild(javadocCommentContent);
273         javadocCommentContent.setNextSibling(commentEnd);
274 
275         final DetailAstImpl commentBeginParent = new DetailAstImpl();
276         commentBeginParent.setType(TokenTypes.MODIFIERS);
277         commentBeginParent.setFirstChild(commentBegin);
278 
279         final DetailAstImpl aJavadocPosition = new DetailAstImpl();
280         aJavadocPosition.setType(TokenTypes.METHOD_DEF);
281         aJavadocPosition.setFirstChild(commentBeginParent);
282         assertWithMessage("Should return true when empty javadoc comment ast is passed")
283                 .that(JavadocUtil.isJavadocComment(commentBegin))
284                 .isTrue();
285     }
286 
287     @Test
288     public void testIsProperUtilsClass() throws ReflectiveOperationException {
289         assertWithMessage("Constructor is not private")
290                 .that(isUtilsClassHasPrivateConstructor(JavadocUtil.class))
291                 .isTrue();
292     }
293 
294     @Test
295     public void testGetTokenNameForId() {
296         assertWithMessage("Invalid token name")
297             .that(JavadocUtil.getTokenName(JavadocCommentsTokenTypes.JAVADOC_CONTENT))
298             .isEqualTo("JAVADOC_CONTENT");
299     }
300 
301     @Test
302     public void testGetTokenNameForLargeId() {
303         final IllegalArgumentException exc =
304                 getExpectedThrowable(IllegalArgumentException.class, () -> {
305                     JavadocUtil.getTokenName(30073);
306                 });
307         assertWithMessage("Invalid exception message")
308             .that(exc.getMessage())
309             .isEqualTo("Unknown javadoc token id. Given id: 30073");
310     }
311 
312     @Test
313     public void testGetTokenNameForInvalidId() {
314         final IllegalArgumentException exc =
315                 getExpectedThrowable(IllegalArgumentException.class, () -> {
316                     JavadocUtil.getTokenName(110);
317                 });
318         assertWithMessage("Invalid exception message")
319             .that(exc.getMessage())
320             .isEqualTo("Unknown javadoc token id. Given id: 110");
321     }
322 
323     @Test
324     public void testGetTokenNameForLowerBoundInvalidId() {
325         final IllegalArgumentException exc =
326                 getExpectedThrowable(IllegalArgumentException.class, () -> {
327                     JavadocUtil.getTokenName(10095);
328                 });
329         assertWithMessage("Invalid exception message")
330             .that(exc.getMessage())
331             .isEqualTo("Unknown javadoc token id. Given id: 10095");
332     }
333 
334     @Test
335     public void testGetTokenIdThatIsUnknown() {
336         final IllegalArgumentException exc =
337                 getExpectedThrowable(IllegalArgumentException.class, () -> {
338                     JavadocUtil.getTokenId("");
339                 });
340         assertWithMessage("Invalid exception message")
341             .that(exc.getMessage())
342             .isEqualTo("Unknown javadoc token name. Given name ");
343     }
344 
345     @Test
346     public void testGetTokenId() {
347         final int tokenId = JavadocUtil.getTokenId("JAVADOC_CONTENT");
348 
349         assertWithMessage("Invalid token id")
350             .that(tokenId)
351             .isEqualTo(JavadocCommentsTokenTypes.JAVADOC_CONTENT);
352     }
353 
354     @Test
355     public void testGetAllNodesOfTypeForNoChildren() {
356         final JavadocNodeImpl parent = new JavadocNodeImpl();
357 
358         final List<DetailNode> nodes = JavadocUtil.getAllNodesOfType(parent,
359                 JavadocCommentsTokenTypes.TEXT);
360 
361         assertWithMessage("Invalid nodes")
362             .that(nodes)
363             .isEmpty();
364     }
365 
366     @Test
367     public void testGetAllNodesOfTypeForNoMatches() {
368         final JavadocNodeImpl parent = new JavadocNodeImpl();
369         parent.addChild(createJavadocNode(JavadocCommentsTokenTypes.TAG_NAME));
370         parent.addChild(createJavadocNode(JavadocCommentsTokenTypes.EQUALS));
371 
372         final List<DetailNode> nodes = JavadocUtil.getAllNodesOfType(parent,
373                 JavadocCommentsTokenTypes.TEXT);
374 
375         assertWithMessage("Invalid nodes")
376             .that(nodes)
377             .isEmpty();
378     }
379 
380     @Test
381     public void testGetAllNodesOfType() {
382         final JavadocNodeImpl parent = new JavadocNodeImpl();
383         final JavadocNodeImpl firstTextNode = createJavadocNode(JavadocCommentsTokenTypes.TEXT);
384         final JavadocNodeImpl tagNameNode = createJavadocNode(JavadocCommentsTokenTypes.TAG_NAME);
385         final JavadocNodeImpl secondTextNode = createJavadocNode(JavadocCommentsTokenTypes.TEXT);
386         tagNameNode.addChild(createJavadocNode(JavadocCommentsTokenTypes.TEXT));
387 
388         parent.addChild(firstTextNode);
389         parent.addChild(tagNameNode);
390         parent.addChild(secondTextNode);
391 
392         final List<DetailNode> nodes = JavadocUtil.getAllNodesOfType(parent,
393                 JavadocCommentsTokenTypes.TEXT);
394 
395         assertWithMessage("Invalid nodes")
396             .that(nodes)
397             .containsExactly(firstTextNode, secondTextNode)
398             .inOrder();
399     }
400 
401     @Test
402     public void testGetJavadocCommentContent() {
403         final DetailAstImpl detailAST = new DetailAstImpl();
404         final DetailAstImpl javadoc = new DetailAstImpl();
405 
406         javadoc.setText("1javadoc");
407         detailAST.setFirstChild(javadoc);
408         final String commentContent = JavadocUtil.getJavadocCommentContent(detailAST);
409 
410         assertWithMessage("Invalid comment content")
411             .that(commentContent)
412             .isEqualTo("javadoc");
413     }
414 
415     @Test
416     public void testGetLastTokenName() {
417         assertWithMessage("Unexpected token name")
418             .that(JavadocUtil.getTokenName(103))
419             .isEqualTo("CUSTOM_BLOCK_TAG");
420     }
421 
422     @Test
423     public void testEscapeAllControlChars() {
424         assertWithMessage("invalid result")
425             .that(JavadocUtil.escapeAllControlChars("abc"))
426             .isEqualTo("abc");
427         assertWithMessage("invalid result")
428             .that(JavadocUtil.escapeAllControlChars("1\\r2\\n3\\t"))
429             .isEqualTo("1\\r2\\n3\\t");
430     }
431 
432     private static void assertTag(String message, InvalidJavadocTag expected,
433             InvalidJavadocTag actual) {
434         assertWithMessage("%s line", message)
435             .that(actual.getLine())
436             .isEqualTo(expected.getLine());
437         assertWithMessage("%s column", message)
438             .that(actual.getCol())
439             .isEqualTo(expected.getCol());
440         assertWithMessage("%s name", message)
441             .that(actual.getName())
442             .isEqualTo(expected.getName());
443     }
444 
445     private static void assertTag(String message, JavadocTag expected,
446             JavadocTag actual) {
447         assertWithMessage("%s line", message)
448             .that(actual.getLineNo())
449             .isEqualTo(expected.getLineNo());
450         assertWithMessage("%s column", message)
451             .that(actual.getColumnNo())
452             .isEqualTo(expected.getColumnNo());
453         assertWithMessage("%s first arg", message)
454             .that(actual.getFirstArg())
455             .isEqualTo(expected.getFirstArg());
456         assertWithMessage("%s string", message)
457             .that(actual.toString())
458             .isEqualTo(expected.toString());
459     }
460 
461     private static JavadocNodeImpl createJavadocNode(int tokenType) {
462         final JavadocNodeImpl result = new JavadocNodeImpl();
463         result.setType(tokenType);
464         return result;
465     }
466 
467     @Test
468     public void testGetAttachedJavadocCommentForMethodDefinitions() throws Exception {
469         final String[] expected = {
470             "20: " + getCheckMessage(JavadocMethodCheck.class, MSG_RETURN_EXPECTED),
471             "26:29: " + getCheckMessage(JavadocMethodCheck.class,
472                     MSG_EXPECTED_TAG, "@param", "value"),
473             "34: " + getCheckMessage(JavadocMethodCheck.class, MSG_RETURN_EXPECTED),
474             "34:6: " + getCheckMessage(JavadocMethodCheck.class,
475                     MSG_EXPECTED_TAG, "@param", "<T>"),
476             "34:31: " + getCheckMessage(JavadocMethodCheck.class,
477                     MSG_EXPECTED_TAG, "@param", "value"),
478             "40: " + getCheckMessage(JavadocMethodCheck.class, MSG_RETURN_EXPECTED),
479             "60: " + getCheckMessage(JavadocMethodCheck.class, MSG_RETURN_EXPECTED),
480         };
481         verifyWithInlineConfigParser(
482                 getPath("InputJavadocUtilMethodDefComments.java"), expected);
483     }
484 
485     @Test
486     public void testGetAttachedJavadocCommentForConstructorDefinitions()
487             throws Exception {
488         final String[] expected = {
489             "21:26: " + getCheckMessage(JavadocMethodCheck.class,
490                     MSG_EXPECTED_TAG, "@param", "value"),
491             "27:39: " + getCheckMessage(JavadocMethodCheck.class,
492                     MSG_EXPECTED_TAG, "@param", "value"),
493             "36:10: " + getCheckMessage(JavadocMethodCheck.class,
494                     MSG_EXPECTED_TAG, "@param", "<T>"),
495             "36:33: " + getCheckMessage(JavadocMethodCheck.class,
496                     MSG_EXPECTED_TAG, "@param", "value"),
497             "51:26: " + getCheckMessage(JavadocMethodCheck.class,
498                     MSG_EXPECTED_TAG, "@param", "value"),
499         };
500         verifyWithInlineConfigParser(
501                 getPath("InputJavadocUtilCtorDefComments.java"), expected);
502     }
503 
504     @Test
505     public void testGetAttachedJavadocCommentForAnnotationFieldDefinitions()
506             throws Exception {
507         final String[] expected = {
508             "20: " + getCheckMessage(JavadocMethodCheck.class, MSG_RETURN_EXPECTED),
509             "24: " + getCheckMessage(JavadocMethodCheck.class, MSG_RETURN_EXPECTED),
510             "30: " + getCheckMessage(JavadocMethodCheck.class, MSG_RETURN_EXPECTED),
511         };
512         verifyWithInlineConfigParser(
513                 getPath("InputJavadocUtilAnnotationFieldDefComments.java"), expected);
514     }
515 
516     @Test
517     public void testGetAttachedJavadocCommentForCompactConstructorDefinitions()
518             throws Exception {
519         final String[] expected = {
520             "22:9: " + getCheckMessage(JavadocMethodCheck.class,
521                     MSG_EXPECTED_TAG, "@param", "value"),
522             "41:9: " + getCheckMessage(JavadocMethodCheck.class,
523                     MSG_EXPECTED_TAG, "@param", "value"),
524         };
525         verifyWithInlineConfigParser(
526                 getPath("InputJavadocUtilCompactCtorDefComments.java"), expected);
527     }
528 
529     @Test
530     public void testGetAttachedJavadocCommentForVariableDefinitions() throws Exception {
531         final String[] expected = {
532             "17:5: " + getCheckMessage(JavadocVariableCheck.class,
533                     MSG_JAVADOC_MISSING, "modifierPath"),
534             "20:5: " + getCheckMessage(JavadocVariableCheck.class,
535                     MSG_JAVADOC_MISSING, "annotationPath"),
536             "24:5: " + getCheckMessage(JavadocVariableCheck.class,
537                     MSG_JAVADOC_MISSING, "noJavadoc"),
538             "32:5: " + getCheckMessage(JavadocVariableCheck.class,
539                     MSG_JAVADOC_MISSING, "initializerCommentOnly"),
540         };
541         verifyWithInlineConfigParser(
542                 getPath("InputJavadocUtilVariableDefComments.java"), expected);
543     }
544 
545     @Test
546     public void testGetAttachedJavadocCommentForEnumConstantDefinitions()
547             throws Exception {
548         final String[] expected = {
549             "21:5: " + getCheckMessage(JavadocVariableCheck.class,
550                     MSG_JAVADOC_MISSING, "BODY"),
551             "28:5: " + getCheckMessage(JavadocVariableCheck.class,
552                     MSG_JAVADOC_MISSING, "NO_JAVADOC"),
553             "31:5: " + getCheckMessage(JavadocVariableCheck.class,
554                     MSG_JAVADOC_MISSING, "REAL"),
555         };
556         verifyWithInlineConfigParser(
557                 getPath("InputJavadocUtilEnumConstantDefComments.java"), expected);
558     }
559 
560     @Test
561     public void testGetAttachedJavadocCommentForPackageDirectSibling() throws Exception {
562         final String[] expected = {
563             "12:1: " + getCheckMessage(InappropriateJavadocBlockTagsOnPackageCheck.class,
564                     MSG_INAPPROPRIATE_TAG, "return", "package"),
565         };
566         verifyWithInlineConfigParser(
567                 getPath("InputJavadocUtilPackageDirectSibling.java"), expected);
568     }
569 
570     @Test
571     public void testGetAttachedJavadocCommentForPackageNoComment() throws Exception {
572         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
573         verifyWithInlineConfigParser(
574                 getPath("InputJavadocUtilPackageNoComment.java"), expected);
575     }
576 
577     @Test
578     public void testGetAttachedJavadocCommentForPackageNonJavadocComment() throws Exception {
579         final String[] expected = CommonUtil.EMPTY_STRING_ARRAY;
580         verifyWithInlineConfigParser(
581                 getPath("InputJavadocUtilPackageNonJavadocComment.java"), expected);
582     }
583 
584     @Test
585     public void testGetAttachedJavadocCommentForPackageNonJavadocComment2() throws Exception {
586         final String[] expected = {
587             "7:1: " + getCheckMessage(MissingJavadocPackageCheck.class, MSG_PKG_JAVADOC_MISSING),
588         };
589         verifyWithInlineConfigParser(
590                 getPath("nonjavadoc/package-info.java"), expected);
591     }
592 
593     @Test
594     public void testGetAttachedJavadocCommentForPackageViaAnnotation() throws Exception {
595         final String[] expected = {
596             "13:1: " + getCheckMessage(InappropriateJavadocBlockTagsOnPackageCheck.class,
597                     MSG_INAPPROPRIATE_TAG, "return", "package"),
598         };
599         verifyWithInlineConfigParser(
600                 getPath("annotation/package-info.java"), expected);
601     }
602 
603     @Test
604     public void testMissingJavadocPackage() throws Exception {
605         final String[] expected = {
606             "6:1: " + getCheckMessage(MissingJavadocPackageCheck.class, MSG_PKG_JAVADOC_MISSING),
607         };
608         verifyWithInlineConfigParser(
609                 getPath("annotationnojavadoc/package-info.java"), expected);
610     }
611 
612     private static JavadocTags getJavadocTags(TextBlock textBlock, JavadocTagType tagType) {
613         final String[] text = textBlock.getText();
614         final List<TagInfo> tags = new ArrayList<>();
615         final boolean isBlockTags = tagType == JavadocTagType.ALL
616                 || tagType == JavadocTagType.BLOCK;
617         if (isBlockTags) {
618             tags.addAll(BlockTagUtil.extractBlockTags(text));
619         }
620         final boolean isInlineTags = tagType == JavadocTagType.ALL
621                 || tagType == JavadocTagType.INLINE;
622         if (isInlineTags) {
623             tags.addAll(InlineTagUtil.extractInlineTags(text));
624         }
625 
626         final List<JavadocTag> validTags = new ArrayList<>();
627         final List<InvalidJavadocTag> invalidTags = new ArrayList<>();
628 
629         for (TagInfo tag : tags) {
630             final LineColumn position = tag.getPosition();
631             final int col = position.getColumn();
632             // Add the starting line of the comment to the line number to get the actual line number
633             // in the source.
634             // Lines are one-indexed, so need an off-by-one correction.
635             final int line = textBlock.getStartLineNo() + position.getLine() - 1;
636 
637             final String tagName = tag.getName();
638             try {
639                 validTags.add(new JavadocTag(line, col, tagName, tag.getValue()));
640             }
641             catch (IllegalArgumentException ignored) {
642                 invalidTags.add(new InvalidJavadocTag(line, col, tagName));
643             }
644         }
645 
646         return new JavadocTags(List.copyOf(validTags), List.copyOf(invalidTags));
647     }
648 
649     private record JavadocTags(List<JavadocTag> validTags,
650                                List<InvalidJavadocTag> invalidTags) {
651     }
652 
653     private enum JavadocTagType {
654 
655         BLOCK,
656         INLINE,
657         ALL,
658 
659     }
660 
661 }