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.Arrays;
23 import java.util.Collections;
24 import java.util.HashSet;
25 import java.util.Set;
26 import java.util.regex.Pattern;
27
28 import com.puppycrawl.tools.checkstyle.StatelessCheck;
29 import com.puppycrawl.tools.checkstyle.api.DetailNode;
30 import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes;
31 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
32 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
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 @StatelessCheck
67 public class SummaryJavadocCheck extends AbstractJavadocCheck {
68
69
70
71
72
73 public static final String MSG_SUMMARY_FIRST_SENTENCE = "summary.first.sentence";
74
75
76
77
78
79 public static final String MSG_SUMMARY_JAVADOC = "summary.javaDoc";
80
81
82
83
84 public static final String MSG_SUMMARY_JAVADOC_MISSING = "summary.javaDoc.missing";
85
86
87
88 private static final Pattern JAVADOC_MULTILINE_TO_SINGLELINE_PATTERN =
89 Pattern.compile("\n[ ]+(\\*)|^[ ]+(\\*)");
90
91
92 private static final String PERIOD = ".";
93
94
95 private static final Set<Integer> ALLOWED_TYPES = Collections.unmodifiableSet(
96 new HashSet<>(Arrays.asList(JavadocTokenTypes.TEXT,
97 JavadocTokenTypes.WS))
98 );
99
100
101 private Pattern forbiddenSummaryFragments = CommonUtil.createPattern("^$");
102
103
104 private String period = PERIOD;
105
106
107
108
109
110 public void setForbiddenSummaryFragments(Pattern pattern) {
111 forbiddenSummaryFragments = pattern;
112 }
113
114
115
116
117
118 public void setPeriod(String period) {
119 this.period = period;
120 }
121
122 @Override
123 public int[] getDefaultJavadocTokens() {
124 return new int[] {
125 JavadocTokenTypes.JAVADOC,
126 };
127 }
128
129 @Override
130 public int[] getRequiredJavadocTokens() {
131 return getAcceptableJavadocTokens();
132 }
133
134 @Override
135 public void visitJavadocToken(DetailNode ast) {
136 if (!startsWithInheritDoc(ast)) {
137 final String summaryDoc = getSummarySentence(ast);
138 if (summaryDoc.isEmpty()) {
139 log(ast.getLineNumber(), MSG_SUMMARY_JAVADOC_MISSING);
140 }
141 else if (!period.isEmpty()) {
142 final String firstSentence = getFirstSentence(ast);
143 final int endOfSentence = firstSentence.lastIndexOf(period);
144 if (!summaryDoc.contains(period)) {
145 log(ast.getLineNumber(), MSG_SUMMARY_FIRST_SENTENCE);
146 }
147 if (endOfSentence != -1
148 && containsForbiddenFragment(firstSentence.substring(0, endOfSentence))) {
149 log(ast.getLineNumber(), MSG_SUMMARY_JAVADOC);
150 }
151 }
152 }
153 }
154
155
156
157
158
159
160 private static boolean startsWithInheritDoc(DetailNode root) {
161 boolean found = false;
162 final DetailNode[] children = root.getChildren();
163
164 for (int i = 0; !found; i++) {
165 final DetailNode child = children[i];
166 if (child.getType() == JavadocTokenTypes.JAVADOC_INLINE_TAG
167 && child.getChildren()[1].getType() == JavadocTokenTypes.INHERIT_DOC_LITERAL) {
168 found = true;
169 }
170 else if (child.getType() != JavadocTokenTypes.LEADING_ASTERISK
171 && !CommonUtil.isBlank(child.getText())) {
172 break;
173 }
174 }
175
176 return found;
177 }
178
179
180
181
182
183
184 private static String getSummarySentence(DetailNode ast) {
185 boolean flag = true;
186 final StringBuilder result = new StringBuilder(256);
187 for (DetailNode child : ast.getChildren()) {
188 if (ALLOWED_TYPES.contains(child.getType())) {
189 result.append(child.getText());
190 }
191 else if (child.getType() == JavadocTokenTypes.HTML_ELEMENT
192 && CommonUtil.isBlank(result.toString().trim())) {
193 result.append(getStringInsideTag(result.toString(),
194 child.getChildren()[0].getChildren()[0]));
195 }
196 else if (child.getType() == JavadocTokenTypes.JAVADOC_TAG) {
197 flag = false;
198 }
199 if (!flag) {
200 break;
201 }
202 }
203 return result.toString().trim();
204 }
205
206
207
208
209
210
211
212 private static String getStringInsideTag(String result, DetailNode detailNode) {
213 final StringBuilder contents = new StringBuilder(result);
214 DetailNode tempNode = detailNode;
215 while (tempNode != null) {
216 if (tempNode.getType() == JavadocTokenTypes.TEXT) {
217 contents.append(tempNode.getText());
218 }
219 tempNode = JavadocUtil.getNextSibling(tempNode);
220 }
221 return contents.toString();
222 }
223
224
225
226
227
228
229 private static String getFirstSentence(DetailNode ast) {
230 final StringBuilder result = new StringBuilder(256);
231 final String periodSuffix = PERIOD + ' ';
232 for (DetailNode child : ast.getChildren()) {
233 final String text;
234 if (child.getChildren().length == 0) {
235 text = child.getText();
236 }
237 else {
238 text = getFirstSentence(child);
239 }
240
241 if (text.contains(periodSuffix)) {
242 result.append(text, 0, text.indexOf(periodSuffix) + 1);
243 break;
244 }
245 else {
246 result.append(text);
247 }
248 }
249 return result.toString();
250 }
251
252
253
254
255
256
257 private boolean containsForbiddenFragment(String firstSentence) {
258 final String javadocText = JAVADOC_MULTILINE_TO_SINGLELINE_PATTERN
259 .matcher(firstSentence).replaceAll(" ").trim();
260 return forbiddenSummaryFragments.matcher(trimExcessWhitespaces(javadocText)).find();
261 }
262
263
264
265
266
267
268 private static String trimExcessWhitespaces(String text) {
269 final StringBuilder result = new StringBuilder(100);
270 boolean previousWhitespace = true;
271
272 for (char letter : text.toCharArray()) {
273 final char print;
274 if (Character.isWhitespace(letter)) {
275 if (previousWhitespace) {
276 continue;
277 }
278
279 previousWhitespace = true;
280 print = ' ';
281 }
282 else {
283 previousWhitespace = false;
284 print = letter;
285 }
286
287 result.append(print);
288 }
289
290 return result.toString();
291 }
292
293 }