View Javadoc
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 }