View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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   * <p>
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   * </p>
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   * <p>
51   * For getters and setters for the property {@code allowMissingPropertyJavadoc}, the methods must
52   * match exactly the structures below.
53   * </p>
54   * <pre>
55   * public void setNumber(final int number)
56   * {
57   *     mNumber = number;
58   * }
59   *
60   * public int getNumber()
61   * {
62   *     return mNumber;
63   * }
64   *
65   * public boolean isSomething()
66   * {
67   *     return false;
68   * }
69   * </pre>
70   * <ul>
71   * <li>
72   * Property {@code allowMissingPropertyJavadoc} - Control whether to allow missing Javadoc on
73   * accessor methods for properties (setters and getters).
74   * Type is {@code boolean}.
75   * Default value is {@code false}.
76   * </li>
77   * <li>
78   * Property {@code allowedAnnotations} - Configure annotations that allow missed
79   * documentation.
80   * Type is {@code java.lang.String[]}.
81   * Default value is {@code Override}.
82   * </li>
83   * <li>
84   * Property {@code excludeScope} - Specify the visibility scope where Javadoc comments are
85   * not checked.
86   * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}.
87   * Default value is {@code null}.
88   * </li>
89   * <li>
90   * Property {@code ignoreMethodNamesRegex} - Ignore method whose names are matching specified
91   * regex.
92   * Type is {@code java.util.regex.Pattern}.
93   * Default value is {@code null}.
94   * </li>
95   * <li>
96   * Property {@code minLineCount} - Control the minimal amount of lines in method to allow no
97   * documentation.
98   * Type is {@code int}.
99   * Default value is {@code -1}.
100  * </li>
101  * <li>
102  * Property {@code scope} - Specify the visibility scope where Javadoc comments are checked.
103  * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}.
104  * Default value is {@code public}.
105  * </li>
106  * <li>
107  * Property {@code tokens} - tokens to check
108  * Type is {@code java.lang.String[]}.
109  * Validation type is {@code tokenSet}.
110  * Default value is:
111  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
112  * METHOD_DEF</a>,
113  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
114  * CTOR_DEF</a>,
115  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">
116  * ANNOTATION_FIELD_DEF</a>,
117  * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
118  * COMPACT_CTOR_DEF</a>.
119  * </li>
120  * </ul>
121  * <p>
122  * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
123  * </p>
124  * <p>
125  * Violation Message Keys:
126  * </p>
127  * <ul>
128  * <li>
129  * {@code javadoc.missing}
130  * </li>
131  * </ul>
132  *
133  * @since 8.21
134  */
135 @FileStatefulCheck
136 public class MissingJavadocMethodCheck extends AbstractCheck {
137 
138     /**
139      * A key is pointing to the warning message text in "messages.properties"
140      * file.
141      */
142     public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
143 
144     /** Maximum children allowed in setter/getter. */
145     private static final int SETTER_GETTER_MAX_CHILDREN = 7;
146 
147     /** Pattern matching names of getter methods. */
148     private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*");
149 
150     /** Pattern matching names of setter methods. */
151     private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
152 
153     /** Maximum nodes allowed in a body of setter. */
154     private static final int SETTER_BODY_SIZE = 3;
155 
156     /** Default value of minimal amount of lines in method to allow no documentation.*/
157     private static final int DEFAULT_MIN_LINE_COUNT = -1;
158 
159     /** Specify the visibility scope where Javadoc comments are checked. */
160     private Scope scope = Scope.PUBLIC;
161 
162     /** Specify the visibility scope where Javadoc comments are not checked. */
163     private Scope excludeScope;
164 
165     /** Control the minimal amount of lines in method to allow no documentation.*/
166     private int minLineCount = DEFAULT_MIN_LINE_COUNT;
167 
168     /**
169      * Control whether to allow missing Javadoc on accessor methods for
170      * properties (setters and getters).
171      */
172     private boolean allowMissingPropertyJavadoc;
173 
174     /** Ignore method whose names are matching specified regex. */
175     private Pattern ignoreMethodNamesRegex;
176 
177     /** Configure annotations that allow missed documentation. */
178     private Set<String> allowedAnnotations = Set.of("Override");
179 
180     /**
181      * Setter to configure annotations that allow missed documentation.
182      *
183      * @param userAnnotations user's value.
184      * @since 8.21
185      */
186     public void setAllowedAnnotations(String... userAnnotations) {
187         allowedAnnotations = Set.of(userAnnotations);
188     }
189 
190     /**
191      * Setter to ignore method whose names are matching specified regex.
192      *
193      * @param pattern a pattern.
194      * @since 8.21
195      */
196     public void setIgnoreMethodNamesRegex(Pattern pattern) {
197         ignoreMethodNamesRegex = pattern;
198     }
199 
200     /**
201      * Setter to control the minimal amount of lines in method to allow no documentation.
202      *
203      * @param value user's value.
204      * @since 8.21
205      */
206     public void setMinLineCount(int value) {
207         minLineCount = value;
208     }
209 
210     /**
211      * Setter to control whether to allow missing Javadoc on accessor methods for properties
212      * (setters and getters).
213      *
214      * @param flag a {@code Boolean} value
215      * @since 8.21
216      */
217     public void setAllowMissingPropertyJavadoc(final boolean flag) {
218         allowMissingPropertyJavadoc = flag;
219     }
220 
221     /**
222      * Setter to specify the visibility scope where Javadoc comments are checked.
223      *
224      * @param scope a scope.
225      * @since 8.21
226      */
227     public void setScope(Scope scope) {
228         this.scope = scope;
229     }
230 
231     /**
232      * Setter to specify the visibility scope where Javadoc comments are not checked.
233      *
234      * @param excludeScope a scope.
235      * @since 8.21
236      */
237     public void setExcludeScope(Scope excludeScope) {
238         this.excludeScope = excludeScope;
239     }
240 
241     @Override
242     public final int[] getRequiredTokens() {
243         return CommonUtil.EMPTY_INT_ARRAY;
244     }
245 
246     @Override
247     public int[] getDefaultTokens() {
248         return getAcceptableTokens();
249     }
250 
251     @Override
252     public int[] getAcceptableTokens() {
253         return new int[] {
254             TokenTypes.METHOD_DEF,
255             TokenTypes.CTOR_DEF,
256             TokenTypes.ANNOTATION_FIELD_DEF,
257             TokenTypes.COMPACT_CTOR_DEF,
258         };
259     }
260 
261     // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
262     @SuppressWarnings("deprecation")
263     @Override
264     public final void visitToken(DetailAST ast) {
265         final Scope theScope = ScopeUtil.getScope(ast);
266         if (shouldCheck(ast, theScope)) {
267             final FileContents contents = getFileContents();
268             final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo());
269 
270             if (textBlock == null && !isMissingJavadocAllowed(ast)) {
271                 log(ast, MSG_JAVADOC_MISSING);
272             }
273         }
274     }
275 
276     /**
277      * Some javadoc.
278      *
279      * @param methodDef Some javadoc.
280      * @return Some javadoc.
281      */
282     private static int getMethodsNumberOfLine(DetailAST methodDef) {
283         final int numberOfLines;
284         final DetailAST lcurly = methodDef.getLastChild();
285         final DetailAST rcurly = lcurly.getLastChild();
286 
287         if (lcurly.getFirstChild() == rcurly) {
288             numberOfLines = 1;
289         }
290         else {
291             numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1;
292         }
293         return numberOfLines;
294     }
295 
296     /**
297      * Checks if a missing Javadoc is allowed by the check's configuration.
298      *
299      * @param ast the tree node for the method or constructor.
300      * @return True if this method or constructor doesn't need Javadoc.
301      */
302     private boolean isMissingJavadocAllowed(final DetailAST ast) {
303         return allowMissingPropertyJavadoc
304                 && (isSetterMethod(ast) || isGetterMethod(ast))
305             || matchesSkipRegex(ast)
306             || isContentsAllowMissingJavadoc(ast);
307     }
308 
309     /**
310      * Checks if the Javadoc can be missing if the method or constructor is
311      * below the minimum line count or has a special annotation.
312      *
313      * @param ast the tree node for the method or constructor.
314      * @return True if this method or constructor doesn't need Javadoc.
315      */
316     private boolean isContentsAllowMissingJavadoc(DetailAST ast) {
317         return ast.getType() != TokenTypes.ANNOTATION_FIELD_DEF
318                 && (getMethodsNumberOfLine(ast) <= minLineCount
319                     || AnnotationUtil.containsAnnotation(ast, allowedAnnotations));
320     }
321 
322     /**
323      * Checks if the given method name matches the regex. In that case
324      * we skip enforcement of javadoc for this method
325      *
326      * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF}
327      * @return true if given method name matches the regex.
328      */
329     private boolean matchesSkipRegex(DetailAST methodDef) {
330         boolean result = false;
331         if (ignoreMethodNamesRegex != null) {
332             final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT);
333             final String methodName = ident.getText();
334 
335             final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName);
336             if (matcher.matches()) {
337                 result = true;
338             }
339         }
340         return result;
341     }
342 
343     /**
344      * Whether we should check this node.
345      *
346      * @param ast a given node.
347      * @param nodeScope the scope of the node.
348      * @return whether we should check a given node.
349      */
350     private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) {
351         final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast);
352 
353         return nodeScope != excludeScope
354                 && surroundingScope != excludeScope
355                 && nodeScope.isIn(scope)
356                 && surroundingScope.isIn(scope);
357     }
358 
359     /**
360      * Returns whether an AST represents a getter method.
361      *
362      * @param ast the AST to check with
363      * @return whether the AST represents a getter method
364      */
365     public static boolean isGetterMethod(final DetailAST ast) {
366         boolean getterMethod = false;
367 
368         // Check have a method with exactly 7 children which are all that
369         // is allowed in a proper getter method which does not throw any
370         // exceptions.
371         if (ast.getType() == TokenTypes.METHOD_DEF
372                 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
373             final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
374             final String name = type.getNextSibling().getText();
375             final boolean matchesGetterFormat = GETTER_PATTERN.matcher(name).matches();
376 
377             final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
378             final boolean noParams = params.getChildCount(TokenTypes.PARAMETER_DEF) == 0;
379 
380             if (matchesGetterFormat && noParams) {
381                 // Now verify that the body consists of:
382                 // SLIST -> RETURN
383                 // RCURLY
384                 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
385 
386                 if (slist != null) {
387                     final DetailAST expr = slist.getFirstChild();
388                     getterMethod = expr.getType() == TokenTypes.LITERAL_RETURN;
389                 }
390             }
391         }
392         return getterMethod;
393     }
394 
395     /**
396      * Returns whether an AST represents a setter method.
397      *
398      * @param ast the AST to check with
399      * @return whether the AST represents a setter method
400      */
401     public static boolean isSetterMethod(final DetailAST ast) {
402         boolean setterMethod = false;
403 
404         // Check have a method with exactly 7 children which are all that
405         // is allowed in a proper setter method which does not throw any
406         // exceptions.
407         if (ast.getType() == TokenTypes.METHOD_DEF
408                 && ast.getChildCount() == SETTER_GETTER_MAX_CHILDREN) {
409             final DetailAST type = ast.findFirstToken(TokenTypes.TYPE);
410             final String name = type.getNextSibling().getText();
411             final boolean matchesSetterFormat = SETTER_PATTERN.matcher(name).matches();
412 
413             final DetailAST params = ast.findFirstToken(TokenTypes.PARAMETERS);
414             final boolean singleParam = params.getChildCount(TokenTypes.PARAMETER_DEF) == 1;
415 
416             if (matchesSetterFormat && singleParam) {
417                 // Now verify that the body consists of:
418                 // SLIST -> EXPR -> ASSIGN
419                 // SEMI
420                 // RCURLY
421                 final DetailAST slist = ast.findFirstToken(TokenTypes.SLIST);
422 
423                 if (slist != null && slist.getChildCount() == SETTER_BODY_SIZE) {
424                     final DetailAST expr = slist.getFirstChild();
425                     setterMethod = expr.getFirstChild().getType() == TokenTypes.ASSIGN;
426                 }
427             }
428         }
429         return setterMethod;
430     }
431 }