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 @StatelessCheck
46 public class WriteTagCheck
47 extends AbstractCheck {
48
49
50
51
52
53 public static final String MSG_MISSING_TAG = "type.missingTag";
54
55
56
57
58
59 public static final String MSG_WRITE_TAG = "javadoc.writeTag";
60
61
62
63
64
65 public static final String MSG_TAG_FORMAT = "type.tagFormat";
66
67
68 private static final Pattern LINE_SPLIT_PATTERN = Pattern.compile("\\R");
69
70
71 private Pattern tagRegExp;
72
73 private Pattern tagFormat;
74
75
76 private String tag;
77
78 private SeverityLevel tagSeverity = SeverityLevel.INFO;
79
80
81
82
83
84
85
86 public void setTag(String tag) {
87 this.tag = tag;
88 tagRegExp = CommonUtil.createPattern(tag + "\\s*(.*$)");
89 }
90
91
92
93
94
95
96
97 public void setTagFormat(Pattern pattern) {
98 tagFormat = pattern;
99 }
100
101
102
103
104
105
106
107
108 public final void setTagSeverity(SeverityLevel severity) {
109 tagSeverity = severity;
110 }
111
112 @Override
113 public int[] getDefaultTokens() {
114 return new int[] {
115 TokenTypes.INTERFACE_DEF,
116 TokenTypes.CLASS_DEF,
117 TokenTypes.ENUM_DEF,
118 TokenTypes.ANNOTATION_DEF,
119 TokenTypes.RECORD_DEF,
120 };
121 }
122
123 @Override
124 public int[] getAcceptableTokens() {
125 return new int[] {
126 TokenTypes.INTERFACE_DEF,
127 TokenTypes.CLASS_DEF,
128 TokenTypes.ENUM_DEF,
129 TokenTypes.ANNOTATION_DEF,
130 TokenTypes.METHOD_DEF,
131 TokenTypes.CTOR_DEF,
132 TokenTypes.ENUM_CONSTANT_DEF,
133 TokenTypes.ANNOTATION_FIELD_DEF,
134 TokenTypes.RECORD_DEF,
135 TokenTypes.COMPACT_CTOR_DEF,
136 };
137 }
138
139 @Override
140 public boolean isCommentNodesRequired() {
141 return true;
142 }
143
144 @Override
145 public int[] getRequiredTokens() {
146 return CommonUtil.EMPTY_INT_ARRAY;
147 }
148
149 @Override
150 public void visitToken(DetailAST ast) {
151 final DetailAST javadoc = getJavadoc(ast);
152
153 if (javadoc != null) {
154 final String[] cmtLines = LINE_SPLIT_PATTERN
155 .split(JavadocUtil.getJavadocCommentContent(javadoc));
156
157 checkTag(javadoc.getLineNo(),
158 javadoc.getLineNo() + countCommentLines(javadoc),
159 cmtLines);
160 }
161 }
162
163
164
165
166
167
168
169
170 @Nullable
171 private static DetailAST getJavadoc(DetailAST ast) {
172
173 DetailAST cmt = ast.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
174 if (cmt == null) {
175
176 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
177 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
178
179 cmt = modifiers.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
180 if (cmt == null && type != null) {
181 cmt = type.findFirstToken(TokenTypes.BLOCK_COMMENT_BEGIN);
182 }
183 }
184
185 final DetailAST javadoc;
186 if (cmt != null && JavadocUtil.isJavadocComment(cmt)) {
187 javadoc = cmt;
188 }
189 else {
190 javadoc = null;
191 }
192
193 return javadoc;
194 }
195
196
197
198
199
200
201
202 private static int countCommentLines(DetailAST blockComment) {
203 final String content = JavadocUtil.getBlockCommentContent(blockComment);
204 return LINE_SPLIT_PATTERN.split(content).length;
205 }
206
207
208
209
210
211
212
213
214 private void checkTag(int astLineNo, int javadocLineNo, String... comment) {
215 if (tagRegExp != null) {
216 boolean hasTag = false;
217 for (int i = 0; i < comment.length; i++) {
218 final String commentValue = comment[i];
219 final Matcher matcher = tagRegExp.matcher(commentValue);
220 if (matcher.find()) {
221 hasTag = true;
222 final int contentStart = matcher.start(1);
223 final String content = commentValue.substring(contentStart);
224 if (tagFormat == null || tagFormat.matcher(content).find()) {
225 logTag(astLineNo + i, tag, content);
226 }
227 else {
228 log(astLineNo + i, MSG_TAG_FORMAT, tag, tagFormat.pattern());
229 }
230 }
231 }
232 if (!hasTag) {
233 log(javadocLineNo, MSG_MISSING_TAG, tag);
234 }
235 }
236 }
237
238
239
240
241
242
243
244
245
246
247 private void logTag(int line, String tagName, String tagValue) {
248 final String originalSeverity = getSeverity();
249 setSeverity(tagSeverity.getName());
250
251 log(line, MSG_WRITE_TAG, tagName, tagValue);
252
253 setSeverity(originalSeverity);
254 }
255 }