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