1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 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 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 * Returns whether an AST represents a getter method.
301 *
302 * @param ast the AST to check with
303 * @return whether the AST represents a getter method
304 */
305 public static boolean isGetterMethod(final DetailAST ast) {
306 boolean getterMethod = false;
307
308 // Check have a method with exactly 7 children which are all that
309 // is allowed in a proper getter method which does not throw any
310 // exceptions.
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 // Now verify that the body consists of:
322 // SLIST -> RETURN
323 // RCURLY
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 * Returns whether an AST represents a setter method.
337 *
338 * @param ast the AST to check with
339 * @return whether the AST represents a setter method
340 */
341 public static boolean isSetterMethod(final DetailAST ast) {
342 boolean setterMethod = false;
343
344 // Check have a method with exactly 7 children which are all that
345 // is allowed in a proper setter method which does not throw any
346 // exceptions.
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 // Now verify that the body consists of:
358 // SLIST -> EXPR -> ASSIGN
359 // SEMI
360 // RCURLY
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 }