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, or where the nesting types accessibility is more restrictive and hides the
048 * nested field. Only fields matching the specified modifiers will be analyzed.
049 * Type is {@code com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption[]}.
050 * Default value is {@code public, protected, package, private}.
051 * </li>
052 * <li>
053 * Property {@code ignoreNamePattern} - Specify the regexp to define variable names to ignore.
054 * Type is {@code java.util.regex.Pattern}.
055 * Default value is {@code null}.
056 * </li>
057 * <li>
058 * Property {@code tokens} - tokens to check
059 * Type is {@code java.lang.String[]}.
060 * Validation type is {@code tokenSet}.
061 * Default value is:
062 * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
063 * ENUM_CONSTANT_DEF</a>.
064 * </li>
065 * </ul>
066 *
067 * <p>
068 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
069 * </p>
070 *
071 * <p>
072 * Violation Message Keys:
073 * </p>
074 * <ul>
075 * <li>
076 * {@code javadoc.missing}
077 * </li>
078 * </ul>
079 *
080 * @since 3.0
081 */
082@StatelessCheck
083public class JavadocVariableCheck
084    extends AbstractCheck {
085
086    /**
087     * A key is pointing to the warning message text in "messages.properties"
088     * file.
089     */
090
091    public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
092    /**
093     * Specify the set of access modifiers used to determine which fields should be checked.
094     *  This includes both explicitly declared modifiers and implicit ones, such as package-private
095     *  for fields without an explicit modifier. It also accounts for special cases where fields
096     *  have implicit modifiers, such as {@code public static final} for interface fields and
097     *  {@code public static} for enum constants, or where the nesting types accessibility is more
098     *  restrictive and hides the nested field.
099     *  Only fields matching the specified modifiers will be analyzed.
100     */
101    private AccessModifierOption[] accessModifiers = {
102        AccessModifierOption.PUBLIC,
103        AccessModifierOption.PROTECTED,
104        AccessModifierOption.PACKAGE,
105        AccessModifierOption.PRIVATE,
106    };
107
108    /** Specify the regexp to define variable names to ignore. */
109    private Pattern ignoreNamePattern;
110
111    /**
112     * Setter to specify the set of access modifiers used to determine which fields should be
113     * checked. This includes both explicitly declared modifiers and implicit ones, such as
114     * package-private for fields without an explicit modifier. It also accounts for special
115     * cases where fields have implicit modifiers, such as {@code public static final}
116     * for interface fields and {@code public static} for enum constants, or where the nesting
117     * types accessibility is more restrictive and hides the nested field.
118     * Only fields matching the specified modifiers will be analyzed.
119     *
120     * @param accessModifiers access modifiers of fields to check.
121     * @since 10.22.0
122     */
123    public void setAccessModifiers(AccessModifierOption... accessModifiers) {
124        this.accessModifiers =
125            UnmodifiableCollectionUtil.copyOfArray(accessModifiers, accessModifiers.length);
126    }
127
128    /**
129     * Setter to specify the regexp to define variable names to ignore.
130     *
131     * @param pattern a pattern.
132     * @since 5.8
133     */
134    public void setIgnoreNamePattern(Pattern pattern) {
135        ignoreNamePattern = pattern;
136    }
137
138    @Override
139    public int[] getDefaultTokens() {
140        return getAcceptableTokens();
141    }
142
143    @Override
144    public int[] getAcceptableTokens() {
145        return new int[] {
146            TokenTypes.VARIABLE_DEF,
147            TokenTypes.ENUM_CONSTANT_DEF,
148        };
149    }
150
151    /*
152     * Skipping enum values is requested.
153     * Checkstyle's issue #1669: https://github.com/checkstyle/checkstyle/issues/1669
154     */
155    @Override
156    public int[] getRequiredTokens() {
157        return new int[] {
158            TokenTypes.VARIABLE_DEF,
159        };
160    }
161
162    // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
163    @SuppressWarnings("deprecation")
164    @Override
165    public void visitToken(DetailAST ast) {
166        if (shouldCheck(ast)) {
167            final FileContents contents = getFileContents();
168            final TextBlock textBlock =
169                contents.getJavadocBefore(ast.getLineNo());
170
171            if (textBlock == null) {
172                log(ast, MSG_JAVADOC_MISSING);
173            }
174        }
175    }
176
177    /**
178     * Decides whether the variable name of an AST is in the ignore list.
179     *
180     * @param ast the AST to check
181     * @return true if the variable name of ast is in the ignore list.
182     */
183    private boolean isIgnored(DetailAST ast) {
184        final String name = ast.findFirstToken(TokenTypes.IDENT).getText();
185        return ignoreNamePattern != null && ignoreNamePattern.matcher(name).matches()
186            || "serialVersionUID".equals(name);
187    }
188
189    /**
190     * Checks whether a method has the correct access modifier to be checked.
191     *
192     * @param accessModifier the access modifier of the method.
193     * @return whether the method matches the expected access modifier.
194     */
195    private boolean matchAccessModifiers(AccessModifierOption accessModifier) {
196        return Arrays.stream(accessModifiers)
197            .anyMatch(modifier -> modifier == accessModifier);
198    }
199
200    /**
201     * Whether we should check this node.
202     *
203     * @param ast a given node.
204     * @return whether we should check a given node.
205     */
206    private boolean shouldCheck(final DetailAST ast) {
207        boolean result = false;
208        if (!ScopeUtil.isInCodeBlock(ast) && !isIgnored(ast)) {
209            final AccessModifierOption accessModifier =
210                    getAccessModifierFromModifiersTokenWithPrivateEnumSupport(ast);
211            result = matchAccessModifiers(accessModifier);
212        }
213        return result;
214    }
215
216    /**
217     * A derivative of {@link CheckUtil#getAccessModifierFromModifiersToken(DetailAST)} that
218     * considers enum definitions' visibility when evaluating the accessibility of an enum
219     * constant.
220     * <br>
221     * <a href="https://github.com/checkstyle/checkstyle/pull/16787/files#r2073671898">Implemented
222     * separately</a> to reduce scope of fix for
223     * <a href="https://github.com/checkstyle/checkstyle/issues/16786">issue #16786</a> until a
224     * wider solution can be developed.
225     *
226     * @param ast the token of the method/constructor.
227     * @return the access modifier of the method/constructor.
228     */
229    public static AccessModifierOption getAccessModifierFromModifiersTokenWithPrivateEnumSupport(
230            DetailAST ast) {
231        // In some scenarios we want to investigate a parent AST instead
232        DetailAST selectedAst = ast;
233
234        if (selectedAst.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
235            // Enum constants don't have modifiers
236            // implicitly public but validate against parent(s)
237            while (selectedAst.getType() != TokenTypes.ENUM_DEF) {
238                selectedAst = selectedAst.getParent();
239            }
240        }
241
242        return CheckUtil.getAccessModifierFromModifiersToken(selectedAst);
243    }
244}