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.Set;
23
24 import javax.annotation.Nullable;
25
26 import com.puppycrawl.tools.checkstyle.StatelessCheck;
27 import com.puppycrawl.tools.checkstyle.api.DetailNode;
28 import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
29 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
30 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
31
32
33
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
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117 @StatelessCheck
118 public class JavadocParagraphCheck extends AbstractJavadocCheck {
119
120
121
122
123
124 public static final String MSG_TAG_AFTER = "javadoc.paragraph.tag.after";
125
126
127
128
129
130 public static final String MSG_LINE_BEFORE = "javadoc.paragraph.line.before";
131
132
133
134
135
136 public static final String MSG_REDUNDANT_PARAGRAPH = "javadoc.paragraph.redundant.paragraph";
137
138
139
140
141
142 public static final String MSG_MISPLACED_TAG = "javadoc.paragraph.misplaced.tag";
143
144
145
146
147
148 public static final String MSG_PRECEDED_BLOCK_TAG = "javadoc.paragraph.preceded.block.tag";
149
150
151
152
153 private static final Set<String> BLOCK_TAGS =
154 Set.of("address", "blockquote", "div", "dl",
155 "h1", "h2", "h3", "h4", "h5", "h6", "hr",
156 "ol", "p", "pre", "table", "ul");
157
158
159
160
161 private boolean allowNewlineParagraph = true;
162
163
164
165
166
167
168
169
170 public void setAllowNewlineParagraph(boolean value) {
171 allowNewlineParagraph = value;
172 }
173
174 @Override
175 public int[] getDefaultJavadocTokens() {
176 return new int[] {
177 JavadocTokenTypes.NEWLINE,
178 JavadocTokenTypes.HTML_ELEMENT,
179 };
180 }
181
182 @Override
183 public int[] getRequiredJavadocTokens() {
184 return getAcceptableJavadocTokens();
185 }
186
187 @Override
188 public void visitJavadocToken(DetailNode ast) {
189 if (ast.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(ast)) {
190 checkEmptyLine(ast);
191 }
192 else if (ast.getType() == JavadocTokenTypes.HTML_ELEMENT
193 && (JavadocUtil.getFirstChild(ast).getType() == JavadocTokenTypes.P_TAG_START
194 || JavadocUtil.getFirstChild(ast).getType() == JavadocTokenTypes.PARAGRAPH)) {
195 checkParagraphTag(ast);
196 }
197 }
198
199
200
201
202
203
204 private void checkEmptyLine(DetailNode newline) {
205 final DetailNode nearestToken = getNearestNode(newline);
206 if (nearestToken.getType() == JavadocTokenTypes.TEXT
207 && !CommonUtil.isBlank(nearestToken.getText())) {
208 log(newline.getLineNumber(), newline.getColumnNumber(), MSG_TAG_AFTER);
209 }
210 }
211
212
213
214
215
216
217 private void checkParagraphTag(DetailNode tag) {
218 if (!isNestedParagraph(tag)) {
219 final DetailNode newLine = getNearestEmptyLine(tag);
220 if (isFirstParagraph(tag)) {
221 log(tag.getLineNumber(), tag.getColumnNumber(), MSG_REDUNDANT_PARAGRAPH);
222 }
223 else if (newLine == null || tag.getLineNumber() - newLine.getLineNumber() != 1) {
224 log(tag.getLineNumber(), tag.getColumnNumber(), MSG_LINE_BEFORE);
225 }
226
227 final String blockTagName = findFollowedBlockTagName(tag);
228 if (blockTagName != null) {
229 log(tag.getLineNumber(), tag.getColumnNumber(),
230 MSG_PRECEDED_BLOCK_TAG, blockTagName);
231 }
232
233 if (!allowNewlineParagraph && isImmediatelyFollowedByNewLine(tag)) {
234 log(tag.getLineNumber(), tag.getColumnNumber(), MSG_MISPLACED_TAG);
235 }
236 if (isImmediatelyFollowedByText(tag)) {
237 log(tag.getLineNumber(), tag.getColumnNumber(), MSG_MISPLACED_TAG);
238 }
239 }
240 }
241
242
243
244
245
246
247
248 private static boolean isNestedParagraph(DetailNode tag) {
249 boolean nested = false;
250 DetailNode parent = tag;
251
252 while (parent != null) {
253 if (parent.getType() == JavadocTokenTypes.PARAGRAPH) {
254 nested = true;
255 break;
256 }
257 parent = parent.getParent();
258 }
259
260 return nested;
261 }
262
263
264
265
266
267
268
269 @Nullable
270 private static String findFollowedBlockTagName(DetailNode tag) {
271 final DetailNode htmlElement = findFirstHtmlElementAfter(tag);
272 String blockTagName = null;
273
274 if (htmlElement != null) {
275 blockTagName = getHtmlElementName(htmlElement);
276 }
277
278 return blockTagName;
279 }
280
281
282
283
284
285
286
287 @Nullable
288 private static DetailNode findFirstHtmlElementAfter(DetailNode tag) {
289 DetailNode htmlElement = getNextSibling(tag);
290
291 while (htmlElement != null
292 && htmlElement.getType() != JavadocTokenTypes.HTML_ELEMENT
293 && htmlElement.getType() != JavadocTokenTypes.HTML_TAG) {
294 if ((htmlElement.getType() == JavadocTokenTypes.TEXT
295 || htmlElement.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG)
296 && !CommonUtil.isBlank(htmlElement.getText())) {
297 htmlElement = null;
298 break;
299 }
300 htmlElement = JavadocUtil.getNextSibling(htmlElement);
301 }
302
303 return htmlElement;
304 }
305
306
307
308
309
310
311
312 @Nullable
313 private static String getHtmlElementName(DetailNode htmlElement) {
314 final DetailNode htmlTag;
315 if (htmlElement.getType() == JavadocTokenTypes.HTML_TAG) {
316 htmlTag = htmlElement;
317 }
318 else {
319 htmlTag = JavadocUtil.getFirstChild(htmlElement);
320 }
321 final DetailNode htmlTagFirstChild = JavadocUtil.getFirstChild(htmlTag);
322 final DetailNode htmlTagName =
323 JavadocUtil.findFirstToken(htmlTagFirstChild, JavadocTokenTypes.HTML_TAG_NAME);
324 String blockTagName = null;
325 if (htmlTagName != null && BLOCK_TAGS.contains(htmlTagName.getText())) {
326 blockTagName = htmlTagName.getText();
327 }
328
329 return blockTagName;
330 }
331
332
333
334
335
336
337
338 private static DetailNode getNearestNode(DetailNode node) {
339 DetailNode currentNode = node;
340 while (currentNode.getType() == JavadocTokenTypes.LEADING_ASTERISK
341 || currentNode.getType() == JavadocTokenTypes.NEWLINE) {
342 currentNode = JavadocUtil.getNextSibling(currentNode);
343 }
344 return currentNode;
345 }
346
347
348
349
350
351
352
353 private static boolean isEmptyLine(DetailNode newLine) {
354 boolean result = false;
355 DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
356 if (previousSibling != null
357 && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) {
358 if (previousSibling.getType() == JavadocTokenTypes.TEXT
359 && CommonUtil.isBlank(previousSibling.getText())) {
360 previousSibling = JavadocUtil.getPreviousSibling(previousSibling);
361 }
362 result = previousSibling != null
363 && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK;
364 }
365 return result;
366 }
367
368
369
370
371
372
373
374 private static boolean isFirstParagraph(DetailNode paragraphTag) {
375 boolean result = true;
376 DetailNode previousNode = JavadocUtil.getPreviousSibling(paragraphTag);
377 while (previousNode != null) {
378 if (previousNode.getType() == JavadocTokenTypes.TEXT
379 && !CommonUtil.isBlank(previousNode.getText())
380 || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK
381 && previousNode.getType() != JavadocTokenTypes.NEWLINE
382 && previousNode.getType() != JavadocTokenTypes.TEXT) {
383 result = false;
384 break;
385 }
386 previousNode = JavadocUtil.getPreviousSibling(previousNode);
387 }
388 return result;
389 }
390
391
392
393
394
395
396
397 private static DetailNode getNearestEmptyLine(DetailNode node) {
398 DetailNode newLine = node;
399 while (newLine != null) {
400 final DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
401 if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) {
402 break;
403 }
404 newLine = previousSibling;
405 }
406 return newLine;
407 }
408
409
410
411
412
413
414
415 private static boolean isImmediatelyFollowedByText(DetailNode tag) {
416 final DetailNode nextSibling = getNextSibling(tag);
417
418 return nextSibling.getType() == JavadocTokenTypes.EOF
419 || nextSibling.getText().startsWith(" ");
420 }
421
422
423
424
425
426
427
428 private static boolean isImmediatelyFollowedByNewLine(DetailNode tag) {
429 return getNextSibling(tag).getType() == JavadocTokenTypes.NEWLINE;
430 }
431
432
433
434
435
436
437
438
439 private static DetailNode getNextSibling(DetailNode tag) {
440 DetailNode nextSibling;
441
442 if (JavadocUtil.getFirstChild(tag).getType() == JavadocTokenTypes.PARAGRAPH) {
443 final DetailNode paragraphToken = JavadocUtil.getFirstChild(tag);
444 final DetailNode paragraphStartTagToken = JavadocUtil.getFirstChild(paragraphToken);
445 nextSibling = JavadocUtil.getNextSibling(paragraphStartTagToken);
446 }
447 else {
448 nextSibling = JavadocUtil.getNextSibling(tag);
449 }
450
451 if (nextSibling.getType() == JavadocTokenTypes.HTML_COMMENT) {
452 nextSibling = JavadocUtil.getNextSibling(nextSibling);
453 }
454
455 return nextSibling;
456 }
457 }