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