1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
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.Scope;
30 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
32 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
33 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
34 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
35
36 /**
37 * <div>
38 * Checks for missing Javadoc comments for a method or constructor. The scope to verify is
39 * specified using the {@code Scope} class and defaults to {@code Scope.PUBLIC}. To verify
40 * another scope, set property scope to a different
41 * <a href="https://checkstyle.org/property_types.html#Scope">scope</a>.
42 * </div>
43 *
44 * <p>
45 * Javadoc is not required on a method that is tagged with the {@code @Override} annotation.
46 * However, under Java 5 it is not possible to mark a method required for an interface (this
47 * was <i>corrected</i> under Java 6). Hence, Checkstyle supports using the convention of using
48 * a single {@code {@inheritDoc}} tag instead of all the other tags.
49 * </p>
50 *
51 * <p>
52 * For getters and setters for the property {@code allowMissingPropertyJavadoc}, the methods must
53 * match exactly the structures below.
54 * </p>
55 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
56 * public void setNumber(final int number)
57 * {
58 * mNumber = number;
59 * }
60 *
61 * public int getNumber()
62 * {
63 * return mNumber;
64 * }
65 *
66 * public boolean isSomething()
67 * {
68 * return false;
69 * }
70 * </code></pre></div>
71 *
72 * @since 8.21
73 */
74 @FileStatefulCheck
75 public class MissingJavadocMethodCheck extends AbstractCheck {
76
77 /**
78 * A key is pointing to the warning message text in "messages.properties"
79 * file.
80 */
81 public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
82
83 /** Maximum children allowed in setter/getter. */
84 private static final int SETTER_GETTER_MAX_CHILDREN = 7;
85
86 /** Pattern matching names of getter methods. */
87 private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*");
88
89 /** Pattern matching names of setter methods. */
90 private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
91
92 /** Maximum nodes allowed in a body of setter. */
93 private static final int SETTER_BODY_SIZE = 3;
94
95 /** Default value of minimal amount of lines in method to allow no documentation.*/
96 private static final int DEFAULT_MIN_LINE_COUNT = -1;
97
98 /** Specify the visibility scope where Javadoc comments are checked. */
99 private Scope scope = Scope.PUBLIC;
100
101 /** Specify the visibility scope where Javadoc comments are not checked. */
102 private Scope excludeScope;
103
104 /** Control the minimal amount of lines in method to allow no documentation.*/
105 private int minLineCount = DEFAULT_MIN_LINE_COUNT;
106
107 /**
108 * Control whether to allow missing Javadoc on accessor methods for
109 * properties (setters and getters).
110 */
111 private boolean allowMissingPropertyJavadoc;
112
113 /** Ignore method whose names are matching specified regex. */
114 private Pattern ignoreMethodNamesRegex;
115
116 /** Configure annotations that allow missed documentation. */
117 private Set<String> allowedAnnotations = Set.of("Override");
118
119 /**
120 * Setter to configure annotations that allow missed documentation.
121 *
122 * @param userAnnotations user's value.
123 * @since 8.21
124 */
125 public void setAllowedAnnotations(String... userAnnotations) {
126 allowedAnnotations = Set.of(userAnnotations);
127 }
128
129 /**
130 * Setter to ignore method whose names are matching specified regex.
131 *
132 * @param pattern a pattern.
133 * @since 8.21
134 */
135 public void setIgnoreMethodNamesRegex(Pattern pattern) {
136 ignoreMethodNamesRegex = pattern;
137 }
138
139 /**
140 * Setter to control the minimal amount of lines in method to allow no documentation.
141 *
142 * @param value user's value.
143 * @since 8.21
144 */
145 public void setMinLineCount(int value) {
146 minLineCount = value;
147 }
148
149 /**
150 * Setter to control whether to allow missing Javadoc on accessor methods for properties
151 * (setters and getters).
152 *
153 * @param flag a {@code Boolean} value
154 * @since 8.21
155 */
156 public void setAllowMissingPropertyJavadoc(final boolean flag) {
157 allowMissingPropertyJavadoc = flag;
158 }
159
160 /**
161 * Setter to specify the visibility scope where Javadoc comments are checked.
162 *
163 * @param scope a scope.
164 * @since 8.21
165 */
166 public void setScope(Scope scope) {
167 this.scope = scope;
168 }
169
170 /**
171 * Setter to specify the visibility scope where Javadoc comments are not checked.
172 *
173 * @param excludeScope a scope.
174 * @since 8.21
175 */
176 public void setExcludeScope(Scope excludeScope) {
177 this.excludeScope = excludeScope;
178 }
179
180 @Override
181 public final int[] getRequiredTokens() {
182 return CommonUtil.EMPTY_INT_ARRAY;
183 }
184
185 @Override
186 public int[] getDefaultTokens() {
187 return getAcceptableTokens();
188 }
189
190 @Override
191 public int[] getAcceptableTokens() {
192 return new int[] {
193 TokenTypes.METHOD_DEF,
194 TokenTypes.CTOR_DEF,
195 TokenTypes.ANNOTATION_FIELD_DEF,
196 TokenTypes.COMPACT_CTOR_DEF,
197 };
198 }
199
200 @Override
201 public boolean isCommentNodesRequired() {
202 return true;
203 }
204
205 @Override
206 public final void visitToken(DetailAST ast) {
207 final Scope theScope = ScopeUtil.getScope(ast);
208 if (shouldCheck(ast, theScope)) {
209 final DetailAST blockCommentNode = JavadocUtil.getAttachedJavadocComment(ast);
210 if (blockCommentNode == null && !isMissingJavadocAllowed(ast)) {
211 log(ast, MSG_JAVADOC_MISSING);
212 }
213 }
214 }
215
216 /**
217 * Some javadoc.
218 *
219 * @param methodDef Some javadoc.
220 * @return Some javadoc.
221 */
222 private static int getMethodsNumberOfLine(DetailAST methodDef) {
223 int numberOfLines = 1;
224 final DetailAST lcurly = methodDef.getLastChild();
225 final DetailAST rcurly = lcurly.getLastChild();
226 if (rcurly != null && lcurly.getLineNo() != rcurly.getLineNo()) {
227 numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1;
228 }
229
230 return numberOfLines;
231 }
232
233 /**
234 * Checks if a missing Javadoc is allowed by the check's configuration.
235 *
236 * @param ast the tree node for the method or constructor.
237 * @return True if this method or constructor doesn't need Javadoc.
238 */
239 private boolean isMissingJavadocAllowed(final DetailAST ast) {
240 return allowMissingPropertyJavadoc
241 && (isSetterMethod(ast) || isGetterMethod(ast))
242 || matchesSkipRegex(ast)
243 || isContentsAllowMissingJavadoc(ast);
244 }
245
246 /**
247 * Checks if the Javadoc can be missing if the method or constructor is
248 * below the minimum line count or has a special annotation.
249 *
250 * @param ast the tree node for the method or constructor.
251 * @return True if this method or constructor doesn't need Javadoc.
252 */
253 private boolean isContentsAllowMissingJavadoc(DetailAST ast) {
254 return ast.getType() != TokenTypes.ANNOTATION_FIELD_DEF
255 && (getMethodsNumberOfLine(ast) <= minLineCount
256 || AnnotationUtil.containsAnnotation(ast, allowedAnnotations));
257 }
258
259 /**
260 * Checks if the given method name matches the regex. In that case
261 * we skip enforcement of javadoc for this method
262 *
263 * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF}
264 * @return true if given method name matches the regex.
265 */
266 private boolean matchesSkipRegex(DetailAST methodDef) {
267 boolean result = false;
268 if (ignoreMethodNamesRegex != null) {
269 final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT);
270 final String methodName = ident.getText();
271
272 final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName);
273 if (matcher.matches()) {
274 result = true;
275 }
276 }
277 return result;
278 }
279
280 /**
281 * Whether we should check this node.
282 *
283 * @param ast a given node.
284 * @param nodeScope the scope of the node.
285 * @return whether we should check a given node.
286 */
287 private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) {
288 return ScopeUtil.getSurroundingScope(ast)
289 .map(surroundingScope -> {
290 return nodeScope != excludeScope
291 && surroundingScope != excludeScope
292 && nodeScope.isIn(scope)
293 && surroundingScope.isIn(scope);
294 })
295 .orElse(Boolean.FALSE);
296 }
297
298 /**
299 * Returns whether an AST represents a getter method.
300 *
301 * @param ast the AST to check with
302 * @return whether the AST represents a getter method
303 */
304 public static boolean isGetterMethod(final DetailAST ast) {
305 boolean getterMethod = false;
306
307 // Check have a method with exactly 7 children which are all that
308 // is allowed in a proper getter method which does not throw any
309 // exceptions.
310 if (ast.getType() == TokenTypes.METHOD_DEF
311 && getChildCount(ast) == SETTER_GETTER_MAX_CHILDREN) {
312 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
313 final String name = type.getNextSibling().getText();
314 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
315
316 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
317 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
318
319 if (matchesGetterFormat && noParams) {
320 // Now verify that the body consists of:
321 // SLIST -> RETURN
322 // RCURLY
323 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
324
325 if (slist != null) {
326 DetailAST expr = slist.getFirstChild();
327 while (expr.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
328 expr = expr.getNextSibling();
329 }
330 getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN;
331 }
332 }
333 }
334 return getterMethod;
335 }
336
337 /**
338 * Returns whether an AST represents a setter method.
339 *
340 * @param ast the AST to check with
341 * @return whether the AST represents a setter method
342 */
343 public static boolean isSetterMethod(final DetailAST ast) {
344 boolean setterMethod = false;
345
346 // Check have a method with exactly 7 children which are all that
347 // is allowed in a proper setter method which does not throw any
348 // exceptions.
349 if (ast.getType() == TokenTypes.METHOD_DEF
350 && getChildCount(ast) == SETTER_GETTER_MAX_CHILDREN) {
351 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
352 final String name = type.getNextSibling().getText();
353 final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
354
355 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
356 final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
357
358 if (matchesSetterFormat && singleParam) {
359 // Now verify that the body consists of:
360 // SLIST -> EXPR -> ASSIGN
361 // SEMI
362 // RCURLY
363 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
364
365 if (slist != null && getChildCount(slist) == SETTER_BODY_SIZE) {
366 final DetailAST expr = slist.getFirstChild();
367 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
368 }
369 }
370 }
371 return setterMethod;
372 }
373
374 /**
375 * Returns the number of children without counting comments.
376 *
377 * @param detailAst parent ast
378 * @return the number of children
379 */
380 private static int getChildCount(DetailAST detailAst) {
381 int childCount = 0;
382 DetailAST child = detailAst.getFirstChild();
383
384 while (child != null) {
385 if (child.getType() != TokenTypes.SINGLE_LINE_COMMENT) {
386 childCount += 1;
387 }
388 child = child.getNextSibling();
389 }
390 return childCount;
391 }
392 }