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.Set; 023import java.util.regex.Pattern; 024 025import com.puppycrawl.tools.checkstyle.StatelessCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.TokenTypes; 029import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 030import com.puppycrawl.tools.checkstyle.utils.NullUtil; 031 032/** 033 * <div> 034 * Checks that method names conform to the 035 * <a href= 036 * "https://google.github.io/styleguide/javaguide.html#s5.2.3-method-names"> 037 * Google Java Style Guide</a> for method naming. 038 * </div> 039 * 040 * <p> 041 * This check enforces Google's specific method naming requirements: 042 * </p> 043 * <ul> 044 * <li>Method names must start with a lowercase letter and use uppercase letters 045 * for word boundaries.</li> 046 * <li>Underscores may appear in JUnit test method names to separate logical 047 * components.</li> 048 * <li>Underscores may be used to separate adjacent numbers (e.g., version 049 * numbers like 050 * {@code guava33_4_5}), but NOT between letters and digits.</li> 051 * </ul> 052 * 053 * @since 13.1.0 054 */ 055@StatelessCheck 056public class GoogleMethodNameCheck extends AbstractCheck { 057 058 /** 059 * A key is pointing to the violation message text in "messages.properties" file. 060 */ 061 public static final String MSG_KEY_FORMAT_REGULAR = "google.method.name.format.regular"; 062 063 /** 064 * A key is pointing to the violation message text in "messages.properties" file. 065 */ 066 public static final String MSG_KEY_FORMAT_TEST = "google.method.name.format.test"; 067 068 /** 069 * A key is pointing to the violation message text in "messages.properties" file. 070 */ 071 public static final String MSG_KEY_UNDERSCORE_REGULAR = "google.method.name.underscore.regular"; 072 073 /** 074 * A key is pointing to the violation message text in "messages.properties" file. 075 */ 076 public static final String MSG_KEY_UNDERSCORE_TEST = "google.method.name.underscore.test"; 077 078 /** 079 * Pattern for valid regular method names in Google style. 080 * Format: start with lowercase, have at least 2 chars, optionally followed by numbering suffix. 081 * 082 * <p> 083 * Explanation: 084 * <ul> 085 * <li>{@code ^(?![a-z]$)} - Negative lookahead: cannot be single lowercase char</li> 086 * <li>{@code (?![a-z][A-Z])} - Negative lookahead: cannot be like "fO"</li> 087 * <li>{@code [a-z]} - Must start with lowercase</li> 088 * <li>{@code [a-z0-9]*+} - Followed by lowercase or digits</li> 089 * <li>{@code (?:[A-Z][a-z0-9]*+)*+} - CamelCase humps (uppercase followed by 090 * lowercase/digits)</li> 091 * <li>{@code $} - End of string (numbering suffix validated separately)</li> 092 * </ul> 093 */ 094 private static final Pattern REGULAR_METHOD_NAME_PATTERN = Pattern 095 .compile("^(?![a-z]$)(?![a-z][A-Z])[a-z][a-z0-9]*+(?:[A-Z][a-z0-9]*+)*+$"); 096 097 /** 098 * Pattern for valid test method names in Google style. 099 * Each segment between underscores must: 100 * <ul> 101 * <li>Start with lowercase</li> 102 * <li>Be at least 2 characters long</li> 103 * <li>Not start with single lowercase followed by uppercase (e.g., "fO")</li> 104 * </ul> 105 */ 106 private static final Pattern TEST_METHOD_NAME_PATTERN = Pattern.compile( 107 "^(?)(?![a-z][A-Z])[a-z][a-z0-9]*+(?:[A-Z][a-z0-9]*+)*+" 108 + "(?:_(?)(?![a-z][A-Z])[a-z][a-z0-9]*+(?:[A-Z][a-z0-9]*+)*+)*+$"); 109 110 /** 111 * Pattern to strip trailing numbering suffix (underscore followed by digits). 112 */ 113 private static final Pattern NUMBERING_SUFFIX_PATTERN = Pattern.compile("(?:_[0-9]++)+$"); 114 115 /** 116 * Matches invalid underscore usage for regular methods: leading, trailing, double, 117 * or between any characters (letter-letter, letter-digit, digit-letter). 118 */ 119 private static final Pattern INVALID_UNDERSCORE_PATTERN_REGULAR = 120 Pattern.compile("^_|_$|__|[a-zA-Z]_[a-zA-Z]|[a-zA-Z]_\\d|\\d_[a-zA-Z]"); 121 122 /** 123 * Matches invalid underscore usage for test methods: leading, trailing, double, or between 124 * letter-digit/digit-letter. 125 */ 126 private static final Pattern INVALID_UNDERSCORE_PATTERN_TEST = 127 Pattern.compile("^_|_$|__|[a-zA-Z]_\\d|\\d_[a-zA-Z]"); 128 129 /** 130 * Set of JUnit 5 test annotation names that indicate a test method. 131 */ 132 private static final Set<String> TEST_ANNOTATIONS = Set.of( 133 "Test", 134 "org.junit.jupiter.api.Test", 135 "org.junit.Test", 136 "ParameterizedTest", 137 "org.junit.jupiter.params.ParameterizedTest", 138 "RepeatedTest", 139 "org.junit.jupiter.api.RepeatedTest"); 140 141 @Override 142 public int[] getDefaultTokens() { 143 return getRequiredTokens(); 144 } 145 146 @Override 147 public int[] getAcceptableTokens() { 148 return getRequiredTokens(); 149 } 150 151 @Override 152 public int[] getRequiredTokens() { 153 return new int[] {TokenTypes.METHOD_DEF}; 154 } 155 156 @Override 157 public void visitToken(DetailAST ast) { 158 if (!AnnotationUtil.hasOverrideAnnotation(ast)) { 159 final DetailAST nameAst = NullUtil.notNull(ast.findFirstToken(TokenTypes.IDENT)); 160 final String methodName = nameAst.getText(); 161 162 if (hasTestAnnotation(ast)) { 163 validateTestMethodName(nameAst, methodName); 164 } 165 else { 166 validateRegularMethodName(nameAst, methodName); 167 } 168 } 169 } 170 171 /** 172 * Checks if the method has any test annotation. 173 * 174 * @param methodDef the METHOD_DEF AST node 175 * @return true if the method has @Test, @ParameterizedTest, or @RepeatedTest annotation. 176 */ 177 private static boolean hasTestAnnotation(DetailAST methodDef) { 178 return AnnotationUtil.containsAnnotation(methodDef, TEST_ANNOTATIONS); 179 } 180 181 /** 182 * Validates a regular (non-test) method name according to Google style. 183 * 184 * @param nameAst the IDENT AST node containing the method name 185 * @param methodName the method name string 186 */ 187 private void validateRegularMethodName(DetailAST nameAst, String methodName) { 188 if (INVALID_UNDERSCORE_PATTERN_REGULAR.matcher(methodName).find()) { 189 log(nameAst, MSG_KEY_UNDERSCORE_REGULAR, methodName); 190 } 191 else { 192 final String nameWithoutNumberingSuffix = NUMBERING_SUFFIX_PATTERN 193 .matcher(methodName).replaceAll(""); 194 if (!REGULAR_METHOD_NAME_PATTERN.matcher(nameWithoutNumberingSuffix).matches()) { 195 log(nameAst, MSG_KEY_FORMAT_REGULAR, methodName); 196 } 197 } 198 } 199 200 /** 201 * Validates a test method name according to Google style. 202 * 203 * @param nameAst the IDENT AST node containing the method name 204 * @param methodName the method name string 205 */ 206 private void validateTestMethodName(DetailAST nameAst, String methodName) { 207 208 if (INVALID_UNDERSCORE_PATTERN_TEST.matcher(methodName).find()) { 209 log(nameAst, MSG_KEY_UNDERSCORE_TEST, methodName); 210 } 211 else { 212 213 final String nameWithoutSuffix = NUMBERING_SUFFIX_PATTERN 214 .matcher(methodName).replaceAll(""); 215 if (!TEST_METHOD_NAME_PATTERN.matcher(nameWithoutSuffix).matches()) { 216 log(nameAst, MSG_KEY_FORMAT_TEST, methodName); 217 } 218 } 219 } 220}