diff options
author | emcmanus <emcmanus@google.com> | 2016-04-11 14:16:30 -0700 |
---|---|---|
committer | Éamonn McManus <eamonn@mcmanus.net> | 2016-05-10 08:35:15 -0700 |
commit | fbb0ce4965052128373626d06a28f32a849858fc (patch) | |
tree | 274c70dd26a05f3983deb7ba1968c2a77e422652 | |
parent | 10da2957f3dd7316c40bba00b16ad0c6244d53d5 (diff) | |
download | auto-fbb0ce4965052128373626d06a28f32a849858fc.tar.gz |
Add logic to AutoValue to detect the confusing case where you think you are using JavaBeans conventions (like getFoo()) but you aren't because at least one method isn't. Then a Builder setter like setFoo will be rejected because it would have had to be called setGetFoo. This change detects that this might have happened and shows a list of the methods that prevented the JavaBeans conventions from being applied.
-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=119569372
5 files changed, 81 insertions, 10 deletions
diff --git a/value/pom.xml b/value/pom.xml index 52ae4642..cf8e5424 100644 --- a/value/pom.xml +++ b/value/pom.xml @@ -79,7 +79,7 @@ <dependency> <groupId>com.google.testing.compile</groupId> <artifactId>compile-testing</artifactId> - <version>0.6</version> + <version>0.9</version> <scope>test</scope> </dependency> <dependency> diff --git a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java index b527b739..d2bf1fa5 100644 --- a/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java +++ b/value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java @@ -517,12 +517,12 @@ public class AutoValueProcessor extends AbstractProcessor { } private ImmutableBiMap<String, ExecutableElement> propertyNameToMethodMap( - Iterable<ExecutableElement> propertyMethods) { + Set<ExecutableElement> propertyMethods) { Map<String, ExecutableElement> map = Maps.newLinkedHashMap(); - boolean allGetters = allGetters(propertyMethods); + boolean allPrefixed = gettersAllPrefixed(propertyMethods); for (ExecutableElement method : propertyMethods) { String methodName = method.getSimpleName().toString(); - String name = allGetters ? nameWithoutPrefix(methodName) : methodName; + String name = allPrefixed ? nameWithoutPrefix(methodName) : methodName; Object old = map.put(name, method); if (old != null) { errorReporter.reportError("More than one @AutoValue property called " + name, method); @@ -531,18 +531,23 @@ public class AutoValueProcessor extends AbstractProcessor { return ImmutableBiMap.copyOf(map); } - private static boolean allGetters(Iterable<ExecutableElement> methods) { + private static boolean gettersAllPrefixed(Set<ExecutableElement> methods) { + return prefixedGettersIn(methods).size() == methods.size(); + } + + static ImmutableSet<ExecutableElement> prefixedGettersIn(Iterable<ExecutableElement> methods) { + ImmutableSet.Builder<ExecutableElement> getters = ImmutableSet.builder(); for (ExecutableElement method : methods) { String name = method.getSimpleName().toString(); // TODO(emcmanus): decide whether getfoo() (without a capital) is a getter. Currently it is. boolean get = name.startsWith("get") && !name.equals("get"); boolean is = name.startsWith("is") && !name.equals("is") && method.getReturnType().getKind() == TypeKind.BOOLEAN; - if (!get && !is) { - return false; + if (get || is) { + getters.add(method); } } - return true; + return getters.build(); } private Set<TypeMirror> allMethodAnnotationTypes(Iterable<ExecutableElement> methods) { diff --git a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java index 866632fd..56148565 100644 --- a/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java +++ b/value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java @@ -212,8 +212,11 @@ class BuilderMethodClassifier { /** * Classifies a method given that it has no arguments. Currently a method with no - * arguments can only be a {@code build()} method, meaning that its return type must be the - * {@code @AutoValue} class. + * arguments can be a {@code build()} method, meaning that its return type must be the + * {@code @AutoValue} class; it can be a getter, with the same signature as one of + * the property getters in the {@code @AutoValue} class; or it can be a property builder, + * like {@code ImmutableList.Builder<String> foosBuilder()} for the property defined by + * {@code ImmutableList<String> foos()} or {@code getFoos()}. * * @return true if the method was successfully classified, false if an error has been reported. */ @@ -328,6 +331,7 @@ class BuilderMethodClassifier { // propertyNameToSetters can't be null when we call put on it below. errorReporter.reportError( "Method does not correspond to a property of " + autoValueClass, method); + checkForFailedJavaBean(method); return false; } if (!checkSetterParameter(valueGetter, method)) { @@ -342,6 +346,25 @@ class BuilderMethodClassifier { } } + // A frequence source of problems is where the JavaBeans conventions have been followed for + // most but not all getters. Then AutoValue considers that they haven't been followed at all, + // so you might have a property called getFoo where you thought it was called just foo, and + // you might not understand why your setter called setFoo is rejected (it would have to be called + // setGetFoo). + private void checkForFailedJavaBean(ExecutableElement rejectedSetter) { + ImmutableSet<ExecutableElement> allGetters = getterToPropertyName.keySet(); + ImmutableSet<ExecutableElement> prefixedGetters = + AutoValueProcessor.prefixedGettersIn(allGetters); + if (prefixedGetters.size() < allGetters.size() + && prefixedGetters.size() >= allGetters.size() / 2) { + String note = + "This might be because you are using the getFoo() convention" + + " for some but not all methods. These methods don't follow the convention: " + + Sets.difference(allGetters, prefixedGetters); + errorReporter.reportNote(note, rejectedSetter); + } + } + /** * Checks that the given setter method has a parameter type that is compatible with the return * type of the given getter. Compatible means either that it is the same, or that it is a type diff --git a/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java b/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java index 6e519213..23f7e5ce 100644 --- a/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java +++ b/value/src/main/java/com/google/auto/value/processor/ErrorReporter.java @@ -36,6 +36,16 @@ class ErrorReporter { } /** + * Issue a compilation note. + * + * @param msg the text of the note + * @param e the element to which it pertains + */ + void reportNote(String msg, Element e) { + messager.printMessage(Diagnostic.Kind.NOTE, msg, e); + } + + /** * Issue a compilation warning. * * @param msg the text of the warning diff --git a/value/src/test/java/com/google/auto/value/processor/CompilationTest.java b/value/src/test/java/com/google/auto/value/processor/CompilationTest.java index ea9f37c9..0b38f10a 100644 --- a/value/src/test/java/com/google/auto/value/processor/CompilationTest.java +++ b/value/src/test/java/com/google/auto/value/processor/CompilationTest.java @@ -1131,6 +1131,39 @@ public class CompilationTest { .in(javaFileObject).onLine(12); } + // Check that we get a helpful error message if some of your properties look like getters but + // others don't. + @Test + public void autoValueBuilderBeansConfusion() { + JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( + "foo.bar.Item", + "package foo.bar;", + "", + "import com.google.auto.value.AutoValue;", + "", + "@AutoValue", + "public abstract class Item {", + " abstract String getTitle();", + " abstract boolean hasThumbnail();", + "", + " @AutoValue.Builder", + " public interface Builder {", + " Builder setTitle(String title);", + " Builder setHasThumbnail(boolean t);", + " Item build();", + " }", + "}"); + assertAbout(javaSource()) + .that(javaFileObject) + .processedWith(new AutoValueProcessor(), new AutoValueBuilderProcessor()) + .failsToCompile() + .withErrorContaining("Method does not correspond to a property of foo.bar.Item") + .in(javaFileObject).onLine(12) + .and() + .withNoteContaining("hasThumbnail") + .in(javaFileObject).onLine(12); + } + @Test public void autoValueBuilderExtraSetter() { JavaFileObject javaFileObject = JavaFileObjects.forSourceLines( |