001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks.javadoc;
021
022import java.util.Set;
023import java.util.regex.Matcher;
024import java.util.regex.Pattern;
025
026import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
027import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
028import com.puppycrawl.tools.checkstyle.api.DetailAST;
029import com.puppycrawl.tools.checkstyle.api.FileContents;
030import com.puppycrawl.tools.checkstyle.api.Scope;
031import com.puppycrawl.tools.checkstyle.api.TextBlock;
032import com.puppycrawl.tools.checkstyle.api.TokenTypes;
033import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
034import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
035import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
036import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
037
038/**
039 * <p>
040 * Checks for missing Javadoc comments for a method or constructor. The scope to verify is
041 * specified using the {@code Scope} class and defaults to {@code Scope.PUBLIC}. To verify
042 * another scope, set property scope to a different
043 * <a href="https://checkstyle.org/property_types.html#Scope">scope</a>.
044 * </p>
045 * <p>
046 * Javadoc is not required on a method that is tagged with the {@code @Override} annotation.
047 * However, under Java 5 it is not possible to mark a method required for an interface (this
048 * was <i>corrected</i> under Java 6). Hence, Checkstyle supports using the convention of using
049 * a single {@code {@inheritDoc}} tag instead of all the other tags.
050 * </p>
051 * <p>
052 * For getters and setters for the property {@code allowMissingPropertyJavadoc}, the methods must
053 * match exactly the structures below.
054 * </p>
055 * <pre>
056 * public void setNumber(final int number)
057 * {
058 *     mNumber = number;
059 * }
060 *
061 * public int getNumber()
062 * {
063 *     return mNumber;
064 * }
065 *
066 * public boolean isSomething()
067 * {
068 *     return false;
069 * }
070 * </pre>
071 * <ul>
072 * <li>
073 * Property {@code minLineCount} - Control the minimal amount of lines in method to allow no
074 * documentation.
075 * Type is {@code int}.
076 * Default value is {@code -1}.
077 * </li>
078 * <li>
079 * Property {@code allowedAnnotations} - Configure annotations that allow missed
080 * documentation.
081 * Type is {@code java.lang.String[]}.
082 * Default value is {@code Override}.
083 * </li>
084 * <li>
085 * Property {@code scope} - Specify the visibility scope where Javadoc comments are checked.
086 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}.
087 * Default value is {@code public}.
088 * </li>
089 * <li>
090 * Property {@code excludeScope} - Specify the visibility scope where Javadoc comments are
091 * not checked.
092 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}.
093 * Default value is {@code null}.
094 * </li>
095 * <li>
096 * Property {@code allowMissingPropertyJavadoc} - Control whether to allow missing Javadoc on
097 * accessor methods for properties (setters and getters).
098 * Type is {@code boolean}.
099 * Default value is {@code false}.
100 * </li>
101 * <li>
102 * Property {@code ignoreMethodNamesRegex} - ignore method whose names are matching specified
103 * regex.
104 * Type is {@code java.util.regex.Pattern}.
105 * Default value is {@code null}.
106 * </li>
107 * <li>
108 * Property {@code tokens} - tokens to check
109 * Type is {@code java.lang.String[]}.
110 * Validation type is {@code tokenSet}.
111 * Default value is:
112 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#METHOD_DEF">
113 * METHOD_DEF</a>,
114 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#CTOR_DEF">
115 * CTOR_DEF</a>,
116 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ANNOTATION_FIELD_DEF">
117 * ANNOTATION_FIELD_DEF</a>,
118 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#COMPACT_CTOR_DEF">
119 * COMPACT_CTOR_DEF</a>.
120 * </li>
121 * </ul>
122 * <p>
123 * To configure the default check:
124 * </p>
125 * <pre>
126 * &lt;module name="MissingJavadocMethod"/&gt;
127 * </pre>
128 * <p>
129 * Example:
130 * </p>
131 * <pre>
132 * public class Test {
133 *   public Test() {} // violation, missing javadoc for constructor
134 *   public void test() {} // violation, missing javadoc for method
135 *   &#47;**
136 *    * Some description here.
137 *    *&#47;
138 *   public void test2() {} // OK
139 *
140 *   &#64;Override
141 *   public String toString() { // OK
142 *     return "Some string";
143 *   }
144 *
145 *   private void test1() {} // OK
146 *   protected void test2() {} // OK
147 *   void test3() {} // OK
148 * }
149 * </pre>
150 *
151 * <p>
152 * To configure the check for {@code private} scope:
153 * </p>
154 * <pre>
155 * &lt;module name="MissingJavadocMethod"&gt;
156 *   &lt;property name="scope" value="private"/&gt;
157 * &lt;/module&gt;
158 * </pre>
159 * <p>Example:</p>
160 * <pre>
161 * public class Test {
162 *   private void test1() {} // violation, the private method is missing javadoc
163 * }
164 * </pre>
165 *
166 * <p>
167 * To configure the check for methods which are in {@code private}, but not in {@code protected}
168 * scope:
169 * </p>
170 * <pre>
171 * &lt;module name="MissingJavadocMethod"&gt;
172 *   &lt;property name="scope" value="private"/&gt;
173 *   &lt;property name="excludeScope" value="protected"/&gt;
174 * &lt;/module&gt;
175 * </pre>
176 * <p>Example:</p>
177 * <pre>
178 * public class Test {
179 *   private void test1() {} // violation, the private method is missing javadoc
180 *   &#47;**
181 *    * Some description here
182 *    *&#47;
183 *   private void test1() {} // OK
184 *   protected void test2() {} // OK
185 * }
186 * </pre>
187 *
188 * <p>
189 * To configure the check for ignoring methods named {@code foo(),foo1(),foo2()}, etc.:
190 * </p>
191 * <pre>
192 * &lt;module name="MissingJavadocMethod"&gt;
193 *   &lt;property name="ignoreMethodNamesRegex" value="^foo.*$"/&gt;
194 * &lt;/module&gt;
195 * </pre>
196 * <p>Example:</p>
197 * <pre>
198 * public class Test {
199 *   public void test1() {} // violation, method is missing javadoc
200 *   public void foo() {} // OK
201 *   public void foobar() {} // OK
202 * }
203 * </pre>
204 *
205 * <p>
206 * To configure the check for ignoring missing javadoc for accessor methods:
207 * </p>
208 * <pre>
209 * &lt;module name="MissingJavadocMethod"&gt;
210 *   &lt;property name="allowMissingPropertyJavadoc" value="true"/&gt;
211 * &lt;/module&gt;
212 * </pre>
213 * <p>Example:</p>
214 * <pre>
215 * public class Test {
216 *   private String text;
217 *
218 *   public void test() {} // violation, method is missing javadoc
219 *   public String getText() { return text; } // OK
220 *   public void setText(String text) { this.text = text; } // OK
221 * }
222 * </pre>
223 *
224 * <p>
225 * To configure the check with annotations that allow missed documentation:
226 * </p>
227 * <pre>
228 * &lt;module name="MissingJavadocMethod"&gt;
229 *   &lt;property name="allowedAnnotations" value="Override,Deprecated"/&gt;
230 * &lt;/module&gt;
231 * </pre>
232 * <p>Example:</p>
233 * <pre>
234 * public class Test {
235 *   public void test() {} // violation, method is missing javadoc
236 *   &#64;Override
237 *   public void test1() {} // OK
238 *   &#64;Deprecated
239 *   public void test2() {} // OK
240 *   &#64;SuppressWarnings
241 *   public void test3() {} // violation, method is missing javadoc
242 *   &#47;**
243 *    * Some description here.
244 *    *&#47;
245 *   &#64;SuppressWarnings
246 *   public void test4() {} // OK
247 * }
248 * </pre>
249 * <p>
250 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
251 * </p>
252 * <p>
253 * Violation Message Keys:
254 * </p>
255 * <ul>
256 * <li>
257 * {@code javadoc.missing}
258 * </li>
259 * </ul>
260 *
261 * @since 8.21
262 */
263@FileStatefulCheck
264public class MissingJavadocMethodCheck extends AbstractCheck {
265
266    /**
267     * A key is pointing to the warning message text in "messages.properties"
268     * file.
269     */
270    public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
271
272    /** Default value of minimal amount of lines in method to allow no documentation.*/
273    private static final int DEFAULT_MIN_LINE_COUNT = -1;
274
275    /** Specify the visibility scope where Javadoc comments are checked. */
276    private Scope scope = Scope.PUBLIC;
277
278    /** Specify the visibility scope where Javadoc comments are not checked. */
279    private Scope excludeScope;
280
281    /** Control the minimal amount of lines in method to allow no documentation.*/
282    private int minLineCount = DEFAULT_MIN_LINE_COUNT;
283
284    /**
285     * Control whether to allow missing Javadoc on accessor methods for
286     * properties (setters and getters).
287     */
288    private boolean allowMissingPropertyJavadoc;
289
290    /** Ignore method whose names are matching specified regex. */
291    private Pattern ignoreMethodNamesRegex;
292
293    /** Configure annotations that allow missed documentation. */
294    private Set<String> allowedAnnotations = Set.of("Override");
295
296    /**
297     * Setter to configure annotations that allow missed documentation.
298     *
299     * @param userAnnotations user's value.
300     */
301    public void setAllowedAnnotations(String... userAnnotations) {
302        allowedAnnotations = Set.of(userAnnotations);
303    }
304
305    /**
306     * Setter to ignore method whose names are matching specified regex.
307     *
308     * @param pattern a pattern.
309     */
310    public void setIgnoreMethodNamesRegex(Pattern pattern) {
311        ignoreMethodNamesRegex = pattern;
312    }
313
314    /**
315     * Setter to control the minimal amount of lines in method to allow no documentation.
316     *
317     * @param value user's value.
318     */
319    public void setMinLineCount(int value) {
320        minLineCount = value;
321    }
322
323    /**
324     * Setter to control whether to allow missing Javadoc on accessor methods for properties
325     * (setters and getters).
326     *
327     * @param flag a {@code Boolean} value
328     */
329    public void setAllowMissingPropertyJavadoc(final boolean flag) {
330        allowMissingPropertyJavadoc = flag;
331    }
332
333    /**
334     * Setter to specify the visibility scope where Javadoc comments are checked.
335     *
336     * @param scope a scope.
337     */
338    public void setScope(Scope scope) {
339        this.scope = scope;
340    }
341
342    /**
343     * Setter to specify the visibility scope where Javadoc comments are not checked.
344     *
345     * @param excludeScope a scope.
346     */
347    public void setExcludeScope(Scope excludeScope) {
348        this.excludeScope = excludeScope;
349    }
350
351    @Override
352    public final int[] getRequiredTokens() {
353        return CommonUtil.EMPTY_INT_ARRAY;
354    }
355
356    @Override
357    public int[] getDefaultTokens() {
358        return getAcceptableTokens();
359    }
360
361    @Override
362    public int[] getAcceptableTokens() {
363        return new int[] {
364            TokenTypes.METHOD_DEF,
365            TokenTypes.CTOR_DEF,
366            TokenTypes.ANNOTATION_FIELD_DEF,
367            TokenTypes.COMPACT_CTOR_DEF,
368        };
369    }
370
371    // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
372    @SuppressWarnings("deprecation")
373    @Override
374    public final void visitToken(DetailAST ast) {
375        final Scope theScope = ScopeUtil.getScope(ast);
376        if (shouldCheck(ast, theScope)) {
377            final FileContents contents = getFileContents();
378            final TextBlock textBlock = contents.getJavadocBefore(ast.getLineNo());
379
380            if (textBlock == null && !isMissingJavadocAllowed(ast)) {
381                log(ast, MSG_JAVADOC_MISSING);
382            }
383        }
384    }
385
386    /**
387     * Some javadoc.
388     *
389     * @param methodDef Some javadoc.
390     * @return Some javadoc.
391     */
392    private static int getMethodsNumberOfLine(DetailAST methodDef) {
393        final int numberOfLines;
394        final DetailAST lcurly = methodDef.getLastChild();
395        final DetailAST rcurly = lcurly.getLastChild();
396
397        if (lcurly.getFirstChild() == rcurly) {
398            numberOfLines = 1;
399        }
400        else {
401            numberOfLines = rcurly.getLineNo() - lcurly.getLineNo() - 1;
402        }
403        return numberOfLines;
404    }
405
406    /**
407     * Checks if a missing Javadoc is allowed by the check's configuration.
408     *
409     * @param ast the tree node for the method or constructor.
410     * @return True if this method or constructor doesn't need Javadoc.
411     */
412    private boolean isMissingJavadocAllowed(final DetailAST ast) {
413        return allowMissingPropertyJavadoc
414                && (CheckUtil.isSetterMethod(ast) || CheckUtil.isGetterMethod(ast))
415            || matchesSkipRegex(ast)
416            || isContentsAllowMissingJavadoc(ast);
417    }
418
419    /**
420     * Checks if the Javadoc can be missing if the method or constructor is
421     * below the minimum line count or has a special annotation.
422     *
423     * @param ast the tree node for the method or constructor.
424     * @return True if this method or constructor doesn't need Javadoc.
425     */
426    private boolean isContentsAllowMissingJavadoc(DetailAST ast) {
427        return (ast.getType() == TokenTypes.METHOD_DEF
428                || ast.getType() == TokenTypes.CTOR_DEF
429                || ast.getType() == TokenTypes.COMPACT_CTOR_DEF)
430                && (getMethodsNumberOfLine(ast) <= minLineCount
431                    || AnnotationUtil.containsAnnotation(ast, allowedAnnotations));
432    }
433
434    /**
435     * Checks if the given method name matches the regex. In that case
436     * we skip enforcement of javadoc for this method
437     *
438     * @param methodDef {@link TokenTypes#METHOD_DEF METHOD_DEF}
439     * @return true if given method name matches the regex.
440     */
441    private boolean matchesSkipRegex(DetailAST methodDef) {
442        boolean result = false;
443        if (ignoreMethodNamesRegex != null) {
444            final DetailAST ident = methodDef.findFirstToken(TokenTypes.IDENT);
445            final String methodName = ident.getText();
446
447            final Matcher matcher = ignoreMethodNamesRegex.matcher(methodName);
448            if (matcher.matches()) {
449                result = true;
450            }
451        }
452        return result;
453    }
454
455    /**
456     * Whether we should check this node.
457     *
458     * @param ast a given node.
459     * @param nodeScope the scope of the node.
460     * @return whether we should check a given node.
461     */
462    private boolean shouldCheck(final DetailAST ast, final Scope nodeScope) {
463        final Scope surroundingScope = ScopeUtil.getSurroundingScope(ast);
464
465        return (excludeScope == null
466                || nodeScope != excludeScope
467                && surroundingScope != excludeScope)
468            && nodeScope.isIn(scope)
469            && surroundingScope.isIn(scope);
470    }
471
472}