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.Set;
23 import java.util.regex.Pattern;
24
25 import com.puppycrawl.tools.checkstyle.StatelessCheck;
26 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
27 import com.puppycrawl.tools.checkstyle.api.DetailAST;
28 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
29 import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
30 import com.puppycrawl.tools.checkstyle.utils.NullUtil;
31
32 /**
33 * <div>
34 * Checks that method names conform to the
35 * <a href=
36 * "https://google.github.io/styleguide/javaguide.html#s5.2.3-method-names">
37 * Google Java Style Guide</a> for method naming.
38 * </div>
39 *
40 * <p>Regular method names must:</p>
41 * <ul>
42 * <li>Be at least 2 characters long.</li>
43 * <li>Start with a lowercase letter.</li>
44 * <li>Not have a single lowercase letter followed by uppercase.</li>
45 * <li>Not have a consecutive uppercase.</li>
46 * <li>Contain only letters and digits and underscores.</li>
47 * </ul>
48 *
49 * <p>Test method names must:</p>
50 * <ul>
51 * <li>Be at least 2 characters long per segment.</li>
52 * <li>Start each segment with a lowercase letter.</li>
53 * <li>Not have a single lowercase letter followed by uppercase in any segment.</li>
54 * <li>Not have a consecutive uppercase.</li>
55 * <li>Allow underscores to separate segment and digits.</li>
56 * <li>Not have leading, trailing, or consecutive underscores.</li>
57 * </ul>
58 *
59 * <p>
60 * Notes:
61 * Methods annotated with {@code @Override} are ignored, as they must
62 * match the name defined in the parent class or interface.
63 * </p>
64 *
65 * @since 13.10.0
66 */
67 @StatelessCheck
68 public class GoogleMethodNameCheck extends AbstractCheck {
69
70 /**
71 * A key is pointing to the violation message text in "messages.properties" file.
72 */
73 public static final String MSG_KEY_FORMAT_REGULAR = "google.method.name.format.regular";
74
75 /**
76 * A key is pointing to the violation message text in "messages.properties" file.
77 */
78 public static final String MSG_KEY_FORMAT_TEST = "google.method.name.format.test";
79
80 /**
81 * A key is pointing to the violation message text in "messages.properties" file.
82 */
83 public static final String MSG_KEY_UNDERSCORE_REGULAR = "google.method.name.underscore.regular";
84
85 /**
86 * A key is pointing to the violation message text in "messages.properties" file.
87 */
88 public static final String MSG_KEY_UNDERSCORE_TEST = "google.method.name.underscore.test";
89
90 /**
91 * Pattern for valid regular method names in Google style.
92 *
93 * <p>
94 * Explanation:
95 * <ul>
96 * <li>{@code ^} - Start of string</li>
97 * <li>{@code [a-z]} - Must start with a single lowercase letter</li>
98 * <li>{@code [a-z0-9]++} - One or more lowercase letters or digits possessively
99 * (ensures the initial lowercase block is at least 2 characters long)</li>
100 * <li>{@code (?:[A-Z][a-z0-9]++)*+} - Zero or more camelCase humps possessively,
101 * where each uppercase letter must be followed by one or more lowercase letters or digits</li>
102 * <li>{@code [A-Z]?} - An optional single uppercase letter allowed at the very end</li>
103 * <li>{@code $} - End of string</li>
104 * </ul>
105 */
106 private static final Pattern REGULAR_METHOD_NAME_PATTERN = Pattern
107 .compile("^[a-z][a-z0-9]++(?:[A-Z][a-z0-9]++)*+[A-Z]?$");
108 /**
109 * Pattern for valid test method names in Google style.
110 * <ul>
111 * <li>Single segment (no underscore): follows regular method naming rules</li>
112 * <li>Multi-segment (with underscore): each segment must be a valid lowerCamelCase name</li>
113 * <li>Each segment must start with lowercase</li>
114 * <li>Each segment must be at least 2 characters long</li>
115 * <li>No segment may start with a single lowercase followed by uppercase (e.g., "fO")</li>
116 * </ul>
117 */
118 private static final Pattern TEST_METHOD_NAME_PATTERN = Pattern.compile(
119 "^(?:[a-z][a-z0-9]++(?:[A-Z][a-z0-9]++)*+[A-Z]?$"
120 + "|[a-z][a-z0-9]++(?:[A-Z][a-z0-9]++)*+[A-Z]?"
121 + "(?:_[a-z][a-z0-9]++(?:[A-Z][a-z0-9]++)*+[A-Z]?)+)$");
122
123 /**
124 * Pattern to strip trailing numbering suffix (underscore followed by digits).
125 */
126 private static final Pattern NUMBERING_SUFFIX_PATTERN = Pattern.compile("(?:_[0-9]++)+$");
127
128 /**
129 * Matches invalid underscore usage for regular methods: leading, trailing, double,
130 * or between any characters (letter-letter, letter-digit, digit-letter).
131 */
132 private static final Pattern INVALID_UNDERSCORE_PATTERN_REGULAR =
133 Pattern.compile("^_|_$|__|[a-zA-Z]_[a-zA-Z]|[a-zA-Z]_\\d|\\d_[a-zA-Z]");
134
135 /**
136 * Matches invalid underscore usage for test methods: leading, trailing, or double.
137 */
138 private static final Pattern INVALID_UNDERSCORE_PATTERN_TEST =
139 Pattern.compile("^_|_$|__|[a-zA-Z]_\\d|\\d_[a-zA-Z]");
140
141 /**
142 * Set of JUnit test annotation names that indicate a test method.
143 */
144 private static final Set<String> TEST_ANNOTATIONS = Set.of(
145 "Test",
146 "org.junit.jupiter.api.Test",
147 "org.junit.Test",
148 "ParameterizedTest",
149 "org.junit.jupiter.params.ParameterizedTest",
150 "RepeatedTest",
151 "org.junit.jupiter.api.RepeatedTest",
152 "TestFactory",
153 "org.junit.jupiter.api.TestFactory"
154 );
155
156 /** Creates a new {@code GoogleMethodNameCheck} instance. */
157 public GoogleMethodNameCheck() {
158 // no code by default
159 }
160
161 @Override
162 public int[] getDefaultTokens() {
163 return getRequiredTokens();
164 }
165
166 @Override
167 public int[] getAcceptableTokens() {
168 return getRequiredTokens();
169 }
170
171 @Override
172 public int[] getRequiredTokens() {
173 return new int[] {TokenTypes.METHOD_DEF};
174 }
175
176 @Override
177 public void visitToken(DetailAST ast) {
178 if (!AnnotationUtil.hasOverrideAnnotation(ast)) {
179 final DetailAST nameAst = getIdent(ast);
180 final String methodName = nameAst.getText();
181 if (hasTestAnnotation(ast)) {
182 validateTestMethodName(nameAst, methodName);
183 }
184 else {
185 validateRegularMethodName(nameAst, methodName);
186 }
187 }
188 }
189
190 /**
191 * Returns the IDENT node of the given AST.
192 *
193 * @param ast the AST node
194 * @return the IDENT child node
195 */
196 private static DetailAST getIdent(DetailAST ast) {
197 return NullUtil.notNull(ast.findFirstToken(TokenTypes.IDENT));
198 }
199
200 /**
201 * Checks if the method has any test annotation.
202 *
203 * @param methodDef the METHOD_DEF AST node
204 * @return true if the method has @Test, @ParameterizedTest, or @RepeatedTest annotation.
205 */
206 private static boolean hasTestAnnotation(DetailAST methodDef) {
207 return AnnotationUtil.containsAnnotation(methodDef, TEST_ANNOTATIONS);
208 }
209
210 /**
211 * Validates a regular (non-test) method name according to Google style.
212 *
213 * @param nameAst the IDENT AST node containing the method name
214 * @param methodName the method name string
215 */
216 private void validateRegularMethodName(DetailAST nameAst, String methodName) {
217 if (INVALID_UNDERSCORE_PATTERN_REGULAR.matcher(methodName).find()) {
218 log(nameAst, MSG_KEY_UNDERSCORE_REGULAR, methodName);
219 }
220 else {
221 final String nameWithoutNumberingSuffix = NUMBERING_SUFFIX_PATTERN
222 .matcher(methodName).replaceAll("");
223 if (!REGULAR_METHOD_NAME_PATTERN.matcher(nameWithoutNumberingSuffix).matches()) {
224 log(nameAst, MSG_KEY_FORMAT_REGULAR, methodName);
225 }
226 }
227 }
228
229 /**
230 * Validates a test method name according to Google style.
231 *
232 * @param nameAst the IDENT AST node containing the method name
233 * @param methodName the method name string
234 */
235 private void validateTestMethodName(DetailAST nameAst, String methodName) {
236
237 if (INVALID_UNDERSCORE_PATTERN_TEST.matcher(methodName).find()) {
238 log(nameAst, MSG_KEY_UNDERSCORE_TEST, methodName);
239 }
240 else {
241
242 final String nameWithoutSuffix = NUMBERING_SUFFIX_PATTERN
243 .matcher(methodName).replaceAll("");
244 if (!TEST_METHOD_NAME_PATTERN.matcher(nameWithoutSuffix).matches()) {
245 log(nameAst, MSG_KEY_FORMAT_TEST, methodName);
246 }
247 }
248 }
249
250 }