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      @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 }