Skip to content

Commit 464188e

Browse files
aclferomani
authored andcommitted
Issue #16087: Refractor JavadocTypCheck
1 parent dd0b3d3 commit 464188e

8 files changed

Lines changed: 483 additions & 18 deletions

File tree

config/checker-framework-suppressions/checker-nullness-optional-interning-suppressions.xml

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6465,6 +6465,18 @@
64656465
<lineContent>&amp;&amp; varType.getFirstChild().getType() == TokenTypes.ARRAY_DECLARATOR</lineContent>
64666466
</checkerFrameworkError>
64676467

6468+
<checkerFrameworkError unstable="false">
6469+
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.java</fileName>
6470+
<specifier>argument</specifier>
6471+
<message>incompatible argument for parameter elements of String.join.</message>
6472+
<lineContent>final String textBefore = String.join(&quot;\n&quot;, Arrays.copyOfRange(lines, 0, tagLineIndex));</lineContent>
6473+
<details>
6474+
found : @Initialized @Nullable String @Initialized @NonNull []
6475+
required: @Initialized @NonNull CharSequence @Initialized @NonNull []
6476+
</details>
6477+
</checkerFrameworkError>
6478+
6479+
64686480
<checkerFrameworkError unstable="false">
64696481
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.java</fileName>
64706482
<specifier>dereference.of.nullable</specifier>
@@ -6476,7 +6488,7 @@
64766488
<fileName>src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.java</fileName>
64776489
<specifier>dereference.of.nullable</specifier>
64786490
<message>dereference of possibly-null reference matchInAngleBrackets.group(1)</message>
6479-
<lineContent>typeParamName = matchInAngleBrackets.group(1).trim();</lineContent>
6491+
<lineContent>paramName = matchInAngleBrackets.group(1).trim();</lineContent>
64806492
</checkerFrameworkError>
64816493

64826494
<checkerFrameworkError unstable="false">

config/linkcheck-suppressions.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -478,6 +478,9 @@
478478
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTags.html#%3Cinit%3E(java.util.Collection,java.util.Collection)">#%3Cinit%3E(java.util.Collection,java.util.Collection)</a>: doesn't exist.
479479
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTags.html#%3Cinit%3E(java.util.Collection,java.util.Collection)">../JavadocTags.html#%3Cinit%3E(java.util.Collection,java.util.Collection)</a>: doesn't exist.
480480
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
481+
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.html#isTagInsideCodeOrLiteralBlock(java.lang.String%5B%5D,com.puppycrawl.tools.checkstyle.api.TextBlock,com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag)">#isTagInsideCodeOrLiteralBlock(java.lang.String%5B%5D,com.puppycrawl.tools.checkstyle.api.TextBlock,com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag)</a>: doesn't exist.
482+
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.html#isTagInsideCodeOrLiteralBlock(java.lang.String%5B%5D,com.puppycrawl.tools.checkstyle.api.TextBlock,com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag)">../../checks/javadoc/JavadocTypeCheck.html#isTagInsideCodeOrLiteralBlock(java.lang.String%5B%5D,com.puppycrawl.tools.checkstyle.api.TextBlock,com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag)</a>: doesn't exist.
483+
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.html#isTagInsideCodeOrLiteralBlock(java.lang.String%5B%5D,com.puppycrawl.tools.checkstyle.api.TextBlock,com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag)">../JavadocTypeCheck.html#isTagInsideCodeOrLiteralBlock(java.lang.String%5B%5D,com.puppycrawl.tools.checkstyle.api.TextBlock,com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag)</a>: doesn't exist.
481484
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocVariableCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
482485
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/javadoc/MissingJavadocMethodCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
483486
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/javadoc/MissingJavadocPackageCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
@@ -1122,6 +1125,7 @@
11221125
<a href="com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTagInfo.Type.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTagInfo.Type.html#%3Cinit%3E()</a>: doesn't exist.
11231126
<a href="com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTags.html#%3Cinit%3E(java.util.Collection,java.util.Collection)">com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTags.html#%3Cinit%3E(java.util.Collection,java.util.Collection)</a>: doesn't exist.
11241127
<a href="com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.html#%3Cinit%3E()</a>: doesn't exist.
1128+
<a href="com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.html#isTagInsideCodeOrLiteralBlock(java.lang.String%5B%5D,com.puppycrawl.tools.checkstyle.api.TextBlock,com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag)">com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.html#isTagInsideCodeOrLiteralBlock(java.lang.String%5B%5D,com.puppycrawl.tools.checkstyle.api.TextBlock,com.puppycrawl.tools.checkstyle.checks.javadoc.InvalidJavadocTag)</a>: doesn't exist.
11251129
<a href="com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocVariableCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocVariableCheck.html#%3Cinit%3E()</a>: doesn't exist.
11261130
<a href="com/puppycrawl/tools/checkstyle/checks/javadoc/MissingJavadocMethodCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/javadoc/MissingJavadocMethodCheck.html#%3Cinit%3E()</a>: doesn't exist.
11271131
<a href="com/puppycrawl/tools/checkstyle/checks/javadoc/MissingJavadocPackageCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/javadoc/MissingJavadocPackageCheck.html#%3Cinit%3E()</a>: doesn't exist.

src/main/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheck.java

Lines changed: 77 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package com.puppycrawl.tools.checkstyle.checks.javadoc;
2121

2222
import java.util.ArrayList;
23+
import java.util.Arrays;
2324
import java.util.Collection;
2425
import java.util.List;
2526
import java.util.Set;
@@ -110,6 +111,9 @@ public class JavadocTypeCheck
110111
/** Space literal. */
111112
private static final String SPACE = " ";
112113

114+
/** Javadoc tag token literal. */
115+
private static final String JAVADOC_TAG_TOKEN = "@";
116+
113117
/** Pattern to match type name within angle brackets in javadoc param tag. */
114118
private static final Pattern TYPE_NAME_IN_JAVADOC_TAG =
115119
Pattern.compile("^<([^>]+)");
@@ -296,13 +300,69 @@ private List<JavadocTag> getJavadocTags(TextBlock textBlock) {
296300
final JavadocTags tags = JavadocUtil.getJavadocTags(textBlock,
297301
JavadocUtil.JavadocTagType.BLOCK);
298302
if (!allowUnknownTags) {
299-
tags.getInvalidTags().forEach(tag -> {
300-
log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG, tag.getName());
301-
});
303+
final String[] lines = textBlock.getText();
304+
tags.getInvalidTags().stream()
305+
.filter(tag -> !isTagInsideCodeOrLiteralBlock(lines, textBlock, tag))
306+
.forEach(tag -> {
307+
log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG, tag.getName());
308+
});
302309
}
303310
return tags.getValidTags();
304311
}
305312

313+
/**
314+
* Checks if a tag is positioned inside a {@code @code} or {@code @literal} inline tag block.
315+
* Since block tags must appear at line-start position (per BlockTagUtil regex pattern),
316+
* we only need to check content from previous lines - there cannot be inline content
317+
* before a block tag on the same line.
318+
*
319+
* @param lines the Javadoc comment lines.
320+
* @param textBlock the text block containing the Javadoc.
321+
* @param tag the invalid tag to check.
322+
* @return true if the tag is inside a code or literal block.
323+
*/
324+
private static boolean isTagInsideCodeOrLiteralBlock(String[] lines,
325+
TextBlock textBlock,
326+
InvalidJavadocTag tag) {
327+
final int tagLineIndex = tag.getLine() - textBlock.getStartLineNo();
328+
329+
final String textBefore = String.join("\n", Arrays.copyOfRange(lines, 0, tagLineIndex));
330+
return isInsideInlineTag(textBefore);
331+
}
332+
333+
/**
334+
* Determines if the position is inside an unclosed {@code @code}, {@code @literal},
335+
* or {@code @snippet} inline tag by counting opening and closing braces.
336+
* These tags display content verbatim and should not be parsed for Javadoc block tags.
337+
*
338+
* @param textBefore the text from the start of Javadoc up to the tag position.
339+
* @return true if inside an unclosed code, literal, or snippet inline tag.
340+
*/
341+
private static boolean isInsideInlineTag(String textBefore) {
342+
boolean insideVerbatimTag = false;
343+
int braceDepth = 0;
344+
345+
for (int index = 0; index < textBefore.length(); index++) {
346+
final char ch = textBefore.charAt(index);
347+
if (ch == '{') {
348+
if (textBefore.startsWith("{@code", index)
349+
|| textBefore.startsWith("{@literal", index)
350+
|| textBefore.startsWith("{@snippet", index)) {
351+
insideVerbatimTag = true;
352+
}
353+
braceDepth++;
354+
}
355+
else if (ch == '}') {
356+
braceDepth--;
357+
if (braceDepth == 0) {
358+
insideVerbatimTag = false;
359+
}
360+
}
361+
}
362+
363+
return insideVerbatimTag;
364+
}
365+
306366
/**
307367
* Verifies that a type definition has a required tag.
308368
*
@@ -315,18 +375,18 @@ private void checkTag(DetailAST ast, Iterable<JavadocTag> tags, String tagName,
315375
Pattern formatPattern) {
316376
if (formatPattern != null) {
317377
boolean hasTag = false;
318-
final String tagPrefix = "@";
319378

320-
for (final JavadocTag tag :tags) {
379+
for (final JavadocTag tag : tags) {
321380
if (tag.getTagName().equals(tagName)) {
322381
hasTag = true;
323382
if (!formatPattern.matcher(tag.getFirstArg()).find()) {
324-
log(ast, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern());
383+
log(ast, MSG_TAG_FORMAT, JAVADOC_TAG_TOKEN + tagName,
384+
formatPattern.pattern());
325385
}
326386
}
327387
}
328388
if (!hasTag) {
329-
log(ast, MSG_MISSING_TAG, tagPrefix + tagName);
389+
log(ast, MSG_MISSING_TAG, JAVADOC_TAG_TOKEN + tagName);
330390
}
331391
}
332392
}
@@ -397,15 +457,15 @@ private void checkUnusedParamTags(
397457
|| recordComponentNames.contains(paramName);
398458

399459
if (!found) {
400-
if (paramName.isEmpty()) {
460+
final String displayName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER
461+
.split(tag.getFirstArg(), -1)[0];
462+
if (displayName.isEmpty()) {
401463
log(tag.getLineNo(), tag.getColumnNo(), MSG_UNUSED_TAG_GENERAL);
402464
}
403465
else {
404-
final String actualParamName =
405-
TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg(), -1)[0];
406466
log(tag.getLineNo(), tag.getColumnNo(),
407467
MSG_UNUSED_TAG,
408-
JavadocTagInfo.PARAM.getText(), actualParamName);
468+
JavadocTagInfo.PARAM.getText(), displayName);
409469
}
410470
}
411471
}
@@ -420,16 +480,16 @@ private void checkUnusedParamTags(
420480
* @return extracts type parameter name from tag
421481
*/
422482
private static String extractParamNameFromTag(JavadocTag tag) {
423-
final String typeParamName;
424-
final Matcher matchInAngleBrackets =
425-
TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg());
483+
final String firstArg = tag.getFirstArg();
484+
final Matcher matchInAngleBrackets = TYPE_NAME_IN_JAVADOC_TAG.matcher(firstArg);
485+
final String paramName;
426486
if (matchInAngleBrackets.find()) {
427-
typeParamName = matchInAngleBrackets.group(1).trim();
487+
paramName = matchInAngleBrackets.group(1).trim();
428488
}
429489
else {
430-
typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg(), -1)[0];
490+
paramName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(firstArg, -1)[0];
431491
}
432-
return typeParamName;
492+
return paramName;
433493
}
434494

435495
/**

src/test/java/com/puppycrawl/tools/checkstyle/checks/javadoc/JavadocTypeCheckTest.java

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -496,4 +496,50 @@ public void testJavadocTypeWithBlockComment() throws Exception {
496496
verifyWithInlineConfigParser(
497497
getPath("InputJavadocTypeWithBlockComment.java"), expected);
498498
}
499+
500+
@Test
501+
public void testAnnotationsInCodeBlock() throws Exception {
502+
final String[] expected = {
503+
"99:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
504+
"106:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
505+
};
506+
verifyWithInlineConfigParser(
507+
getPath("InputJavadocTypeAnnotationsInCodeBlock.java"), expected);
508+
}
509+
510+
@Test
511+
public void testAnnotationsInCodeBlock2() throws Exception {
512+
final String[] expected = {
513+
"28:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
514+
"45:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
515+
"59:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
516+
"67:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
517+
};
518+
verifyWithInlineConfigParser(
519+
getPath("InputJavadocTypeAnnotationsInCodeBlock2.java"), expected);
520+
}
521+
522+
@Test
523+
public void testAnnotationsInCodeBlock3() throws Exception {
524+
final String[] expected = {
525+
"32:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
526+
"78:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
527+
"86:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
528+
"94:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
529+
};
530+
verifyWithInlineConfigParser(
531+
getPath("InputJavadocTypeAnnotationsInCodeBlock3.java"), expected);
532+
}
533+
534+
@Test
535+
public void testAnnotationsInCodeBlock4() throws Exception {
536+
final String[] expected = {
537+
"29:5: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
538+
"42:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
539+
"50:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
540+
"58:4: " + getCheckMessage(MSG_UNKNOWN_TAG, "unknown"),
541+
};
542+
verifyWithInlineConfigParser(
543+
getPath("InputJavadocTypeAnnotationsInCodeBlock4.java"), expected);
544+
}
499545
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
JavadocType
3+
scope = (default)private
4+
excludeScope = (default)null
5+
authorFormat = (default)null
6+
versionFormat = (default)null
7+
allowMissingParamTags = (default)false
8+
allowUnknownTags = (default)false
9+
allowedAnnotations = (default)Generated
10+
tokens = (default)INTERFACE_DEF, CLASS_DEF, ENUM_DEF, ANNOTATION_DEF, RECORD_DEF
11+
12+
13+
*/
14+
15+
package com.puppycrawl.tools.checkstyle.checks.javadoc.javadoctype;
16+
17+
/**
18+
* Annotations inside pre/code blocks should NOT trigger unknown tag violations.
19+
* <pre>{@code
20+
* @Internal
21+
* class Other {
22+
* @Nullable String test(String param) {
23+
* }
24+
* }
25+
* }</pre>
26+
*/
27+
public class InputJavadocTypeAnnotationsInCodeBlock {}
28+
29+
/**
30+
* Example with inline code tag on single line.
31+
* {@code @Pooled class Example {}}
32+
*/
33+
class InlineCodeExample {}
34+
35+
/**
36+
* Example with literal tag.
37+
* {@literal @SomeAnnotation}
38+
*/
39+
class LiteralExample {}
40+
41+
/**
42+
* Multi-line code block with annotations.
43+
* {@code
44+
* @Annotation
45+
* class Foo {}
46+
* }
47+
* This should still work.
48+
*/
49+
class MultiLineCodeExample {}
50+
51+
/**
52+
* Snippet tag example (Java 18+).
53+
* {@snippet lang="java" :
54+
* @Pooled(value = 5)
55+
* public class LegacyApiClient {
56+
* public Response call() { return null; }
57+
* }
58+
* }
59+
*/
60+
class SnippetExample {}
61+
62+
/**
63+
* Complex snippet with multiple annotations.
64+
* {@snippet lang="java" :
65+
* @Pooled
66+
* public class RandomMethod {
67+
* public void myMethod() { }
68+
* }
69+
*
70+
* public class Checkstyle {
71+
*
72+
* @Inject
73+
* Checkstyle checkstyle;
74+
*
75+
* public void run() {
76+
* checkstyle.run();
77+
* }
78+
* }
79+
* }
80+
*/
81+
class ComplexSnippetExample {}
82+
83+
/**
84+
* Code block with nested braces (class definitions).
85+
* {@code
86+
* class Outer {
87+
* @Nested
88+
* class Inner {
89+
* void method() {}
90+
* }
91+
* }
92+
* }
93+
*/
94+
class NestedBracesExample {}
95+
96+
// violation 3 lines below 'Unknown tag 'unknown'.'
97+
/**
98+
* Real unknown tag outside code block.
99+
* @unknown Hello
100+
*/
101+
class RealUnknownTag {}
102+
103+
// violation 3 lines below 'Unknown tag 'unknown'.'
104+
/**
105+
* Mixed content: real tag and code block.
106+
* @unknown outside
107+
* {@code @goodtag inside code block}
108+
*/
109+
class MixedContentExample {}

0 commit comments

Comments
 (0)