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.ArrayList;
23 import java.util.List;
24
25 import com.puppycrawl.tools.checkstyle.StatelessCheck;
26 import com.puppycrawl.tools.checkstyle.api.DetailNode;
27 import com.puppycrawl.tools.checkstyle.api.JavadocCommentsTokenTypes;
28 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
29 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48 @StatelessCheck
49 public class JavadocTagContinuationIndentationCheck extends AbstractJavadocCheck {
50
51
52
53
54
55 public static final String MSG_KEY = "tag.continuation.indent";
56
57
58 private static final int DEFAULT_INDENTATION = 4;
59
60
61
62
63 private static final String PRE_TAG = "pre";
64
65
66
67
68 private int offset = DEFAULT_INDENTATION;
69
70
71
72
73
74
75
76 public void setOffset(int offset) {
77 this.offset = offset;
78 }
79
80 @Override
81 public int[] getDefaultJavadocTokens() {
82 return new int[] {
83 JavadocCommentsTokenTypes.HTML_ELEMENT,
84 JavadocCommentsTokenTypes.DESCRIPTION,
85 };
86 }
87
88 @Override
89 public int[] getRequiredJavadocTokens() {
90 return getAcceptableJavadocTokens();
91 }
92
93 @Override
94 public void visitJavadocToken(DetailNode ast) {
95 if (isBlockDescription(ast) && !isInlineDescription(ast)) {
96 final List<DetailNode> textNodes = getTargetedTextNodes(ast);
97 for (DetailNode textNode : textNodes) {
98 if (isViolation(textNode)) {
99 log(textNode.getLineNumber(), MSG_KEY, offset);
100 }
101 }
102 }
103 }
104
105
106
107
108
109
110
111
112
113 private static List<DetailNode> getTargetedTextNodes(DetailNode ast) {
114 final List<DetailNode> textNodes;
115 if (ast.getType() == JavadocCommentsTokenTypes.DESCRIPTION) {
116 textNodes = getTargetedTextNodesInsideDescription(ast);
117 }
118 else {
119 textNodes = getTargetedTextNodesInsideHtmlElement(ast);
120 }
121 return textNodes;
122 }
123
124
125
126
127
128
129
130 private static List<DetailNode> getTargetedTextNodesInsideHtmlElement(DetailNode ast) {
131 final List<DetailNode> textNodes = new ArrayList<>();
132
133 if (!JavadocUtil.isTag(ast, PRE_TAG) && !isInsidePreTag(ast)) {
134 DetailNode node = ast.getFirstChild();
135 while (node != null) {
136 if (node.getType() == JavadocCommentsTokenTypes.HTML_CONTENT) {
137
138
139 textNodes.addAll(getTargetedTextNodesInsideDescription(node));
140 }
141 else if (subtreeContainsAttributeValue(node)) {
142 textNodes.addAll(getTargetedTextNodesInsideHtmlElement(node));
143 }
144 else if (isTargetTextNode(node)) {
145 textNodes.add(node);
146 }
147 node = node.getNextSibling();
148 }
149 }
150 return textNodes;
151 }
152
153
154
155
156
157
158
159
160 private static boolean subtreeContainsAttributeValue(DetailNode node) {
161 return node.getType() == JavadocCommentsTokenTypes.HTML_TAG_START
162 || node.getType() == JavadocCommentsTokenTypes.HTML_ATTRIBUTES
163 || node.getType() == JavadocCommentsTokenTypes.HTML_ATTRIBUTE;
164 }
165
166
167
168
169
170
171
172 private static List<DetailNode> getTargetedTextNodesInsideDescription(
173 DetailNode descriptionNode) {
174 final List<DetailNode> textNodes = new ArrayList<>();
175 DetailNode node = descriptionNode.getFirstChild();
176 final DetailNode previousSibling = descriptionNode.getPreviousSibling();
177
178
179 if (isTargetTextNode(previousSibling)) {
180 textNodes.add(previousSibling);
181 }
182
183
184
185 if (descriptionNode.getPreviousSibling().getType()
186 == JavadocCommentsTokenTypes.LEADING_ASTERISK) {
187 textNodes.add(node);
188 }
189
190 while (node != null) {
191 if (isTargetTextNode(node)) {
192 textNodes.add(node);
193 }
194 node = node.getNextSibling();
195 }
196
197 return textNodes;
198 }
199
200
201
202
203
204
205
206 private static boolean isTargetTextNode(DetailNode node) {
207 final DetailNode previousSibling = node.getPreviousSibling();
208
209 return previousSibling != null
210 && isTextOrAttributeValueNode(node)
211 && !isBeforePreTag(node)
212 && previousSibling.getType() == JavadocCommentsTokenTypes.LEADING_ASTERISK;
213 }
214
215
216
217
218
219
220
221 private static boolean isBeforePreTag(DetailNode node) {
222 final DetailNode nextSibling = node.getNextSibling();
223 final boolean isBeforePreTag;
224 if (nextSibling != null
225 && nextSibling.getType() == JavadocCommentsTokenTypes.DESCRIPTION) {
226 isBeforePreTag = JavadocUtil.isTag(nextSibling.getFirstChild(), PRE_TAG);
227 }
228 else if (nextSibling != null) {
229 isBeforePreTag = JavadocUtil.isTag(nextSibling, PRE_TAG);
230 }
231 else {
232 isBeforePreTag = false;
233 }
234 return isBeforePreTag;
235 }
236
237
238
239
240
241
242
243 private static boolean isInsidePreTag(DetailNode node) {
244 final DetailNode htmlElementParent = node.getParent().getParent();
245 return JavadocUtil.isTag(htmlElementParent, PRE_TAG);
246 }
247
248
249
250
251
252
253
254 private static boolean isTextOrAttributeValueNode(DetailNode node) {
255 return node.getType() == JavadocCommentsTokenTypes.TEXT
256 || node.getType() == JavadocCommentsTokenTypes.ATTRIBUTE_VALUE;
257 }
258
259
260
261
262
263
264
265
266
267
268
269 private boolean isViolation(DetailNode textNode) {
270 boolean result = false;
271 final String text = textNode.getText();
272 if (text.length() <= offset) {
273 if (CommonUtil.isBlank(text)) {
274 final DetailNode nextNode = textNode.getNextSibling();
275 if (nextNode.getType() != JavadocCommentsTokenTypes.NEWLINE) {
276
277 result = true;
278 }
279 }
280 else {
281
282 result = true;
283 }
284 }
285 else if (!CommonUtil.isBlank(text.substring(1, offset + 1))) {
286
287 result = true;
288 }
289 return result;
290 }
291
292
293
294
295
296
297
298 private static boolean isBlockDescription(DetailNode description) {
299 boolean isBlock = false;
300 DetailNode currentNode = description;
301 while (currentNode != null) {
302 if (currentNode.getType() == JavadocCommentsTokenTypes.JAVADOC_BLOCK_TAG) {
303 isBlock = true;
304 break;
305 }
306 currentNode = currentNode.getParent();
307 }
308 return isBlock;
309 }
310
311
312
313
314
315
316
317 private static boolean isInlineDescription(DetailNode description) {
318 boolean isInline = false;
319 DetailNode currentNode = description;
320 while (currentNode != null) {
321 if (currentNode.getType() == JavadocCommentsTokenTypes.JAVADOC_INLINE_TAG) {
322 isInline = true;
323 break;
324 }
325 currentNode = currentNode.getParent();
326 }
327 return isInline;
328 }
329 }