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