1 ///////////////////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3 // Copyright (C) 2001-2026 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ///////////////////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.naming;
21
22 import java.util.regex.Pattern;
23
24 import com.puppycrawl.tools.checkstyle.StatelessCheck;
25 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
26 import com.puppycrawl.tools.checkstyle.api.DetailAST;
27 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
28 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
29 import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
30
31 /**
32 * <div>
33 * Checks that non-constant field names conform to the
34 * <a href=
35 * "https://google.github.io/styleguide/javaguide.html#s5.2.5-non-constant-field-names">
36 * Google Java Style Guide</a> for non-constant field naming.
37 * </div>
38 *
39 * <p>
40 * This check enforces Google's specific non-constant field naming requirements:
41 * </p>
42 * <ul>
43 * <li>Non-constant field names must start with a lowercase letter and use uppercase letters
44 * for word boundaries.</li>
45 * <li>Underscores may be used to separate adjacent numbers (e.g., version
46 * numbers like {@code guava33_4_5}), but NOT between letters and digits.</li>
47 * </ul>
48 *
49 * <p>
50 * Static fields are skipped because Checkstyle cannot determine type immutability
51 * to distinguish constants from non-constants. Fields in interfaces and annotations
52 * are also skipped because they are implicitly {@code public static final} (constants)
53 * </p>
54 *
55 * @since 13.3.0
56 */
57 @StatelessCheck
58 public class GoogleNonConstantFieldNameCheck extends AbstractCheck {
59
60 /**
61 * A key is pointing to the violation message text in "messages.properties" file.
62 */
63 public static final String MSG_KEY_INVALID_FORMAT = "google.non.constant.field.name.format";
64
65 /**
66 * Pattern for valid non-constant field name in Google style.
67 * Format: start with lowercase, have at least 2 chars, optionally followed by numbering suffix.
68 *
69 * <p>
70 * Explanation:
71 * <ul>
72 * <li>{@code ^(?![a-z]$)} - Negative lookahead: cannot be single lowercase char</li>
73 * <li>{@code (?![a-z][A-Z])} - Negative lookahead: cannot be like "fO"</li>
74 * <li>{@code [a-z]} - Must start with lowercase</li>
75 * <li>{@code [a-z0-9]*+} - Followed by lowercase or digits</li>
76 * <li>{@code (?:[A-Z][a-z0-9]*+)*+} - CamelCase humps (uppercase followed by lowercase)</li>
77 * <li>{@code $} - End of string (numbering suffix validated separately)</li>
78 * </ul>
79 */
80 private static final Pattern NON_CONSTANT_FIELD_NAME_PATTERN = Pattern
81 .compile("^(?![a-z]$)(?![a-z][A-Z])[a-z][a-z0-9]*+(?:[A-Z][a-z0-9]*+)*+$");
82
83 /**
84 * Pattern to strip trailing numbering suffix (underscore followed by digits).
85 */
86 private static final Pattern NUMBERING_SUFFIX_PATTERN = Pattern.compile("(?:_[0-9]++)+$");
87
88 /**
89 * Pattern to detect invalid underscore usage: leading, trailing, consecutive,
90 * or between letter-letter, letter-digit, or digit-letter combinations.
91 */
92 private static final Pattern INVALID_UNDERSCORE_PATTERN =
93 Pattern.compile("^_|_$|__|[a-zA-Z]_[a-zA-Z]|[a-zA-Z]_\\d|\\d_[a-zA-Z]");
94
95 /**
96 * Creates a new {@code GoogleNonConstantFieldNameCheck} instance.
97 */
98 public GoogleNonConstantFieldNameCheck() {
99 // 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 }