001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
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 * </code></pre></div>
072 *
073 * @since 8.21
074 */
075@FileStatefulCheck
076public class MissingJavadocMethodCheck extends AbstractCheck {
077
078    /**
079     * A key is pointing to the warning message text in "messages.properties"
080     * file.
081     */
082    public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
083
084    /** Maximum children allowed in setter/getter. */
085    private static final int SETTER_GETTER_MAX_CHILDREN = 7;
086
087    /** Pattern matching names of getter methods. */
088    private static final Pattern GETTER_PATTERN = Pattern.compile("^(is|get)[A-Z].*");
089
090    /** Pattern matching names of setter methods. */
091    private static final Pattern SETTER_PATTERN = Pattern.compile("^set[A-Z].*");
092
093    /** Maximum nodes allowed in a body of setter. */
094    private static final int SETTER_BODY_SIZE = 3;
095
096    /** Default value of minimal amount of lines in method to allow no documentation.*/
097    private static final int DEFAULT_MIN_LINE_COUNT = -1;
098
099    /** 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    @SuppressWarnings("deprecation")
203    @Override
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}