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.naming; 021 022import java.util.regex.Pattern; 023 024import com.puppycrawl.tools.checkstyle.StatelessCheck; 025import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 026import com.puppycrawl.tools.checkstyle.api.DetailAST; 027import com.puppycrawl.tools.checkstyle.api.TokenTypes; 028import com.puppycrawl.tools.checkstyle.utils.NullUtil; 029import com.puppycrawl.tools.checkstyle.utils.ScopeUtil; 030 031/** 032 * <div> 033 * Checks that non-constant field names conform to the 034 * <a href= 035 * "https://google.github.io/styleguide/javaguide.html#s5.2.5-non-constant-field-names"> 036 * Google Java Style Guide</a> for non-constant field naming. 037 * </div> 038 * 039 * <p> 040 * This check enforces Google's specific non-constant field naming requirements: 041 * </p> 042 * <ul> 043 * <li>Non-constant field names must start with a lowercase letter and use uppercase letters 044 * for word boundaries.</li> 045 * <li>Underscores may be used to separate adjacent numbers (e.g., version 046 * numbers like {@code guava33_4_5}), but NOT between letters and digits.</li> 047 * </ul> 048 * 049 * <p> 050 * Static fields are skipped because Checkstyle cannot determine type immutability 051 * to distinguish constants from non-constants. Fields in interfaces and annotations 052 * are also skipped because they are implicitly {@code public static final} (constants) 053 * </p> 054 * 055 * @since 13.3.0 056 */ 057@StatelessCheck 058public class GoogleNonConstantFieldNameCheck extends AbstractCheck { 059 060 /** 061 * A key is pointing to the violation message text in "messages.properties" file. 062 */ 063 public static final String MSG_KEY_INVALID_FORMAT = "google.non.constant.field.name.format"; 064 065 /** 066 * Pattern for valid non-constant field name in Google style. 067 * Format: start with lowercase, have at least 2 chars, optionally followed by numbering suffix. 068 * 069 * <p> 070 * Explanation: 071 * <ul> 072 * <li>{@code ^(?![a-z]$)} - Negative lookahead: cannot be single lowercase char</li> 073 * <li>{@code (?![a-z][A-Z])} - Negative lookahead: cannot be like "fO"</li> 074 * <li>{@code [a-z]} - Must start with lowercase</li> 075 * <li>{@code [a-z0-9]*+} - Followed by lowercase or digits</li> 076 * <li>{@code (?:[A-Z][a-z0-9]*+)*+} - CamelCase humps (uppercase followed by lowercase)</li> 077 * <li>{@code $} - End of string (numbering suffix validated separately)</li> 078 * </ul> 079 */ 080 private static final Pattern NON_CONSTANT_FIELD_NAME_PATTERN = Pattern 081 .compile("^(?![a-z]$)(?![a-z][A-Z])[a-z][a-z0-9]*+(?:[A-Z][a-z0-9]*+)*+$"); 082 083 /** 084 * Pattern to strip trailing numbering suffix (underscore followed by digits). 085 */ 086 private static final Pattern NUMBERING_SUFFIX_PATTERN = Pattern.compile("(?:_[0-9]++)+$"); 087 088 /** 089 * Pattern to detect invalid underscore usage: leading, trailing, consecutive, 090 * or between letter-letter, letter-digit, or digit-letter combinations. 091 */ 092 private static final Pattern INVALID_UNDERSCORE_PATTERN = 093 Pattern.compile("^_|_$|__|[a-zA-Z]_[a-zA-Z]|[a-zA-Z]_\\d|\\d_[a-zA-Z]"); 094 095 /** 096 * Creates a new {@code GoogleNonConstantFieldNameCheck} instance. 097 */ 098 public GoogleNonConstantFieldNameCheck() { 099 // no code by default 100 } 101 102 @Override 103 public int[] getDefaultTokens() { 104 return getRequiredTokens(); 105 } 106 107 @Override 108 public int[] getAcceptableTokens() { 109 return getRequiredTokens(); 110 } 111 112 @Override 113 public int[] getRequiredTokens() { 114 return new int[] {TokenTypes.VARIABLE_DEF}; 115 } 116 117 @Override 118 public void visitToken(DetailAST ast) { 119 if (shouldCheckFieldName(ast)) { 120 final DetailAST nameAst = getIdent(ast); 121 final String fieldName = nameAst.getText(); 122 123 validateNonConstantFieldName(nameAst, fieldName); 124 } 125 } 126 127 /** 128 * Returns the IDENT node of the given AST. 129 * 130 * @param ast the AST node 131 * @return the IDENT child node 132 */ 133 private static DetailAST getIdent(DetailAST ast) { 134 return NullUtil.notNull(ast.findFirstToken(TokenTypes.IDENT)); 135 } 136 137 /** 138 * Checks if this field should be validated. Returns true for instance fields only. 139 * Static fields are excluded because Checkstyle cannot determine type immutability. 140 * Local variables and interface/annotation fields are also excluded. 141 * 142 * @param ast the VARIABLE_DEF AST node 143 * @return true if this variable should be checked 144 */ 145 private static boolean shouldCheckFieldName(DetailAST ast) { 146 final DetailAST modifiersAST = getModifiers(ast); 147 final boolean isStatic = 148 modifiersAST.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 149 150 return !isStatic 151 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast) 152 && !ScopeUtil.isLocalVariableDef(ast); 153 } 154 155 /** 156 * Returns the MODIFIERS node of the given AST. 157 * The MODIFIERS node is always present in the AST for type, method, and field declarations, 158 * even when no modifiers are explicitly written in code (e.g., {@code int x;} still 159 * has an empty MODIFIERS node). 160 * 161 * @param ast the AST node 162 * @return the MODIFIERS child node 163 */ 164 private static DetailAST getModifiers(DetailAST ast) { 165 return NullUtil.notNull(ast.findFirstToken(TokenTypes.MODIFIERS)); 166 } 167 168 /** 169 * Validates a non-constant field name according to Google style. 170 * 171 * @param nameAst the IDENT AST node containing the field name 172 * @param fieldName the field name string 173 */ 174 private void validateNonConstantFieldName(DetailAST nameAst, String fieldName) { 175 final String nameWithoutNumberingSuffix = NUMBERING_SUFFIX_PATTERN 176 .matcher(fieldName).replaceAll(""); 177 178 if (INVALID_UNDERSCORE_PATTERN.matcher(fieldName).find() 179 || !NON_CONSTANT_FIELD_NAME_PATTERN.matcher(nameWithoutNumberingSuffix).matches()) { 180 log(nameAst, MSG_KEY_INVALID_FORMAT, fieldName); 181 } 182 } 183 184}