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

com.squareup.anvil.compiler.codegen.dagger.MembersInjectorCodeGen.kt Maven / Gradle / Ivy

Go to download

The core implementation module for Anvil, responsible for hooking into the Kotlin compiler and orchestrating code generation

There is a newer version: 0.4.0
Show newest version
package com.squareup.anvil.compiler.codegen.dagger

import com.google.auto.service.AutoService
import com.google.devtools.ksp.isPrivate
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
import com.google.devtools.ksp.processing.SymbolProcessorProvider
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.KSPropertySetter
import com.google.devtools.ksp.symbol.Modifier
import com.squareup.anvil.compiler.api.AnvilApplicabilityChecker
import com.squareup.anvil.compiler.api.AnvilContext
import com.squareup.anvil.compiler.api.CodeGenerator
import com.squareup.anvil.compiler.api.GeneratedFileWithSources
import com.squareup.anvil.compiler.api.createGeneratedFile
import com.squareup.anvil.compiler.codegen.PrivateCodeGenerator
import com.squareup.anvil.compiler.codegen.ksp.AnvilSymbolProcessor
import com.squareup.anvil.compiler.codegen.ksp.AnvilSymbolProcessorProvider
import com.squareup.anvil.compiler.injectFqName
import com.squareup.anvil.compiler.internal.capitalize
import com.squareup.anvil.compiler.internal.createAnvilSpec
import com.squareup.anvil.compiler.internal.joinSimpleNames
import com.squareup.anvil.compiler.internal.reference.ClassReference
import com.squareup.anvil.compiler.internal.reference.Visibility
import com.squareup.anvil.compiler.internal.reference.asClassName
import com.squareup.anvil.compiler.internal.reference.classAndInnerClassReferences
import com.squareup.anvil.compiler.internal.safePackageString
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier.OVERRIDE
import com.squareup.kotlinpoet.KModifier.PRIVATE
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.TypeVariableName
import com.squareup.kotlinpoet.asClassName
import com.squareup.kotlinpoet.jvm.jvmStatic
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeVariableName
import com.squareup.kotlinpoet.ksp.writeTo
import dagger.MembersInjector
import dagger.internal.InjectedFieldSignature
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
import org.jetbrains.kotlin.psi.KtFile
import java.io.File

internal object MembersInjectorCodeGen : AnvilApplicabilityChecker {
  override fun isApplicable(context: AnvilContext) = context.generateFactories

  internal class KspGenerator(
    override val env: SymbolProcessorEnvironment,
  ) : AnvilSymbolProcessor() {
    @AutoService(SymbolProcessorProvider::class)
    class Provider : AnvilSymbolProcessorProvider(MembersInjectorCodeGen, ::KspGenerator)

    override fun processChecked(resolver: Resolver): List {
      resolver.getSymbolsWithAnnotation(injectFqName.asString())
        .mapNotNull {
          when (it) {
            is KSPropertySetter -> SettableProperty.Setter(it)
            is KSPropertyDeclaration -> SettableProperty.Declaration(it)
            else -> null
          }
        }
        .filterNot { it.isPrivate }
        .filter { it.parentDeclaration is KSClassDeclaration }
        .groupBy { it.parentDeclaration as KSClassDeclaration }
        .forEach { (clazz, _) ->
          val typeParameters = clazz.typeParameters
            .map { it.toTypeVariableName() }
          val isGeneric = typeParameters.isNotEmpty()

          generateMembersInjectorClass(
            origin = clazz.toClassName(),
            isGeneric = isGeneric,
            typeParameters = typeParameters,
            parameters = clazz.memberInjectParameters(),
          )
            .writeTo(env.codeGenerator, aggregating = false, listOf(clazz.containingFile!!))
        }

      return emptyList()
    }

    /**
     * When searching for settable properties, they may come down as a [KSPropertyDeclaration]
     * or [KSPropertySetter], so we hide them behind a simple abstraction.
     */
    private sealed interface SettableProperty {
      val parentDeclaration: KSDeclaration?
      val isPrivate: Boolean

      @JvmInline
      value class Declaration(
        val node: KSPropertyDeclaration,
      ) : SettableProperty {
        override val parentDeclaration: KSDeclaration?
          get() = node.parentDeclaration
        override val isPrivate: Boolean
          get() = node.isPrivate()
      }

      @JvmInline
      value class Setter(
        val node: KSPropertySetter,
      ) : SettableProperty {
        override val parentDeclaration: KSDeclaration?
          get() = node.receiver.parentDeclaration
        override val isPrivate: Boolean
          get() = Modifier.PRIVATE in node.modifiers || node.receiver.isPrivate()
      }
    }
  }

  @AutoService(CodeGenerator::class)
  internal class Embedded : PrivateCodeGenerator() {

    override fun isApplicable(context: AnvilContext) = MembersInjectorCodeGen.isApplicable(context)

    override fun generateCodePrivate(
      codeGenDir: File,
      module: ModuleDescriptor,
      projectFiles: Collection,
    ): Collection = projectFiles
      .classAndInnerClassReferences(module)
      .filterNot { it.isInterface() }
      .filter { clazz ->
        // Only generate a MembersInjector if the target class declares its own member-injected
        // properties. If it does, then any properties from superclasses must be added as well
        // (clazz.memberInjectParameters() will do this).
        clazz.properties
          .filter { it.visibility() != Visibility.PRIVATE }
          .any { it.isAnnotatedWith(injectFqName) }
      }
      .map { clazz ->

        generateMembersInjectorClass(
          codeGenDir = codeGenDir,
          clazz = clazz,
          parameters = clazz.memberInjectParameters(),
        )
      }
      .toList()

    private fun generateMembersInjectorClass(
      codeGenDir: File,
      clazz: ClassReference.Psi,
      parameters: List,
    ): GeneratedFileWithSources {
      val isGeneric = clazz.isGenericClass()
      val typeParameters = clazz.typeParameters
        .map { it.typeVariableName }

      val spec = generateMembersInjectorClass(
        origin = clazz.asClassName(),
        isGeneric = isGeneric,
        typeParameters = typeParameters,
        parameters = parameters,
      )

      return createGeneratedFile(
        codeGenDir = codeGenDir,
        packageName = spec.packageName,
        fileName = spec.name,
        content = spec.toString(),
        sourceFile = clazz.containingFileAsJavaFile,
      )
    }
  }

  private fun generateMembersInjectorClass(
    origin: ClassName,
    isGeneric: Boolean,
    typeParameters: List,
    parameters: List,
  ): FileSpec {
    val memberInjectorClass = origin.joinSimpleNames(suffix = "_MembersInjector")
    val packageName = memberInjectorClass.packageName.safePackageString()
    val fileName = memberInjectorClass.simpleName

    val classType = origin
      .let {
        if (isGeneric) {
          it.parameterizedBy(typeParameters)
        } else {
          it
        }
      }

    val membersInjectorType = MembersInjector::class.asClassName().parameterizedBy(classType)

    fun createArgumentList(asProvider: Boolean): String {
      return parameters
        .map { it.name }
        .let { list ->
          if (asProvider) list.map { "$it.get()" } else list
        }
        .joinToString()
    }

    val spec = FileSpec.createAnvilSpec(packageName, fileName) {
      addType(
        TypeSpec
          .classBuilder(memberInjectorClass)
          .addSuperinterface(membersInjectorType)
          .apply {
            typeParameters.forEach { typeParameter ->
              addTypeVariable(typeParameter)
            }
            primaryConstructor(
              FunSpec.constructorBuilder()
                .apply {
                  parameters.forEach { parameter ->
                    addParameter(parameter.name, parameter.resolvedProviderTypeName)
                  }
                }
                .build(),
            )

            parameters.forEach { parameter ->
              addProperty(
                PropertySpec.builder(parameter.name, parameter.resolvedProviderTypeName)
                  .initializer(parameter.name)
                  .addModifiers(PRIVATE)
                  .build(),
              )
            }
          }
          .addFunction(
            FunSpec.builder("injectMembers")
              .addModifiers(OVERRIDE)
              .addParameter("instance", classType)
              .addMemberInjection(parameters, "instance")
              .build(),
          )
          .addType(
            TypeSpec
              .companionObjectBuilder()
              .addFunction(
                FunSpec.builder("create")
                  .jvmStatic()
                  .apply {
                    typeParameters.forEach { typeParameter ->
                      addTypeVariable(typeParameter)
                    }

                    parameters.forEach { parameter ->
                      addParameter(parameter.name, parameter.resolvedProviderTypeName)
                    }

                    addStatement(
                      "return %T(${createArgumentList(false)})",
                      memberInjectorClass,
                    )
                  }
                  .returns(membersInjectorType)
                  .build(),
              )
              .apply {
                parameters
                  // Don't generate the static single-property "inject___" functions for super-classes
                  .filter { it.memberInjectorClassName == memberInjectorClass }
                  .forEach { parameter ->

                    val name = parameter.name

                    addFunction(
                      FunSpec.builder("inject${parameter.accessName.capitalize()}")
                        .jvmStatic()
                        .apply {
                          typeParameters.forEach { typeParameter ->
                            addTypeVariable(typeParameter)
                          }

                          // Don't add @InjectedFieldSignature when it's calling a setter method
                          if (!parameter.isSetterInjected) {
                            addAnnotation(
                              AnnotationSpec.builder(InjectedFieldSignature::class)
                                .addMember("%S", parameter.injectedFieldSignature)
                                .build(),
                            )
                          }
                        }
                        .addAnnotations(parameter.qualifierAnnotationSpecs)
                        .addParameter("instance", classType)
                        .addParameter(name, parameter.originalTypeName)
                        .addStatement("instance.${parameter.originalName} = $name")
                        .build(),
                    )
                  }
              }
              .build(),
          )
          .build(),
      )
    }

    return spec
  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy