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.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 * <div>
39 * Checks for missing Javadoc comments for a method or constructor. The scope to verify is
40 * specified using the {@code Scope} class and defaults to {@code Scope.PUBLIC}. To verify
41 * another scope, set property scope to a different
42 * <a href="https://checkstyle.org/property_types.html#Scope">scope</a>.
43 * </div>
44 *
45 * <p>
46 * Javadoc is not required on a method that is tagged with the {@code @Override} annotation.
47 * However, under Java 5 it is not possible to mark a method required for an interface (this
48 * was <i>corrected</i> under Java 6). Hence, Checkstyle supports using the convention of using
49 * a single {@code {@inheritDoc}} tag instead of all the other tags.
50 * </p>
51 *
52 * <p>
53 * For getters and setters for the property {@code allowMissingPropertyJavadoc}, the methods must
54 * match exactly the structures below.
55 * </p>
56 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
57 * public void setNumber(final int number)
58 * {
59 * mNumber = number;
60 * }
61 *
62 * public int getNumber()
63 * {
64 * return mNumber;
65 * }
66 *
67 * public boolean isSomething()
68 * {
69 * return false;
70 * }
71 * </code></pre></div>
72 *
73 * @since 8.21
74 */
75 @FileStatefulCheck
76 public class MissingJavadocMethodCheck extends AbstractCheck {
77
78 /**
79 * A key is pointing to the warning message text in "messages.properties"
80 * file.
81 */
82 public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
83
84 /** Maximum children allowed in setter/getter. */
85 private static final int SETTER_GETTER_MAX_CHILDREN = 7;
86
87 /** Pattern matching names of getter methods. */
88 private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*");
89
90 /** Pattern matching names of setter methods. */
91 private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
92
93 /** Maximum nodes allowed in a body of setter. */
94 private static final int SETTER_BODY_SIZE = 3;
95
96 /** Default value of minimal amount of lines in method to allow no documentation.*/
97 private static final int DEFAULT_MIN_LINE_COUNT = -1;
98
99 /** Specify the visibility scope where Javadoc comments are checked. */
100 private Scope scope = Scope.PUBLIC;
101
102 /** Specify the visibility scope where Javadoc comments are not checked. */
103 private Scope excludeScope;
104
105 /** Control the minimal amount of lines in method to allow no documentation.*/
106 private int minLineCount = DEFAULT_MIN_LINE_COUNT;
107
108 /**
109 * Control whether to allow missing Javadoc on accessor methods for
110 * properties (setters and getters).
111 */
112 private boolean allowMissingPropertyJavadoc;
113
114 /** Ignore method whose names are matching specified regex. */
115 private Pattern ignoreMethodNamesRegex;
116
117 /** Configure annotations that allow missed documentation. */
118 private Set<String> allowedAnnotations = Set.of("Override");
119
120 /**
121 * Setter to configure annotations that allow missed documentation.
122 *
123 * @param userAnnotations user's value.
124 * @since 8.21
125 */
126 public void setAllowedAnnotations(String... userAnnotations) {
127 allowedAnnotations = Set.of(userAnnotations);
128 }
129
130 /**
131 * Setter to ignore method whose names are matching specified regex.
132 *
133 * @param pattern a pattern.
134 * @since 8.21
135 */
136 public void setIgnoreMethodNamesRegex(Pattern pattern) {
137 ignoreMethodNamesRegex = pattern;
138 }
139
140 /**
141 * Setter to control the minimal amount of lines in method to allow no documentation.
142 *
143 * @param value user's value.
144 * @since 8.21
145 */
146 public void setMinLineCount(int value) {
147 minLineCount = value;
148 }
149
150 /**
151 * Setter to control whether to allow missing Javadoc on accessor methods for properties
152 * (setters and getters).
153 *
154 * @param flag a {@code Boolean} value
155 * @since 8.21
156 */
157 public void setAllowMissingPropertyJavadoc(final boolean flag) {
158 allowMissingPropertyJavadoc = flag;
159 }
160
161 /**
162 * Setter to specify the visibility scope where Javadoc comments are checked.
163 *
164 * @param scope a scope.
165 * @since 8.21
166 */
167 public void setScope(Scope scope) {
168 this.scope = scope;
169 }
170
171 /**
172 * Setter to specify the visibility scope where Javadoc comments are not checked.
173 *
174 * @param excludeScope a scope.
175 * @since 8.21
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 // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
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 * Some javadoc.
218 *
219 * @param methodDef Some javadoc.
220 * @return Some javadoc.
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 * Checks if a missing Javadoc is allowed by the check's configuration.
238 *
239 * @param ast the tree node for the method or constructor.
240 * @return True if this method or constructor doesn't need Javadoc.
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 * Checks if the Javadoc can be missing if the method or constructor is
251 * below the minimum line count or has a special annotation.
252 *
253 * @param ast the tree node for the method or constructor.
254 * @return True if this method or constructor doesn't need Javadoc.
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 * Checks if the given method name matches the regex. In that case
264 * we skip enforcement of javadoc for this method
265 *
266 * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF}
267 * @return true if given method name matches the regex.
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 * Whether we should check this node.
285 *
286 * @param ast a given node.
287 * @param nodeScope the scope of the node.
288 * @return whether we should check a given node.
289 */
290 private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) {
291 return ScopeUtil.getSurroundingScope(ast)
292 .map(surroundingScope -> {
293 return nodeScope != excludeScope
294 && surroundingScope != excludeScope
295 && nodeScope.isIn(scope)
296 && surroundingScope.isIn(scope);
297 })
298 .orElse(Boolean.FALSE);
299 }
300
301 /**
302 * Returns whether an AST represents a getter method.
303 *
304 * @param ast the AST to check with
305 * @return whether the AST represents a getter method
306 */
307 public static boolean isGetterMethod(final DetailAST ast) {
308 boolean getterMethod = false;
309
310 // Check have a method with exactly 7 children which are all that
311 // is allowed in a proper getter method which does not throw any
312 // exceptions.
313 if (ast.getType() == TokenTypes.METHOD_DEF
314 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
315 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
316 final String name = type.getNextSibling().getText();
317 final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
318
319 final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
320 final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
321
322 if (matchesGetterFormat && noParams) {
323 // Now verify that the body consists of:
324 // SLIST -> RETURN
325 // RCURLY
326 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
327
328 if (slist != null) {
329 final DetailAST expr = slist.getFirstChild();
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 && ast.getChildCount() == 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 && slist.getChildCount() == SETTER_BODY_SIZE) {
366 final DetailAST expr = slist.getFirstChild();
367 setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
368 }
369 }
370 }
371 return setterMethod;
372 }
373 }