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   *
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 }