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