Skip to content

Commit 8a63c69

Browse files
authored
[javaparser] Unify isLikelyClassName into shared ClassNames (#446)
Extract the PascalCase class-name heuristic used by both `ClasspathParser` and `KtParser` into a shared `ClassNames` utility class with a unit test. The unified implementation follows the `KtParser` behavior: names must start with an uppercase letter and contain at least one lowercase letter. This rejects single uppercase chars (type parameters like T) and ALL_CAPS constants (SNAKE_CASE), which is the safer default for both languages.
1 parent e9036d7 commit 8a63c69

6 files changed

Lines changed: 102 additions & 43 deletions

File tree

java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ load("@rules_java//java:defs.bzl", "java_binary", "java_library")
33
java_library(
44
name = "generators",
55
srcs = [
6+
"ClassNames.java",
67
"ClasspathParser.java",
78
"GrpcServer.java",
89
"JavaIdentifier.java",
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.github.bazel_contrib.contrib_rules_jvm.javaparser.generators;
2+
3+
/**
4+
* Shared heuristics for deciding whether a simple name looks like a class name (PascalCase) vs. a
5+
* constant, type parameter, or other identifier.
6+
*/
7+
final class ClassNames {
8+
9+
private ClassNames() {}
10+
11+
/**
12+
* Returns true if the given simple name looks like a PascalCase class name.
13+
*
14+
* <p>Returns false for: empty strings, names that don't start with an uppercase letter, single
15+
* uppercase characters (likely type parameters), and ALL_CAPS_SNAKE_CASE constants.
16+
*/
17+
static boolean isLikelyClassName(String name) {
18+
if (name.isEmpty()) {
19+
return false;
20+
}
21+
if (!firstLetterIsUppercase(name)) {
22+
return false;
23+
}
24+
// Check that there is at least one lowercase letter after the first character.
25+
// This rejects single uppercase chars (type parameters like T) and
26+
// ALL_CAPS / SCREAMING_SNAKE_CASE constants.
27+
for (int i = 1; i < name.length(); i++) {
28+
char c = name.charAt(i);
29+
if (Character.isLetter(c) && Character.isLowerCase(c)) {
30+
return true;
31+
}
32+
}
33+
return false;
34+
}
35+
36+
private static boolean firstLetterIsUppercase(String value) {
37+
for (int i = 0; i < value.length(); i++) {
38+
char c = value.charAt(i);
39+
if (Character.isLetter(c)) {
40+
return Character.isUpperCase(c);
41+
}
42+
}
43+
return false;
44+
}
45+
}

java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/ClasspathParser.java

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.github.bazel_contrib.contrib_rules_jvm.javaparser.generators;
22

3+
import static com.github.bazel_contrib.contrib_rules_jvm.javaparser.generators.ClassNames.isLikelyClassName;
34
import static javax.lang.model.element.Modifier.PRIVATE;
45
import static javax.lang.model.element.Modifier.PUBLIC;
56
import static javax.lang.model.element.Modifier.STATIC;
@@ -407,22 +408,7 @@ public Void visitMethodInvocation(MethodInvocationTree node, Void v) {
407408
}
408409

409410
private boolean looksLikeClassName(String identifier) {
410-
if (identifier.isEmpty()) {
411-
return false;
412-
}
413-
// Classes start with UpperCase.
414-
if (!Character.isUpperCase(identifier.charAt(0))) {
415-
return false;
416-
}
417-
// Single-char upper-case may well be a class-name.
418-
if (identifier.length() == 1) {
419-
return true;
420-
}
421-
// SNAKE_CASE is for constants not classes.
422-
if (identifier.chars().allMatch(c -> Character.isUpperCase(c) || c == '_')) {
423-
return false;
424-
}
425-
return true;
411+
return isLikelyClassName(identifier);
426412
}
427413

428414
@Override

java/src/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/KtParser.java

Lines changed: 2 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.github.bazel_contrib.contrib_rules_jvm.javaparser.generators;
22

3+
import static com.github.bazel_contrib.contrib_rules_jvm.javaparser.generators.ClassNames.isLikelyClassName;
4+
35
import com.intellij.openapi.util.Disposer;
46
import com.intellij.openapi.vfs.VirtualFile;
57
import com.intellij.openapi.vfs.VirtualFileManager;
@@ -755,33 +757,6 @@ private FqName packageRelativeName(FqName name, KtFile file) {
755757
return FqNamesUtilKt.tail(name, file.getPackageFqName());
756758
}
757759

758-
/** Returns true if this simple name is PascalCase. */
759-
private boolean isLikelyClassName(String name) {
760-
if (name.isEmpty() || !firstLetterIsUppercase(name)) {
761-
return false;
762-
}
763-
// If the name is all uppercase, assume it's a constant. At worst, we'll still
764-
// import the package, which seems a safer default than assuming it's a class
765-
// that we then can't find.
766-
for (int i = 1; i < name.length(); i++) {
767-
char c = name.charAt(i);
768-
if (Character.isLetter(c) && Character.isLowerCase(c)) {
769-
return true;
770-
}
771-
}
772-
return false;
773-
}
774-
775-
private boolean firstLetterIsUppercase(String value) {
776-
for (int i = 0; i < value.length(); i++) {
777-
char c = value.charAt(i);
778-
if (Character.isLetter(c)) {
779-
return Character.isUpperCase(c);
780-
}
781-
}
782-
return false;
783-
}
784-
785760
private Optional<KtNamedFunction> retrievePossibleMainFunction(KtObjectDeclaration object) {
786761
KtClassBody body = object.getBody();
787762
for (KtNamedFunction function : body.getFunctions()) {

java/test/com/github/bazel_contrib/contrib_rules_jvm/javaparser/generators/BUILD.bazel

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ java_test_suite(
66
name = "generators",
77
size = "small",
88
srcs = [
9+
"ClassNamesTest.java",
910
"ClasspathParserTest.java",
1011
"JavaIdentifierTest.java",
1112
"KtParserTest.java",
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package com.github.bazel_contrib.contrib_rules_jvm.javaparser.generators;
2+
3+
import static com.github.bazel_contrib.contrib_rules_jvm.javaparser.generators.ClassNames.isLikelyClassName;
4+
import static org.junit.jupiter.api.Assertions.assertFalse;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import org.junit.jupiter.api.Test;
8+
9+
public class ClassNamesTest {
10+
11+
@Test
12+
void pascalCaseNamesAreClassNames() {
13+
assertTrue(isLikelyClassName("String"));
14+
assertTrue(isLikelyClassName("ArrayList"));
15+
assertTrue(isLikelyClassName("KtParser"));
16+
assertTrue(isLikelyClassName("IOException"));
17+
}
18+
19+
@Test
20+
void emptyStringIsNotClassName() {
21+
assertFalse(isLikelyClassName(""));
22+
}
23+
24+
@Test
25+
void lowercaseStartIsNotClassName() {
26+
assertFalse(isLikelyClassName("string"));
27+
assertFalse(isLikelyClassName("myClass"));
28+
assertFalse(isLikelyClassName("parseInt"));
29+
}
30+
31+
@Test
32+
void singleUppercaseCharIsNotClassName() {
33+
// Single uppercase letters are typically type parameters (T, E, K, V)
34+
assertFalse(isLikelyClassName("T"));
35+
assertFalse(isLikelyClassName("E"));
36+
}
37+
38+
@Test
39+
void allCapsConstantsAreNotClassNames() {
40+
assertFalse(isLikelyClassName("MAX_VALUE"));
41+
assertFalse(isLikelyClassName("HTTP"));
42+
assertFalse(isLikelyClassName("SQL"));
43+
assertFalse(isLikelyClassName("IO"));
44+
}
45+
46+
@Test
47+
void twoCharPascalCaseIsClassName() {
48+
assertTrue(isLikelyClassName("Io"));
49+
assertTrue(isLikelyClassName("Ok"));
50+
}
51+
}

0 commit comments

Comments
 (0)