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.ArrayDeque;
23 import java.util.Deque;
24 import java.util.regex.Pattern;
25
26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.DetailNode;
29 import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
30 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
32 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 @FileStatefulCheck
49 public class WriteTagCheck extends AbstractJavadocCheck {
50
51
52
53
54
55 public static final String MSG_MISSING_TAG = "writetag.missingTag";
56
57
58
59
60
61 public static final String MSG_TAG_FORMAT = "writetag.tagFormat";
62
63
64 private Pattern tagFormat;
65
66
67 private String tag;
68
69
70 private boolean tagFound;
71
72
73 private DetailAST parentAst;
74
75
76
77
78 public WriteTagCheck() {
79
80 }
81
82
83
84
85
86
87
88 public void setTag(String tag) {
89 this.tag = tag;
90 }
91
92
93
94
95
96
97
98 public void setTagFormat(Pattern pattern) {
99 tagFormat = pattern;
100 }
101
102
103
104
105
106
107
108
109
110
111
112 @Override
113 public void setViolateExecutionOnNonTightHtml(boolean shouldReportViolation) {
114 super.setViolateExecutionOnNonTightHtml(shouldReportViolation);
115 }
116
117 @Override
118 public int[] getRequiredTokens() {
119 return CommonUtil.EMPTY_INT_ARRAY;
120 }
121
122 @Override
123 public int[] getDefaultTokens() {
124 return new int[] {
125 TokenTypes.INTERFACE_DEF,
126 TokenTypes.CLASS_DEF,
127 TokenTypes.ENUM_DEF,
128 TokenTypes.ANNOTATION_DEF,
129 TokenTypes.RECORD_DEF,
130 };
131 }
132
133 @Override
134 public int[] getAcceptableTokens() {
135 return new int[] {
136 TokenTypes.INTERFACE_DEF,
137 TokenTypes.CLASS_DEF,
138 TokenTypes.ENUM_DEF,
139 TokenTypes.ANNOTATION_DEF,
140 TokenTypes.METHOD_DEF,
141 TokenTypes.CTOR_DEF,
142 TokenTypes.ENUM_CONSTANT_DEF,
143 TokenTypes.ANNOTATION_FIELD_DEF,
144 TokenTypes.RECORD_DEF,
145 TokenTypes.COMPACT_CTOR_DEF,
146 };
147 }
148
149 @Override
150 public int[] getDefaultJavadocTokens() {
151 return new int[] {
152 JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG,
153 };
154 }
155
156 @Override
157 public int[] getRequiredJavadocTokens() {
158 return getAcceptableJavadocTokens();
159 }
160
161 @Override
162 public void visitToken(DetailAST ast) {
163 final DetailAST javadocComment = JavadocUtil.getAttachedJavadocComment(ast);
164 if (javadocComment != null) {
165 parentAst = ast;
166 super.visitToken(javadocComment);
167 }
168 }
169
170 @Override
171 public void beginJavadocTree(DetailNode rootAst) {
172 tagFound = false;
173 }
174
175 @Override
176 public void visitJavadocToken(DetailNode ast) {
177 final String tagName = "@" + JavadocUtil.getTagName(ast);
178 if (tagName.equals(tag)) {
179 tagFound = true;
180 final String content = getTagContent(ast);
181
182 if (tagFormat != null && !tagFormat.matcher(content).find()) {
183 log(ast.getLineNumber(), MSG_TAG_FORMAT, tag, tagFormat.pattern());
184 }
185 }
186 }
187
188 @Override
189 public void finishJavadocTree(DetailNode rootAst) {
190 if (tag != null && !tagFound) {
191 log(parentAst, MSG_MISSING_TAG, tag);
192 }
193 }
194
195
196
197
198
199
200
201
202 private static String getTagContent(DetailNode javadocBlockTagNode) {
203 final DetailNode tagNodeNextSibling = JavadocUtil.findFirstToken(
204 javadocBlockTagNode.getFirstChild(),
205 JavadocCommentsTokenTypes.TAG_NAME).getNextSibling();
206
207 final int stringBuilderCapacity = 128;
208 final StringBuilder rawTextBuilder = new StringBuilder(stringBuilderCapacity);
209 if (tagNodeNextSibling != null) {
210
211 final Deque<DetailNode> stack = new ArrayDeque<>();
212 stack.push(tagNodeNextSibling);
213
214 while (!stack.isEmpty()) {
215 final DetailNode currentNode = stack.pop();
216
217
218 if (currentNode.getFirstChild() == null) {
219 rawTextBuilder.append(currentNode.getText());
220 }
221
222 final DetailNode nextSibling = currentNode.getNextSibling();
223 final DetailNode firstChild = currentNode.getFirstChild();
224
225 if (nextSibling != null) {
226 stack.push(nextSibling);
227 }
228 if (firstChild != null) {
229 stack.push(firstChild);
230 }
231 }
232 }
233
234 return rawTextBuilder.toString().stripLeading();
235 }
236
237 }