diff --git a/core/src/main/java/com/google/errorprone/bugpatterns/MemberName.java b/core/src/main/java/com/google/errorprone/bugpatterns/MemberName.java index 9506d9ed517..2a161c419f0 100644 --- a/core/src/main/java/com/google/errorprone/bugpatterns/MemberName.java +++ b/core/src/main/java/com/google/errorprone/bugpatterns/MemberName.java @@ -42,16 +42,19 @@ import com.google.common.collect.ImmutableSet; import com.google.errorprone.BugPattern; import com.google.errorprone.VisitorState; +import com.google.errorprone.bugpatterns.BugChecker.ClassTreeMatcher; import com.google.errorprone.bugpatterns.BugChecker.MethodTreeMatcher; import com.google.errorprone.bugpatterns.BugChecker.VariableTreeMatcher; import com.google.errorprone.bugpatterns.argumentselectiondefects.NamedParameterComment; import com.google.errorprone.matchers.Description; import com.google.errorprone.suppliers.Supplier; import com.google.errorprone.util.ASTHelpers; +import com.sun.source.tree.ClassTree; import com.sun.source.tree.MethodTree; import com.sun.source.tree.Tree.Kind; import com.sun.source.tree.VariableTree; import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Symbol.ClassSymbol; import com.sun.tools.javac.code.Symbol.MethodSymbol; import com.sun.tools.javac.code.Symbol.VarSymbol; import com.sun.tools.javac.util.Name; @@ -66,7 +69,8 @@ summary = "Methods and non-static variables should be named in lowerCamelCase", linkType = CUSTOM, link = "https://google.github.io/styleguide/javaguide.html#s5.2-specific-identifier-names") -public final class MemberName extends BugChecker implements MethodTreeMatcher, VariableTreeMatcher { +public final class MemberName extends BugChecker + implements ClassTreeMatcher, MethodTreeMatcher, VariableTreeMatcher { private static final Supplier> EXEMPTED_CLASS_ANNOTATIONS = VisitorState.memoize( s -> @@ -93,6 +97,28 @@ public final class MemberName extends BugChecker implements MethodTreeMatcher, V ", with acronyms treated as words" + " (https://google.github.io/styleguide/javaguide.html#s5.3-camel-case)"; + @Override + public Description matchClass(ClassTree tree, VisitorState state) { + ClassSymbol symbol = getSymbol(tree); + String name = tree.getSimpleName().toString(); + if (isConformantUpperCamelName(name)) { + return NO_MATCH; + } + String renamed = suggestedClassRename(name); + String suggested = fixInitialisms(renamed); + boolean fixable = !suggested.equals(name) && canBeRemoved(symbol); + String diagnostic = + "Classes should be named in UpperCamelCase" + + (suggested.equals(renamed) ? "" : INITIALISM_DETAIL); + return buildDescription(tree) + .setMessage( + fixable + ? diagnostic + : diagnostic + String.format("; did you" + " mean '%s'?", suggested)) + .addFix(emptyFix()) + .build(); + } + @Override public Description matchMethod(MethodTree tree, VisitorState state) { MethodSymbol symbol = getSymbol(tree); @@ -216,6 +242,18 @@ private static String suggestedRename(Symbol symbol, String name) { .collect(joining(""))); } + private static String suggestedClassRename(String name) { + if (LOWER_UNDERSCORE_PATTERN.matcher(name).matches()) { + return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.UPPER_CAMEL, name); + } + return CaseFormat.LOWER_CAMEL.to( + CaseFormat.UPPER_CAMEL, + UNDERSCORE_SPLITTER + .splitToStream(name) + .map(c -> CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_CAMEL, c)) + .collect(joining(""))); + } + private static boolean canBeRenamed(Symbol symbol) { return symbol.isPrivate() || LOCAL_VARIABLE_KINDS.contains(symbol.getKind()); } @@ -240,6 +278,12 @@ private static boolean isConformantLowerCamelName(String name) { && !PROBABLE_INITIALISM.matcher(name).find(); } + private static boolean isConformantUpperCamelName(String name) { + return !name.contains("_") + && isUpperCase(name.charAt(0)) + && !PROBABLE_INITIALISM.matcher(name).find(); + } + private static boolean isStaticVariable(Symbol symbol) { return symbol instanceof VarSymbol && isStatic(symbol); } diff --git a/core/src/test/java/com/google/errorprone/bugpatterns/MemberNameTest.java b/core/src/test/java/com/google/errorprone/bugpatterns/MemberNameTest.java index 81d5cf0c681..0b60ab6402b 100644 --- a/core/src/test/java/com/google/errorprone/bugpatterns/MemberNameTest.java +++ b/core/src/test/java/com/google/errorprone/bugpatterns/MemberNameTest.java @@ -589,4 +589,37 @@ void call() { """) .doTest(); } + + @Test + public void className_badInitialism() { + helper + .addSourceLines( + "Test.java", + "// BUG: Diagnostic contains: RpcServiceTester", + "class RPCServiceTester {", + "}") + .doTest(); + } + + @Test + public void className_lowerCamelCase() { + helper + .addSourceLines( + "Test.java", // + "// BUG: Diagnostic contains: FooBar", + "class fooBar {", + "}") + .doTest(); + } + + @Test + public void className_underscore() { + helper + .addSourceLines( + "Test.java", // + "// BUG: Diagnostic contains:", + "class Foo_Bar {", + "}") + .doTest(); + } }