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