Skip to content

Commit 2508be9

Browse files
committed
Issue #17842: Add GoogleMemberNameCheck for Google Java Style compliance
1 parent c8c0a9f commit 2508be9

40 files changed

Lines changed: 1123 additions & 4 deletions

File tree

config/checkstyle-non-main-files-suppressions.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@
327327
files="src[\\/]site[\\/]xdoc[\\/]checks\.xml\.template"/>
328328
<suppress id="propertiesMacroMustExist"
329329
files="src[\\/]site[\\/]xdoc[\\/]checks[\\/]coding[\\/]textblockgooglestyleformatting.xml.template"/>
330+
<suppress id="propertiesMacroMustExist"
331+
files="src[\\/]site[\\/]xdoc[\\/]checks[\\/]naming[\\/]googlemembername.xml.template"/>
330332

331333
<!-- Index templates don't need since macro by design -->
332334
<suppress id="sinceMacroMustExist"

config/jsoref-spellchecker/whitelist.words

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -516,6 +516,7 @@ google
516516
googleblog
517517
googleecommon
518518
googlegroups
519+
googlemembername
519520
googlesource
520521
govstrangefolder
521522
GPath

config/linkcheck-suppressions.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,7 @@
533533
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/naming/CatchParameterNameCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
534534
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/naming/ClassTypeParameterNameCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
535535
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/naming/ConstantNameCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
536+
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/naming/GoogleMemberNameCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
536537
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/naming/IllegalIdentifierNameCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
537538
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/naming/InterfaceTypeParameterNameCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
538539
<a href="apidocs/com/puppycrawl/tools/checkstyle/checks/naming/LambdaParameterNameCheck.html#%3Cinit%3E()">#%3Cinit%3E()</a>: doesn't exist.
@@ -1166,6 +1167,7 @@
11661167
<a href="com/puppycrawl/tools/checkstyle/checks/naming/CatchParameterNameCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/naming/CatchParameterNameCheck.html#%3Cinit%3E()</a>: doesn't exist.
11671168
<a href="com/puppycrawl/tools/checkstyle/checks/naming/ClassTypeParameterNameCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/naming/ClassTypeParameterNameCheck.html#%3Cinit%3E()</a>: doesn't exist.
11681169
<a href="com/puppycrawl/tools/checkstyle/checks/naming/ConstantNameCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/naming/ConstantNameCheck.html#%3Cinit%3E()</a>: doesn't exist.
1170+
<a href="com/puppycrawl/tools/checkstyle/checks/naming/GoogleMemberNameCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/naming/GoogleMemberNameCheck.html#%3Cinit%3E()</a>: doesn't exist.
11691171
<a href="com/puppycrawl/tools/checkstyle/checks/naming/IllegalIdentifierNameCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/naming/IllegalIdentifierNameCheck.html#%3Cinit%3E()</a>: doesn't exist.
11701172
<a href="com/puppycrawl/tools/checkstyle/checks/naming/InterfaceTypeParameterNameCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/naming/InterfaceTypeParameterNameCheck.html#%3Cinit%3E()</a>: doesn't exist.
11711173
<a href="com/puppycrawl/tools/checkstyle/checks/naming/LambdaParameterNameCheck.html#%3Cinit%3E()">com/puppycrawl/tools/checkstyle/checks/naming/LambdaParameterNameCheck.html#%3Cinit%3E()</a>: doesn't exist.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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 org.checkstyle.suppressionxpathfilter.naming;
21+
22+
import java.io.File;
23+
import java.util.List;
24+
25+
import org.checkstyle.suppressionxpathfilter.AbstractXpathTestSupport;
26+
import org.junit.jupiter.api.Test;
27+
28+
import com.puppycrawl.tools.checkstyle.DefaultConfiguration;
29+
import com.puppycrawl.tools.checkstyle.checks.naming.GoogleMemberNameCheck;
30+
31+
public class XpathRegressionGoogleMemberNameTest extends AbstractXpathTestSupport {
32+
33+
private static final Class<GoogleMemberNameCheck> CLASS = GoogleMemberNameCheck.class;
34+
35+
@Override
36+
protected String getCheckName() {
37+
return CLASS.getSimpleName();
38+
}
39+
40+
@Override
41+
public String getPackageLocation() {
42+
return "org/checkstyle/suppressionxpathfilter/naming/googlemembername";
43+
}
44+
45+
@Test
46+
public void testMemberName() throws Exception {
47+
final File fileToProcess = new File(
48+
getPath("InputXpathGoogleMemberName.java"));
49+
50+
final DefaultConfiguration moduleConfig = createModuleConfig(CLASS);
51+
52+
final String[] expectedViolation = {
53+
"5:9: " + getCheckMessage(CLASS, GoogleMemberNameCheck.MSG_KEY_INVALID_FORMAT, "Foo"),
54+
};
55+
56+
final List<String> expectedXpathQueries = List.of(
57+
"/COMPILATION_UNIT/CLASS_DEF"
58+
+ "[./IDENT[@text='InputXpathGoogleMemberName']]"
59+
+ "/OBJBLOCK/VARIABLE_DEF/IDENT[@text='Foo']"
60+
);
61+
62+
runVerifications(moduleConfig, fileToProcess, expectedViolation,
63+
expectedXpathQueries);
64+
}
65+
66+
@Test
67+
public void testInvalidUnderscore() throws Exception {
68+
final File fileToProcess = new File(
69+
getPath("InputXpathGoogleMemberNameInvalidUnderscore.java"));
70+
final DefaultConfiguration moduleConfig = createModuleConfig(CLASS);
71+
final String[] expectedViolation = {
72+
"5:9: " + getCheckMessage(CLASS, GoogleMemberNameCheck.MSG_KEY_INVALID_UNDERSCORE,
73+
"gradle_9_5_1"),
74+
};
75+
final List<String> expectedXpathQueries = List.of(
76+
"/COMPILATION_UNIT/CLASS_DEF"
77+
+ "[./IDENT[@text='InputXpathGoogleMemberNameInvalidUnderscore']]"
78+
+ "/OBJBLOCK/VARIABLE_DEF/IDENT[@text='gradle_9_5_1']"
79+
);
80+
runVerifications(moduleConfig, fileToProcess, expectedViolation,
81+
expectedXpathQueries);
82+
}
83+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.checkstyle.suppressionxpathfilter.naming.googlemembername;
2+
3+
public class InputXpathGoogleMemberName {
4+
5+
int Foo; // warn 'Member name 'Foo' must start with a lowercase letter, min 2 chars'
6+
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package org.checkstyle.suppressionxpathfilter.naming.googlemembername;
2+
3+
public class InputXpathGoogleMemberNameInvalidUnderscore {
4+
5+
int gradle_9_5_1; // warn 'Member name 'gradle_9_5_1' has invalid underscore usage'
6+
7+
}

src/main/java/com/puppycrawl/tools/checkstyle/PackageObjectFactory.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,8 @@ private static void fillChecksFromNamingPackage() {
791791
BASE_PACKAGE + ".checks.naming.LocalVariableNameCheck");
792792
NAME_TO_FULL_MODULE_NAME.put("MemberNameCheck",
793793
BASE_PACKAGE + ".checks.naming.MemberNameCheck");
794+
NAME_TO_FULL_MODULE_NAME.put("GoogleMemberNameCheck",
795+
BASE_PACKAGE + ".checks.naming.GoogleMemberNameCheck");
794796
NAME_TO_FULL_MODULE_NAME.put("MethodNameCheck",
795797
BASE_PACKAGE + ".checks.naming.MethodNameCheck");
796798
NAME_TO_FULL_MODULE_NAME.put("MethodTypeParameterNameCheck",
Lines changed: 159 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
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 member 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 member naming requirements:
41+
* </p>
42+
* <ul>
43+
* <li>Member 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+
* @since 13.1.0
50+
*/
51+
@StatelessCheck
52+
public class GoogleMemberNameCheck extends AbstractCheck {
53+
54+
/**
55+
* A key is pointing to the violation message text in "messages.properties" file.
56+
*/
57+
public static final String MSG_KEY_INVALID_FORMAT = "google.member.name.format";
58+
59+
/**
60+
* A key pointing to the violation message for invalid underscore usage.
61+
*/
62+
public static final String MSG_KEY_INVALID_UNDERSCORE = "google.member.name.underscore";
63+
64+
/**
65+
* Pattern for valid member names in Google style.
66+
* Format: lowerCamelCase, optionally followed by numbering suffix.
67+
*
68+
* <p>
69+
* Explanation:
70+
* <ul>
71+
* <li>{@code ^(?![a-z]$)} - Negative lookahead: cannot be single lowercase char</li>
72+
* <li>{@code (?![a-z][A-Z])} - Negative lookahead: cannot be like "fO"</li>
73+
* <li>{@code [a-z]} - Must start with lowercase</li>
74+
* <li>{@code [a-z0-9]*} - Followed by lowercase or digits</li>
75+
* <li>{@code (?:[A-Z][a-z0-9]*)*} - CamelCase humps (uppercase followed by lowercase)</li>
76+
* <li>{@code $} - End of string (numbering suffix validated separately)</li>
77+
* </ul>
78+
*/
79+
private static final Pattern MEMBER_NAME_PATTERN = Pattern
80+
.compile("^(?![a-z]$)(?![a-z][A-Z])[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$");
81+
82+
/**
83+
* Pattern to strip trailing numbering suffix (underscore followed by digits).
84+
*/
85+
private static final Pattern NUMBERING_SUFFIX_PATTERN = Pattern.compile("(?:_[0-9]+)+$");
86+
87+
/**
88+
* Pattern to detect invalid underscore usage: leading, trailing, consecutive,
89+
* or between letter-letter, letter-digit, or digit-letter combinations.
90+
*/
91+
private static final Pattern INVALID_UNDERSCORE_PATTERN =
92+
Pattern.compile("^_|_$|__|[a-zA-Z]_[a-zA-Z]|[a-zA-Z]_\\d|\\d_[a-zA-Z]");
93+
94+
@Override
95+
public int[] getDefaultTokens() {
96+
return getRequiredTokens();
97+
}
98+
99+
@Override
100+
public int[] getAcceptableTokens() {
101+
return getRequiredTokens();
102+
}
103+
104+
@Override
105+
public int[] getRequiredTokens() {
106+
return new int[] {TokenTypes.VARIABLE_DEF};
107+
}
108+
109+
@Override
110+
public void visitToken(DetailAST ast) {
111+
if (mustCheckName(ast)) {
112+
final DetailAST nameAst = NullUtil.notNull(ast.findFirstToken(TokenTypes.IDENT));
113+
final String memberName = nameAst.getText();
114+
115+
validateMemberName(nameAst, memberName);
116+
}
117+
}
118+
119+
/**
120+
* Checks if this field should be validated. Returns true for instance fields
121+
* and static non-final fields. Constants (static final), local variables,
122+
* and interface/annotation fields are excluded.
123+
*
124+
* @param ast the VARIABLE_DEF AST node
125+
* @return true if this variable should be checked
126+
*/
127+
private static boolean mustCheckName(DetailAST ast) {
128+
final DetailAST modifiersAST = NullUtil.notNull(ast.findFirstToken(TokenTypes.MODIFIERS));
129+
final boolean isStatic =
130+
modifiersAST.findFirstToken(TokenTypes.LITERAL_STATIC) != null;
131+
final boolean isFinal =
132+
modifiersAST.findFirstToken(TokenTypes.FINAL) != null;
133+
134+
final boolean isConstant = isStatic && isFinal;
135+
136+
return !isConstant
137+
&& !ScopeUtil.isInInterfaceOrAnnotationBlock(ast)
138+
&& !ScopeUtil.isLocalVariableDef(ast);
139+
}
140+
141+
/**
142+
* Validates a member name according to Google style.
143+
*
144+
* @param nameAst the IDENT AST node containing the member name
145+
* @param memberName the member name string
146+
*/
147+
private void validateMemberName(DetailAST nameAst, String memberName) {
148+
if (INVALID_UNDERSCORE_PATTERN.matcher(memberName).find()) {
149+
log(nameAst, MSG_KEY_INVALID_UNDERSCORE, memberName);
150+
}
151+
else {
152+
final String nameWithoutNumberingSuffix = NUMBERING_SUFFIX_PATTERN
153+
.matcher(memberName).replaceAll("");
154+
if (!MEMBER_NAME_PATTERN.matcher(nameWithoutNumberingSuffix).matches()) {
155+
log(nameAst, MSG_KEY_INVALID_FORMAT, memberName);
156+
}
157+
}
158+
}
159+
}

src/main/resources/com/puppycrawl/tools/checkstyle/checks/naming/messages.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
abbreviation.as.word=Abbreviation in name ''{0}'' must contain no more than ''{1}'' consecutive capital letters.
2+
google.member.name.format=Member name ''{0}'' must start with a lowercase letter, min 2 chars, avoid single lowercase letter followed by uppercase.
3+
google.member.name.underscore=Member name ''{0}'' has invalid underscore usage, underscores are only allowed between adjacent digits.
24
illegal.abstract.class.name=Name ''{0}'' must match pattern ''{1}''.
35
method.name.equals.class.name=Method Name ''{0}'' must not equal the enclosing class name.
46
name.invalidPattern=Name ''{0}'' must match pattern ''{1}''.

src/main/resources/com/puppycrawl/tools/checkstyle/checks/naming/messages_de.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
abbreviation.as.word=Die Abkürzung im Bezeichner ''{0}'' darf höchstens aus ''{1}'' Großbuchstaben bestehen.
2+
google.member.name.format=Der Mitgliedsname ''{0}'' muss mit einem Kleinbuchstaben beginnen, mindestens 2 Zeichen lang sein und einen einzelnen Kleinbuchstaben gefolgt von einem Großbuchstaben vermeiden.
3+
google.member.name.underscore=Der Mitgliedsname ''{0}'' weist eine ungültige Verwendung von Unterstrichen auf. Unterstriche sind nur zwischen benachbarten Ziffern zulässig.
24
illegal.abstract.class.name=Der Bezeichner der Klasse ''{0}'' entspricht nicht dem Muster ''{1}''.
35
method.name.equals.class.name=Der Bezeichner der Methode ''{0}'' darf nicht dem Bezeichner der umgebenden Klasse entsprechen.
46
name.invalidPattern=Der Bezeichner ''{0}'' entspricht nicht dem Muster ''{1}''.

0 commit comments

Comments
 (0)