001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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.CommonUtil;
035import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
036
037/**
038 * <div>
039 * Checks for missing Javadoc comments for a method or constructor. The scope to verify is
040 * specified using the {@code Scope} class and defaults to {@code Scope.PUBLIC}. To verify
041 * another scope, set property scope to a different
042 * <a href="https://checkstyle.org/property_types.html#Scope">scope</a>.
043 * </div>
044 *
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 *
052 * <p>
053 * For getters and setters for the property {@code allowMissingPropertyJavadoc}, the methods must
054 * match exactly the structures below.
055 * </p>
056 * <pre>
057 * public void setNumber(final int number)
058 * {
059 *     mNumber = number;
060 * }
061 *
062 * public int getNumber()
063 * {
064 *     return mNumber;
065 * }
066 *
067 * public boolean isSomething()
068 * {
069 *     return false;
070 * }
071 * </pre>
072 * <ul>
073 * <li>
074 * Property {@code allowMissingPropertyJavadoc} - Control whether to allow missing Javadoc on
075 * accessor methods for properties (setters and getters).
076 * Type is {@code boolean}.
077 * Default value is {@code false}.
078 * </li>
079 * <li>
080 * Property {@code allowedAnnotations} - Configure annotations that allow missed
081 * documentation.
082 * Type is {@code java.lang.String[]}.
083 * Default value is {@code Override}.
084 * </li>
085 * <li>
086 * Property {@code excludeScope} - Specify the visibility scope where Javadoc comments are
087 * not checked.
088 * Type is {@code com.puppycrawl.tools.checkstyle.api.Scope}.
089 * Default value is {@code null}.
090 * </li>
091 * <li>
092 * Property {@code ignoreMethodNamesRegex} - Ignore method whose names are matching specified
093 * regex.
094 * Type is {@code java.util.regex.Pattern}.
095 * Default value is {@code null}.
096 * </li>
097 * <li>
098 * Property {@code minLineCount} - Control the minimal amount of lines in method to allow no
099 * 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
140public 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}