View Javadoc
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   * <ul>
41   * <li>
42   * Property {@code accessModifiers} - Specify the set of access modifiers used to determine which
43   * fields should be checked. This includes both explicitly declared modifiers and implicit ones,
44   * such as package-private for fields without an explicit modifier.
45   * It also accounts for special cases where fields have implicit modifiers,
46   * such as {@code public static final} for interface fields and {@code public static}
47   * for enum constants, or where the nesting types accessibility is more restrictive and hides the
48   * nested field. Only fields matching the specified modifiers will be analyzed.
49   * Type is {@code com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption[]}.
50   * Default value is {@code public, protected, package, private}.
51   * </li>
52   * <li>
53   * Property {@code ignoreNamePattern} - Specify the regexp to define variable names to ignore.
54   * Type is {@code java.util.regex.Pattern}.
55   * Default value is {@code null}.
56   * </li>
57   * <li>
58   * Property {@code tokens} - tokens to check
59   * Type is {@code java.lang.String[]}.
60   * Validation type is {@code tokenSet}.
61   * Default value is:
62   * <a href="https://checkstyle.org/apidocs/com/puppycrawl/tools/checkstyle/api/TokenTypes.html#ENUM_CONSTANT_DEF">
63   * ENUM_CONSTANT_DEF</a>.
64   * </li>
65   * </ul>
66   *
67   * <p>
68   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
69   * </p>
70   *
71   * <p>
72   * Violation Message Keys:
73   * </p>
74   * <ul>
75   * <li>
76   * {@code javadoc.missing}
77   * </li>
78   * </ul>
79   *
80   * @since 3.0
81   */
82  @StatelessCheck
83  public class JavadocVariableCheck
84      extends AbstractCheck {
85  
86      /**
87       * A key is pointing to the warning message text in "messages.properties"
88       * file.
89       */
90  
91      public static final String MSG_JAVADOC_MISSING = "javadoc.missing";
92      /**
93       * Specify the set of access modifiers used to determine which fields should be checked.
94       *  This includes both explicitly declared modifiers and implicit ones, such as package-private
95       *  for fields without an explicit modifier. It also accounts for special cases where fields
96       *  have implicit modifiers, such as {@code public static final} for interface fields and
97       *  {@code public static} for enum constants, or where the nesting types accessibility is more
98       *  restrictive and hides the nested field.
99       *  Only fields matching the specified modifiers will be analyzed.
100      */
101     private AccessModifierOption[] accessModifiers = {
102         AccessModifierOption.PUBLIC,
103         AccessModifierOption.PROTECTED,
104         AccessModifierOption.PACKAGE,
105         AccessModifierOption.PRIVATE,
106     };
107 
108     /** Specify the regexp to define variable names to ignore. */
109     private Pattern ignoreNamePattern;
110 
111     /**
112      * Setter to specify the set of access modifiers used to determine which fields should be
113      * checked. This includes both explicitly declared modifiers and implicit ones, such as
114      * package-private for fields without an explicit modifier. It also accounts for special
115      * cases where fields have implicit modifiers, such as {@code public static final}
116      * for interface fields and {@code public static} for enum constants, or where the nesting
117      * types accessibility is more restrictive and hides the nested field.
118      * Only fields matching the specified modifiers will be analyzed.
119      *
120      * @param accessModifiers access modifiers of fields to check.
121      * @since 10.22.0
122      */
123     public void setAccessModifiers(AccessModifierOption... accessModifiers) {
124         this.accessModifiers =
125             UnmodifiableCollectionUtil.copyOfArray(accessModifiers, accessModifiers.length);
126     }
127 
128     /**
129      * Setter to specify the regexp to define variable names to ignore.
130      *
131      * @param pattern a pattern.
132      * @since 5.8
133      */
134     public void setIgnoreNamePattern(Pattern pattern) {
135         ignoreNamePattern = pattern;
136     }
137 
138     @Override
139     public int[] getDefaultTokens() {
140         return getAcceptableTokens();
141     }
142 
143     @Override
144     public int[] getAcceptableTokens() {
145         return new int[] {
146             TokenTypes.VARIABLE_DEF,
147             TokenTypes.ENUM_CONSTANT_DEF,
148         };
149     }
150 
151     /*
152      * Skipping enum values is requested.
153      * Checkstyle's issue #1669: https://github.com/checkstyle/checkstyle/issues/1669
154      */
155     @Override
156     public int[] getRequiredTokens() {
157         return new int[] {
158             TokenTypes.VARIABLE_DEF,
159         };
160     }
161 
162     // suppress deprecation until https://github.com/checkstyle/checkstyle/issues/11166
163     @SuppressWarnings("deprecation")
164     @Override
165     public void visitToken(DetailAST ast) {
166         if (shouldCheck(ast)) {
167             final FileContents contents = getFileContents();
168             final TextBlock textBlock =
169                 contents.getJavadocBefore(ast.getLineNo());
170 
171             if (textBlock == null) {
172                 log(ast, MSG_JAVADOC_MISSING);
173             }
174         }
175     }
176 
177     /**
178      * Decides whether the variable name of an AST is in the ignore list.
179      *
180      * @param ast the AST to check
181      * @return true if the variable name of ast is in the ignore list.
182      */
183     private boolean isIgnored(DetailAST ast) {
184         final String name = ast.findFirstToken(TokenTypes.IDENT).getText();
185         return ignoreNamePattern != null && ignoreNamePattern.matcher(name).matches()
186             || "serialVersionUID".equals(name);
187     }
188 
189     /**
190      * Checks whether a method has the correct access modifier to be checked.
191      *
192      * @param accessModifier the access modifier of the method.
193      * @return whether the method matches the expected access modifier.
194      */
195     private boolean matchAccessModifiers(AccessModifierOption accessModifier) {
196         return Arrays.stream(accessModifiers)
197             .anyMatch(modifier -> modifier == accessModifier);
198     }
199 
200     /**
201      * Whether we should check this node.
202      *
203      * @param ast a given node.
204      * @return whether we should check a given node.
205      */
206     private boolean shouldCheck(final DetailAST ast) {
207         boolean result = false;
208         if (!ScopeUtil.isInCodeBlock(ast) && !isIgnored(ast)) {
209             final AccessModifierOption accessModifier =
210                     getAccessModifierFromModifiersTokenWithPrivateEnumSupport(ast);
211             result = matchAccessModifiers(accessModifier);
212         }
213         return result;
214     }
215 
216     /**
217      * A derivative of {@link CheckUtil#getAccessModifierFromModifiersToken(DetailAST)} that
218      * considers enum definitions' visibility when evaluating the accessibility of an enum
219      * constant.
220      * <br>
221      * <a href="https://github.com/checkstyle/checkstyle/pull/16787/files#r2073671898">Implemented
222      * separately</a> to reduce scope of fix for
223      * <a href="https://github.com/checkstyle/checkstyle/issues/16786">issue #16786</a> until a
224      * wider solution can be developed.
225      *
226      * @param ast the token of the method/constructor.
227      * @return the access modifier of the method/constructor.
228      */
229     public static AccessModifierOption getAccessModifierFromModifiersTokenWithPrivateEnumSupport(
230             DetailAST ast) {
231         // In some scenarios we want to investigate a parent AST instead
232         DetailAST selectedAst = ast;
233 
234         if (selectedAst.getType() == TokenTypes.ENUM_CONSTANT_DEF) {
235             // Enum constants don't have modifiers
236             // implicitly public but validate against parent(s)
237             while (selectedAst.getType() != TokenTypes.ENUM_DEF) {
238                 selectedAst = selectedAst.getParent();
239             }
240         }
241 
242         return CheckUtil.getAccessModifierFromModifiersToken(selectedAst);
243     }
244 }