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. 052 * </p> 053 * 054 * @since 13.2.0 055 */ 056@StatelessCheck 057public class GoogleNonConstantFieldNameCheck extends AbstractCheck { 058 059 /** 060 * A key is pointing to the violation message text in "messages.properties" file. 061 */ 062 public static final String MSG_KEY_INVALID_FORMAT = "google.non.constant.field.name.format"; 063 064 /** 065 * A key pointing to the violation message for invalid underscore usage. 066 */ 067 public static final String MSG_KEY_INVALID_UNDERSCORE = 068 "google.non.constant.field.name.underscore"; 069 070 /** 071 * Pattern for valid non-constant field name in Google style. 072 * Format: start with lowercase, have at least 2 chars, optionally followed by numbering suffix. 073 * 074 * <p> 075 * Explanation: 076 * <ul> 077 * <li>{@code ^(?![a-z]$)} - Negative lookahead: cannot be single lowercase char</li> 078 * <li>{@code (?![a-z][A-Z])} - Negative lookahead: cannot be like "fO"</li> 079 * <li>{@code [a-z]} - Must start with lowercase</li> 080 * <li>{@code [a-z0-9]*+} - Followed by lowercase or digits</li> 081 * <li>{@code (?:[A-Z][a-z0-9]*+)*+} - CamelCase humps (uppercase followed by lowercase)</li> 082 * <li>{@code $} - End of string (numbering suffix validated separately)</li> 083 * </ul> 084 */ 085 private static final Pattern NON_CONSTANT_FIELD_NAME_PATTERN = Pattern 086 .compile("^(?![a-z]$)(?![a-z][A-Z])[a-z][a-z0-9]*+(?:[A-Z][a-z0-9]*+)*+$"); 087 088 /** 089 * Pattern to strip trailing numbering suffix (underscore followed by digits). 090 */ 091 private static final Pattern NUMBERING_SUFFIX_PATTERN = Pattern.compile("(?:_[0-9]++)+$"); 092 093 /** 094 * Pattern to detect invalid underscore usage: leading, trailing, consecutive, 095 * or between letter-letter, letter-digit, or digit-letter combinations. 096 */ 097 private static final Pattern INVALID_UNDERSCORE_PATTERN = 098 Pattern.compile("^_|_$|__|[a-zA-Z]_[a-zA-Z]|[a-zA-Z]_\\d|\\d_[a-zA-Z]"); 099 100 @Override 101 public int[] getDefaultTokens() { 102 return getRequiredTokens(); 103 } 104 105 @Override 106 public int[] getAcceptableTokens() { 107 return getRequiredTokens(); 108 } 109 110 @Override 111 public int[] getRequiredTokens() { 112 return new int[] {TokenTypes.VARIABLE_DEF}; 113 } 114 115 @Override 116 public void visitToken(DetailAST ast) { 117 if (mustCheckName(ast)) { 118 final DetailAST nameAst = NullUtil.notNull(ast.findFirstToken(TokenTypes.IDENT)); 119 final String fieldName = nameAst.getText(); 120 121 validateNonConstantFieldName(nameAst, fieldName); 122 } 123 } 124 125 /** 126 * Checks if this field should be validated. Returns true for instance fields only. 127 * Static fields are excluded because Checkstyle cannot determine type immutability. 128 * Local variables and interface/annotation fields are also excluded. 129 * 130 * @param ast the VARIABLE_DEF AST node 131 * @return true if this variable should be checked 132 */ 133 private static boolean mustCheckName(DetailAST ast) { 134 final DetailAST modifiersAST = NullUtil.notNull(ast.findFirstToken(TokenTypes.MODIFIERS)); 135 final boolean isStatic = 136 modifiersAST.findFirstToken(TokenTypes.LITERAL_STATIC) != null; 137 138 return !isStatic 139 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast) 140 && !ScopeUtil.isLocalVariableDef(ast); 141 } 142 143 /** 144 * Validates a non-constant field according to Google style. 145 * 146 * @param nameAst the IDENT AST node containing the field name 147 * @param fieldName the field name string 148 */ 149 private void validateNonConstantFieldName(DetailAST nameAst, String fieldName) { 150 if (INVALID_UNDERSCORE_PATTERN.matcher(fieldName).find()) { 151 log(nameAst, MSG_KEY_INVALID_UNDERSCORE, fieldName); 152 } 153 else { 154 final String nameWithoutNumberingSuffix = NUMBERING_SUFFIX_PATTERN 155 .matcher(fieldName).replaceAll(""); 156 if (!NON_CONSTANT_FIELD_NAME_PATTERN.matcher(nameWithoutNumberingSuffix).matches()) { 157 log(nameAst, MSG_KEY_INVALID_FORMAT, fieldName); 158 } 159 } 160 } 161}