blob: a9c650a1bf6653243d191fa8b2bbb6a3dc12cbf8 [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.validation;
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.langmodel.DaggerElements.elementEncloses;
import com.google.common.collect.ImmutableSet;
import com.google.errorprone.annotations.FormatMethod;
import dagger.model.BindingGraph;
import dagger.model.BindingGraph.ChildFactoryMethodEdge;
import dagger.model.BindingGraph.ComponentNode;
import dagger.model.BindingGraph.DependencyEdge;
import dagger.model.BindingGraph.MaybeBinding;
import dagger.spi.BindingGraphPlugin;
import dagger.spi.DiagnosticReporter;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import javax.annotation.processing.Filer;
import javax.inject.Inject;
import javax.lang.model.util.Elements; // ALLOW_TYPES_ELEMENTS because of interface dependencies
import javax.lang.model.util.Types; // ALLOW_TYPES_ELEMENTS because of interface dependencies
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.
*/
public final class CompositeBindingGraphPlugin implements BindingGraphPlugin {
private final ImmutableSet<BindingGraphPlugin> plugins;
private final String pluginName;
private final DiagnosticMessageGenerator.Factory messageGeneratorFactory;
/** Factory class for {@link CompositeBindingGraphPlugin}. */
public static final class Factory {
private final DiagnosticMessageGenerator.Factory messageGeneratorFactory;
@Inject Factory(DiagnosticMessageGenerator.Factory messageGeneratorFactory) {
this.messageGeneratorFactory = messageGeneratorFactory;
}
public CompositeBindingGraphPlugin create(
ImmutableSet<BindingGraphPlugin> plugins, String pluginName) {
return new CompositeBindingGraphPlugin(plugins, pluginName, messageGeneratorFactory);
}
}
private CompositeBindingGraphPlugin(
ImmutableSet<BindingGraphPlugin> plugins,
String pluginName,
DiagnosticMessageGenerator.Factory messageGeneratorFactory) {
this.plugins = plugins;
this.pluginName = pluginName;
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);
});
aggregatingDiagnosticReporter.report();
}
@Override
public void initFiler(Filer filer) {
plugins.forEach(plugin -> plugin.initFiler(filer));
}
@Override
public void initTypes(Types types) {
plugins.forEach(plugin -> plugin.initTypes(types));
}
@Override
public void initElements(Elements elements) {
plugins.forEach(plugin -> plugin.initElements(elements));
}
@Override
public void initOptions(Map<String, String> options) {
plugins.forEach(plugin -> plugin.initOptions(options));
}
@Override
public Set<String> supportedOptions() {
return plugins.stream().flatMap(
plugin -> plugin.supportedOptions().stream()).collect(toImmutableSet());
}
@Override
public String pluginName() {
return pluginName;
}
// 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;
}
/** 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()));
}
}
@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 (elementEncloses(
graph.rootComponentNode().componentPath().currentComponent(),
childFactoryMethodEdge.factoryMethod())) {
// 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()), 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);
}
}
}
}