blob: a3281583d1c4fde997ca14e8bb42c5caa536dd67 [file] [log] [blame]
/*
* Copyright (C) 2019 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.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Lists.asList;
import static dagger.internal.codegen.base.ElementFormatter.elementToString;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.xprocessing.XElements.transitivelyEncloses;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.FormatMethod;
import dagger.assisted.Assisted;
import dagger.assisted.AssistedFactory;
import dagger.assisted.AssistedInject;
import dagger.internal.codegen.validation.DiagnosticMessageGenerator;
import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
import dagger.spi.model.BindingGraph;
import dagger.spi.model.BindingGraph.ChildFactoryMethodEdge;
import dagger.spi.model.BindingGraph.ComponentNode;
import dagger.spi.model.BindingGraph.DependencyEdge;
import dagger.spi.model.BindingGraph.MaybeBinding;
import dagger.spi.model.BindingGraphPlugin;
import dagger.spi.model.DaggerProcessingEnv;
import dagger.spi.model.DiagnosticReporter;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.tools.Diagnostic;
/**
* Combines many {@link BindingGraphPlugin} implementations. This helps reduce spam by combining all
* of the messages that are reported on the root component.
*/
final class CompositeBindingGraphPlugin extends ValidationBindingGraphPlugin {
@AssistedFactory
interface Factory {
CompositeBindingGraphPlugin create(ImmutableSet<ValidationBindingGraphPlugin> plugins);
}
private final ImmutableSet<ValidationBindingGraphPlugin> plugins;
private final DiagnosticMessageGenerator.Factory messageGeneratorFactory;
private final Map<ComponentNode, String> errorMessages = new HashMap<>();
@AssistedInject
CompositeBindingGraphPlugin(
@Assisted ImmutableSet<ValidationBindingGraphPlugin> plugins,
DiagnosticMessageGenerator.Factory messageGeneratorFactory) {
this.plugins = plugins;
this.messageGeneratorFactory = messageGeneratorFactory;
}
@Override
public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
AggregatingDiagnosticReporter aggregatingDiagnosticReporter = new AggregatingDiagnosticReporter(
bindingGraph, diagnosticReporter, messageGeneratorFactory.create(bindingGraph));
plugins.forEach(
plugin -> {
aggregatingDiagnosticReporter.setCurrentPlugin(plugin.pluginName());
plugin.visitGraph(bindingGraph, aggregatingDiagnosticReporter);
if (plugin.visitFullGraphRequested(bindingGraph)) {
requestVisitFullGraph(bindingGraph);
}
});
if (visitFullGraphRequested(bindingGraph)) {
errorMessages.put(
bindingGraph.rootComponentNode(), aggregatingDiagnosticReporter.getMessage());
} else {
aggregatingDiagnosticReporter.report();
}
}
@Override
public void revisitFullGraph(
BindingGraph prunedGraph, BindingGraph fullGraph, DiagnosticReporter diagnosticReporter) {
AggregatingDiagnosticReporter aggregatingDiagnosticReporter =
new AggregatingDiagnosticReporter(
fullGraph,
diagnosticReporter,
errorMessages.get(prunedGraph.rootComponentNode()),
messageGeneratorFactory.create(fullGraph));
plugins.stream()
.filter(plugin -> plugin.visitFullGraphRequested(prunedGraph))
.forEach(
plugin -> {
aggregatingDiagnosticReporter.setCurrentPlugin(plugin.pluginName());
plugin.revisitFullGraph(prunedGraph, fullGraph, aggregatingDiagnosticReporter);
});
aggregatingDiagnosticReporter.report();
}
@Override
public void init(DaggerProcessingEnv processingEnv, Map<String, String> options) {
plugins.forEach(plugin -> plugin.init(processingEnv, options));
}
@Override
public void onPluginEnd() {
plugins.forEach(BindingGraphPlugin::onPluginEnd);
}
@Override
public Set<String> supportedOptions() {
return plugins.stream()
.flatMap(plugin -> plugin.supportedOptions().stream())
.collect(toImmutableSet());
}
@Override
public String pluginName() {
return "Dagger/Validation";
}
// TODO(erichang): This kind of breaks some of the encapsulation by relying on or repeating
// logic within DiagnosticReporterImpl. Hopefully if the experiment for aggregated messages
// goes well though this can be merged with that implementation.
private static final class AggregatingDiagnosticReporter implements DiagnosticReporter {
private final DiagnosticReporter delegate;
private final BindingGraph graph;
// Initialize with a new line so the first message appears below the reported component
private final StringBuilder messageBuilder = new StringBuilder("\n");
private final DiagnosticMessageGenerator messageGenerator;
private Optional<Diagnostic.Kind> mergedDiagnosticKind = Optional.empty();
private String currentPluginName = null;
AggregatingDiagnosticReporter(
BindingGraph graph,
DiagnosticReporter delegate,
DiagnosticMessageGenerator messageGenerator) {
this.graph = graph;
this.delegate = delegate;
this.messageGenerator = messageGenerator;
}
AggregatingDiagnosticReporter(
BindingGraph graph,
DiagnosticReporter delegate,
String baseMessage,
DiagnosticMessageGenerator messageGenerator) {
this.graph = graph;
this.delegate = delegate;
this.messageGenerator = messageGenerator;
this.messageBuilder.append(baseMessage);
}
/** Sets the currently running aggregated plugin. Used to add a diagnostic prefix. */
void setCurrentPlugin(String pluginName) {
currentPluginName = pluginName;
}
/** Reports all of the stored diagnostics. */
void report() {
if (mergedDiagnosticKind.isPresent()) {
delegate.reportComponent(
mergedDiagnosticKind.get(),
graph.rootComponentNode(),
PackageNameCompressor.compressPackagesInMessage(messageBuilder.toString()));
}
}
String getMessage() {
return messageBuilder.toString();
}
@Override
public void reportComponent(Diagnostic.Kind diagnosticKind, ComponentNode componentNode,
String message) {
addMessage(diagnosticKind, message);
messageGenerator.appendComponentPathUnlessAtRoot(messageBuilder, componentNode);
}
@Override
@FormatMethod
public void reportComponent(
Diagnostic.Kind diagnosticKind,
ComponentNode componentNode,
String messageFormat,
Object firstArg,
Object... moreArgs) {
reportComponent(
diagnosticKind, componentNode, formatMessage(messageFormat, firstArg, moreArgs));
}
@Override
public void reportBinding(Diagnostic.Kind diagnosticKind, MaybeBinding binding,
String message) {
addMessage(diagnosticKind,
String.format("%s%s", message, messageGenerator.getMessage(binding)));
}
@Override
@FormatMethod
public void reportBinding(
Diagnostic.Kind diagnosticKind,
MaybeBinding binding,
String messageFormat,
Object firstArg,
Object... moreArgs) {
reportBinding(diagnosticKind, binding, formatMessage(messageFormat, firstArg, moreArgs));
}
@Override
public void reportDependency(
Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message) {
addMessage(diagnosticKind,
String.format("%s%s", message, messageGenerator.getMessage(dependencyEdge)));
}
@Override
@FormatMethod
public void reportDependency(
Diagnostic.Kind diagnosticKind,
DependencyEdge dependencyEdge,
String messageFormat,
Object firstArg,
Object... moreArgs) {
reportDependency(
diagnosticKind, dependencyEdge, formatMessage(messageFormat, firstArg, moreArgs));
}
@Override
public void reportSubcomponentFactoryMethod(
Diagnostic.Kind diagnosticKind,
ChildFactoryMethodEdge childFactoryMethodEdge,
String message) {
// TODO(erichang): This repeats some of the logic in DiagnosticReporterImpl. Remove when
// merged.
if (transitivelyEncloses(
graph.rootComponentNode().componentPath().currentComponent().xprocessing(),
childFactoryMethodEdge.factoryMethod().xprocessing())) {
// Let this pass through since it is not an error reported on the root component
delegate.reportSubcomponentFactoryMethod(diagnosticKind, childFactoryMethodEdge, message);
} else {
addMessage(
diagnosticKind,
String.format(
"[%s] %s",
elementToString(childFactoryMethodEdge.factoryMethod().xprocessing()), message));
}
}
@Override
@FormatMethod
public void reportSubcomponentFactoryMethod(
Diagnostic.Kind diagnosticKind,
ChildFactoryMethodEdge childFactoryMethodEdge,
String messageFormat,
Object firstArg,
Object... moreArgs) {
reportSubcomponentFactoryMethod(
diagnosticKind, childFactoryMethodEdge, formatMessage(messageFormat, firstArg, moreArgs));
}
/** Adds a message to the stored aggregated message. */
private void addMessage(Diagnostic.Kind diagnosticKind, String message) {
checkNotNull(diagnosticKind);
checkNotNull(message);
checkState(currentPluginName != null);
// Add a separator if this isn't the first message
if (mergedDiagnosticKind.isPresent()) {
messageBuilder.append("\n\n");
}
mergeDiagnosticKind(diagnosticKind);
// Adds brackets as well as special color strings to make the string red and bold.
messageBuilder.append(String.format("\033[1;31m[%s]\033[0m ", currentPluginName));
messageBuilder.append(message);
}
private static String formatMessage(String messageFormat, Object firstArg, Object[] moreArgs) {
return String.format(messageFormat, asList(firstArg, moreArgs).toArray());
}
private void mergeDiagnosticKind(Diagnostic.Kind diagnosticKind) {
checkArgument(diagnosticKind != Diagnostic.Kind.MANDATORY_WARNING,
"Dagger plugins should not be issuing mandatory warnings");
if (!mergedDiagnosticKind.isPresent()) {
mergedDiagnosticKind = Optional.of(diagnosticKind);
return;
}
Diagnostic.Kind current = mergedDiagnosticKind.get();
if (current == Diagnostic.Kind.ERROR || diagnosticKind == Diagnostic.Kind.ERROR) {
mergedDiagnosticKind = Optional.of(Diagnostic.Kind.ERROR);
} else if (current == Diagnostic.Kind.WARNING || diagnosticKind == Diagnostic.Kind.WARNING) {
mergedDiagnosticKind = Optional.of(Diagnostic.Kind.WARNING);
} else if (current == Diagnostic.Kind.NOTE || diagnosticKind == Diagnostic.Kind.NOTE) {
mergedDiagnosticKind = Optional.of(Diagnostic.Kind.NOTE);
} else {
mergedDiagnosticKind = Optional.of(Diagnostic.Kind.OTHER);
}
}
}
}