133 lines
5.2 KiB
Java
133 lines
5.2 KiB
Java
/*
|
|
* Copyright (C) 2018 The Dagger Authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
package dagger.internal.codegen.bindinggraphvalidation;
|
|
|
|
import static com.google.common.base.Verify.verify;
|
|
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.extension.DaggerStreams.instancesOf;
|
|
import static javax.tools.Diagnostic.Kind.ERROR;
|
|
|
|
import dagger.internal.codegen.binding.InjectBindingRegistry;
|
|
import dagger.internal.codegen.langmodel.DaggerTypes;
|
|
import dagger.model.BindingGraph;
|
|
import dagger.model.BindingGraph.ComponentNode;
|
|
import dagger.model.BindingGraph.DependencyEdge;
|
|
import dagger.model.BindingGraph.MissingBinding;
|
|
import dagger.model.BindingGraph.Node;
|
|
import dagger.model.Key;
|
|
import dagger.spi.BindingGraphPlugin;
|
|
import dagger.spi.DiagnosticReporter;
|
|
import javax.inject.Inject;
|
|
import javax.lang.model.type.TypeKind;
|
|
|
|
/** Reports errors for missing bindings. */
|
|
final class MissingBindingValidator implements BindingGraphPlugin {
|
|
|
|
private final DaggerTypes types;
|
|
private final InjectBindingRegistry injectBindingRegistry;
|
|
|
|
@Inject
|
|
MissingBindingValidator(
|
|
DaggerTypes types, InjectBindingRegistry injectBindingRegistry) {
|
|
this.types = types;
|
|
this.injectBindingRegistry = injectBindingRegistry;
|
|
}
|
|
|
|
@Override
|
|
public String pluginName() {
|
|
return "Dagger/MissingBinding";
|
|
}
|
|
|
|
@Override
|
|
public void visitGraph(BindingGraph graph, DiagnosticReporter diagnosticReporter) {
|
|
// Don't report missing bindings when validating a full binding graph or a graph built from a
|
|
// subcomponent.
|
|
if (graph.isFullBindingGraph() || graph.rootComponentNode().isSubcomponent()) {
|
|
return;
|
|
}
|
|
graph
|
|
.missingBindings()
|
|
.forEach(missingBinding -> reportMissingBinding(missingBinding, graph, diagnosticReporter));
|
|
}
|
|
|
|
private void reportMissingBinding(
|
|
MissingBinding missingBinding, BindingGraph graph, DiagnosticReporter diagnosticReporter) {
|
|
diagnosticReporter.reportBinding(
|
|
ERROR, missingBinding, missingBindingErrorMessage(missingBinding, graph));
|
|
}
|
|
|
|
private String missingBindingErrorMessage(MissingBinding missingBinding, BindingGraph graph) {
|
|
Key key = missingBinding.key();
|
|
StringBuilder errorMessage = new StringBuilder();
|
|
// Wildcards should have already been checked by DependencyRequestValidator.
|
|
verify(!key.type().getKind().equals(TypeKind.WILDCARD), "unexpected wildcard request: %s", key);
|
|
// TODO(ronshapiro): replace "provided" with "satisfied"?
|
|
errorMessage.append(key).append(" cannot be provided without ");
|
|
if (isValidImplicitProvisionKey(key, types)) {
|
|
errorMessage.append("an @Inject constructor or ");
|
|
}
|
|
errorMessage.append("an @Provides-"); // TODO(dpb): s/an/a
|
|
if (allIncomingDependenciesCanUseProduction(missingBinding, graph)) {
|
|
errorMessage.append(" or @Produces-");
|
|
}
|
|
errorMessage.append("annotated method.");
|
|
if (isValidMembersInjectionKey(key) && typeHasInjectionSites(key)) {
|
|
errorMessage.append(
|
|
" This type supports members injection but cannot be implicitly provided.");
|
|
}
|
|
graph.bindings(key).stream()
|
|
.map(binding -> binding.componentPath().currentComponent())
|
|
.distinct()
|
|
.forEach(
|
|
component ->
|
|
errorMessage
|
|
.append("\nA binding with matching key exists in component: ")
|
|
.append(component.getQualifiedName()));
|
|
return errorMessage.toString();
|
|
}
|
|
|
|
private boolean allIncomingDependenciesCanUseProduction(
|
|
MissingBinding missingBinding, BindingGraph graph) {
|
|
return graph.network().inEdges(missingBinding).stream()
|
|
.flatMap(instancesOf(DependencyEdge.class))
|
|
.allMatch(edge -> dependencyCanBeProduction(edge, graph));
|
|
}
|
|
|
|
// TODO(ronshapiro): merge with
|
|
// ProvisionDependencyOnProduerBindingValidator.dependencyCanUseProduction
|
|
private boolean dependencyCanBeProduction(DependencyEdge edge, BindingGraph graph) {
|
|
Node source = graph.network().incidentNodes(edge).source();
|
|
if (source instanceof ComponentNode) {
|
|
return canBeSatisfiedByProductionBinding(edge.dependencyRequest().kind());
|
|
}
|
|
if (source instanceof dagger.model.Binding) {
|
|
return ((dagger.model.Binding) source).isProduction();
|
|
}
|
|
throw new IllegalArgumentException(
|
|
"expected a dagger.model.Binding or ComponentNode: " + source);
|
|
}
|
|
|
|
private boolean typeHasInjectionSites(Key key) {
|
|
return injectBindingRegistry
|
|
.getOrFindMembersInjectionBinding(key)
|
|
.map(binding -> !binding.injectionSites().isEmpty())
|
|
.orElse(false);
|
|
}
|
|
}
|