blob: 02ac06a2525582bfdc2695fec5dccc50c5c45341 [file] [log] [blame]
/*
* Copyright (C) 2014 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.auto.common.AnnotationMirrors.getAnnotatedAnnotations;
import static com.google.auto.common.MoreElements.isAnnotationPresent;
import static com.google.auto.common.MoreTypes.asTypeElement;
import static com.google.auto.common.Visibility.PRIVATE;
import static com.google.auto.common.Visibility.PUBLIC;
import static com.google.auto.common.Visibility.effectiveVisibilityOfElement;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.base.ComponentAnnotation.componentAnnotation;
import static dagger.internal.codegen.base.ComponentAnnotation.isComponentAnnotation;
import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation;
import static dagger.internal.codegen.base.ModuleAnnotation.isModuleAnnotation;
import static dagger.internal.codegen.base.ModuleAnnotation.moduleAnnotation;
import static dagger.internal.codegen.base.MoreAnnotationMirrors.simpleName;
import static dagger.internal.codegen.base.MoreAnnotationValues.asType;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.ComponentCreatorAnnotation.getCreatorAnnotations;
import static dagger.internal.codegen.binding.ConfigurationAnnotations.getSubcomponentCreator;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.langmodel.DaggerElements.getAnnotationMirror;
import static dagger.internal.codegen.langmodel.DaggerElements.isAnyAnnotationPresent;
import static java.util.stream.Collectors.joining;
import static javax.lang.model.element.Modifier.ABSTRACT;
import static javax.lang.model.element.Modifier.STATIC;
import static javax.lang.model.util.ElementFilter.methodsIn;
import com.google.auto.common.MoreElements;
import com.google.auto.common.MoreTypes;
import com.google.auto.common.Visibility;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableListMultimap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.common.collect.Multimaps;
import com.google.common.collect.Sets;
import com.google.errorprone.annotations.FormatMethod;
import dagger.Module;
import dagger.Subcomponent;
import dagger.internal.codegen.base.ModuleAnnotation;
import dagger.internal.codegen.binding.BindingGraphFactory;
import dagger.internal.codegen.binding.ComponentCreatorAnnotation;
import dagger.internal.codegen.binding.ComponentDescriptorFactory;
import dagger.internal.codegen.binding.MethodSignatureFormatter;
import dagger.internal.codegen.binding.ModuleKind;
import dagger.internal.codegen.kotlin.KotlinMetadataUtil;
import dagger.internal.codegen.langmodel.DaggerElements;
import dagger.internal.codegen.langmodel.DaggerTypes;
import dagger.model.BindingGraph;
import dagger.producers.ProducerModule;
import dagger.producers.ProductionSubcomponent;
import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Scope;
import javax.inject.Singleton;
import javax.lang.model.element.AnnotationMirror;
import javax.lang.model.element.AnnotationValue;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Name;
import javax.lang.model.element.TypeElement;
import javax.lang.model.type.DeclaredType;
import javax.lang.model.type.TypeMirror;
import javax.lang.model.util.SimpleAnnotationValueVisitor8;
import javax.lang.model.util.SimpleTypeVisitor8;
/** A {@linkplain ValidationReport validator} for {@link Module}s or {@link ProducerModule}s. */
@Singleton
public final class ModuleValidator {
private static final ImmutableSet<Class<? extends Annotation>> SUBCOMPONENT_TYPES =
ImmutableSet.of(Subcomponent.class, ProductionSubcomponent.class);
private static final ImmutableSet<Class<? extends Annotation>> SUBCOMPONENT_CREATOR_TYPES =
ImmutableSet.of(
Subcomponent.Builder.class,
Subcomponent.Factory.class,
ProductionSubcomponent.Builder.class,
ProductionSubcomponent.Factory.class);
private static final Optional<Class<?>> ANDROID_PROCESSOR;
private static final String CONTRIBUTES_ANDROID_INJECTOR_NAME =
"dagger.android.ContributesAndroidInjector";
private static final String ANDROID_PROCESSOR_NAME = "dagger.android.processor.AndroidProcessor";
static {
Class<?> clazz;
try {
clazz = Class.forName(ANDROID_PROCESSOR_NAME, false, ModuleValidator.class.getClassLoader());
} catch (ClassNotFoundException ignored) {
clazz = null;
}
ANDROID_PROCESSOR = Optional.ofNullable(clazz);
}
private final DaggerTypes types;
private final DaggerElements elements;
private final AnyBindingMethodValidator anyBindingMethodValidator;
private final MethodSignatureFormatter methodSignatureFormatter;
private final ComponentDescriptorFactory componentDescriptorFactory;
private final BindingGraphFactory bindingGraphFactory;
private final BindingGraphValidator bindingGraphValidator;
private final KotlinMetadataUtil metadataUtil;
private final Map<TypeElement, ValidationReport<TypeElement>> cache = new HashMap<>();
private final Set<TypeElement> knownModules = new HashSet<>();
@Inject
ModuleValidator(
DaggerTypes types,
DaggerElements elements,
AnyBindingMethodValidator anyBindingMethodValidator,
MethodSignatureFormatter methodSignatureFormatter,
ComponentDescriptorFactory componentDescriptorFactory,
BindingGraphFactory bindingGraphFactory,
BindingGraphValidator bindingGraphValidator,
KotlinMetadataUtil metadataUtil) {
this.types = types;
this.elements = elements;
this.anyBindingMethodValidator = anyBindingMethodValidator;
this.methodSignatureFormatter = methodSignatureFormatter;
this.componentDescriptorFactory = componentDescriptorFactory;
this.bindingGraphFactory = bindingGraphFactory;
this.bindingGraphValidator = bindingGraphValidator;
this.metadataUtil = metadataUtil;
}
/**
* Adds {@code modules} to the set of module types that will be validated during this compilation
* step. If a component or module includes a module that is not in this set, that included module
* is assumed to be valid because it was processed in a previous compilation step. If it were
* invalid, that previous compilation step would have failed and blocked this one.
*
* <p>This logic depends on this method being called before {@linkplain #validate(TypeElement)
* validating} any module or {@linkplain #validateReferencedModules(TypeElement, AnnotationMirror,
* ImmutableSet, Set) component}.
*/
public void addKnownModules(Collection<TypeElement> modules) {
knownModules.addAll(modules);
}
/** Returns a validation report for a module type. */
public ValidationReport<TypeElement> validate(TypeElement module) {
return validate(module, new HashSet<>());
}
private ValidationReport<TypeElement> validate(
TypeElement module, Set<TypeElement> visitedModules) {
if (visitedModules.add(module)) {
return reentrantComputeIfAbsent(cache, module, m -> validateUncached(module, visitedModules));
}
return ValidationReport.about(module).build();
}
private ValidationReport<TypeElement> validateUncached(
TypeElement module, Set<TypeElement> visitedModules) {
ValidationReport.Builder<TypeElement> builder = ValidationReport.about(module);
ModuleKind moduleKind = ModuleKind.forAnnotatedElement(module).get();
TypeElement contributesAndroidInjectorElement =
elements.getTypeElement(CONTRIBUTES_ANDROID_INJECTOR_NAME);
TypeMirror contributesAndroidInjector =
contributesAndroidInjectorElement != null
? contributesAndroidInjectorElement.asType()
: null;
List<ExecutableElement> moduleMethods = methodsIn(module.getEnclosedElements());
List<ExecutableElement> bindingMethods = new ArrayList<>();
for (ExecutableElement moduleMethod : moduleMethods) {
if (anyBindingMethodValidator.isBindingMethod(moduleMethod)) {
builder.addSubreport(anyBindingMethodValidator.validate(moduleMethod));
bindingMethods.add(moduleMethod);
}
for (AnnotationMirror annotation : moduleMethod.getAnnotationMirrors()) {
if (!ANDROID_PROCESSOR.isPresent()
&& MoreTypes.equivalence()
.equivalent(contributesAndroidInjector, annotation.getAnnotationType())) {
builder.addSubreport(
ValidationReport.about(moduleMethod)
.addError(
String.format(
"@%s was used, but %s was not found on the processor path",
CONTRIBUTES_ANDROID_INJECTOR_NAME, ANDROID_PROCESSOR_NAME))
.build());
break;
}
}
}
if (bindingMethods.stream()
.map(ModuleMethodKind::ofMethod)
.collect(toImmutableSet())
.containsAll(
EnumSet.of(ModuleMethodKind.ABSTRACT_DECLARATION, ModuleMethodKind.INSTANCE_BINDING))) {
builder.addError(
String.format(
"A @%s may not contain both non-static and abstract binding methods",
moduleKind.annotation().getSimpleName()));
}
validateModuleVisibility(module, moduleKind, builder);
ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName =
Multimaps.index(bindingMethods, ExecutableElement::getSimpleName);
validateMethodsWithSameName(builder, bindingMethodsByName);
if (module.getKind() != ElementKind.INTERFACE) {
validateBindingMethodOverrides(
module,
builder,
Multimaps.index(moduleMethods, ExecutableElement::getSimpleName),
bindingMethodsByName);
}
validateModifiers(module, builder);
validateReferencedModules(module, moduleKind, visitedModules, builder);
validateReferencedSubcomponents(module, moduleKind, builder);
validateNoScopeAnnotationsOnModuleElement(module, moduleKind, builder);
validateSelfCycles(module, builder);
if (metadataUtil.hasEnclosedCompanionObject(module)) {
validateCompanionModule(module, builder);
}
if (builder.build().isClean()
&& bindingGraphValidator.shouldDoFullBindingGraphValidation(module)) {
validateModuleBindings(module, builder);
}
return builder.build();
}
private void validateReferencedSubcomponents(
final TypeElement subject,
ModuleKind moduleKind,
final ValidationReport.Builder<TypeElement> builder) {
// TODO(ronshapiro): use validateTypesAreDeclared when it is checked in
ModuleAnnotation moduleAnnotation = moduleAnnotation(moduleKind.getModuleAnnotation(subject));
for (AnnotationValue subcomponentAttribute :
moduleAnnotation.subcomponentsAsAnnotationValues()) {
asType(subcomponentAttribute)
.accept(
new SimpleTypeVisitor8<Void, Void>() {
@Override
protected Void defaultAction(TypeMirror e, Void aVoid) {
builder.addError(
e + " is not a valid subcomponent type",
subject,
moduleAnnotation.annotation(),
subcomponentAttribute);
return null;
}
@Override
public Void visitDeclared(DeclaredType declaredType, Void aVoid) {
TypeElement attributeType = asTypeElement(declaredType);
if (isAnyAnnotationPresent(attributeType, SUBCOMPONENT_TYPES)) {
validateSubcomponentHasBuilder(
attributeType, moduleAnnotation.annotation(), builder);
} else {
builder.addError(
isAnyAnnotationPresent(attributeType, SUBCOMPONENT_CREATOR_TYPES)
? moduleSubcomponentsIncludesCreator(attributeType)
: moduleSubcomponentsIncludesNonSubcomponent(attributeType),
subject,
moduleAnnotation.annotation(),
subcomponentAttribute);
}
return null;
}
},
null);
}
}
private static String moduleSubcomponentsIncludesNonSubcomponent(TypeElement notSubcomponent) {
return notSubcomponent.getQualifiedName()
+ " is not a @Subcomponent or @ProductionSubcomponent";
}
private static String moduleSubcomponentsIncludesCreator(
TypeElement moduleSubcomponentsAttribute) {
TypeElement subcomponentType =
MoreElements.asType(moduleSubcomponentsAttribute.getEnclosingElement());
ComponentCreatorAnnotation creatorAnnotation =
getOnlyElement(getCreatorAnnotations(moduleSubcomponentsAttribute));
return String.format(
"%s is a @%s.%s. Did you mean to use %s?",
moduleSubcomponentsAttribute.getQualifiedName(),
subcomponentAnnotation(subcomponentType).get().simpleName(),
creatorAnnotation.creatorKind().typeName(),
subcomponentType.getQualifiedName());
}
private static void validateSubcomponentHasBuilder(
TypeElement subcomponentAttribute,
AnnotationMirror moduleAnnotation,
ValidationReport.Builder<TypeElement> builder) {
if (getSubcomponentCreator(subcomponentAttribute).isPresent()) {
return;
}
builder.addError(
moduleSubcomponentsDoesntHaveCreator(subcomponentAttribute, moduleAnnotation),
builder.getSubject(),
moduleAnnotation);
}
private static String moduleSubcomponentsDoesntHaveCreator(
TypeElement subcomponent, AnnotationMirror moduleAnnotation) {
return String.format(
"%1$s doesn't have a @%2$s.Builder or @%2$s.Factory, which is required when used with "
+ "@%3$s.subcomponents",
subcomponent.getQualifiedName(),
subcomponentAnnotation(subcomponent).get().simpleName(),
simpleName(moduleAnnotation));
}
enum ModuleMethodKind {
ABSTRACT_DECLARATION,
INSTANCE_BINDING,
STATIC_BINDING,
;
static ModuleMethodKind ofMethod(ExecutableElement moduleMethod) {
if (moduleMethod.getModifiers().contains(STATIC)) {
return STATIC_BINDING;
} else if (moduleMethod.getModifiers().contains(ABSTRACT)) {
return ABSTRACT_DECLARATION;
} else {
return INSTANCE_BINDING;
}
}
}
private void validateModifiers(
TypeElement subject, ValidationReport.Builder<TypeElement> builder) {
// This coupled with the check for abstract modules in ComponentValidator guarantees that
// only modules without type parameters are referenced from @Component(modules={...}).
if (!subject.getTypeParameters().isEmpty() && !subject.getModifiers().contains(ABSTRACT)) {
builder.addError("Modules with type parameters must be abstract", subject);
}
}
private void validateMethodsWithSameName(
ValidationReport.Builder<TypeElement> builder,
ListMultimap<Name, ExecutableElement> bindingMethodsByName) {
for (Entry<Name, Collection<ExecutableElement>> entry :
bindingMethodsByName.asMap().entrySet()) {
if (entry.getValue().size() > 1) {
for (ExecutableElement offendingMethod : entry.getValue()) {
builder.addError(
String.format(
"Cannot have more than one binding method with the same name in a single module"),
offendingMethod);
}
}
}
}
private void validateReferencedModules(
TypeElement subject,
ModuleKind moduleKind,
Set<TypeElement> visitedModules,
ValidationReport.Builder<TypeElement> builder) {
// Validate that all the modules we include are valid for inclusion.
AnnotationMirror mirror = moduleKind.getModuleAnnotation(subject);
builder.addSubreport(
validateReferencedModules(
subject, mirror, moduleKind.legalIncludedModuleKinds(), visitedModules));
}
/**
* Validates modules included in a given module or installed in a given component.
*
* <p>Checks that the referenced modules are non-generic types annotated with {@code @Module} or
* {@code @ProducerModule}.
*
* <p>If the referenced module is in the {@linkplain #addKnownModules(Collection) known modules
* set} and has errors, reports an error at that module's inclusion.
*
* @param annotatedType the annotated module or component
* @param annotation the annotation specifying the referenced modules ({@code @Component},
* {@code @ProductionComponent}, {@code @Subcomponent}, {@code @ProductionSubcomponent},
* {@code @Module}, or {@code @ProducerModule})
* @param validModuleKinds the module kinds that the annotated type is permitted to include
*/
ValidationReport<TypeElement> validateReferencedModules(
TypeElement annotatedType,
AnnotationMirror annotation,
ImmutableSet<ModuleKind> validModuleKinds,
Set<TypeElement> visitedModules) {
ValidationReport.Builder<TypeElement> subreport = ValidationReport.about(annotatedType);
ImmutableSet<? extends Class<? extends Annotation>> validModuleAnnotations =
validModuleKinds.stream().map(ModuleKind::annotation).collect(toImmutableSet());
for (AnnotationValue includedModule : getModules(annotation)) {
asType(includedModule)
.accept(
new SimpleTypeVisitor8<Void, Void>() {
@Override
protected Void defaultAction(TypeMirror mirror, Void p) {
reportError("%s is not a valid module type.", mirror);
return null;
}
@Override
public Void visitDeclared(DeclaredType t, Void p) {
TypeElement module = MoreElements.asType(t.asElement());
if (!t.getTypeArguments().isEmpty()) {
reportError(
"%s is listed as a module, but has type parameters",
module.getQualifiedName());
}
if (!isAnyAnnotationPresent(module, validModuleAnnotations)) {
reportError(
"%s is listed as a module, but is not annotated with %s",
module.getQualifiedName(),
(validModuleAnnotations.size() > 1 ? "one of " : "")
+ validModuleAnnotations.stream()
.map(otherClass -> "@" + otherClass.getSimpleName())
.collect(joining(", ")));
} else if (knownModules.contains(module)
&& !validate(module, visitedModules).isClean()) {
reportError("%s has errors", module.getQualifiedName());
}
if (metadataUtil.isCompanionObjectClass(module)) {
reportError(
"%s is listed as a module, but it is a companion object class. "
+ "Add @Module to the enclosing class and reference that instead.",
module.getQualifiedName());
}
return null;
}
@FormatMethod
private void reportError(String format, Object... args) {
subreport.addError(
String.format(format, args), annotatedType, annotation, includedModule);
}
},
null);
}
return subreport.build();
}
private static ImmutableList<AnnotationValue> getModules(AnnotationMirror annotation) {
if (isModuleAnnotation(annotation)) {
return moduleAnnotation(annotation).includesAsAnnotationValues();
}
if (isComponentAnnotation(annotation)) {
return componentAnnotation(annotation).moduleValues();
}
throw new IllegalArgumentException(String.format("unsupported annotation: %s", annotation));
}
private void validateBindingMethodOverrides(
TypeElement subject,
ValidationReport.Builder<TypeElement> builder,
ImmutableListMultimap<Name, ExecutableElement> moduleMethodsByName,
ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName) {
// For every binding method, confirm it overrides nothing *and* nothing overrides it.
// Consider the following hierarchy:
// class Parent {
// @Provides Foo a() {}
// @Provides Foo b() {}
// Foo c() {}
// }
// class Child extends Parent {
// @Provides Foo a() {}
// Foo b() {}
// @Provides Foo c() {}
// }
// In each of those cases, we want to fail. "a" is clear, "b" because Child is overriding
// a binding method in Parent, and "c" because Child is defining a binding method that overrides
// Parent.
TypeElement currentClass = subject;
TypeMirror objectType = elements.getTypeElement(Object.class).asType();
// We keep track of methods that failed so we don't spam with multiple failures.
Set<ExecutableElement> failedMethods = Sets.newHashSet();
ListMultimap<Name, ExecutableElement> allMethodsByName =
MultimapBuilder.hashKeys().arrayListValues().build(moduleMethodsByName);
while (!types.isSameType(currentClass.getSuperclass(), objectType)) {
currentClass = MoreElements.asType(types.asElement(currentClass.getSuperclass()));
List<ExecutableElement> superclassMethods = methodsIn(currentClass.getEnclosedElements());
for (ExecutableElement superclassMethod : superclassMethods) {
Name name = superclassMethod.getSimpleName();
// For each method in the superclass, confirm our binding methods don't override it
for (ExecutableElement bindingMethod : bindingMethodsByName.get(name)) {
if (failedMethods.add(bindingMethod)
&& elements.overrides(bindingMethod, superclassMethod, subject)) {
builder.addError(
String.format(
"Binding methods may not override another method. Overrides: %s",
methodSignatureFormatter.format(superclassMethod)),
bindingMethod);
}
}
// For each binding method in superclass, confirm our methods don't override it.
if (anyBindingMethodValidator.isBindingMethod(superclassMethod)) {
for (ExecutableElement method : allMethodsByName.get(name)) {
if (failedMethods.add(method)
&& elements.overrides(method, superclassMethod, subject)) {
builder.addError(
String.format(
"Binding methods may not be overridden in modules. Overrides: %s",
methodSignatureFormatter.format(superclassMethod)),
method);
}
}
}
allMethodsByName.put(superclassMethod.getSimpleName(), superclassMethod);
}
}
}
private void validateModuleVisibility(
final TypeElement moduleElement,
ModuleKind moduleKind,
final ValidationReport.Builder<?> reportBuilder) {
ModuleAnnotation moduleAnnotation =
moduleAnnotation(getAnnotationMirror(moduleElement, moduleKind.annotation()).get());
Visibility moduleVisibility = Visibility.ofElement(moduleElement);
Visibility moduleEffectiveVisibility = effectiveVisibilityOfElement(moduleElement);
if (moduleVisibility.equals(PRIVATE)) {
reportBuilder.addError("Modules cannot be private.", moduleElement);
} else if (moduleEffectiveVisibility.equals(PRIVATE)) {
reportBuilder.addError("Modules cannot be enclosed in private types.", moduleElement);
}
switch (moduleElement.getNestingKind()) {
case ANONYMOUS:
throw new IllegalStateException("Can't apply @Module to an anonymous class");
case LOCAL:
throw new IllegalStateException("Local classes shouldn't show up in the processor");
case MEMBER:
case TOP_LEVEL:
if (moduleEffectiveVisibility.equals(PUBLIC)) {
ImmutableSet<TypeElement> invalidVisibilityIncludes =
getModuleIncludesWithInvalidVisibility(moduleAnnotation);
if (!invalidVisibilityIncludes.isEmpty()) {
reportBuilder.addError(
String.format(
"This module is public, but it includes non-public (or effectively non-public) "
+ "modules (%s) that have non-static, non-abstract binding methods. Either "
+ "reduce the visibility of this module, make the included modules "
+ "public, or make all of the binding methods on the included modules "
+ "abstract or static.",
formatListForErrorMessage(invalidVisibilityIncludes.asList())),
moduleElement);
}
}
}
}
private ImmutableSet<TypeElement> getModuleIncludesWithInvalidVisibility(
ModuleAnnotation moduleAnnotation) {
return moduleAnnotation.includes().stream()
.filter(include -> !effectiveVisibilityOfElement(include).equals(PUBLIC))
.filter(this::requiresModuleInstance)
.collect(toImmutableSet());
}
/**
* Returns {@code true} if a module instance is needed for any of the binding methods on the given
* {@code module}. This is the case when the module has any binding methods that are neither
* {@code abstract} nor {@code static}. Alternatively, if the module is a Kotlin Object then the
* binding methods are considered {@code static}, requiring no module instance.
*/
private boolean requiresModuleInstance(TypeElement module) {
// Note elements.getAllMembers(module) rather than module.getEnclosedElements() here: we need to
// include binding methods declared in supertypes because unlike most other validations being
// done in this class, which assume that supertype binding methods will be validated in a
// separate call to the validator since the supertype itself must be a @Module, we need to look
// at all the binding methods in the module's type hierarchy here.
boolean isKotlinObject =
metadataUtil.isObjectClass(module) || metadataUtil.isCompanionObjectClass(module);
if (isKotlinObject) {
return false;
}
return methodsIn(elements.getAllMembers(module)).stream()
.filter(anyBindingMethodValidator::isBindingMethod)
.map(ExecutableElement::getModifiers)
.anyMatch(modifiers -> !modifiers.contains(ABSTRACT) && !modifiers.contains(STATIC));
}
private void validateNoScopeAnnotationsOnModuleElement(
TypeElement module, ModuleKind moduleKind, ValidationReport.Builder<TypeElement> report) {
for (AnnotationMirror scope : getAnnotatedAnnotations(module, Scope.class)) {
report.addError(
String.format(
"@%ss cannot be scoped. Did you mean to scope a method instead?",
moduleKind.annotation().getSimpleName()),
module,
scope);
}
}
private void validateSelfCycles(
TypeElement module, ValidationReport.Builder<TypeElement> builder) {
ModuleAnnotation moduleAnnotation = moduleAnnotation(module).get();
moduleAnnotation
.includesAsAnnotationValues()
.forEach(
value ->
value.accept(
new SimpleAnnotationValueVisitor8<Void, Void>() {
@Override
public Void visitType(TypeMirror includedModule, Void aVoid) {
if (MoreTypes.equivalence().equivalent(module.asType(), includedModule)) {
String moduleKind = moduleAnnotation.annotationName();
builder.addError(
String.format("@%s cannot include themselves.", moduleKind),
module,
moduleAnnotation.annotation(),
value);
}
return null;
}
},
null));
}
private void validateCompanionModule(
TypeElement module, ValidationReport.Builder<TypeElement> builder) {
checkArgument(metadataUtil.hasEnclosedCompanionObject(module));
TypeElement companionModule = metadataUtil.getEnclosedCompanionObject(module);
List<ExecutableElement> companionModuleMethods =
methodsIn(companionModule.getEnclosedElements());
List<ExecutableElement> companionBindingMethods = new ArrayList<>();
for (ExecutableElement companionModuleMethod : companionModuleMethods) {
if (anyBindingMethodValidator.isBindingMethod(companionModuleMethod)) {
builder.addSubreport(anyBindingMethodValidator.validate(companionModuleMethod));
companionBindingMethods.add(companionModuleMethod);
}
// On normal modules only overriding other binding methods is disallowed, but for companion
// objects we are prohibiting any override. For this can rely on checking the @Override
// annotation since the Kotlin compiler will always produce them for overriding methods.
if (isAnnotationPresent(companionModuleMethod, Override.class)) {
builder.addError(
"Binding method in companion object may not override another method.",
companionModuleMethod);
}
// TODO(danysantiago): Be strict about the usage of @JvmStatic, i.e. tell user to remove it.
}
ImmutableListMultimap<Name, ExecutableElement> bindingMethodsByName =
Multimaps.index(companionBindingMethods, ExecutableElement::getSimpleName);
validateMethodsWithSameName(builder, bindingMethodsByName);
// If there are provision methods, then check the visibility. Companion objects are composed by
// an inner class and a static field, it is not enough to check the visibility on the type
// element or the field, therefore we check the metadata.
if (!companionBindingMethods.isEmpty() && metadataUtil.isVisibilityPrivate(companionModule)) {
builder.addError(
"A Companion Module with binding methods cannot be private.", companionModule);
}
}
private void validateModuleBindings(
TypeElement module, ValidationReport.Builder<TypeElement> report) {
BindingGraph bindingGraph =
bindingGraphFactory.create(
componentDescriptorFactory.moduleComponentDescriptor(module), true)
.topLevelBindingGraph();
if (!bindingGraphValidator.isValid(bindingGraph)) {
// Since the validator uses a DiagnosticReporter to report errors, the ValdiationReport won't
// have any Items for them. We have to tell the ValidationReport that some errors were
// reported for the subject.
report.markDirty();
}
}
private static String formatListForErrorMessage(List<?> things) {
switch (things.size()) {
case 0:
return "";
case 1:
return things.get(0).toString();
default:
StringBuilder output = new StringBuilder();
Joiner.on(", ").appendTo(output, things.subList(0, things.size() - 1));
output.append(" and ").append(things.get(things.size() - 1));
return output.toString();
}
}
}