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 && isImmediatelyFollowedByNewLine(tag)) {
185 log(tag.getLineNumber(), MSG_MISPLACED_TAG);
186 }
187 if (isImmediatelyFollowedByText(tag)) {
188 log(tag.getLineNumber(), MSG_MISPLACED_TAG);
189 }
190 }
191
192
193
194
195
196
197
198 private static DetailNode getNearestNode(DetailNode node) {
199 DetailNode currentNode = node;
200 while (currentNode.getType() == JavadocTokenTypes.LEADING_ASTERISK
201 || currentNode.getType() == JavadocTokenTypes.NEWLINE) {
202 currentNode = JavadocUtil.getNextSibling(currentNode);
203 }
204 return currentNode;
205 }
206
207
208
209
210
211
212
213 private static boolean isEmptyLine(DetailNode newLine) {
214 boolean result = false;
215 DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
216 if (previousSibling != null
217 && previousSibling.getParent().getType() == JavadocTokenTypes.JAVADOC) {
218 if (previousSibling.getType() == JavadocTokenTypes.TEXT
219 && CommonUtil.isBlank(previousSibling.getText())) {
220 previousSibling = JavadocUtil.getPreviousSibling(previousSibling);
221 }
222 result = previousSibling != null
223 && previousSibling.getType() == JavadocTokenTypes.LEADING_ASTERISK;
224 }
225 return result;
226 }
227
228
229
230
231
232
233
234 private static boolean isFirstParagraph(DetailNode paragraphTag) {
235 boolean result = true;
236 DetailNode previousNode = JavadocUtil.getPreviousSibling(paragraphTag);
237 while (previousNode != null) {
238 if (previousNode.getType() == JavadocTokenTypes.TEXT
239 && !CommonUtil.isBlank(previousNode.getText())
240 || previousNode.getType() != JavadocTokenTypes.LEADING_ASTERISK
241 && previousNode.getType() != JavadocTokenTypes.NEWLINE
242 && previousNode.getType() != JavadocTokenTypes.TEXT) {
243 result = false;
244 break;
245 }
246 previousNode = JavadocUtil.getPreviousSibling(previousNode);
247 }
248 return result;
249 }
250
251
252
253
254
255
256
257 private static DetailNode getNearestEmptyLine(DetailNode node) {
258 DetailNode newLine = node;
259 while (newLine != null) {
260 final DetailNode previousSibling = JavadocUtil.getPreviousSibling(newLine);
261 if (newLine.getType() == JavadocTokenTypes.NEWLINE && isEmptyLine(newLine)) {
262 break;
263 }
264 newLine = previousSibling;
265 }
266 return newLine;
267 }
268
269
270
271
272
273
274
275 private static boolean isImmediatelyFollowedByText(DetailNode tag) {
276 final DetailNode nextSibling = JavadocUtil.getNextSibling(tag);
277 return nextSibling.getType() == JavadocTokenTypes.EOF
278 || nextSibling.getText().startsWith(" ");
279 }
280
281
282
283
284
285
286
287 private static boolean isImmediatelyFollowedByNewLine(DetailNode tag) {
288 final DetailNode nextSibling = JavadocUtil.getNextSibling(tag);
289 return nextSibling.getType() == JavadocTokenTypes.NEWLINE;
290 }
291
292 }