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.regex.Matcher;
23 import java.util.regex.Pattern;
24
25 import javax.annotation.Nullable;
26
27 import com.puppycrawl.tools.checkstyle.StatelessCheck;
28 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29 import com.puppycrawl.tools.checkstyle.api.DetailAST;
30 import com.puppycrawl.tools.checkstyle.api.SeverityLevel;
31 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
32 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
33 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98 @StatelessCheck
99 public class WriteTagCheck
100 extends AbstractCheck {
101
102
103
104
105
106 public static final String MSG_MISSING_TAG = "type.missingTag";
107
108
109
110
111
112 public static final String MSG_WRITE_TAG = "javadoc.writeTag";
113
114
115
116
117
118 public static final String MSG_TAG_FORMAT = "type.tagFormat";
119
120
121 private static final Pattern LINE_SPLIT_PATTERN = Pattern.compile("\\R");
122
123
124 private Pattern tagRegExp;
125
126 private Pattern tagFormat;
127
128
129 private String tag;
130
131 private SeverityLevel tagSeverity = SeverityLevel.INFO;
132
133
134
135
136
137
138
139 public void setTag(String tag) {
140 this.tag = tag;
141 tagRegExp = CommonUtil.createPattern(tag + "\\s*(.*$)");
142 }
143
144
145
146
147
148
149
150 public void setTagFormat(Pattern pattern) {
151 tagFormat = pattern;
152 }
153
154
155
156
157
158
159
160
161 public final void setTagSeverity(SeverityLevel severity) {
162 tagSeverity = severity;
163 }
164
165 @Override
166 public int[] getDefaultTokens() {
167 return new int[] {
168 TokenTypes.INTERFACE_DEF,
169 TokenTypes.CLASS_DEF,
170 TokenTypes.ENUM_DEF,
171 TokenTypes.ANNOTATION_DEF,
172 TokenTypes.RECORD_DEF,
173 };
174 }
175
176 @Override
177 public int[] getAcceptableTokens() {
178 return new int[] {
179 TokenTypes.INTERFACE_DEF,
180 TokenTypes.CLASS_DEF,
181 TokenTypes.ENUM_DEF,
182 TokenTypes.ANNOTATION_DEF,
183 TokenTypes.METHOD_DEF,
184 TokenTypes.CTOR_DEF,
185 TokenTypes.ENUM_CONSTANT_DEF,
186 TokenTypes.ANNOTATION_FIELD_DEF,
187 TokenTypes.RECORD_DEF,
188 TokenTypes.COMPACT_CTOR_DEF,
189 };
190 }
191
192 @Override
193 public boolean isCommentNodesRequired() {
194 return true;
195 }
196
197 @Override
198 public int[] getRequiredTokens() {
199 return CommonUtil.EMPTY_INT_ARRAY;
200 }
201
202 @Override
203 public void visitToken(DetailAST ast) {
204 final DetailAST javadoc = getJavadoc(ast);
205
206 if (javadoc != null) {
207 final String[] cmtLines = LINE_SPLIT_PATTERN
208 .split(JavadocUtil.getJavadocCommentContent(javadoc));
209
210 checkTag(javadoc.getLineNo(),
211 javadoc.getLineNo() + countCommentLines(javadoc),
212 cmtLines);
213 }
214 }
215
216
217
218
219
220
221
222
223 @Nullable
224 private static DetailAST getJavadoc(DetailAST ast) {
225
226 DetailAST cmt = ast.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
227 if (cmt == null) {
228
229 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
230 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
231
232 cmt = modifiers.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
233 if (cmt == null && type != null) {
234 cmt = type.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
235 }
236 }
237
238 final DetailAST javadoc;
239 if (cmt != null && JavadocUtil.isJavadocComment(cmt)) {
240 javadoc = cmt;
241 }
242 else {
243 javadoc = null;
244 }
245
246 return javadoc;
247 }
248
249
250
251
252
253
254
255 private static int countCommentLines(DetailAST blockComment) {
256 final String content = JavadocUtil.getBlockCommentContent(blockComment);
257 return LINE_SPLIT_PATTERN.split(content).length;
258 }
259
260
261
262
263
264
265
266
267 private void checkTag(int astLineNo, int javadocLineNo, String... comment) {
268 if (tagRegExp != null) {
269 boolean hasTag = false;
270 for (int i = 0; i < comment.length; i++) {
271 final String commentValue = comment[i];
272 final Matcher matcher = tagRegExp.matcher(commentValue);
273 if (matcher.find()) {
274 hasTag = true;
275 final int contentStart = matcher.start(1);
276 final String content = commentValue.substring(contentStart);
277 if (tagFormat == null || tagFormat.matcher(content).find()) {
278 logTag(astLineNo + i, tag, content);
279 }
280 else {
281 log(astLineNo + i, MSG_TAG_FORMAT, tag, tagFormat.pattern());
282 }
283 }
284 }
285 if (!hasTag) {
286 log(javadocLineNo, MSG_MISSING_TAG, tag);
287 }
288 }
289 }
290
291
292
293
294
295
296
297
298
299
300 private void logTag(int line, String tagName, String tagValue) {
301 final String originalSeverity = getSeverity();
302 setSeverity(tagSeverity.getName());
303
304 log(line, MSG_WRITE_TAG, tagName, tagValue);
305
306 setSeverity(originalSeverity);
307 }
308 }