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