aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBrad Corso <bcorso@google.com>2024-02-08 15:51:23 -0800
committerDagger Team <dagger-dev+copybara@google.com>2024-02-08 15:54:37 -0800
commitc8722386a1fb10d9e70c6fbccba01ebdd5e56967 (patch)
tree4551eff20bfead1ce628e53d1f17c316803b50df
parent7ac1dcc36010cea6b2ba6170de863cff4af87ba8 (diff)
downloaddagger2-c8722386a1fb10d9e70c6fbccba01ebdd5e56967.tar.gz
Improve Dagger error messages to give more information and be more consistent.
RELNOTES=Improve Dagger error messages to give more information and be more consistent. PiperOrigin-RevId: 605450793
-rw-r--r--java/dagger/internal/codegen/base/ElementFormatter.java25
-rw-r--r--java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java180
-rw-r--r--java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java33
-rw-r--r--javatests/dagger/internal/codegen/MissingBindingValidationTest.java371
4 files changed, 368 insertions, 241 deletions
diff --git a/java/dagger/internal/codegen/base/ElementFormatter.java b/java/dagger/internal/codegen/base/ElementFormatter.java
index 16381e32f..8e3edc955 100644
--- a/java/dagger/internal/codegen/base/ElementFormatter.java
+++ b/java/dagger/internal/codegen/base/ElementFormatter.java
@@ -36,7 +36,8 @@ import javax.inject.Inject;
*
* <p>Elements directly enclosed by a type are preceded by the enclosing type's qualified name.
*
- * <p>Parameters are given with their enclosing executable, with other parameters elided.
+ * <p>If the element is a parameter, the returned string will include the enclosing executable,
+ * with other parameters elided.
*/
public final class ElementFormatter extends Formatter<XElement> {
@Inject
@@ -52,15 +53,29 @@ public final class ElementFormatter extends Formatter<XElement> {
*
* <p>Elements directly enclosed by a type are preceded by the enclosing type's qualified name.
*
- * <p>Parameters are given with their enclosing executable, with other parameters elided.
+ * <p>If the element is a parameter, the returned string will include the enclosing executable,
+ * with other parameters elided.
*/
public static String elementToString(XElement element) {
+ return elementToString(element, /* elideMethodParameterTypes= */ false);
+ }
+
+ /**
+ * Returns a useful string form for an element.
+ *
+ * <p>Elements directly enclosed by a type are preceded by the enclosing type's qualified name.
+ *
+ * <p>Parameters are given with their enclosing executable, with other parameters elided.
+ */
+ public static String elementToString(XElement element, boolean elideMethodParameterTypes) {
if (isExecutable(element)) {
return enclosingTypeAndMemberName(element)
.append(
- asExecutable(element).getParameters().stream()
- .map(parameter -> XTypes.toStableString(parameter.getType()))
- .collect(joining(", ", "(", ")")))
+ elideMethodParameterTypes
+ ? (asExecutable(element).getParameters().isEmpty() ? "()" : "(…)")
+ : asExecutable(element).getParameters().stream()
+ .map(parameter -> XTypes.toStableString(parameter.getType()))
+ .collect(joining(", ", "(", ")")))
.toString();
} else if (isMethodParameter(element)) {
XExecutableElement methodOrConstructor = asMethodParameter(element).getEnclosingElement();
diff --git a/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java b/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java
index 774ee5d94..fd9e42efb 100644
--- a/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java
+++ b/java/dagger/internal/codegen/bindinggraphvalidation/MissingBindingValidator.java
@@ -20,12 +20,13 @@ import static androidx.room.compiler.processing.compat.XConverters.getProcessing
import static com.google.common.base.Verify.verify;
import static com.google.common.collect.Iterables.getLast;
import static com.google.common.collect.Iterables.getOnlyElement;
+import static dagger.internal.codegen.base.ElementFormatter.elementToString;
+import static dagger.internal.codegen.base.Formatter.INDENT;
import static dagger.internal.codegen.base.Keys.isValidImplicitProvisionKey;
import static dagger.internal.codegen.base.Keys.isValidMembersInjectionKey;
import static dagger.internal.codegen.base.RequestKinds.canBeSatisfiedByProductionBinding;
import static dagger.internal.codegen.binding.DependencyRequestFormatter.DOUBLE_INDENT;
import static dagger.internal.codegen.extension.DaggerStreams.instancesOf;
-import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
import static dagger.internal.codegen.xprocessing.XTypes.isWildcard;
@@ -38,6 +39,7 @@ import com.google.common.collect.Iterators;
import com.google.common.collect.Lists;
import com.squareup.javapoet.TypeName;
import com.squareup.javapoet.WildcardTypeName;
+import dagger.internal.codegen.binding.ComponentNodeImpl;
import dagger.internal.codegen.binding.DependencyRequestFormatter;
import dagger.internal.codegen.binding.InjectBindingRegistry;
import dagger.internal.codegen.model.Binding;
@@ -47,7 +49,6 @@ import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
import dagger.internal.codegen.model.BindingGraph.Edge;
import dagger.internal.codegen.model.BindingGraph.MissingBinding;
import dagger.internal.codegen.model.BindingGraph.Node;
-import dagger.internal.codegen.model.ComponentPath;
import dagger.internal.codegen.model.DaggerAnnotation;
import dagger.internal.codegen.model.DiagnosticReporter;
import dagger.internal.codegen.model.Key;
@@ -103,62 +104,20 @@ final class MissingBindingValidator extends ValidationBindingGraphPlugin {
prunedGraph
.missingBindings()
.forEach(
- missingBinding ->
- reportMissingBinding(missingBinding, prunedGraph, fullGraph, diagnosticReporter));
+ missingBinding -> reportMissingBinding(missingBinding, fullGraph, diagnosticReporter));
}
private void reportMissingBinding(
MissingBinding missingBinding,
- BindingGraph prunedGraph,
- BindingGraph fullGraph,
+ BindingGraph graph,
DiagnosticReporter diagnosticReporter) {
- ImmutableSet<Binding> wildcardAlternatives =
- getSimilarTypeBindings(fullGraph, missingBinding.key());
- String wildcardTypeErrorMessage = "";
- if (!wildcardAlternatives.isEmpty()) {
- wildcardTypeErrorMessage =
- String.format(
- "\nFound similar bindings:\n %s",
- String.join(
- "\n ",
- wildcardAlternatives.stream()
- .map(
- binding -> binding.key().type() + " in [" + binding.componentPath() + "]")
- .collect(toImmutableSet())))
- + "\n(In Kotlin source, a type like 'Set<Foo>' may"
- + " be translated as 'Set<? extends Foo>'. To avoid this implicit"
- + " conversion you can add '@JvmSuppressWildcards' on the associated type"
- + " argument, e.g. 'Set<@JvmSuppressWildcards Foo>'.)";
- }
-
- List<ComponentPath> alternativeComponents =
- wildcardAlternatives.isEmpty()
- ? fullGraph.bindings(missingBinding.key()).stream()
- .map(Binding::componentPath)
- .distinct()
- .collect(toImmutableList())
- : ImmutableList.of();
- // Print component name for each binding along the dependency path if the missing binding
- // exists in a different component than expected
- if (alternativeComponents.isEmpty()) {
- // TODO(b/266993189): the passed in diagnostic reporter is constructed with full graph, so it
- // doesn't print out full dependency path for a binding when invoking reportBinding on it.
- // Therefore, we manually constructed the binding dependency path and passed into
- // reportComponent.
- diagnosticReporter.reportComponent(
- ERROR,
- fullGraph.componentNode(missingBinding.componentPath()).get(),
- missingBindingErrorMessage(missingBinding, fullGraph)
- + wildcardTypeErrorMessage
- + "\n\nMissing binding usage:"
- + diagnosticMessageGeneratorFactory.create(prunedGraph).getMessage(missingBinding));
- } else {
- diagnosticReporter.reportComponent(
- ERROR,
- fullGraph.componentNode(missingBinding.componentPath()).get(),
- missingBindingErrorMessage(missingBinding, fullGraph)
- + wrongComponentErrorMessage(missingBinding, alternativeComponents, prunedGraph));
- }
+ diagnosticReporter.reportComponent(
+ ERROR,
+ graph.componentNode(missingBinding.componentPath()).get(),
+ missingBindingErrorMessage(missingBinding, graph)
+ + missingBindingDependencyTraceMessage(missingBinding, graph)
+ + alternativeBindingsMessage(missingBinding, graph)
+ + similarBindingsMessage(missingBinding, graph));
}
private static ImmutableSet<Binding> getSimilarTypeBindings(
@@ -222,50 +181,24 @@ final class MissingBindingValidator extends ValidationBindingGraphPlugin {
return errorMessage.toString();
}
- private String wrongComponentErrorMessage(
- MissingBinding missingBinding,
- List<ComponentPath> alternativeComponentPath,
- BindingGraph graph) {
+ private String missingBindingDependencyTraceMessage(
+ MissingBinding missingBinding, BindingGraph graph) {
ImmutableSet<DependencyEdge> entryPoints =
graph.entryPointEdgesDependingOnBinding(missingBinding);
DiagnosticMessageGenerator generator = diagnosticMessageGeneratorFactory.create(graph);
ImmutableList<DependencyEdge> dependencyTrace =
generator.dependencyTrace(missingBinding, entryPoints);
StringBuilder message =
- graph.isFullBindingGraph()
- ? new StringBuilder()
- : new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */);
- // Check in which component the missing binding is requested. This can be different from the
- // component the missing binding is in because we'll try to search up the parent components for
- // a binding which makes missing bindings end up at the root component. This is different from
- // the place we are logically requesting the binding from. Note that this is related to the
- // particular dependency trace being shown and so is not necessarily stable.
- String missingComponentName =
- getComponentFromDependencyEdge(dependencyTrace.get(0), graph, false);
- boolean hasSameComponentName = false;
- for (ComponentPath component : alternativeComponentPath) {
- message.append("\nNote: A binding for ").append(missingBinding.key()).append(" exists in ");
- String currentComponentName = component.currentComponent().className().canonicalName();
- if (currentComponentName.contentEquals(missingComponentName)) {
- hasSameComponentName = true;
- message.append("[").append(component).append("]");
- } else {
- message.append(currentComponentName);
- }
- message.append(":");
- }
+ new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */).append("\n");
for (DependencyEdge edge : dependencyTrace) {
String line = dependencyRequestFormatter.format(edge.dependencyRequest());
if (line.isEmpty()) {
continue;
}
- // If we ran into a rare case where the component names collide and we need to show the full
- // path, only show the full path for the first dependency request. This is guaranteed to be
- // the component in question since the logic for checking for a collision uses the first
- // edge in the trace. Do not expand subsequent component paths to reduce spam.
- String componentName =
- String.format("[%s] ", getComponentFromDependencyEdge(edge, graph, hasSameComponentName));
- hasSameComponentName = false;
+ // We don't have to check for cases where component names collide since
+ // 1. We always show the full classname of the component, and
+ // 2. We always show the full component path at the end of the dependency trace (below).
+ String componentName = String.format("[%s] ", getComponentFromDependencyEdge(edge, graph));
message.append("\n").append(line.replace(DOUBLE_INDENT, DOUBLE_INDENT + componentName));
}
if (!dependencyTrace.isEmpty()) {
@@ -277,6 +210,71 @@ final class MissingBindingValidator extends ValidationBindingGraphPlugin {
return message.toString();
}
+ private String alternativeBindingsMessage(
+ MissingBinding missingBinding, BindingGraph graph) {
+ ImmutableSet<Binding> alternativeBindings = graph.bindings(missingBinding.key());
+ if (alternativeBindings.isEmpty()) {
+ return "";
+ }
+ StringBuilder message = new StringBuilder();
+ message.append("\n\nNote: ")
+ .append(missingBinding.key())
+ .append(" is provided in the following other components:");
+ for (Binding alternativeBinding : alternativeBindings) {
+ // Some alternative bindings appear multiple times because they were re-resolved in multiple
+ // components (e.g. due to multibinding contributions). To avoid the noise, we only report
+ // the binding where the module is contributed.
+ if (alternativeBinding.contributingModule().isPresent()
+ && !((ComponentNodeImpl) graph.componentNode(alternativeBinding.componentPath()).get())
+ .componentDescriptor()
+ .moduleTypes()
+ .contains(alternativeBinding.contributingModule().get().xprocessing())) {
+ continue;
+ }
+ message.append("\n").append(INDENT).append(asString(alternativeBinding));
+ }
+ return message.toString();
+ }
+
+ private String similarBindingsMessage(
+ MissingBinding missingBinding, BindingGraph graph) {
+ ImmutableSet<Binding> similarBindings =
+ getSimilarTypeBindings(graph, missingBinding.key());
+ if (similarBindings.isEmpty()) {
+ return "";
+ }
+ StringBuilder message =
+ new StringBuilder(
+ "\n\nNote: A similar binding is provided in the following other components:");
+ for (Binding similarBinding : similarBindings) {
+ message
+ .append("\n")
+ .append(INDENT)
+ .append(similarBinding.key())
+ .append(" is provided at:")
+ .append("\n")
+ .append(DOUBLE_INDENT)
+ .append(asString(similarBinding));
+ }
+ message.append("\n")
+ .append(
+ "(For Kotlin sources, you may need to use '@JvmSuppressWildcards' or '@JvmWildcard' if "
+ + "you need to explicitly control the wildcards at a particular usage site.)");
+ return message.toString();
+ }
+
+ private String asString(Binding binding) {
+ return String.format(
+ "[%s] %s",
+ binding.componentPath().currentComponent().xprocessing().getQualifiedName(),
+ binding.bindingElement().isPresent()
+ ? elementToString(
+ binding.bindingElement().get().xprocessing(),
+ /* elideMethodParameterTypes= */ true)
+ // For synthetic bindings just print the Binding#toString()
+ : binding);
+ }
+
private boolean allIncomingDependenciesCanUseProduction(
MissingBinding missingBinding, BindingGraph graph) {
return graph.network().inEdges(missingBinding).stream()
@@ -305,15 +303,11 @@ final class MissingBindingValidator extends ValidationBindingGraphPlugin {
.orElse(false);
}
- private static String getComponentFromDependencyEdge(
- DependencyEdge edge, BindingGraph graph, boolean completePath) {
- ComponentPath componentPath = graph.network().incidentNodes(edge).source().componentPath();
- return completePath
- ? componentPath.toString()
- : componentPath.currentComponent().className().canonicalName();
+ private static String getComponentFromDependencyEdge(DependencyEdge edge, BindingGraph graph) {
+ return source(edge, graph).componentPath().currentComponent().className().canonicalName();
}
- private Node source(Edge edge, BindingGraph graph) {
+ private static Node source(Edge edge, BindingGraph graph) {
return graph.network().incidentNodes(edge).source();
}
diff --git a/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java b/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java
index adf1e3a8e..24ad67691 100644
--- a/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java
+++ b/java/dagger/internal/codegen/validation/DiagnosticMessageGenerator.java
@@ -164,18 +164,11 @@ public final class DiagnosticMessageGenerator {
ImmutableList<DependencyEdge> dependencyTrace,
ImmutableSet<DependencyEdge> requests,
ImmutableSet<DependencyEdge> entryPoints) {
- StringBuilder message =
- graph.isFullBindingGraph()
- ? new StringBuilder()
- : new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */);
-
- // Print the dependency trace unless it's a full binding graph
- if (!graph.isFullBindingGraph()) {
- dependencyTrace.forEach(
- edge -> dependencyRequestFormatter.appendFormatLine(message, edge.dependencyRequest()));
- if (!dependencyTrace.isEmpty()) {
- appendComponentPathUnlessAtRoot(message, source(getLast(dependencyTrace)));
- }
+ StringBuilder message = new StringBuilder(dependencyTrace.size() * 100 /* a guess heuristic */);
+ dependencyTrace.forEach(
+ edge -> dependencyRequestFormatter.appendFormatLine(message, edge.dependencyRequest()));
+ if (!dependencyTrace.isEmpty()) {
+ appendComponentPathUnlessAtRoot(message, source(getLast(dependencyTrace)));
}
message.append(getRequestsNotInTrace(dependencyTrace, requests, entryPoints));
return message.toString();
@@ -190,25 +183,19 @@ public final class DiagnosticMessageGenerator {
ImmutableSet<XElement> requestsToPrint =
requests.stream()
// if printing entry points, skip entry points and the traced request
- .filter(
- request ->
- graph.isFullBindingGraph()
- || (!request.isEntryPoint() && !isTracedRequest(dependencyTrace, request)))
+ .filter(request -> !request.isEntryPoint())
+ .filter(request -> !isTracedRequest(dependencyTrace, request))
.map(request -> request.dependencyRequest().requestElement())
.flatMap(presentValues())
.map(DaggerElement::xprocessing)
.collect(toImmutableSet());
if (!requestsToPrint.isEmpty()) {
- message
- .append("\nIt is")
- .append(graph.isFullBindingGraph() ? " " : " also ")
- .append("requested at:");
+ message.append("\nIt is also requested at:");
elementFormatter.formatIndentedList(message, requestsToPrint, 1);
}
- // Print the remaining entry points, showing which component they're in, unless it's a full
- // binding graph
- if (!graph.isFullBindingGraph() && entryPoints.size() > 1) {
+ // Print the remaining entry points, showing which component they're in
+ if (entryPoints.size() > 1) {
message.append("\nThe following other entry points also depend on it:");
entryPointFormatter.formatIndentedList(
message,
diff --git a/javatests/dagger/internal/codegen/MissingBindingValidationTest.java b/javatests/dagger/internal/codegen/MissingBindingValidationTest.java
index 919700ee5..8250861d8 100644
--- a/javatests/dagger/internal/codegen/MissingBindingValidationTest.java
+++ b/javatests/dagger/internal/codegen/MissingBindingValidationTest.java
@@ -18,6 +18,7 @@ package dagger.internal.codegen;
import static com.google.common.truth.Truth.assertThat;
+import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.util.DiagnosticMessage;
import androidx.room.compiler.processing.util.Source;
import com.google.common.collect.ImmutableList;
@@ -348,18 +349,21 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
- "TestClass.A cannot be provided without an @Provides-annotated method.");
- subject.hasErrorContaining(" TestClass.A is injected at");
- subject.hasErrorContaining(" TestClass.B(a)");
- subject.hasErrorContaining(" TestClass.B is injected at");
- subject.hasErrorContaining(" TestClass.C.b");
- subject.hasErrorContaining(" TestClass.C is injected at");
- subject.hasErrorContaining(" TestClass.AComponent.injectC(TestClass.C)");
- subject.hasErrorContaining("The following other entry points also depend on it:");
- subject.hasErrorContaining(" TestClass.AComponent.getFoo()");
- subject.hasErrorContaining(" TestClass.AComponent.cProvider()");
- subject.hasErrorContaining(" TestClass.AComponent.lazyC()");
- subject.hasErrorContaining(" TestClass.AComponent.lazyCProvider()")
+ String.join(
+ "\n",
+ "TestClass.A cannot be provided without an @Provides-annotated method.",
+ "",
+ " TestClass.A is injected at",
+ " [TestClass.AComponent] TestClass.B(a)",
+ " TestClass.B is injected at",
+ " [TestClass.AComponent] TestClass.C.b",
+ " TestClass.C is injected at",
+ " [TestClass.AComponent] TestClass.AComponent.injectC(TestClass.C)",
+ "The following other entry points also depend on it:",
+ " TestClass.AComponent.getFoo()",
+ " TestClass.AComponent.cProvider()",
+ " TestClass.AComponent.lazyC()",
+ " TestClass.AComponent.lazyCProvider()"))
.onSource(component)
.onLineContaining("interface AComponent");
});
@@ -403,14 +407,17 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
- "String cannot be provided without an @Inject constructor or an "
- + "@Provides-annotated method.");
- subject.hasErrorContaining(" String is injected at");
- subject.hasErrorContaining(" TestImplementation(missingBinding)");
- subject.hasErrorContaining(" TestImplementation is injected at");
- subject.hasErrorContaining(" TestModule.bindTestInterface(implementation)");
- subject.hasErrorContaining(" TestInterface is requested at");
- subject.hasErrorContaining(" TestComponent.testInterface()")
+ String.join(
+ "\n",
+ "String cannot be provided without an @Inject constructor or an "
+ + "@Provides-annotated method.",
+ "",
+ " String is injected at",
+ " [TestComponent] TestImplementation(missingBinding)",
+ " TestImplementation is injected at",
+ " [TestComponent] TestModule.bindTestInterface(implementation)",
+ " TestInterface is requested at",
+ " [TestComponent] TestComponent.testInterface()"))
.onSource(component)
.onLineContaining("interface TestComponent");
});
@@ -463,15 +470,18 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
- "List cannot be provided without an @Provides-annotated method.");
- subject.hasErrorContaining(" List is injected at");
- subject.hasErrorContaining(" TestClass(list)");
- subject.hasErrorContaining(" TestClass is injected at");
- subject.hasErrorContaining(" Generic(t)");
- subject.hasErrorContaining(" Generic<TestClass> is injected at");
- subject.hasErrorContaining(" UsesTest(genericTestClass)");
- subject.hasErrorContaining(" UsesTest is requested at");
- subject.hasErrorContaining(" TestComponent.usesTest()");
+ String.join(
+ "\n",
+ "List cannot be provided without an @Provides-annotated method.",
+ "",
+ " List is injected at",
+ " [TestComponent] TestClass(list)",
+ " TestClass is injected at",
+ " [TestComponent] Generic(t)",
+ " Generic<TestClass> is injected at",
+ " [TestComponent] UsesTest(genericTestClass)",
+ " UsesTest is requested at",
+ " [TestComponent] TestComponent.usesTest()"));
});
}
@@ -527,15 +537,18 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
- "List cannot be provided without an @Provides-annotated method.");
- subject.hasErrorContaining(" List is injected at");
- subject.hasErrorContaining(" TestClass(list)");
- subject.hasErrorContaining(" TestClass is injected at");
- subject.hasErrorContaining(" Generic.t");
- subject.hasErrorContaining(" Generic<TestClass> is injected at");
- subject.hasErrorContaining(" UsesTest(genericTestClass)");
- subject.hasErrorContaining(" UsesTest is requested at");
- subject.hasErrorContaining(" TestComponent.usesTest()");
+ String.join(
+ "\n",
+ "List cannot be provided without an @Provides-annotated method.",
+ "",
+ " List is injected at",
+ " [TestComponent] TestClass(list)",
+ " TestClass is injected at",
+ " [TestComponent] Generic.t",
+ " Generic<TestClass> is injected at",
+ " [TestComponent] UsesTest(genericTestClass)",
+ " UsesTest is requested at",
+ " [TestComponent] TestComponent.usesTest()"));
});
}
@@ -669,16 +682,19 @@ public class MissingBindingValidationTest {
subject.hasErrorCount(1);
// TODO(b/243720787): Replace with CompilationResultSubject#hasErrorContainingMatch()
subject.hasErrorContaining(
- "Double cannot be provided without an @Inject constructor or an "
- + "@Provides-annotated method.");
- subject.hasErrorContaining("Double is injected at");
- subject.hasErrorContaining(" ParentModule.missingDependency(dub)");
- subject.hasErrorContaining("Integer is injected at");
- subject.hasErrorContaining(" ChildModule.contributesToSet(i)");
- subject.hasErrorContaining("Set<String> is injected at");
- subject.hasErrorContaining(" ParentModule.dependsOnSet(strings)");
- subject.hasErrorContaining("Object is requested at");
- subject.hasErrorContaining(" Grandchild.object() [Parent → Child → Grandchild]")
+ String.join(
+ "\n",
+ "Double cannot be provided without an @Inject constructor or an "
+ + "@Provides-annotated method.",
+ "",
+ " Double is injected at",
+ " [Parent] ParentModule.missingDependency(dub)",
+ " Integer is injected at",
+ " [Child] ChildModule.contributesToSet(i)",
+ " Set<String> is injected at",
+ " [Child] ParentModule.dependsOnSet(strings)",
+ " Object is requested at",
+ " [Grandchild] Grandchild.object() [Parent → Child → Grandchild]"))
.onSource(parent)
.onLineContaining("interface Parent");
});
@@ -727,16 +743,18 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
- "\033[1;31m[Dagger/MissingBinding]\033[0m "
- + "NotBound cannot be provided without an @Provides-annotated method.");
- subject.hasErrorContaining(" NotBound is injected at");
- subject.hasErrorContaining(" TestModule.object(notBound)");
- subject.hasErrorContaining(" Object is requested at");
- subject.hasErrorContaining(" TestComponent.object()");
- subject.hasErrorContaining("It is also requested at:");
- subject.hasErrorContaining(" TestModule.string(notBound, …)");
- subject.hasErrorContaining("The following other entry points also depend on it:");
- subject.hasErrorContaining(" TestComponent.string()")
+ String.join(
+ "\n",
+ "NotBound cannot be provided without an @Provides-annotated method.",
+ "",
+ " NotBound is injected at",
+ " [TestComponent] TestModule.object(notBound)",
+ " Object is requested at",
+ " [TestComponent] TestComponent.object()",
+ "It is also requested at:",
+ " TestModule.string(notBound, …)",
+ "The following other entry points also depend on it:",
+ " TestComponent.string()"))
.onSource(component)
.onLineContaining("interface TestComponent");
});
@@ -787,22 +805,27 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
- "\033[1;31m[Dagger/MissingBinding]\033[0m String cannot be provided without an "
- + "@Inject constructor or an @Provides-annotated method.");
- subject.hasErrorContaining(" String is requested at");
- subject.hasErrorContaining(" TestComponent.string()");
- subject.hasErrorContaining("It is also requested at:");
- subject.hasErrorContaining(" Foo(one, …)");
- subject.hasErrorContaining(" Foo(…, two, …)");
- subject.hasErrorContaining(" Foo(…, three, …)");
- subject.hasErrorContaining(" Foo(…, four, …)");
- subject.hasErrorContaining(" Foo(…, five, …)");
- subject.hasErrorContaining(" Foo(…, six, …)");
- subject.hasErrorContaining(" Foo(…, seven, …)");
- subject.hasErrorContaining(" Foo(…, eight, …)");
- subject.hasErrorContaining(" Foo(…, nine, …)");
- subject.hasErrorContaining(" Foo(…, ten, …)");
- subject.hasErrorContaining(" and 3 others")
+ String.join(
+ "\n",
+ "String cannot be provided without an @Inject constructor or an "
+ + "@Provides-annotated method.",
+ "",
+ " String is requested at",
+ " [TestComponent] TestComponent.string()",
+ "It is also requested at:",
+ " Foo(one, …)",
+ " Foo(…, two, …)",
+ " Foo(…, three, …)",
+ " Foo(…, four, …)",
+ " Foo(…, five, …)",
+ " Foo(…, six, …)",
+ " Foo(…, seven, …)",
+ " Foo(…, eight, …)",
+ " Foo(…, nine, …)",
+ " Foo(…, ten, …)",
+ " and 3 others",
+ "The following other entry points also depend on it:",
+ " TestComponent.foo()"))
.onSource(component)
.onLineContaining("interface TestComponent");
});
@@ -839,22 +862,25 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
- "\033[1;31m[Dagger/MissingBinding]\033[0m String cannot be provided without an "
- + "@Inject constructor or an @Provides-annotated method.");
- subject.hasErrorContaining(" String is requested at");
- subject.hasErrorContaining(" TestComponent.string1()");
- subject.hasErrorContaining("The following other entry points also depend on it:");
- subject.hasErrorContaining(" TestComponent.string2()");
- subject.hasErrorContaining(" TestComponent.string3()");
- subject.hasErrorContaining(" TestComponent.string4()");
- subject.hasErrorContaining(" TestComponent.string5()");
- subject.hasErrorContaining(" TestComponent.string6()");
- subject.hasErrorContaining(" TestComponent.string7()");
- subject.hasErrorContaining(" TestComponent.string8()");
- subject.hasErrorContaining(" TestComponent.string9()");
- subject.hasErrorContaining(" TestComponent.string10()");
- subject.hasErrorContaining(" TestComponent.string11()");
- subject.hasErrorContaining(" and 1 other")
+ String.join(
+ "\n",
+ "String cannot be provided without an @Inject constructor or an "
+ + "@Provides-annotated method.",
+ "",
+ " String is requested at",
+ " [TestComponent] TestComponent.string1()",
+ "The following other entry points also depend on it:",
+ " TestComponent.string2()",
+ " TestComponent.string3()",
+ " TestComponent.string4()",
+ " TestComponent.string5()",
+ " TestComponent.string6()",
+ " TestComponent.string7()",
+ " TestComponent.string8()",
+ " TestComponent.string9()",
+ " TestComponent.string10()",
+ " TestComponent.string11()",
+ " and 1 other"))
.onSource(component)
.onLineContaining("interface TestComponent");
});
@@ -908,16 +934,19 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
- "\033[1;31m[Dagger/MissingBinding]\033[0m Baz cannot be provided without an "
- + "@Inject constructor or an @Provides-annotated method.");
- subject.hasErrorContaining(" Baz is injected at");
- subject.hasErrorContaining(" Bar(baz)");
- subject.hasErrorContaining(" Bar is requested at");
- subject.hasErrorContaining(" Parent.bar()");
- subject.hasErrorContaining("The following other entry points also depend on it:");
- subject.hasErrorContaining(" Parent.foo()");
- subject.hasErrorContaining(" Child.foo() [Parent → Child]");
- subject.hasErrorContaining(" Child.baz() [Parent → Child]")
+ String.join(
+ "\n",
+ "Baz cannot be provided without an @Inject constructor or an "
+ + "@Provides-annotated method.",
+ "",
+ " Baz is injected at",
+ " [Parent] Bar(baz)",
+ " Bar is requested at",
+ " [Parent] Parent.bar()",
+ "The following other entry points also depend on it:",
+ " Parent.foo()",
+ " Child.foo() [Parent → Child]",
+ " Child.baz() [Parent → Child]"))
.onSource(parent)
.onLineContaining("interface Parent");
});
@@ -1061,9 +1090,14 @@ public class MissingBindingValidationTest {
subject.hasErrorContaining(
String.join(
"\n",
- "A binding for Object exists in Child2:",
- "Object is requested at",
- "[Child1] Child1.getObject() [Parent → Child1]"));
+ "Object cannot be provided without an @Inject constructor or an "
+ + "@Provides-annotated method.",
+ "",
+ " Object is requested at",
+ " [Child1] Child1.getObject() [Parent → Child1]",
+ "",
+ "Note: Object is provided in the following other components:",
+ " [Child2] Child2Module.provideObject()"));
});
}
@@ -1174,10 +1208,17 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject.hasErrorContaining(
- "A binding for Object exists in [Parent → Child2 → RepeatedSub]:");
- subject.hasErrorContaining(
- "[Parent → Child1 → RepeatedSub] RepeatedSub.getObject() [Parent → Child1 →"
- + " RepeatedSub]");
+ String.join(
+ "\n",
+ "Object cannot be provided without an @Inject constructor or an "
+ + "@Provides-annotated method.",
+ "",
+ " Object is requested at",
+ " [RepeatedSub] RepeatedSub.getObject() "
+ + "[Parent → Child1 → RepeatedSub]",
+ "",
+ "Note: Object is provided in the following other components:",
+ " [Child2] Child2Module.provideObject(…)"));
});
}
@@ -1298,9 +1339,17 @@ public class MissingBindingValidationTest {
.compile(
subject -> {
subject.hasErrorCount(1);
- subject.hasErrorContaining("A binding for Object exists in bar.Sub:");
subject.hasErrorContaining(
- "[foo.Sub] foo.Sub.getObject() [Parent → Child1 → foo.Sub]");
+ String.join(
+ "\n",
+ "Object cannot be provided without an @Inject constructor or an "
+ + "@Provides-annotated method.",
+ "",
+ " Object is requested at",
+ " [Sub] Sub.getObject() [Parent → Child1 → Sub]",
+ "",
+ "Note: Object is provided in the following other components:",
+ " [Child2] Child2Module.provideObject(…)"));
});
}
@@ -1384,11 +1433,25 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject
- .hasErrorContaining("Found similar bindings:")
+ .hasErrorContaining(
+ String.join(
+ "\n",
+ "Set<? extends Bar> cannot be provided without an @Provides-annotated "
+ + "method.",
+ "",
+ " Set<? extends Bar> is injected at",
+ " [MyComponent] Foo(bar)",
+ " Foo is requested at",
+ " [MyComponent] MyComponent.getFoo()",
+ "",
+ "Note: Set<? extends Bar> is provided in the following other components:",
+ " [Child] ChildModule.provideBar()",
+ "",
+ "Note: A similar binding is provided in the following other components:",
+ " Set<Bar> is provided at:",
+ " [MyComponent] Dagger-generated binding for Set<Bar>"))
.onSource(component)
.onLineContaining("interface MyComponent");
- subject.hasErrorContaining("Set<Bar> in [MyComponent");
- subject.hasErrorContaining("Set<? extends Bar> in [MyComponent → Child]");
});
}
@@ -1443,7 +1506,20 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject
- .hasErrorContaining("Set<Bar> in [MyComponent]")
+ .hasErrorContaining(
+ String.join(
+ "\n",
+ "Set<? extends Bar> cannot be provided without an @Provides-annotated "
+ + "method.",
+ "",
+ " Set<? extends Bar> is injected at",
+ " [MyComponent] Foo(bar)",
+ " Foo is requested at",
+ " [MyComponent] MyComponent.getFoo()",
+ "",
+ "Note: A similar binding is provided in the following other components:",
+ " Set<Bar> is provided at:",
+ " [MyComponent] Dagger-generated binding for Set<Bar>"))
.onSource(component)
.onLineContaining("interface MyComponent");
});
@@ -1503,10 +1579,22 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject
- .hasErrorContaining("Found similar bindings:")
+ .hasErrorContaining(
+ String.join(
+ "\n",
+ "Set<? extends Bar> cannot be provided without an @Provides-annotated "
+ + "method.",
+ "",
+ " Set<? extends Bar> is injected at",
+ " [MyComponent] Foo.bar",
+ " Foo is requested at",
+ " [MyComponent] MyComponent.getFoo()",
+ "",
+ "Note: A similar binding is provided in the following other components:",
+ " Set<Bar> is provided at:",
+ " [MyComponent] Dagger-generated binding for Set<Bar>"))
.onSource(component)
.onLineContaining("interface MyComponent");
- subject.hasErrorContaining("Set<Bar> in [MyComponent]");
});
}
@@ -1554,10 +1642,21 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject
- .hasErrorContaining("Found similar bindings:")
+ .hasErrorContaining(
+ String.join(
+ "\n",
+ "List<Bar> cannot be provided without an @Provides-annotated method.",
+ "",
+ " List<Bar> is injected at",
+ " [MyComponent] Foo(bar)",
+ " Foo is requested at",
+ " [MyComponent] MyComponent.getFoo()",
+ "",
+ "Note: A similar binding is provided in the following other components:",
+ " List<? extends Bar> is provided at:",
+ " [MyComponent] TestModule.provideBars()"))
.onSource(component)
.onLineContaining("interface MyComponent");
- subject.hasErrorContaining("List<? extends Bar> in [MyComponent]");
});
}
@@ -1613,7 +1712,24 @@ public class MissingBindingValidationTest {
subject -> {
subject.hasErrorCount(1);
subject
- .hasErrorContaining("Bar<Baz,Baz,Set<Baz>> in [MyComponent]")
+ .hasErrorContaining(
+ String.join(
+ "\n",
+ // TODO(b/324325095): Align KSP and KAPT error message.
+ CompilerTests.backend(subject) == XProcessingEnv.Backend.KSP
+ ? "Bar<? extends Baz,Baz,Set<Baz>> cannot be provided without an "
+ + "@Inject constructor or an @Provides-annotated method."
+ : "Bar<? extends Baz,Baz,Set<Baz>> cannot be provided without an "
+ + "@Provides-annotated method.",
+ "",
+ " Bar<? extends Baz,Baz,Set<Baz>> is injected at",
+ " [MyComponent] Foo(bar)",
+ " Foo is requested at",
+ " [MyComponent] MyComponent.getFoo()",
+ "",
+ "Note: A similar binding is provided in the following other components:",
+ " Bar<Baz,Baz,Set<Baz>> is provided at:",
+ " [MyComponent] TestModule.provideBar()"))
.onSource(component)
.onLineContaining("interface MyComponent");
});
@@ -1733,8 +1849,23 @@ public class MissingBindingValidationTest {
.compile(
subject -> {
subject.hasErrorCount(1);
- subject.hasErrorContaining("Found similar bindings:");
- subject.hasErrorContaining("Bar in [MyComponent]");
+ subject.hasErrorContaining(
+ String.join(
+ "\n",
+ // TODO(b/324325095): Align KSP and KAPT error message.
+ CompilerTests.backend(subject) == XProcessingEnv.Backend.KSP
+ ? "Bar<?> cannot be provided without an @Inject constructor or an "
+ + "@Provides-annotated method."
+ : "Bar<?> cannot be provided without an @Provides-annotated method.",
+ "",
+ " Bar<?> is injected at",
+ " [MyComponent] Foo(bar)",
+ " Foo is requested at",
+ " [MyComponent] MyComponent.getFoo()",
+ "",
+ "Note: A similar binding is provided in the following other components:",
+ " Bar is provided at:",
+ " [MyComponent] TestModule.provideBar()"));
});
}