All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dagger.internal.codegen.validation.ModuleValidator Maven / Gradle / Ivy

The newest version!
/*
 * 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.common.collect.Iterables.getOnlyElement;
import static dagger.internal.codegen.base.ComponentAnnotation.isComponentAnnotation;
import static dagger.internal.codegen.base.ComponentAnnotation.subcomponentAnnotation;
import static dagger.internal.codegen.base.ComponentCreatorAnnotation.getCreatorAnnotations;
import static dagger.internal.codegen.base.ModuleAnnotation.isModuleAnnotation;
import static dagger.internal.codegen.base.Util.reentrantComputeIfAbsent;
import static dagger.internal.codegen.binding.ConfigurationAnnotations.getSubcomponentCreator;
import static dagger.internal.codegen.extension.DaggerCollectors.toOptional;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableList;
import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet;
import static dagger.internal.codegen.validation.ModuleValidator.ModuleMethodKind.ABSTRACT_DECLARATION;
import static dagger.internal.codegen.validation.ModuleValidator.ModuleMethodKind.INSTANCE_BINDING;
import static dagger.internal.codegen.xprocessing.XAnnotations.getClassName;
import static dagger.internal.codegen.xprocessing.XElements.getSimpleName;
import static dagger.internal.codegen.xprocessing.XElements.hasAnyAnnotation;
import static dagger.internal.codegen.xprocessing.XTypeElements.hasTypeParameters;
import static dagger.internal.codegen.xprocessing.XTypeElements.isEffectivelyPrivate;
import static dagger.internal.codegen.xprocessing.XTypeElements.isEffectivelyPublic;
import static dagger.internal.codegen.xprocessing.XTypes.areEquivalentTypes;
import static dagger.internal.codegen.xprocessing.XTypes.isDeclared;
import static java.util.stream.Collectors.joining;

import androidx.room.compiler.processing.XAnnotation;
import androidx.room.compiler.processing.XAnnotationValue;
import androidx.room.compiler.processing.XMethodElement;
import androidx.room.compiler.processing.XProcessingEnv;
import androidx.room.compiler.processing.XType;
import androidx.room.compiler.processing.XTypeElement;
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.squareup.javapoet.ClassName;
import com.squareup.javapoet.TypeName;
import dagger.internal.codegen.base.ComponentCreatorAnnotation;
import dagger.internal.codegen.base.DaggerSuperficialValidation;
import dagger.internal.codegen.base.ModuleKind;
import dagger.internal.codegen.binding.BindingGraphFactory;
import dagger.internal.codegen.binding.ComponentDescriptor;
import dagger.internal.codegen.binding.InjectionAnnotations;
import dagger.internal.codegen.binding.MethodSignatureFormatter;
import dagger.internal.codegen.javapoet.TypeNames;
import dagger.internal.codegen.model.BindingGraph;
import dagger.internal.codegen.model.Scope;
import dagger.internal.codegen.xprocessing.XElements;
import dagger.internal.codegen.xprocessing.XTypeElements;
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.Optional;
import java.util.Set;
import javax.inject.Inject;
import javax.inject.Singleton;

/**
 * A {@linkplain ValidationReport validator} for {@link dagger.Module}s or {@link
 * dagger.producers.ProducerModule}s.
 */
@Singleton
public final class ModuleValidator {
  private static final ImmutableSet SUBCOMPONENT_TYPES =
      ImmutableSet.of(TypeNames.SUBCOMPONENT, TypeNames.PRODUCTION_SUBCOMPONENT);
  private static final ImmutableSet SUBCOMPONENT_CREATOR_TYPES =
      ImmutableSet.of(
          TypeNames.SUBCOMPONENT_BUILDER,
          TypeNames.SUBCOMPONENT_FACTORY,
          TypeNames.PRODUCTION_SUBCOMPONENT_BUILDER,
          TypeNames.PRODUCTION_SUBCOMPONENT_FACTORY);
  private static final Optional> ANDROID_PROCESSOR;
  private static final ClassName CONTRIBUTES_ANDROID_INJECTOR_NAME =
      ClassName.get("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 AnyBindingMethodValidator anyBindingMethodValidator;
  private final MethodSignatureFormatter methodSignatureFormatter;
  private final ComponentDescriptor.Factory componentDescriptorFactory;
  private final BindingGraphFactory bindingGraphFactory;
  private final BindingGraphValidator bindingGraphValidator;
  private final InjectionAnnotations injectionAnnotations;
  private final DaggerSuperficialValidation superficialValidation;
  private final XProcessingEnv processingEnv;
  private final Map cache = new HashMap<>();
  private final Set knownModules = new HashSet<>();

  @Inject
  ModuleValidator(
      AnyBindingMethodValidator anyBindingMethodValidator,
      MethodSignatureFormatter methodSignatureFormatter,
      ComponentDescriptor.Factory componentDescriptorFactory,
      BindingGraphFactory bindingGraphFactory,
      BindingGraphValidator bindingGraphValidator,
      InjectionAnnotations injectionAnnotations,
      DaggerSuperficialValidation superficialValidation,
      XProcessingEnv processingEnv) {
    this.anyBindingMethodValidator = anyBindingMethodValidator;
    this.methodSignatureFormatter = methodSignatureFormatter;
    this.componentDescriptorFactory = componentDescriptorFactory;
    this.bindingGraphFactory = bindingGraphFactory;
    this.bindingGraphValidator = bindingGraphValidator;
    this.injectionAnnotations = injectionAnnotations;
    this.superficialValidation = superficialValidation;
    this.processingEnv = processingEnv;
  }

  /**
   * 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.
   *
   * 

This logic depends on this method being called before {@linkplain #validate(XTypeElement) * validating} any module or {@linkplain #validateReferencedModules(XTypeElement, ModuleKind, Set, * DiagnosticReporter.Builder) component}. */ public void addKnownModules(Collection modules) { knownModules.addAll(modules); } /** Returns a validation report for a module type. */ public ValidationReport validate(XTypeElement module) { return validate(module, new HashSet<>()); } private ValidationReport validate(XTypeElement module, Set visitedModules) { if (visitedModules.add(module)) { return reentrantComputeIfAbsent(cache, module, m -> validateUncached(module, visitedModules)); } return ValidationReport.about(module).build(); } private ValidationReport validateUncached(XTypeElement module, Set visitedModules) { ValidationReport.Builder builder = ValidationReport.about(module); ModuleKind moduleKind = ModuleKind.forAnnotatedElement(module).get(); List moduleMethods = module.getDeclaredMethods(); List bindingMethods = new ArrayList<>(); for (XMethodElement moduleMethod : moduleMethods) { if (anyBindingMethodValidator.isBindingMethod(moduleMethod)) { builder.addSubreport(anyBindingMethodValidator.validate(moduleMethod)); bindingMethods.add(moduleMethod); } } validateKotlinObjectDoesNotInheritInstanceBindingMethods(module, moduleKind, builder); validateDaggerAndroidProcessorRequirements(module, builder); if (bindingMethods.stream() .map(ModuleMethodKind::ofMethod) .collect(toImmutableSet()) .containsAll(EnumSet.of(ABSTRACT_DECLARATION, INSTANCE_BINDING))) { builder.addError( String.format( "A @%s may not contain both non-static and abstract binding methods", moduleKind.annotation().simpleName())); } validateModuleVisibility(module, moduleKind, builder); ImmutableListMultimap bindingMethodsByName = Multimaps.index(bindingMethods, XElements::getSimpleName); validateMethodsWithSameName(builder, bindingMethodsByName); if (!module.isInterface()) { validateBindingMethodOverrides( module, builder, Multimaps.index(moduleMethods, XElements::getSimpleName), bindingMethodsByName); } validateModifiers(module, builder); validateReferencedModules(module, moduleKind, visitedModules, builder); validateReferencedSubcomponents(module, moduleKind, builder); validateNoScopeAnnotationsOnModuleElement(module, moduleKind, builder); validateSelfCycles(module, moduleKind, builder); module.getEnclosedTypeElements().stream() .filter(XTypeElement::isCompanionObject) .collect(toOptional()) .ifPresent(companionModule -> validateCompanionModule(companionModule, builder)); if (builder.build().isClean() && bindingGraphValidator.shouldDoFullBindingGraphValidation(module)) { validateModuleBindings(module, builder); } return builder.build(); } private void validateDaggerAndroidProcessorRequirements( XTypeElement module, ValidationReport.Builder builder) { if (ANDROID_PROCESSOR.isPresent() || processingEnv.findTypeElement(CONTRIBUTES_ANDROID_INJECTOR_NAME) == null) { return; } module.getDeclaredMethods().stream() .filter(method -> method.hasAnnotation(CONTRIBUTES_ANDROID_INJECTOR_NAME)) .forEach( method -> builder.addSubreport( ValidationReport.about(method) .addError( String.format( "@%s was used, but %s was not found on the processor path", CONTRIBUTES_ANDROID_INJECTOR_NAME.simpleName(), ANDROID_PROCESSOR_NAME)) .build())); } private void validateKotlinObjectDoesNotInheritInstanceBindingMethods( XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder builder) { if (!module.isKotlinObject()) { return; } XTypeElement currentClass = module; while (!currentClass.getSuperType().getTypeName().equals(TypeName.OBJECT)) { currentClass = currentClass.getSuperType().getTypeElement(); currentClass.getDeclaredMethods().stream() .filter(anyBindingMethodValidator::isBindingMethod) .filter(method -> ModuleMethodKind.ofMethod(method) == INSTANCE_BINDING) .forEach( method -> // TODO(b/264618194): Consider allowing this use case. builder.addError( String.format( "@%s-annotated Kotlin object cannot inherit instance (i.e. non-abstract, " + "non-JVM static) binding method: %s", moduleKind.annotation().simpleName(), methodSignatureFormatter.format(method)))); } } private void validateReferencedSubcomponents( XTypeElement subject, ModuleKind moduleKind, ValidationReport.Builder builder) { XAnnotation moduleAnnotation = moduleKind.getModuleAnnotation(subject); for (XAnnotationValue subcomponentValue : moduleAnnotation.getAsAnnotationValueList("subcomponents")) { XType type = subcomponentValue.asType(); if (!isDeclared(type)) { builder.addError( type + " is not a valid subcomponent type", subject, moduleAnnotation, subcomponentValue); continue; } XTypeElement subcomponentElement = type.getTypeElement(); if (hasAnyAnnotation(subcomponentElement, SUBCOMPONENT_TYPES)) { validateSubcomponentHasBuilder(subject, subcomponentElement, moduleAnnotation, builder); } else { builder.addError( hasAnyAnnotation(subcomponentElement, SUBCOMPONENT_CREATOR_TYPES) ? moduleSubcomponentsIncludesCreator(subcomponentElement) : moduleSubcomponentsIncludesNonSubcomponent(subcomponentElement), subject, moduleAnnotation, subcomponentValue); } } } private static String moduleSubcomponentsIncludesNonSubcomponent(XTypeElement notSubcomponent) { return notSubcomponent.getQualifiedName() + " is not a @Subcomponent or @ProductionSubcomponent"; } private String moduleSubcomponentsIncludesCreator(XTypeElement moduleSubcomponentsAttribute) { XTypeElement subcomponentType = moduleSubcomponentsAttribute.getEnclosingTypeElement(); ComponentCreatorAnnotation creatorAnnotation = getOnlyElement(getCreatorAnnotations(moduleSubcomponentsAttribute)); return String.format( "%s is a @%s.%s. Did you mean to use %s?", moduleSubcomponentsAttribute.getQualifiedName(), subcomponentAnnotation(subcomponentType, superficialValidation).get().simpleName(), creatorAnnotation.creatorKind().typeName(), subcomponentType.getQualifiedName()); } private void validateSubcomponentHasBuilder( XTypeElement subject, XTypeElement subcomponentAttribute, XAnnotation moduleAnnotation, ValidationReport.Builder builder) { if (getSubcomponentCreator(subcomponentAttribute).isPresent()) { return; } builder.addError( moduleSubcomponentsDoesntHaveCreator(subcomponentAttribute, moduleAnnotation), subject, moduleAnnotation); } private String moduleSubcomponentsDoesntHaveCreator( XTypeElement subcomponent, XAnnotation 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, superficialValidation).get().simpleName(), getClassName(moduleAnnotation).simpleName()); } enum ModuleMethodKind { ABSTRACT_DECLARATION, INSTANCE_BINDING, STATIC_BINDING, ; static ModuleMethodKind ofMethod(XMethodElement moduleMethod) { if (moduleMethod.isStatic()) { return STATIC_BINDING; } else if (moduleMethod.isAbstract()) { return ABSTRACT_DECLARATION; } else { return INSTANCE_BINDING; } } } private void validateModifiers(XTypeElement subject, ValidationReport.Builder builder) { // This coupled with the check for abstract modules in ComponentValidator guarantees that // only modules without type parameters are referenced from @Component(modules={...}). if (hasTypeParameters(subject) && !subject.isAbstract()) { builder.addError("Modules with type parameters must be abstract", subject); } } private void validateMethodsWithSameName( ValidationReport.Builder builder, ListMultimap bindingMethodsByName) { bindingMethodsByName.asMap().values().stream() .filter(methods -> methods.size() > 1) .flatMap(Collection::stream) .forEach( duplicateMethod -> { builder.addError( "Cannot have more than one binding method with the same name in a single module", duplicateMethod); }); } private void validateReferencedModules( XTypeElement subject, ModuleKind moduleKind, Set visitedModules, ValidationReport.Builder builder) { // Validate that all the modules we include are valid for inclusion. XAnnotation 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. * *

Checks that the referenced modules are non-generic types annotated with {@code @Module} or * {@code @ProducerModule}. * *

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 validateReferencedModules( XTypeElement annotatedType, XAnnotation annotation, ImmutableSet validModuleKinds, Set visitedModules) { superficialValidation.validateAnnotationOf(annotatedType, annotation); ValidationReport.Builder subreport = ValidationReport.about(annotatedType); // TODO(bcorso): Consider creating a DiagnosticLocation object to encapsulate the location in a // single object to avoid duplication across all reported errors for (XAnnotationValue includedModule : getModules(annotation)) { XType type = includedModule.asType(); if (!isDeclared(type)) { subreport.addError( String.format("%s is not a valid module type.", type), annotatedType, annotation, includedModule); continue; } XTypeElement module = type.getTypeElement(); if (hasTypeParameters(module)) { subreport.addError( String.format( "%s is listed as a module, but has type parameters", module.getQualifiedName()), annotatedType, annotation, includedModule); } ImmutableSet validModuleAnnotations = validModuleKinds.stream().map(ModuleKind::annotation).collect(toImmutableSet()); if (!hasAnyAnnotation(module, validModuleAnnotations)) { subreport.addError( String.format( "%s is listed as a module, but is not annotated with %s", module.getQualifiedName(), (validModuleAnnotations.size() > 1 ? "one of " : "") + validModuleAnnotations.stream() .map(otherClass -> "@" + otherClass.simpleName()) .collect(joining(", "))), annotatedType, annotation, includedModule); } else if (knownModules.contains(module) && !validate(module, visitedModules).isClean()) { subreport.addError( String.format("%s has errors", module.getQualifiedName()), annotatedType, annotation, includedModule); } if (module.isCompanionObject()) { subreport.addError( String.format( "%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()), annotatedType, annotation, includedModule); } } return subreport.build(); } private static ImmutableList getModules(XAnnotation annotation) { if (isModuleAnnotation(annotation)) { return ImmutableList.copyOf(annotation.getAsAnnotationValueList("includes")); } if (isComponentAnnotation(annotation)) { return ImmutableList.copyOf(annotation.getAsAnnotationValueList("modules")); } throw new IllegalArgumentException(String.format("unsupported annotation: %s", annotation)); } private void validateBindingMethodOverrides( XTypeElement subject, ValidationReport.Builder builder, ImmutableListMultimap moduleMethodsByName, ImmutableListMultimap 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. XTypeElement currentClass = subject; // We keep track of visited methods so we don't spam with multiple failures. Set visitedMethods = Sets.newHashSet(); ListMultimap allMethodsByName = MultimapBuilder.hashKeys().arrayListValues().build(moduleMethodsByName); while (!currentClass.getSuperType().getTypeName().equals(TypeName.OBJECT)) { currentClass = currentClass.getSuperType().getTypeElement(); List superclassMethods = currentClass.getDeclaredMethods(); for (XMethodElement superclassMethod : superclassMethods) { String name = getSimpleName(superclassMethod); // For each method in the superclass, confirm our binding methods don't override it for (XMethodElement bindingMethod : bindingMethodsByName.get(name)) { if (visitedMethods.add(bindingMethod) && bindingMethod.overrides(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 (XMethodElement method : allMethodsByName.get(name)) { if (visitedMethods.add(method) && method.overrides(superclassMethod, subject)) { builder.addError( String.format( "Binding methods may not be overridden in modules. Overrides: %s", methodSignatureFormatter.format(superclassMethod)), method); } } } // TODO(b/202521399): Add a test for cases that add to this map. allMethodsByName.put(getSimpleName(superclassMethod), superclassMethod); } } } private void validateModuleVisibility( XTypeElement moduleElement, ModuleKind moduleKind, ValidationReport.Builder reportBuilder) { if (moduleElement.isPrivate() || moduleElement.isKtPrivate()) { reportBuilder.addError("Modules cannot be private.", moduleElement); } else if (isEffectivelyPrivate(moduleElement)) { reportBuilder.addError("Modules cannot be enclosed in private types.", moduleElement); } if (isEffectivelyPublic(moduleElement)) { ImmutableSet invalidVisibilityIncludes = getModuleIncludesWithInvalidVisibility(moduleKind.getModuleAnnotation(moduleElement)); 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.stream() .map(XTypeElement::getClassName) .map(ClassName::canonicalName) .collect(toImmutableList()))), moduleElement); } } } private ImmutableSet getModuleIncludesWithInvalidVisibility( XAnnotation moduleAnnotation) { return moduleAnnotation.getAnnotationValue("includes").asTypeList().stream() .map(XType::getTypeElement) .filter(include -> !isEffectivelyPublic(include)) .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(XTypeElement module) { // Note: We use XTypeElements#getAllMethods() rather than XTypeElement#getDeclaredMethods() here // because 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. return !(module.isKotlinObject() || module.isCompanionObject()) && !XTypeElements.getAllMethods(module).stream() .filter(anyBindingMethodValidator::isBindingMethod) .allMatch(method -> method.isAbstract() || method.isStatic()); } private void validateNoScopeAnnotationsOnModuleElement( XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder report) { for (Scope scope : injectionAnnotations.getScopes(module)) { report.addError( String.format( "@%ss cannot be scoped. Did you mean to scope a method instead?", moduleKind.annotation().simpleName()), module, scope.scopeAnnotation().xprocessing()); } } private void validateSelfCycles( XTypeElement module, ModuleKind moduleKind, ValidationReport.Builder builder) { XAnnotation moduleAnnotation = moduleKind.getModuleAnnotation(module); moduleAnnotation.getAsAnnotationValueList("includes").stream() .filter(includedModule -> areEquivalentTypes(module.getType(), includedModule.asType())) .forEach( includedModule -> builder.addError( String.format( "@%s cannot include themselves.", moduleKind.annotation().simpleName()), module, moduleAnnotation, includedModule)); } private void validateCompanionModule( XTypeElement companionModule, ValidationReport.Builder builder) { List companionBindingMethods = new ArrayList<>(); for (XMethodElement companionMethod : companionModule.getDeclaredMethods()) { if (anyBindingMethodValidator.isBindingMethod(companionMethod)) { builder.addSubreport(anyBindingMethodValidator.validate(companionMethod)); companionBindingMethods.add(companionMethod); } // 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 (companionMethod.hasAnnotation(TypeNames.OVERRIDE)) { builder.addError( "Binding method in companion object may not override another method.", companionMethod); } // TODO(danysantiago): Be strict about the usage of @JvmStatic, i.e. tell user to remove it. } ImmutableListMultimap bindingMethodsByName = Multimaps.index(companionBindingMethods, XElements::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() && companionModule.isPrivate()) { builder.addError( "A Companion Module with binding methods cannot be private.", companionModule); } } private void validateModuleBindings(XTypeElement module, ValidationReport.Builder 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(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy