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 com.puppycrawl.tools.checkstyle.StatelessCheck;
23 import com.puppycrawl.tools.checkstyle.api.DetailNode;
24 import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
25 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
26 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
27
28
29
30
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 @StatelessCheck
92 public class JavadocParagraphCheck extends AbstractJavadocCheck {
93
94
95
96
97
98 public static final String MSG_TAG_AFTER = "javadoc.paragraph.tag.after";
99
100
101
102
103
104 public static final String MSG_LINE_BEFORE = "javadoc.paragraph.line.before";
105
106
107
108
109
110 public static final String MSG_REDUNDANT_PARAGRAPH = "javadoc.paragraph.redundant.paragraph";
111
112
113
114
115
116 public static final String MSG_MISPLACED_TAG = "javadoc.paragraph.misplaced.tag";
117
118
119
120
121 private boolean allowNewlineParagraph = true;
122
123
124
125
126
127
128
129
130 public void setAllowNewlineParagraph(boolean value) {
131 allowNewlineParagraph = value;
132 }
133
134 @Override
135 public int[] getDefaultJavadocTokens() {
136 return new int[] {
137 JavadocTokenTypes.NEWLINE,
138 JavadocTokenTypes.HTML_ELEMENT,
139 };
140 }
141
142 @Override
143 public int[] getRequiredJavadocTokens() {
144 return getAcceptableJavadocTokens();
145 }
146
147 @Override
148 public void visitJavadocToken(DetailNode ast) {
149 if (ast.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(ast)) {
150 checkEmptyLine(ast);
151 }
152 else if (ast.getType() == JavadocTokenTypes.HTML_ELEMENT
153 && JavadocUtil.getFirstChild(ast).getType() == JavadocTokenTypes.P_TAG_START) {
154 checkParagraphTag(ast);
155 }
156 }
157
158
159
160
161
162
163 private void checkEmptyLine(DetailNode newline) {
164 final DetailNode nearestToken = getNearestNode(newline);
165 if (nearestToken.getType() == JavadocTokenTypes.TEXT
166 && !CommonUtil.isBlank(nearestToken.getText())) {
167 log(newline.getLineNumber(), MSG_TAG_AFTER);
168 }
169 }
170
171
172
173
174
175
176 private void checkParagraphTag(DetailNode tag) {
177 final DetailNode newLine = getNearestEmptyLine(tag);
178 if (isFirstParagraph(tag)) {
179 log(tag.getLineNumber(), MSG_REDUNDANT_PARAGRAPH);
180 }
181 else if (newLine == null || tag.getLineNumber() - newLine.getLineNumber() != 1) {
182 log(tag.getLineNumber(), MSG_LINE_BEFORE);
183 }
184 if (allowNewlineParagraph && isImmediatelyFollowedByText(tag)) {
185 log(tag.getLineNumber(), MSG_MISPLACED_TAG);
186 }
187 }
188
189
190
191
192
193
194
195 private static DetailNode getNearestNode(DetailNode node) {
196 DetailNode currentNode = node;
197 while (currentNode.getType() == JavadocTokenTypes.LEADING_ASTERISK
198 || currentNode.getType() == JavadocTokenTypes.NEWLINE) {
199 currentNode = JavadocUtil.getNextSibling(currentNode);
200 }
201 return currentNode;
202 }
203
204
205
206
207
208
209
210 private static boolean isEmptyLine(DetailNode newLine) {
211 boolean result = false;
212 DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
213 if (previousSibling != null
214 && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) {
215 if (previousSibling.getType() == JavadocTokenTypes.TEXT
216 && CommonUtil.isBlank(previousSibling.getText())) {
217 previousSibling = JavadocUtil.getPreviousSibling(previousSibling);
218 }
219 result = previousSibling != null
220 && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK;
221 }
222 return result;
223 }
224
225
226
227
228
229
230
231 private static boolean isFirstParagraph(DetailNode paragraphTag) {
232 boolean result = true;
233 DetailNode previousNode = JavadocUtil.getPreviousSibling(paragraphTag);
234 while (previousNode != null) {
235 if (previousNode.getType() == JavadocTokenTypes.TEXT
236 && !CommonUtil.isBlank(previousNode.getText())
237 || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK
238 && previousNode.getType() != JavadocTokenTypes.NEWLINE
239 && previousNode.getType() != JavadocTokenTypes.TEXT) {
240 result = false;
241 break;
242 }
243 previousNode = JavadocUtil.getPreviousSibling(previousNode);
244 }
245 return result;
246 }
247
248
249
250
251
252
253
254 private static DetailNode getNearestEmptyLine(DetailNode node) {
255 DetailNode newLine = node;
256 while (newLine != null) {
257 final DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
258 if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) {
259 break;
260 }
261 newLine = previousSibling;
262 }
263 return newLine;
264 }
265
266
267
268
269
270
271
272 private static boolean isImmediatelyFollowedByText(DetailNode tag) {
273 final DetailNode nextSibling = JavadocUtil.getNextSibling(tag);
274 return nextSibling.getType() == JavadocTokenTypes.NEWLINE
275 || nextSibling.getType() == JavadocTokenTypes.EOF
276 || nextSibling.getText().startsWith(" ");
277 }
278
279 }