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 non-constant field 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 non-constant field naming requirements:
041 * </p>
042 * <ul>
043 * <li>Non-constant field 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 * <p>
050 * Static fields are skipped because Checkstyle cannot determine type immutability
051 * to distinguish constants from non-constants. Fields in interfaces and annotations
052 * are also skipped because they are implicitly {@code public static final} (constants)
053 * </p>
054 *
055 * @since 13.3.0
056 */
057@StatelessCheck
058public class GoogleNonConstantFieldNameCheck extends AbstractCheck {
059
060    /**
061     * A key is pointing to the violation message text in "messages.properties" file.
062     */
063    public static final String MSG_KEY_INVALID_FORMAT = "google.non.constant.field.name.format";
064
065    /**
066     * Pattern for valid non-constant field name in Google style.
067     * Format: start with lowercase, have at least 2 chars, optionally followed by numbering suffix.
068     *
069     * <p>
070     * Explanation:
071     * <ul>
072     * <li>{@code ^(?![a-z]$)} - Negative lookahead: cannot be single lowercase char</li>
073     * <li>{@code (?![a-z][A-Z])} - Negative lookahead: cannot be like "fO"</li>
074     * <li>{@code [a-z]} - Must start with lowercase</li>
075     * <li>{@code [a-z0-9]*+} - Followed by lowercase or digits</li>
076     * <li>{@code (?:[A-Z][a-z0-9]*+)*+} - CamelCase humps (uppercase followed by lowercase)</li>
077     * <li>{@code $} - End of string (numbering suffix validated separately)</li>
078     * </ul>
079     */
080    private static final Pattern NON_CONSTANT_FIELD_NAME_PATTERN = Pattern
081            .compile("^(?![a-z]$)(?![a-z][A-Z])[a-z][a-z0-9]*+(?:[A-Z][a-z0-9]*+)*+$");
082
083    /**
084     * Pattern to strip trailing numbering suffix (underscore followed by digits).
085     */
086    private static final Pattern NUMBERING_SUFFIX_PATTERN = Pattern.compile("(?:_[0-9]++)+$");
087
088    /**
089     * Pattern to detect invalid underscore usage: leading, trailing, consecutive,
090     * or between letter-letter, letter-digit, or digit-letter combinations.
091     */
092    private static final Pattern INVALID_UNDERSCORE_PATTERN =
093            Pattern.compile("^_|_$|__|[a-zA-Z]_[a-zA-Z]|[a-zA-Z]_\\d|\\d_[a-zA-Z]");
094
095    @Override
096    public int[] getDefaultTokens() {
097        return getRequiredTokens();
098    }
099
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}