• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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 dagger.internal.codegen.base.Formatter.INDENT;
20 import static dagger.internal.codegen.base.Scopes.getReadableSource;
21 import static dagger.internal.codegen.langmodel.DaggerElements.closestEnclosingTypeElement;
22 import static dagger.model.BindingKind.INJECTION;
23 import static java.util.stream.Collectors.joining;
24 import static javax.tools.Diagnostic.Kind.ERROR;
25 
26 import com.google.auto.common.MoreElements;
27 import com.google.common.collect.ImmutableSetMultimap;
28 import com.google.common.collect.Multimaps;
29 import dagger.internal.codegen.base.Scopes;
30 import dagger.internal.codegen.binding.MethodSignatureFormatter;
31 import dagger.internal.codegen.compileroption.CompilerOptions;
32 import dagger.model.Binding;
33 import dagger.model.BindingGraph;
34 import dagger.model.BindingGraph.ComponentNode;
35 import dagger.spi.BindingGraphPlugin;
36 import dagger.spi.DiagnosticReporter;
37 import java.util.Optional;
38 import java.util.Set;
39 import javax.inject.Inject;
40 import javax.tools.Diagnostic;
41 
42 /**
43  * Reports an error for any component that uses bindings with scopes that are not assigned to the
44  * component.
45  */
46 final class IncompatiblyScopedBindingsValidator implements BindingGraphPlugin {
47 
48   private final MethodSignatureFormatter methodSignatureFormatter;
49   private final CompilerOptions compilerOptions;
50 
51   @Inject
IncompatiblyScopedBindingsValidator( MethodSignatureFormatter methodSignatureFormatter, CompilerOptions compilerOptions)52   IncompatiblyScopedBindingsValidator(
53       MethodSignatureFormatter methodSignatureFormatter, CompilerOptions compilerOptions) {
54     this.methodSignatureFormatter = methodSignatureFormatter;
55     this.compilerOptions = compilerOptions;
56   }
57 
58   @Override
pluginName()59   public String pluginName() {
60     return "Dagger/IncompatiblyScopedBindings";
61   }
62 
63   @Override
visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter)64   public void visitGraph(BindingGraph bindingGraph, DiagnosticReporter diagnosticReporter) {
65     ImmutableSetMultimap.Builder<ComponentNode, dagger.model.Binding> incompatibleBindings =
66         ImmutableSetMultimap.builder();
67     for (dagger.model.Binding binding : bindingGraph.bindings()) {
68       binding
69           .scope()
70           .filter(scope -> !scope.isReusable())
71           .ifPresent(
72               scope -> {
73                 ComponentNode componentNode =
74                     bindingGraph.componentNode(binding.componentPath()).get();
75                 if (!componentNode.scopes().contains(scope)) {
76                   // @Inject bindings in module or subcomponent binding graphs will appear at the
77                   // properly scoped ancestor component, so ignore them here.
78                   if (binding.kind().equals(INJECTION)
79                       && (bindingGraph.rootComponentNode().isSubcomponent()
80                           || !bindingGraph.rootComponentNode().isRealComponent())) {
81                     return;
82                   }
83                   incompatibleBindings.put(componentNode, binding);
84                 }
85               });
86     }
87     Multimaps.asMap(incompatibleBindings.build())
88         .forEach((componentNode, bindings) -> report(componentNode, bindings, diagnosticReporter));
89   }
90 
report( ComponentNode componentNode, Set<Binding> bindings, DiagnosticReporter diagnosticReporter)91   private void report(
92       ComponentNode componentNode,
93       Set<Binding> bindings,
94       DiagnosticReporter diagnosticReporter) {
95     Diagnostic.Kind diagnosticKind = ERROR;
96     StringBuilder message =
97         new StringBuilder(componentNode.componentPath().currentComponent().getQualifiedName());
98 
99     if (!componentNode.isRealComponent()) {
100       // If the "component" is really a module, it will have no scopes attached. We want to report
101       // if there is more than one scope in that component.
102       if (bindings.stream().map(Binding::scope).map(Optional::get).distinct().count() <= 1) {
103         return;
104       }
105       message.append(" contains bindings with different scopes:");
106       diagnosticKind = compilerOptions.moduleHasDifferentScopesDiagnosticKind();
107     } else if (componentNode.scopes().isEmpty()) {
108       message.append(" (unscoped) may not reference scoped bindings:");
109     } else {
110       message
111           .append(" scoped with ")
112           .append(
113               componentNode.scopes().stream().map(Scopes::getReadableSource).collect(joining(" ")))
114           .append(" may not reference bindings with different scopes:");
115     }
116 
117     // TODO(ronshapiro): Should we group by scope?
118     for (Binding binding : bindings) {
119       message.append('\n').append(INDENT);
120 
121       // TODO(dpb): Use BindingDeclarationFormatter.
122       // But that doesn't print scopes for @Inject-constructed types.
123       switch (binding.kind()) {
124         case DELEGATE:
125         case PROVISION:
126           message.append(
127               methodSignatureFormatter.format(
128                   MoreElements.asExecutable(binding.bindingElement().get())));
129           break;
130 
131         case INJECTION:
132           message
133               .append(getReadableSource(binding.scope().get()))
134               .append(" class ")
135               .append(
136                   closestEnclosingTypeElement(binding.bindingElement().get()).getQualifiedName());
137           break;
138 
139         default:
140           throw new AssertionError(binding);
141       }
142     }
143     diagnosticReporter.reportComponent(diagnosticKind, componentNode, message.toString());
144   }
145 }
146