1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.javadoc;
21
22 import java.util.Arrays;
23 import java.util.regex.Pattern;
24
25 import com.puppycrawl.tools.checkstyle.StatelessCheck;
26 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
30 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
31 import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
32 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
33 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
34 import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil;
35
36 /**
37 * <div>
38 * Checks that a variable has a Javadoc comment. Ignores {@code serialVersionUID} fields.
39 * </div>
40 *
41 * @since 3.0
42 */
43 @StatelessCheck
44 public class JavadocVariableCheck
45 extends AbstractCheck {
46
47 /**
48 * A key is pointing to the warning message text in "messages.properties"
49 * file.
50 */
51 public static final String MSG_JAVADOC_MISSING = "javadoc.missing.named";
52
53 /**
54 * Specify the set of access modifiers used to determine which fields should be checked.
55 * This includes both explicitly declared modifiers and implicit ones, such as package-private
56 * for fields without an explicit modifier. It also accounts for special cases where fields
57 * have implicit modifiers, such as {@code public static final} for interface fields and
58 * {@code public static} for enum constants, or where the nesting types accessibility is more
59 * restrictive and hides the nested field.
60 * Only fields matching the specified modifiers will be analyzed.
61 */
62 private AccessModifierOption[] accessModifiers = {
63 AccessModifierOption.PUBLIC,
64 AccessModifierOption.PROTECTED,
65 AccessModifierOption.PACKAGE,
66 AccessModifierOption.PRIVATE,
67 };
68
69 /** Specify the regexp to define variable names to ignore. */
70 private Pattern ignoreNamePattern;
71
72 /**
73 * Creates a new {@code JavadocVariableCheck} instance.
74 */
75 public JavadocVariableCheck() {
76 // no code by default
77 }
78
79 /**
80 * Setter to specify the set of access modifiers used to determine which fields should be
81 * checked. This includes both explicitly declared modifiers and implicit ones, such as
82 * package-private for fields without an explicit modifier. It also accounts for special
83 * cases where fields have implicit modifiers, such as {@code public static final}
84 * for interface fields and {@code public static} for enum constants, or where the nesting
85 * types accessibility is more restrictive and hides the nested field.
86 * Only fields matching the specified modifiers will be analyzed.
87 *
88 * @param accessModifiers access modifiers of fields to check.
89 * @since 10.22.0
90 */
91 public void setAccessModifiers(AccessModifierOption... accessModifiers) {
92 this.accessModifiers =
93 UnmodifiableCollectionUtil.copyOfArray(accessModifiers, accessModifiers.length);
94 }
95
96 /**
97 * Setter to specify the regexp to define variable names to ignore.
98 *
99 * @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 }