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 @Override 096 public int[] getDefaultTokens() { 097 return getRequiredTokens(); 098 } 099 100 @Override 101 public int[] getAcceptableTokens() { 102 return getRequiredTokens(); 103 } 104 105 @Override 106 public int[] getRequiredTokens() { 107 return new int[] {TokenTypes.VARIABLE_DEF}; 108 } 109 110 @Override 111 public void visitToken(DetailAST ast) { 112 if (shouldCheckFieldName(ast)) { 113 final DetailAST nameAst = NullUtil.notNull(ast.findFirstToken(TokenTypes.IDENT)); 114 final String fieldName = nameAst.getText(); 115 116 validateNonConstantFieldName(nameAst, fieldName); 117 } 118 } 119 120 /** 121 * Checks if this field should be validated. Returns true for instance fields only. 122 * Static fields are excluded because Checkstyle cannot determine type immutability. 123 * Local variables and interface/annotation fields are also excluded. 124 * 125 * @param ast the VARIABLE_DEF AST node 126 * @return true if this variable should be checked 127 */ 128 private static boolean shouldCheckFieldName(DetailAST ast) { 129 final DetailAST modifiersAST = NullUtil.notNull(ast.findFirstToken(TokenTypes.MODIFIERS)); 130 final boolean isStatic = 131 modifiersAST.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 132 133 return !isStatic 134 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast) 135 && !ScopeUtil.isLocalVariableDef(ast); 136 } 137 138 /** 139 * Validates a non-constant field name according to Google style. 140 * 141 * @param nameAst the IDENT AST node containing the field name 142 * @param fieldName the field name string 143 */ 144 private void validateNonConstantFieldName(DetailAST nameAst, String fieldName) { 145 final String nameWithoutNumberingSuffix = NUMBERING_SUFFIX_PATTERN 146 .matcher(fieldName).replaceAll(""); 147 148 if (INVALID_UNDERSCORE_PATTERN.matcher(fieldName).find() 149 || !NON_CONSTANT_FIELD_NAME_PATTERN.matcher(nameWithoutNumberingSuffix).matches()) { 150 log(nameAst, MSG_KEY_INVALID_FORMAT, fieldName); 151 } 152 } 153}