1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 package com.puppycrawl.tools.checkstyle.checks.javadoc;
21
22 import java.util.Arrays;
23 import java.util.Collections;
24 import java.util.List;
25 import java.util.regex.Matcher;
26 import java.util.regex.Pattern;
27
28 import com.puppycrawl.tools.checkstyle.StatelessCheck;
29 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
30 import com.puppycrawl.tools.checkstyle.api.DetailAST;
31 import com.puppycrawl.tools.checkstyle.api.FileContents;
32 import com.puppycrawl.tools.checkstyle.api.Scope;
33 import com.puppycrawl.tools.checkstyle.api.TextBlock;
34 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
35 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
36 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
37 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
38 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
39 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
40
41
42
43
44
45
46
47
48 @StatelessCheck
49 public class JavadocTypeCheck
50 extends AbstractCheck {
51
52
53
54
55
56 public static final String MSG_UNKNOWN_TAG = "javadoc.unknownTag";
57
58
59
60
61
62 public static final String MSG_TAG_FORMAT = "type.tagFormat";
63
64
65
66
67
68 public static final String MSG_MISSING_TAG = "type.missingTag";
69
70
71
72
73
74 public static final String MSG_UNUSED_TAG = "javadoc.unusedTag";
75
76
77
78
79
80 public static final String MSG_UNUSED_TAG_GENERAL = "javadoc.unusedTagGeneral";
81
82
83 private static final String OPEN_ANGLE_BRACKET = "<";
84
85
86 private static final String CLOSE_ANGLE_BRACKET = ">";
87
88
89 private static final Pattern TYPE_NAME_IN_JAVADOC_TAG =
90 Pattern.compile("\\s*<([^>]+)>.*");
91
92
93 private static final Pattern TYPE_NAME_IN_JAVADOC_TAG_SPLITTER =
94 Pattern.compile("\\s+");
95
96
97 private Scope scope = Scope.PRIVATE;
98
99 private Scope excludeScope;
100
101 private Pattern authorFormat;
102
103 private Pattern versionFormat;
104
105
106
107
108 private boolean allowMissingParamTags;
109
110 private boolean allowUnknownTags;
111
112
113 private List<String> allowedAnnotations = Collections.singletonList("Generated");
114
115
116
117
118
119 public void setScope(Scope scope) {
120 this.scope = scope;
121 }
122
123
124
125
126
127 public void setExcludeScope(Scope excludeScope) {
128 this.excludeScope = excludeScope;
129 }
130
131
132
133
134
135 public void setAuthorFormat(Pattern pattern) {
136 authorFormat = pattern;
137 }
138
139
140
141
142
143 public void setVersionFormat(Pattern pattern) {
144 versionFormat = pattern;
145 }
146
147
148
149
150
151
152
153 public void setAllowMissingParamTags(boolean flag) {
154 allowMissingParamTags = flag;
155 }
156
157
158
159
160
161 public void setAllowUnknownTags(boolean flag) {
162 allowUnknownTags = flag;
163 }
164
165
166
167
168
169 public void setAllowedAnnotations(String... userAnnotations) {
170 allowedAnnotations = Arrays.asList(userAnnotations);
171 }
172
173 @Override
174 public int[] getDefaultTokens() {
175 return getAcceptableTokens();
176 }
177
178 @Override
179 public int[] getAcceptableTokens() {
180 return new int[] {
181 TokenTypes.INTERFACE_DEF,
182 TokenTypes.CLASS_DEF,
183 TokenTypes.ENUM_DEF,
184 TokenTypes.ANNOTATION_DEF,
185 };
186 }
187
188 @Override
189 public int[] getRequiredTokens() {
190 return CommonUtil.EMPTY_INT_ARRAY;
191 }
192
193 @Override
194 public void visitToken(DetailAST ast) {
195 if (shouldCheck(ast)) {
196 final FileContents contents = getFileContents();
197 final int lineNo = ast.getLineNo();
198 final TextBlock textBlock = contents.getJavadocBefore(lineNo);
199 if (textBlock != null) {
200 final List<JavadocTag> tags = getJavadocTags(textBlock);
201 if (ScopeUtil.isOuterMostType(ast)) {
202
203 checkTag(lineNo, tags, JavadocTagInfo.AUTHOR.getName(),
204 authorFormat);
205 checkTag(lineNo, tags, JavadocTagInfo.VERSION.getName(),
206 versionFormat);
207 }
208
209 final List<String> typeParamNames =
210 CheckUtil.getTypeParameterNames(ast);
211
212 if (!allowMissingParamTags) {
213
214 for (final String typeParamName : typeParamNames) {
215 checkTypeParamTag(
216 lineNo, tags, typeParamName);
217 }
218 }
219
220 checkUnusedTypeParamTags(tags, typeParamNames);
221 }
222 }
223 }
224
225
226
227
228
229
230 private boolean shouldCheck(final DetailAST ast) {
231 final Scope customScope;
232
233 if (ScopeUtil.isInInterfaceOrAnnotationBlock(ast)) {
234 customScope = Scope.PUBLIC;
235 }
236 else {
237 final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
238 customScope = ScopeUtil.getScopeFromMods(mods);
239 }
240 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast);
241
242 return customScope.isIn(scope)
243 && (surroundingScope == null || surroundingScope.isIn(scope))
244 && (excludeScope == null
245 || !customScope.isIn(excludeScope)
246 || surroundingScope != null
247 && !surroundingScope.isIn(excludeScope))
248 && !AnnotationUtil.containsAnnotation(ast, allowedAnnotations);
249 }
250
251
252
253
254
255
256 private List<JavadocTag> getJavadocTags(TextBlock textBlock) {
257 final JavadocTags tags = JavadocUtil.getJavadocTags(textBlock,
258 JavadocUtil.JavadocTagType.BLOCK);
259 if (!allowUnknownTags) {
260 for (final InvalidJavadocTag tag : tags.getInvalidTags()) {
261 log(tag.getLine(), tag.getCol(), MSG_UNKNOWN_TAG,
262 tag.getName());
263 }
264 }
265 return tags.getValidTags();
266 }
267
268
269
270
271
272
273
274
275 private void checkTag(int lineNo, List<JavadocTag> tags, String tagName,
276 Pattern formatPattern) {
277 if (formatPattern != null) {
278 boolean hasTag = false;
279 final String tagPrefix = "@";
280 for (int i = tags.size() - 1; i >= 0; i--) {
281 final JavadocTag tag = tags.get(i);
282 if (tag.getTagName().equals(tagName)) {
283 hasTag = true;
284 if (!formatPattern.matcher(tag.getFirstArg()).find()) {
285 log(lineNo, MSG_TAG_FORMAT, tagPrefix + tagName, formatPattern.pattern());
286 }
287 }
288 }
289 if (!hasTag) {
290 log(lineNo, MSG_MISSING_TAG, tagPrefix + tagName);
291 }
292 }
293 }
294
295
296
297
298
299
300
301
302 private void checkTypeParamTag(final int lineNo,
303 final List<JavadocTag> tags, final String typeParamName) {
304 boolean found = false;
305 for (int i = tags.size() - 1; i >= 0; i--) {
306 final JavadocTag tag = tags.get(i);
307 if (tag.isParamTag()
308 && tag.getFirstArg().indexOf(OPEN_ANGLE_BRACKET
309 + typeParamName + CLOSE_ANGLE_BRACKET) == 0) {
310 found = true;
311 break;
312 }
313 }
314 if (!found) {
315 log(lineNo, MSG_MISSING_TAG, JavadocTagInfo.PARAM.getText()
316 + " " + OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET);
317 }
318 }
319
320
321
322
323
324
325 private void checkUnusedTypeParamTags(
326 final List<JavadocTag> tags,
327 final List<String> typeParamNames) {
328 for (int i = tags.size() - 1; i >= 0; i--) {
329 final JavadocTag tag = tags.get(i);
330 if (tag.isParamTag()) {
331 final String typeParamName = extractTypeParamNameFromTag(tag);
332
333 if (!typeParamNames.contains(typeParamName)) {
334 log(tag.getLineNo(), tag.getColumnNo(),
335 MSG_UNUSED_TAG,
336 JavadocTagInfo.PARAM.getText(),
337 OPEN_ANGLE_BRACKET + typeParamName + CLOSE_ANGLE_BRACKET);
338 }
339 }
340 }
341 }
342
343
344
345
346
347
348 private static String extractTypeParamNameFromTag(JavadocTag tag) {
349 final String typeParamName;
350 final Matcher matchInAngleBrackets =
351 TYPE_NAME_IN_JAVADOC_TAG.matcher(tag.getFirstArg());
352 if (matchInAngleBrackets.find()) {
353 typeParamName = matchInAngleBrackets.group(1).trim();
354 }
355 else {
356 typeParamName = TYPE_NAME_IN_JAVADOC_TAG_SPLITTER.split(tag.getFirstArg())[0];
357 }
358 return typeParamName;
359 }
360
361 }