aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoremcmanus <emcmanus@google.com>2016-04-11 14:16:30 -0700
committerÉamonn McManus <eamonn@mcmanus.net>2016-05-10 08:35:15 -0700
commitfbb0ce4965052128373626d06a28f32a849858fc (patch)
tree274c70dd26a05f3983deb7ba1968c2a77e422652
parent10da2957f3dd7316c40bba00b16ad0c6244d53d5 (diff)
downloadauto-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
-rw-r--r--value/pom.xml2
-rw-r--r--value/src/main/java/com/google/auto/value/processor/AutoValueProcessor.java19
-rw-r--r--value/src/main/java/com/google/auto/value/processor/BuilderMethodClassifier.java27
-rw-r--r--value/src/main/java/com/google/auto/value/processor/ErrorReporter.java10
-rw-r--r--value/src/test/java/com/google/auto/value/processor/CompilationTest.java33
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(