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