1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2025 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.FileContents;
29 import com.puppycrawl.tools.checkstyle.api.TextBlock;
30 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31 import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption;
32 import com.puppycrawl.tools.checkstyle.utils.CheckUtil;
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
52 public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
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 * Setter to specify the set of access modifiers used to determine which fields should be
74 * checked. This includes both explicitly declared modifiers and implicit ones, such as
75 * package-private for fields without an explicit modifier. It also accounts for special
76 * cases where fields have implicit modifiers, such as {@code public static final}
77 * for interface fields and {@code public static} for enum constants, or where the nesting
78 * types accessibility is more restrictive and hides the nested field.
79 * Only fields matching the specified modifiers will be analyzed.
80 *
81 * @param accessModifiers access modifiers of fields to check.
82 * @since 10.22.0
83 */
84 public void setAccessModifiers(AccessModifierOption... accessModifiers) {
85 this.accessModifiers =
86 UnmodifiableCollectionUtil.copyOfArray(accessModifiers, accessModifiers.length);
87 }
88
89 /**
90 * Setter to specify the regexp to define variable names to ignore.
91 *
92 * @param pattern a pattern.
93 * @since 5.8
94 */
95 public void setIgnoreNamePattern(Pattern pattern) {
96 ignoreNamePattern = pattern;
97 }
98
99 @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 @Override
125 @SuppressWarnings("deprecation")
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 }