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