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.Arrays;
023import java.util.regex.Pattern;
024
025import com.puppycrawl.tools.checkstyle.StatelessCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.FileContents;
029import com.puppycrawl.tools.checkstyle.api.TextBlock;
030import com.puppycrawl.tools.checkstyle.api.TokenTypes;
031import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
032import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
033import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
034import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil;
035
036/**
037 * <div>
038 * Checks that a variable has a Javadoc comment. Ignores {@code serialVersionUID} fields.
039 * </div>
040 * <ul>
041 * <li>
042 * Property {@code accessModifiers} - Specify the set of access modifiers used to determine which
043 * fields should be checked. This includes both explicitly declared modifiers and implicit ones,
044 * such as package-private for fields without an explicit modifier.
045 * It also accounts for special cases where fields have implicit modifiers,
046 * such as {@code public static final} for interface fields and {@code public static}
047 * for enum constants. Only fields matching the specified modifiers will be analyzed.
048 * Type is {@code com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption[]}.
049 * Default value is {@code public, protected, package, private}.
050 * </li>
051 * <li>
052 * Property {@code ignoreNamePattern} - Specify the regexp to define variable names to ignore.
053 * Type is {@code java.util.regex.Pattern}.
054 * Default value is {@code null}.
055 * </li>
056 * <li>
057 * Property {@code tokens} - tokens to check
058 * Type is {@code java.lang.String[]}.
059 * Validation type is {@code tokenSet}.
060 * Default value is:
061 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
062 * ENUM_CONSTANT_DEF</a>.
063 * </li>
064 * </ul>
065 *
066 * <p>
067 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
068 * </p>
069 *
070 * <p>
071 * Violation Message Keys:
072 * </p>
073 * <ul>
074 * <li>
075 * {@code javadoc.missing}
076 * </li>
077 * </ul>
078 *
079 * @since 3.0
080 */
081@StatelessCheck
082public class JavadocVariableCheck
083    extends AbstractCheck {
084
085    /**
086     * A key is pointing to the warning message text in "messages.properties"
087     * file.
088     */
089
090    public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
091    /**
092     * Specify the set of access modifiers used to determine which fields should be checked.
093     *  This includes both explicitly declared modifiers and implicit ones, such as package-private
094     *  for fields without an explicit modifier. It also accounts for special cases where fields
095     *  have implicit modifiers, such as {@code public static final} for interface fields and
096     *  {@code public static} for enum constants.
097     *  Only fields matching the specified modifiers will be analyzed.
098     */
099    private AccessModifierOption[] accessModifiers = {
100        AccessModifierOption.PUBLIC,
101        AccessModifierOption.PROTECTED,
102        AccessModifierOption.PACKAGE,
103        AccessModifierOption.PRIVATE,
104    };
105
106    /** Specify the regexp to define variable names to ignore. */
107    private Pattern ignoreNamePattern;
108
109    /**
110     * Setter to specify the set of access modifiers used to determine which fields should be
111     * checked. This includes both explicitly declared modifiers and implicit ones, such as
112     * package-private for fields without an explicit modifier. It also accounts for special
113     * cases where fields have implicit modifiers, such as {@code public static final}
114     * for interface fields and {@code public static} for enum constants.
115     * Only fields matching the specified modifiers will be analyzed.
116     *
117     * @param accessModifiers access modifiers of fields to check.
118     * @since 10.22.0
119     */
120    public void setAccessModifiers(AccessModifierOption... accessModifiers) {
121        this.accessModifiers =
122            UnmodifiableCollectionUtil.copyOfArray(accessModifiers, accessModifiers.length);
123    }
124
125    /**
126     * Setter to specify the regexp to define variable names to ignore.
127     *
128     * @param pattern a pattern.
129     * @since 5.8
130     */
131    public void setIgnoreNamePattern(Pattern pattern) {
132        ignoreNamePattern = pattern;
133    }
134
135    @Override
136    public int[] getDefaultTokens() {
137        return getAcceptableTokens();
138    }
139
140    @Override
141    public int[] getAcceptableTokens() {
142        return new int[] {
143            TokenTypes.VARIABLE_DEF,
144            TokenTypes.ENUM_CONSTANT_DEF,
145        };
146    }
147
148    /*
149     * Skipping enum values is requested.
150     * Checkstyle's issue #1669: https://github.com/checkstyle/checkstyle/issues/1669
151     */
152    @Override
153    public int[] getRequiredTokens() {
154        return new int[] {
155            TokenTypes.VARIABLE_DEF,
156        };
157    }
158
159    // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
160    @SuppressWarnings("deprecation")
161    @Override
162    public void visitToken(DetailAST ast) {
163        if (shouldCheck(ast)) {
164            final FileContents contents = getFileContents();
165            final TextBlock textBlock =
166                contents.getJavadocBefore(ast.getLineNo());
167
168            if (textBlock == null) {
169                log(ast, MSG_JAVADOC_MISSING);
170            }
171        }
172    }
173
174    /**
175     * Decides whether the variable name of an AST is in the ignore list.
176     *
177     * @param ast the AST to check
178     * @return true if the variable name of ast is in the ignore list.
179     */
180    private boolean isIgnored(DetailAST ast) {
181        final String name = ast.findFirstToken(TokenTypes.IDENT).getText();
182        return ignoreNamePattern != null && ignoreNamePattern.matcher(name).matches()
183            || "serialVersionUID".equals(name);
184    }
185
186    /**
187     * Checks whether a method has the correct access modifier to be checked.
188     *
189     * @param accessModifier the access modifier of the method.
190     * @return whether the method matches the expected access modifier.
191     */
192    private boolean matchAccessModifiers(AccessModifierOption accessModifier) {
193        return Arrays.stream(accessModifiers)
194            .anyMatch(modifier -> modifier == accessModifier);
195    }
196
197    /**
198     * Whether we should check this node.
199     *
200     * @param ast a given node.
201     * @return whether we should check a given node.
202     */
203    private boolean shouldCheck(final DetailAST ast) {
204        boolean result = false;
205        if (!ScopeUtil.isInCodeBlock(ast) && !isIgnored(ast)) {
206            final AccessModifierOption accessModifier =
207                    CheckUtil.getAccessModifierFromModifiersToken(ast);
208            result = matchAccessModifiers(accessModifier);
209        }
210        return result;
211    }
212}