View Javadoc
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 }