• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2019 The Dagger Authors.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package dagger.internal.codegen.bindinggraphvalidation;
18 
19 import static com.google.common.base.Preconditions.checkArgument;
20 import static com.google.common.base.Preconditions.checkNotNull;
21 import static com.google.common.base.Preconditions.checkState;
22 import static dagger.internal.codegen.base.ElementFormatter.elementToString;
23 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
24 import static dagger.internal.codegen.xprocessing.XElements.transitivelyEncloses;
25 
26 import com.google.common.collect.ImmutableSet;
27 import dagger.assisted.Assisted;
28 import dagger.assisted.AssistedFactory;
29 import dagger.assisted.AssistedInject;
30 import dagger.internal.codegen.model.BindingGraph;
31 import dagger.internal.codegen.model.BindingGraph.ChildFactoryMethodEdge;
32 import dagger.internal.codegen.model.BindingGraph.ComponentNode;
33 import dagger.internal.codegen.model.BindingGraph.DependencyEdge;
34 import dagger.internal.codegen.model.BindingGraph.MaybeBinding;
35 import dagger.internal.codegen.model.BindingGraphPlugin;
36 import dagger.internal.codegen.model.DaggerProcessingEnv;
37 import dagger.internal.codegen.model.DiagnosticReporter;
38 import dagger.internal.codegen.validation.DiagnosticMessageGenerator;
39 import dagger.internal.codegen.validation.ValidationBindingGraphPlugin;
40 import java.util.HashMap;
41 import java.util.Map;
42 import java.util.Optional;
43 import java.util.Set;
44 import javax.tools.Diagnostic;
45 
46 /**
47  * Combines many {@link BindingGraphPlugin} implementations. This helps reduce spam by combining all
48  * of the messages that are reported on the root component.
49  */
50 final class CompositeBindingGraphPlugin extends ValidationBindingGraphPlugin {
51   @AssistedFactory
52   interface Factory {
create(ImmutableSet<ValidationBindingGraphPlugin> plugins)53     CompositeBindingGraphPlugin create(ImmutableSet<ValidationBindingGraphPlugin> plugins);
54   }
55 
56   private final ImmutableSet<ValidationBindingGraphPlugin> plugins;
57   private final DiagnosticMessageGenerator.Factory messageGeneratorFactory;
58   private final Map<ComponentNode, String> errorMessages = new HashMap<>();
59 
60   @AssistedInject
CompositeBindingGraphPlugin( @ssisted ImmutableSet<ValidationBindingGraphPlugin> plugins, DiagnosticMessageGenerator.Factory messageGeneratorFactory)61   CompositeBindingGraphPlugin(
62       @Assisted ImmutableSet<ValidationBindingGraphPlugin> plugins,
63       DiagnosticMessageGenerator.Factory messageGeneratorFactory) {
64     this.plugins = plugins;
65     this.messageGeneratorFactory = messageGeneratorFactory;
66   }
67 
68   @Override
visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)69   public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
70     AggregatingDiagnosticReporter aggregatingDiagnosticReporter = new AggregatingDiagnosticReporter(
71         bindingGraph, diagnosticReporter, messageGeneratorFactory.create(bindingGraph));
72     plugins.forEach(
73         plugin -> {
74           aggregatingDiagnosticReporter.setCurrentPlugin(plugin.pluginName());
75           plugin.visitGraph(bindingGraph, aggregatingDiagnosticReporter);
76           if (plugin.visitFullGraphRequested(bindingGraph)) {
77             requestVisitFullGraph(bindingGraph);
78           }
79         });
80     if (visitFullGraphRequested(bindingGraph)) {
81       errorMessages.put(
82           bindingGraph.rootComponentNode(), aggregatingDiagnosticReporter.getMessage());
83     } else {
84       aggregatingDiagnosticReporter.report();
85     }
86   }
87 
88   @Override
revisitFullGraph( BindingGraph prunedGraph, BindingGraph fullGraph, DiagnosticReporter diagnosticReporter)89   public void revisitFullGraph(
90       BindingGraph prunedGraph, BindingGraph fullGraph, DiagnosticReporter diagnosticReporter) {
91     AggregatingDiagnosticReporter aggregatingDiagnosticReporter =
92         new AggregatingDiagnosticReporter(
93             fullGraph,
94             diagnosticReporter,
95             errorMessages.get(prunedGraph.rootComponentNode()),
96             messageGeneratorFactory.create(fullGraph));
97     plugins.stream()
98         .filter(plugin -> plugin.visitFullGraphRequested(prunedGraph))
99         .forEach(
100             plugin -> {
101               aggregatingDiagnosticReporter.setCurrentPlugin(plugin.pluginName());
102               plugin.revisitFullGraph(prunedGraph, fullGraph, aggregatingDiagnosticReporter);
103             });
104     aggregatingDiagnosticReporter.report();
105   }
106 
107   @Override
init(DaggerProcessingEnv processingEnv, Map<String, String> options)108   public void init(DaggerProcessingEnv processingEnv, Map<String, String> options) {
109     plugins.forEach(plugin -> plugin.init(processingEnv, options));
110   }
111 
112   @Override
onPluginEnd()113   public void onPluginEnd() {
114     plugins.forEach(BindingGraphPlugin::onPluginEnd);
115   }
116 
117   @Override
supportedOptions()118   public Set<String> supportedOptions() {
119     return plugins.stream()
120         .flatMap(plugin -> plugin.supportedOptions().stream())
121         .collect(toImmutableSet());
122   }
123 
124   @Override
pluginName()125   public String pluginName() {
126     return "Dagger/Validation";
127   }
128 
129   // TODO(erichang): This kind of breaks some of the encapsulation by relying on or repeating
130   // logic within DiagnosticReporterImpl. Hopefully if the experiment for aggregated messages
131   // goes well though this can be merged with that implementation.
132   private static final class AggregatingDiagnosticReporter extends DiagnosticReporter {
133     private final DiagnosticReporter delegate;
134     private final BindingGraph graph;
135     // Initialize with a new line so the first message appears below the reported component
136     private final StringBuilder messageBuilder = new StringBuilder("\n");
137     private final DiagnosticMessageGenerator messageGenerator;
138     private Optional<Diagnostic.Kind> mergedDiagnosticKind = Optional.empty();
139     private String currentPluginName = null;
140 
AggregatingDiagnosticReporter( BindingGraph graph, DiagnosticReporter delegate, DiagnosticMessageGenerator messageGenerator)141     AggregatingDiagnosticReporter(
142         BindingGraph graph,
143         DiagnosticReporter delegate,
144         DiagnosticMessageGenerator messageGenerator) {
145       this.graph = graph;
146       this.delegate = delegate;
147       this.messageGenerator = messageGenerator;
148     }
149 
AggregatingDiagnosticReporter( BindingGraph graph, DiagnosticReporter delegate, String baseMessage, DiagnosticMessageGenerator messageGenerator)150     AggregatingDiagnosticReporter(
151         BindingGraph graph,
152         DiagnosticReporter delegate,
153         String baseMessage,
154         DiagnosticMessageGenerator messageGenerator) {
155       this.graph = graph;
156       this.delegate = delegate;
157       this.messageGenerator = messageGenerator;
158       this.messageBuilder.append(baseMessage);
159     }
160 
161     /** Sets the currently running aggregated plugin. Used to add a diagnostic prefix. */
setCurrentPlugin(String pluginName)162     void setCurrentPlugin(String pluginName) {
163       currentPluginName = pluginName;
164     }
165 
166     /** Reports all of the stored diagnostics. */
report()167     void report() {
168       if (mergedDiagnosticKind.isPresent()) {
169         delegate.reportComponent(
170             mergedDiagnosticKind.get(),
171             graph.rootComponentNode(),
172             PackageNameCompressor.compressPackagesInMessage(messageBuilder.toString()));
173       }
174     }
175 
getMessage()176     String getMessage() {
177       return messageBuilder.toString();
178     }
179 
180     @Override
reportComponent( Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String message)181     public void reportComponent(
182         Diagnostic.Kind diagnosticKind, ComponentNode componentNode, String message) {
183       addMessage(diagnosticKind, message);
184       messageGenerator.appendComponentPathUnlessAtRoot(messageBuilder, componentNode);
185     }
186 
187     @Override
reportBinding( Diagnostic.Kind diagnosticKind, MaybeBinding binding, String message)188     public void reportBinding(
189         Diagnostic.Kind diagnosticKind, MaybeBinding binding, String message) {
190       addMessage(diagnosticKind,
191           String.format("%s%s", message, messageGenerator.getMessage(binding)));
192     }
193 
194     @Override
reportDependency( Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message)195     public void reportDependency(
196         Diagnostic.Kind diagnosticKind, DependencyEdge dependencyEdge, String message) {
197       addMessage(diagnosticKind,
198           String.format("%s%s", message, messageGenerator.getMessage(dependencyEdge)));
199     }
200 
201     @Override
reportSubcomponentFactoryMethod( Diagnostic.Kind diagnosticKind, ChildFactoryMethodEdge childFactoryMethodEdge, String message)202     public void reportSubcomponentFactoryMethod(
203         Diagnostic.Kind diagnosticKind,
204         ChildFactoryMethodEdge childFactoryMethodEdge,
205         String message) {
206       // TODO(erichang): This repeats some of the logic in DiagnosticReporterImpl. Remove when
207       // merged.
208       if (transitivelyEncloses(
209           graph.rootComponentNode().componentPath().currentComponent().xprocessing(),
210           childFactoryMethodEdge.factoryMethod().xprocessing())) {
211         // Let this pass through since it is not an error reported on the root component
212         delegate.reportSubcomponentFactoryMethod(diagnosticKind, childFactoryMethodEdge, message);
213       } else {
214         addMessage(
215             diagnosticKind,
216             String.format(
217                 "[%s] %s",
218                 elementToString(childFactoryMethodEdge.factoryMethod().xprocessing()), message));
219       }
220     }
221 
222     /** Adds a message to the stored aggregated message. */
addMessage(Diagnostic.Kind diagnosticKind, String message)223     private void addMessage(Diagnostic.Kind diagnosticKind, String message) {
224       checkNotNull(diagnosticKind);
225       checkNotNull(message);
226       checkState(currentPluginName != null);
227 
228       // Add a separator if this isn't the first message
229       if (mergedDiagnosticKind.isPresent()) {
230         messageBuilder.append("\n\n");
231       }
232 
233       mergeDiagnosticKind(diagnosticKind);
234       // Adds brackets as well as special color strings to make the string red and bold.
235       messageBuilder.append(String.format("\033[1;31m[%s]\033[0m ", currentPluginName));
236       messageBuilder.append(message);
237     }
238 
mergeDiagnosticKind(Diagnostic.Kind diagnosticKind)239     private void mergeDiagnosticKind(Diagnostic.Kind diagnosticKind) {
240       checkArgument(diagnosticKind != Diagnostic.Kind.MANDATORY_WARNING,
241           "Dagger plugins should not be issuing mandatory warnings");
242       if (!mergedDiagnosticKind.isPresent()) {
243         mergedDiagnosticKind = Optional.of(diagnosticKind);
244         return;
245       }
246       Diagnostic.Kind current = mergedDiagnosticKind.get();
247       if (current == Diagnostic.Kind.ERROR || diagnosticKind == Diagnostic.Kind.ERROR) {
248         mergedDiagnosticKind = Optional.of(Diagnostic.Kind.ERROR);
249       } else if (current == Diagnostic.Kind.WARNING || diagnosticKind == Diagnostic.Kind.WARNING) {
250         mergedDiagnosticKind = Optional.of(Diagnostic.Kind.WARNING);
251       } else if (current == Diagnostic.Kind.NOTE || diagnosticKind == Diagnostic.Kind.NOTE) {
252         mergedDiagnosticKind = Optional.of(Diagnostic.Kind.NOTE);
253       } else {
254         mergedDiagnosticKind = Optional.of(Diagnostic.Kind.OTHER);
255       }
256     }
257   }
258 }
259