001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.TokenTypes; 029import com.puppycrawl.tools.checkstyle.checks.naming.AccessModifierOption; 030import com.puppycrawl.tools.checkstyle.utils.CheckUtil; 031import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 032import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 033import com.puppycrawl.tools.checkstyle.utils.UnmodifiableCollectionUtil; 034 035/** 036 * <div> 037 * Checks that a variable has a Javadoc comment. Ignores {@code serialVersionUID} fields. 038 * </div> 039 * 040 * @since 3.0 041 */ 042@StatelessCheck 043public class JavadocVariableCheck 044 extends AbstractCheck { 045 046 /** 047 * A key is pointing to the warning message text in "messages.properties" 048 * file. 049 */ 050 051 public static final String MSG_JAVADOC_MISSING = "javadoc.missing"; 052 /** 053 * Specify the set of access modifiers used to determine which fields should be checked. 054 * This includes both explicitly declared modifiers and implicit ones, such as package-private 055 * for fields without an explicit modifier. It also accounts for special cases where fields 056 * have implicit modifiers, such as {@code public static final} for interface fields and 057 * {@code public static} for enum constants, or where the nesting types accessibility is more 058 * restrictive and hides the nested field. 059 * Only fields matching the specified modifiers will be analyzed. 060 */ 061 private AccessModifierOption[] accessModifiers = { 062 AccessModifierOption.PUBLIC, 063 AccessModifierOption.PROTECTED, 064 AccessModifierOption.PACKAGE, 065 AccessModifierOption.PRIVATE, 066 }; 067 068 /** Specify the regexp to define variable names to ignore. */ 069 private Pattern ignoreNamePattern; 070 071 /** 072 * Creates a new {@code JavadocVariableCheck} instance. 073 */ 074 public JavadocVariableCheck() { 075 // no code by default 076 } 077 078 /** 079 * Setter to specify the set of access modifiers used to determine which fields should be 080 * checked. This includes both explicitly declared modifiers and implicit ones, such as 081 * package-private for fields without an explicit modifier. It also accounts for special 082 * cases where fields have implicit modifiers, such as {@code public static final} 083 * for interface fields and {@code public static} for enum constants, or where the nesting 084 * types accessibility is more restrictive and hides the nested field. 085 * Only fields matching the specified modifiers will be analyzed. 086 * 087 * @param accessModifiers access modifiers of fields to check. 088 * @since 10.22.0 089 */ 090 public void setAccessModifiers(AccessModifierOption... accessModifiers) { 091 this.accessModifiers = 092 UnmodifiableCollectionUtil.copyOfArray(accessModifiers, accessModifiers.length); 093 } 094 095 /** 096 * Setter to specify the regexp to define variable names to ignore. 097 * 098 * @param pattern a pattern. 099 * @since 5.8 100 */ 101 public void setIgnoreNamePattern(Pattern pattern) { 102 ignoreNamePattern = pattern; 103 } 104 105 @Override 106 public boolean isCommentNodesRequired() { 107 return true; 108 } 109 110 @Override 111 public int[] getDefaultTokens() { 112 return getAcceptableTokens(); 113 } 114 115 @Override 116 public int[] getAcceptableTokens() { 117 return new int[] { 118 TokenTypes.VARIABLE_DEF, 119 TokenTypes.ENUM_CONSTANT_DEF, 120 }; 121 } 122 123 /* 124 * Skipping enum values is requested. 125 * Checkstyle's issue #1669: https://github.com/checkstyle/checkstyle/issues/1669 126 */ 127 @Override 128 public int[] getRequiredTokens() { 129 return new int[] { 130 TokenTypes.VARIABLE_DEF, 131 }; 132 } 133 134 @Override 135 public void visitToken(DetailAST ast) { 136 if (shouldCheck(ast)) { 137 final DetailAST blockCommentNode = JavadocUtil.getAttachedJavadocComment(ast); 138 if (blockCommentNode == null) { 139 log(ast, MSG_JAVADOC_MISSING); 140 } 141 } 142 } 143 144 /** 145 * Decides whether the variable name of an AST is in the ignore list. 146 * 147 * @param ast the AST to check 148 * @return true if the variable name of ast is in the ignore list. 149 */ 150 private boolean isIgnored(DetailAST ast) { 151 final String name = ast.findFirstToken(TokenTypes.IDENT).getText(); 152 return ignoreNamePattern != null && ignoreNamePattern.matcher(name).matches() 153 || "serialVersionUID".equals(name); 154 } 155 156 /** 157 * Checks whether a method has the correct access modifier to be checked. 158 * 159 * @param accessModifier the access modifier of the method. 160 * @return whether the method matches the expected access modifier. 161 */ 162 private boolean matchAccessModifiers(AccessModifierOption accessModifier) { 163 return Arrays.stream(accessModifiers) 164 .anyMatch(modifier -> modifier == accessModifier); 165 } 166 167 /** 168 * Whether we should check this node. 169 * 170 * @param ast a given node. 171 * @return whether we should check a given node. 172 */ 173 private boolean shouldCheck(final DetailAST ast) { 174 boolean result = false; 175 if (!ScopeUtil.isInCodeBlock(ast) && !isIgnored(ast)) { 176 final AccessModifierOption accessModifier = 177 getAccessModifierFromModifiersTokenWithPrivateEnumSupport(ast); 178 result = matchAccessModifiers(accessModifier); 179 } 180 return result; 181 } 182 183 /** 184 * A derivative of {@link CheckUtil#getAccessModifierFromModifiersToken(DetailAST)} that 185 * considers enum definitions' visibility when evaluating the accessibility of an enum 186 * constant. 187 * <br> 188 * <a href="https://github.com/checkstyle/checkstyle/pull/16787/files#r2073671898">Implemented 189 * separately</a> to reduce scope of fix for 190 * <a href="https://github.com/checkstyle/checkstyle/issues/16786">issue #16786</a> until a 191 * wider solution can be developed. 192 * 193 * @param ast the token of the method/constructor. 194 * @return the access modifier of the method/constructor. 195 */ 196 public static AccessModifierOption getAccessModifierFromModifiersTokenWithPrivateEnumSupport( 197 DetailAST ast) { 198 // In some scenarios we want to investigate a parent AST instead 199 DetailAST selectedAst = ast; 200 201 if (selectedAst.getType() == TokenTypes.ENUM_CONSTANT_DEF) { 202 // Enum constants don't have modifiers 203 // implicitly public but validate against parent(s) 204 while (selectedAst.getType() != TokenTypes.ENUM_DEF) { 205 selectedAst = selectedAst.getParent(); 206 } 207 } 208 209 return CheckUtil.getAccessModifierFromModifiersToken(selectedAst); 210 } 211 212}