Skip to content

Commit 3d3db5f

Browse files
committed
Check since tags on nested elements
Closes gh-462
1 parent bcd9ab0 commit 3d3db5f

File tree

11 files changed

+238
-17
lines changed

11 files changed

+238
-17
lines changed

spring-javaformat/spring-javaformat-checkstyle/src/main/java/io/spring/javaformat/checkstyle/check/SpringJavadocCheck.java

Lines changed: 128 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ public class SpringJavadocCheck extends AbstractSpringCheck {
8181
@Override
8282
public int[] getDefaultTokens() {
8383
return new int[] { TokenTypes.INTERFACE_DEF, TokenTypes.CLASS_DEF, TokenTypes.ENUM_DEF,
84-
TokenTypes.ANNOTATION_DEF, TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF };
84+
TokenTypes.ANNOTATION_DEF, TokenTypes.METHOD_DEF, TokenTypes.CTOR_DEF, TokenTypes.ANNOTATION_FIELD_DEF };
8585
}
8686

8787
@Override
@@ -130,6 +130,7 @@ private void checkJavadoc(DetailAST ast, TextBlock javadoc) {
130130
checkTagCase(ast, javadoc);
131131
checkSinceTag(ast, javadoc);
132132
checkMethodJavaDoc(ast, javadoc);
133+
checkAnnotationFieldJavaDoc(ast, javadoc);
133134
}
134135

135136
private void checkBannedTags(DetailAST ast, TextBlock javadoc) {
@@ -164,25 +165,54 @@ private void checkSinceTag(DetailAST ast, TextBlock javadoc) {
164165
if (!TOP_LEVEL_TYPES.contains(ast.getType())) {
165166
return;
166167
}
167-
String[] text = javadoc.getText();
168168
DetailAST interfaceOrAnnotationDef = getInterfaceOrAnnotationDef(ast);
169169
boolean privateType = !isPublicOrProtected(ast)
170170
&& (interfaceOrAnnotationDef == null || !isPublicOrProtected(interfaceOrAnnotationDef));
171-
boolean innerType = ast.getParent() != null && ast.getParent().getType() != TokenTypes.COMPILATION_UNIT;
172-
boolean found = false;
173-
for (int i = 0; i < text.length; i++) {
174-
Matcher matcher = SINCE_TAG_PATTERN.matcher(text[i]);
175-
if (matcher.find()) {
176-
found = true;
177-
String description = matcher.group(1).trim();
178-
if (this.publicOnlySinceTags && privateType) {
179-
log(javadoc.getStartLineNo() + i, text[i].length() - description.length(), "javadoc.publicSince");
180-
}
171+
SinceTag sinceTag = SinceTag.find(ast, javadoc);
172+
if (sinceTag != null) {
173+
if (this.publicOnlySinceTags && privateType) {
174+
log(sinceTag.lineNumber, sinceTag.columnNumber, "javadoc.publicSince");
175+
}
176+
else {
177+
checkContainingSince(ast, sinceTag);
181178
}
182179
}
183-
if (this.requireSinceTag && !innerType && !found && !(this.publicOnlySinceTags && privateType)) {
184-
log(javadoc.getStartLineNo(), 0, "javadoc.missingSince");
180+
else {
181+
boolean innerType = ast.getParent() != null && ast.getParent().getType() != TokenTypes.COMPILATION_UNIT;
182+
if (this.requireSinceTag && !innerType && sinceTag == null && !(this.publicOnlySinceTags && privateType)) {
183+
log(javadoc.getStartLineNo(), 0, "javadoc.missingSince");
184+
}
185+
}
186+
}
187+
188+
private void checkContainingSince(DetailAST ast, SinceTag currentTag) {
189+
SinceTag containingSince = findContainingSince(ast);
190+
if (containingSince != null) {
191+
SinceVersion current = currentTag.version;
192+
SinceVersion container = containingSince.version;
193+
int comparison = current.compareTo(container);
194+
if (comparison < 0) {
195+
log(currentTag.lineNumber, currentTag.columnNumber, "javadoc.earlierSince", current, container, containingSince.lineNumber, containingSince.columnNumber);
196+
}
197+
else if (comparison == 0) {
198+
log(currentTag.lineNumber, currentTag.columnNumber, "javadoc.sameSince", current, containingSince.lineNumber, containingSince.columnNumber);
199+
}
200+
}
201+
}
202+
203+
private SinceTag findContainingSince(DetailAST ast) {
204+
DetailAST parent = ast.getParent();
205+
while (parent != null && parent.getType() != TokenTypes.COMPILATION_UNIT) {
206+
TextBlock javadoc = getFileContents().getJavadocBefore(parent.getLineNo());
207+
if (javadoc != null) {
208+
SinceTag sinceTag = SinceTag.find(ast, javadoc);
209+
if (sinceTag != null) {
210+
return sinceTag;
211+
}
212+
}
213+
parent = parent.getParent();
185214
}
215+
return null;
186216
}
187217

188218
private void checkMethodJavaDoc(DetailAST ast, TextBlock javadoc) {
@@ -196,6 +226,20 @@ private void checkMethodJavaDoc(DetailAST ast, TextBlock javadoc) {
196226
log(javadoc.getStartLineNo() + i - 1, 0, "javadoc.emptyLineBeforeTag");
197227
}
198228
}
229+
SinceTag sinceTag = SinceTag.find(ast, javadoc);
230+
if (sinceTag != null) {
231+
checkContainingSince(ast, sinceTag);
232+
}
233+
}
234+
235+
private void checkAnnotationFieldJavaDoc(DetailAST ast, TextBlock javadoc) {
236+
if (TokenTypes.ANNOTATION_FIELD_DEF != ast.getType()) {
237+
return;
238+
}
239+
SinceTag sinceTag = SinceTag.find(ast, javadoc);
240+
if (sinceTag != null) {
241+
checkContainingSince(ast, sinceTag);
242+
}
199243
}
200244

201245
private boolean startsWithUppercase(String description) {
@@ -251,4 +295,74 @@ private boolean isPublicOrProtected(DetailAST ast) {
251295
|| modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) != null;
252296
}
253297

298+
private static final class SinceTag {
299+
300+
private final int lineNumber;
301+
302+
private final int columnNumber;
303+
304+
private final SinceVersion version;
305+
306+
private SinceTag(int lineNumber, int columnNumber, SinceVersion version) {
307+
this.lineNumber = lineNumber;
308+
this.columnNumber = columnNumber;
309+
this.version = version;
310+
}
311+
312+
private static SinceTag find(DetailAST ast, TextBlock javadoc) {
313+
String[] text = javadoc.getText();
314+
for (int i = 0; i < text.length; i++) {
315+
Matcher matcher = SINCE_TAG_PATTERN.matcher(text[i]);
316+
if (matcher.find()) {
317+
String description = matcher.group(1).trim();
318+
return new SinceTag(javadoc.getStartLineNo() + i, text[i].length() - description.length(), SinceVersion.of(description));
319+
}
320+
}
321+
return null;
322+
}
323+
}
324+
325+
private static final class SinceVersion implements Comparable<SinceVersion> {
326+
327+
private final int major;
328+
329+
private final int minor;
330+
331+
private final int patch;
332+
333+
private final String text;
334+
335+
private SinceVersion(int major, int minor, int patch, String text) {
336+
this.major = major;
337+
this.minor = minor;
338+
this.patch = patch;
339+
this.text = text;
340+
}
341+
342+
private static SinceVersion of(String text) {
343+
String[] components = text.split("\\.");
344+
int major = (components.length > 0) ? Integer.parseInt(components[0]) : 0;
345+
int minor = (components.length > 1) ? Integer.parseInt(components[1]) : 0;
346+
int patch = (components.length > 2) ? Integer.parseInt(components[2]) : 0;
347+
return new SinceVersion(major, minor, patch, text);
348+
}
349+
350+
public String toString() {
351+
return this.text;
352+
}
353+
354+
@Override
355+
public int compareTo(SinceVersion other) {
356+
int diff = this.major - other.major;
357+
if (diff == 0) {
358+
diff = this.minor - other.minor;
359+
if (diff == 0) {
360+
diff = this.patch - other.patch;
361+
}
362+
}
363+
return diff;
364+
}
365+
366+
}
367+
254368
}

spring-javaformat/spring-javaformat-checkstyle/src/main/resources/io/spring/javaformat/checkstyle/check/messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ import.avoidStatic=Using a static member import should be avoided - {0}.
1111
import.ordering=Wrong order for ''{0}'' import.
1212
javadoc.badCase=Javadoc element descriptions should not start with an uppercase letter.
1313
javadoc.bannedTag=Javadoc tag ''{0}'' should not be used.
14+
javadoc.earlierSince=Javadoc @since version ''{0}'' is earlier than @since version ''{1}'' at {2}:{3}.
1415
javadoc.missingSince=Missing Javadoc @since tag.
16+
javadoc.sameSince=Javadoc @since version ''{0}'' is unnecessary as it is the same as the @since version at {1}:{2}.
1517
javadoc.publicSince=Javadoc @since tag should not be used on private classes.
1618
javadoc.emptyLineBeforeTag=Method Javadoc should not have empty line before tag.
1719
javadoc.nonJavadocComment=Comments should not include \"(non-Javadoc)\".
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
+28:19: Javadoc @since version '1.1.0' is earlier than @since version '1.2.0' at 21:10.
2+
+1 error
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
+28:19: Javadoc @since version '1.2.3' is earlier than @since version '2.0.0' at 21:10.
2+
+1 error
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
+27:19: Javadoc @since version '1.2.3' is earlier than @since version '2.0.0' at 21:10.
2+
+1 error
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright 2017-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Javadoc with a good since tag.
19+
*
20+
* @author Andy Wilkinson
21+
* @since 1.2.0
22+
*/
23+
public @interface JavadocAnnotationFieldHasEarlierSince {
24+
25+
/**
26+
* An attribute.
27+
* @return the value
28+
* @since 1.1.0
29+
*/
30+
boolean attribute() default true;
31+
32+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
/*
2+
* Copyright 2017-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Javadoc with an earlier since tag on an inner class.
19+
*
20+
* @author Phillip Webb
21+
* @since 2.0.0
22+
*/
23+
class JavadocInnerClassHasEarlierSince {
24+
25+
/**
26+
* Inner class.
27+
*
28+
* @since 1.2.3
29+
*/
30+
private static class Inner {
31+
32+
}
33+
34+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2017-present the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
/**
18+
* Javadoc with an earlier since tag on a method.
19+
*
20+
* @author Andy Wilkinson
21+
* @since 2.0.0
22+
*/
23+
public class JavadocMethodHasEarlierSince {
24+
25+
/**
26+
* Some method.
27+
* @since 1.2.3
28+
*/
29+
public void someMethod() {
30+
31+
}
32+
33+
}

spring-javaformat/spring-javaformat-checkstyle/src/test/resources/source/JavadocNonPublicSince.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class JavadocNonPublicSince {
2525
/**
2626
* Inner class.
2727
*
28-
* @since 1.2.3
28+
* @since 1.2.4
2929
*/
3030
private static class Inner {
3131

spring-javaformat/spring-javaformat-checkstyle/src/test/resources/source/JavadocNonPublicSinceInsideAnnotation.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
/**
2626
* Inner enum.
2727
*
28-
* @since 1.2.3
28+
* @since 1.2.4
2929
*/
3030
enum Inner {
3131

0 commit comments

Comments
 (0)