Skip to content

Commit 594d7c7

Browse files
committed
Add checkstyle check for required @SInCE tag
Closes gh-130
1 parent 840aed6 commit 594d7c7

File tree

5 files changed

+92
-33
lines changed

5 files changed

+92
-33
lines changed

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

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@
1616

1717
package io.spring.javaformat.checkstyle.check;
1818

19+
import java.util.Collections;
20+
import java.util.HashSet;
21+
import java.util.Set;
1922
import java.util.regex.Matcher;
2023
import java.util.regex.Pattern;
2124

@@ -35,6 +38,18 @@ public class SpringJavadocCheck extends AbstractSpringCheck {
3538

3639
private static final Pattern SINCE_TAG_PATTERN = Pattern.compile("@since\\s+(.*)");
3740

41+
private static final Set<Integer> TOP_LEVEL_TYPES;
42+
static {
43+
Set<Integer> topLevelTypes = new HashSet<Integer>();
44+
topLevelTypes.add(TokenTypes.INTERFACE_DEF);
45+
topLevelTypes.add(TokenTypes.CLASS_DEF);
46+
topLevelTypes.add(TokenTypes.ENUM_DEF);
47+
topLevelTypes.add(TokenTypes.ANNOTATION_DEF);
48+
TOP_LEVEL_TYPES = Collections.unmodifiableSet(topLevelTypes);
49+
}
50+
51+
private boolean requireSinceTag;
52+
3853
private boolean publicOnlySinceTags;
3954

4055
@Override
@@ -55,61 +70,59 @@ public void visitToken(DetailAST ast) {
5570
int lineNumber = ast.getLineNo();
5671
TextBlock javadoc = getFileContents().getJavadocBefore(lineNumber);
5772
if (javadoc != null) {
58-
checkParamTags(ast, javadoc);
73+
checkTagCase(ast, javadoc);
74+
checkSinceTag(ast, javadoc);
5975
}
6076
}
6177

62-
private void checkParamTags(DetailAST ast, TextBlock javadoc) {
78+
private void checkTagCase(DetailAST ast, TextBlock javadoc) {
6379
String[] text = javadoc.getText();
6480
for (int i = 0; i < text.length; i++) {
65-
String line = text[i];
66-
int lineNumber = javadoc.getStartLineNo() + i;
67-
checkCase(line, lineNumber);
68-
checkSinceTag(ast, line, lineNumber);
69-
}
70-
}
71-
72-
private void checkCase(String line, int lineNumber) {
73-
for (Pattern pattern : CASE_CHECKED_TAG_PATTERNS) {
74-
Matcher matcher = pattern.matcher(line);
75-
if (matcher.find()) {
76-
String description = matcher.group(1).trim();
77-
if (startsWithUppercase(description)) {
78-
log(lineNumber, line.length() - description.length(), "javadoc.badCase");
81+
for (Pattern pattern : CASE_CHECKED_TAG_PATTERNS) {
82+
Matcher matcher = pattern.matcher(text[i]);
83+
if (matcher.find()) {
84+
String description = matcher.group(1).trim();
85+
if (startsWithUppercase(description)) {
86+
log(javadoc.getStartLineNo() + i, text[i].length() - description.length(), "javadoc.badCase");
87+
}
7988
}
8089
}
8190
}
8291
}
8392

84-
private void checkSinceTag(DetailAST ast, String line, int lineNumber) {
85-
if (this.publicOnlySinceTags) {
86-
Matcher matcher = SINCE_TAG_PATTERN.matcher(line);
93+
private void checkSinceTag(DetailAST ast, TextBlock javadoc) {
94+
if (!TOP_LEVEL_TYPES.contains(ast.getType())) {
95+
return;
96+
}
97+
String[] text = javadoc.getText();
98+
DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
99+
boolean privateType = modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) == null
100+
&& modifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null;
101+
boolean innerType = ast.getParent() != null;
102+
boolean found = false;
103+
for (int i = 0; i < text.length; i++) {
104+
Matcher matcher = SINCE_TAG_PATTERN.matcher(text[i]);
87105
if (matcher.find()) {
106+
found = true;
88107
String description = matcher.group(1).trim();
89-
DetailAST classDef = getClassDef(ast);
90-
DetailAST classModifiers = classDef.findFirstToken(TokenTypes.MODIFIERS);
91-
if (classModifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) == null
92-
&& classModifiers.findFirstToken(TokenTypes.LITERAL_PROTECTED) == null) {
93-
log(lineNumber, line.length() - description.length(), "javadoc.publicSince");
108+
if (this.publicOnlySinceTags && privateType) {
109+
log(javadoc.getStartLineNo() + i, text[i].length() - description.length(), "javadoc.publicSince");
94110
}
95111
}
96112
}
97-
}
98-
99-
private DetailAST getClassDef(DetailAST ast) {
100-
while (ast != null) {
101-
if (ast.getType() == TokenTypes.CLASS_DEF) {
102-
return ast;
103-
}
104-
ast = ast.getParent();
113+
if (this.requireSinceTag && !innerType && !found && !(this.publicOnlySinceTags && privateType)) {
114+
log(javadoc.getStartLineNo(), 0, "javadoc.missingSince");
105115
}
106-
return null;
107116
}
108117

109118
private boolean startsWithUppercase(String description) {
110119
return description.length() > 0 && Character.isUpperCase(description.charAt(0));
111120
}
112121

122+
public void setRequireSinceTag(boolean requireSinceTag) {
123+
this.requireSinceTag = requireSinceTag;
124+
}
125+
113126
public void setPublicOnlySinceTags(boolean publicOnlySinceTags) {
114127
this.publicOnlySinceTags = publicOnlySinceTags;
115128
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ ternary.equalOperator=Ternary operation should use != when testing.
66
catch.singleLetter=Single letter catch variable (use "ex" instead).
77
javadoc.badCase=Javadoc element descriptions should not start with an uppercase letter.
88
javadoc.publicSince=Javadoc @since tag should not be used on private classes.
9+
javadoc.missingSince=Missing Javadoc @since tag.
910
header.unexpected=Unexpected header.
1011
nothis.unexpected=Reference to instance variable ''{0}'' should not use \"this.\".
1112
methodorder.outOfOrder=Method ''{0}'' is out of order, expected {1}.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
+Missing Javadoc @since tag.
2+
+1 error
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
<?xml version="1.0"?>
2+
<!DOCTYPE module PUBLIC
3+
"-//Checkstyle//DTD Checkstyle Configuration 1.3//EN"
4+
"https://checkstyle.org/dtds/configuration_1_3.dtd">
5+
<module name="com.puppycrawl.tools.checkstyle.Checker">
6+
<module name="com.puppycrawl.tools.checkstyle.TreeWalker">
7+
<module name="io.spring.javaformat.checkstyle.check.SpringJavadocCheck">
8+
<property name="publicOnlySinceTags" value="true" />
9+
<property name="requireSinceTag" value="true" />
10+
</module>
11+
</module>
12+
</module>
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/*
2+
* Copyright 2017-2019 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 bad since tag.
19+
*
20+
* @author Phillip Webb
21+
*/
22+
public class JavadocMissingSince {
23+
24+
/**
25+
* Inner class.
26+
*/
27+
private static class Inner {
28+
29+
}
30+
31+
}

0 commit comments

Comments
 (0)