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 @Override
96 public int[] getDefaultTokens() {
97 return getRequiredTokens();
98 }
99
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 = getIdent(ast);
114 final String fieldName = nameAst.getText();
115
116 validateNonConstantFieldName(nameAst, fieldName);
117 }
118 }
119
120 /**
121 * Returns the IDENT node of the given AST.
122 *
123 * @param ast the AST node
124 * @return the IDENT child node
125 */
126 private static DetailAST getIdent(DetailAST ast) {
127 return NullUtil.notNull(ast.findFirstToken(TokenTypes.IDENT));
128 }
129
130 /**
131 * Checks if this field should be validated. Returns true for instance fields only.
132 * Static fields are excluded because Checkstyle cannot determine type immutability.
133 * Local variables and interface/annotation fields are also excluded.
134 *
135 * @param ast the VARIABLE_DEF AST node
136 * @return true if this variable should be checked
137 */
138 private static boolean shouldCheckFieldName(DetailAST ast) {
139 final DetailAST modifiersAST = getModifiers(ast);
140 final boolean isStatic =
141 modifiersAST.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
142
143 return !isStatic
144 && !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)
145 && !ScopeUtil.isLocalVariableDef(ast);
146 }
147
148 /**
149 * Returns the MODIFIERS node of the given AST.
150 * The MODIFIERS node is always present in the AST for type, method, and field declarations,
151 * even when no modifiers are explicitly written in code (e.g., {@code int x;} still
152 * has an empty MODIFIERS node).
153 *
154 * @param ast the AST node
155 * @return the MODIFIERS child node
156 */
157 private static DetailAST getModifiers(DetailAST ast) {
158 return NullUtil.notNull(ast.findFirstToken(TokenTypes.MODIFIERS));
159 }
160
161 /**
162 * Validates a non-constant field name according to Google style.
163 *
164 * @param nameAst the IDENT AST node containing the field name
165 * @param fieldName the field name string
166 */
167 private void validateNonConstantFieldName(DetailAST nameAst, String fieldName) {
168 final String nameWithoutNumberingSuffix = NUMBERING_SUFFIX_PATTERN
169 .matcher(fieldName).replaceAll("");
170
171 if (INVALID_UNDERSCORE_PATTERN.matcher(fieldName).find()
172 || !NON_CONSTANT_FIELD_NAME_PATTERN.matcher(nameWithoutNumberingSuffix).matches()) {
173 log(nameAst, MSG_KEY_INVALID_FORMAT, fieldName);
174 }
175 }
176 }