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 import java.util.regex.Matcher;
24 import java.util.regex.Pattern;
25
26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.FileContents;
30 import com.puppycrawl.tools.checkstyle.api.Scope;
31 import com.puppycrawl.tools.checkstyle.api.TextBlock;
32 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
33 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
34 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
35 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
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 @FileStatefulCheck
76 public class MissingJavadocMethodCheck extends AbstractCheck {
77
78
79
80
81
82 public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
83
84
85 private static final int SETTER_GETTER_MAX_CHILDREN = 7;
86
87
88 private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*");
89
90
91 private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
92
93
94 private static final int SETTER_BODY_SIZE = 3;
95
96
97 private static final int DEFAULT_MIN_LINE_COUNT = -1;
98
99
100 private Scope scope = Scope.PUBLIC;
101
102
103 private Scope excludeScope;
104
105
106 private int minLineCount = DEFAULT_MIN_LINE_COUNT;
107
108
109
110
111
112 private boolean allowMissingPropertyJavadoc;
113
114
115 private Pattern ignoreMethodNamesRegex;
116
117
118 private Set<String> allowedAnnotations = Set.of("Override");
119
120
121
122
123
124
125
126 public void setAllowedAnnotations(String... userAnnotations) {
127 allowedAnnotations = Set.of(userAnnotations);
128 }
129
130
131
132
133
134
135
136 public void setIgnoreMethodNamesRegex(Pattern pattern) {
137 ignoreMethodNamesRegex = pattern;
138 }
139
140
141
142
143
144
145
146 public void setMinLineCount(int value) {
147 minLineCount = value;
148 }
149
150
151
152
153
154
155
156
157 public void setAllowMissingPropertyJavadoc(final boolean flag) {
158 allowMissingPropertyJavadoc = flag;
159 }
160
161
162
163
164
165
166
167 public void setScope(Scope scope) {
168 this.scope = scope;
169 }
170
171
172
173
174
175
176
177 public void setExcludeScope(Scope excludeScope) {
178 this.excludeScope = excludeScope;
179 }
180
181 @Override
182 public final int[] getRequiredTokens() {
183 return CommonUtil.EMPTY_INT_ARRAY;
184 }
185
186 @Override
187 public int[] getDefaultTokens() {
188 return getAcceptableTokens();
189 }
190
191 @Override
192 public int[] getAcceptableTokens() {
193 return new int[] {
194 TokenTypes.METHOD_DEF,
195 TokenTypes.CTOR_DEF,
196 TokenTypes.ANNOTATION_FIELD_DEF,
197 TokenTypes.COMPACT_CTOR_DEF,
198 };
199 }
200
201
202 @Override
203 @SuppressWarnings("deprecation")
204 public final void visitToken(DetailAST ast) {
205 final Scope theScope = ScopeUtil.getScope(ast);
206 if (shouldCheck(ast, theScope)) {
207 final FileContents contents = getFileContents();
208 final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo());
209
210 if (textBlock == null && !isMissingJavadocAllowed(ast)) {
211 log(ast, MSG_JAVADOC_MISSING);
212 }
213 }
214 }
215
216
217
218
219
220
221
222 private static int getMethodsNumberOfLine(DetailAST methodDef) {
223 final int numberOfLines;
224 final DetailAST lcurly = methodDef.getLastChild();
225 final DetailAST rcurly = lcurly.getLastChild();
226
227 if (lcurly.getFirstChild() == rcurly) {
228 numberOfLines = 1;
229 }
230 else {
231 numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1;
232 }
233 return numberOfLines;
234 }
235
236
237
238
239
240
241
242 private boolean isMissingJavadocAllowed(final DetailAST ast) {
243 return allowMissingPropertyJavadoc
244 && (isSetterMethod(ast) || isGetterMethod(ast))
245 || matchesSkipRegex(ast)
246 || isContentsAllowMissingJavadoc(ast);
247 }
248
249
250
251
252
253
254
255
256 private boolean isContentsAllowMissingJavadoc(DetailAST ast) {
257 return ast.getType() != TokenTypes.ANNOTATION_FIELD_DEF
258 && (getMethodsNumberOfLine(ast) <= minLineCount
259 || AnnotationUtil.containsAnnotation(ast, allowedAnnotations));
260 }
261
262
263
264
265
266
267
268
269 private boolean matchesSkipRegex(DetailAST methodDef) {
270 boolean result = false;
271 if (ignoreMethodNamesRegex != null) {
272 final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT);
273 final String methodName = ident.getText();
274
275 final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName);
276 if (matcher.matches()) {
277 result = true;
278 }
279 }
280 return result;
281 }
282
283
284
285
286
287
288
289
290 private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) {
291 final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast);
292
293 return nodeScope != excludeScope
294 && surroundingScope != excludeScope
295 && nodeScope.isIn(scope)
296 && surroundingScope.isIn(scope);
297 }
298
299
300
301
302
303
304
305 public static boolean isGetterMethod(final DetailAST ast) {
306 boolean getterMethod = false;
307
308
309
310
311 if (ast.getType() == TokenTypes.METHOD_DEF
312 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
313 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
314 final String name = type.getNextSibling().getText();
315 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
316
317 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
318 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
319
320 if (matchesGetterFormat && noParams) {
321
322
323
324 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
325
326 if (slist != null) {
327 final DetailAST expr = slist.getFirstChild();
328 getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN;
329 }
330 }
331 }
332 return getterMethod;
333 }
334
335
336
337
338
339
340
341 public static boolean isSetterMethod(final DetailAST ast) {
342 boolean setterMethod = false;
343
344
345
346
347 if (ast.getType() == TokenTypes.METHOD_DEF
348 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
349 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
350 final String name = type.getNextSibling().getText();
351 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
352
353 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
354 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
355
356 if (matchesSetterFormat && singleParam) {
357
358
359
360
361 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
362
363 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
364 final DetailAST expr = slist.getFirstChild();
365 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
366 }
367 }
368 }
369 return setterMethod;
370 }
371 }