diff options
author | Brad Corso <bcorso@google.com> | 2024-02-08 15:51:23 -0800 |
---|---|---|
committer | Dagger Team <dagger-dev+copybara@google.com> | 2024-02-08 15:54:37 -0800 |
commit | c8722386a1fb10d9e70c6fbccba01ebdd5e56967 (patch) | |
tree | 4551eff20bfead1ce628e53d1f17c316803b50df | |
parent | 7ac1dcc36010cea6b2ba6170de863cff4af87ba8 (diff) | |
download | dagger2-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
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()")); }); } |