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