MicroUtils/koin/generator/src/main/kotlin/Processor.kt

270 lines
12 KiB
Kotlin
Raw Normal View History

2023-02-21 19:34:42 +00:00
package dev.inmo.micro_utils.koin.generator
import com.google.devtools.ksp.KSTypeNotPresentException
import com.google.devtools.ksp.KSTypesNotPresentException
import com.google.devtools.ksp.KspExperimental
import com.google.devtools.ksp.getAnnotationsByType
import com.google.devtools.ksp.processing.CodeGenerator
import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.processing.SymbolProcessor
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSFile
2023-06-18 17:38:36 +00:00
import com.google.devtools.ksp.symbol.Modifier
import com.squareup.kotlinpoet.AnnotationSpec
2023-02-21 19:34:42 +00:00
import com.squareup.kotlinpoet.ClassName
2023-06-18 17:38:36 +00:00
import com.squareup.kotlinpoet.CodeBlock
2023-02-21 19:34:42 +00:00
import com.squareup.kotlinpoet.FileSpec
import com.squareup.kotlinpoet.FunSpec
2023-06-18 17:38:36 +00:00
import com.squareup.kotlinpoet.KModifier
2023-02-21 19:34:42 +00:00
import com.squareup.kotlinpoet.ParameterSpec
import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy
import com.squareup.kotlinpoet.PropertySpec
2023-06-18 17:38:36 +00:00
import com.squareup.kotlinpoet.TypeVariableName
2023-02-21 19:34:42 +00:00
import com.squareup.kotlinpoet.asTypeName
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeName
import com.squareup.kotlinpoet.ksp.writeTo
import dev.inmo.micro_utils.koin.annotations.GenerateKoinDefinition
import org.koin.core.Koin
import org.koin.core.module.Module
2023-06-18 17:38:36 +00:00
import org.koin.core.parameter.ParametersDefinition
2023-02-21 19:34:42 +00:00
import org.koin.core.scope.Scope
import java.io.File
import kotlin.reflect.KClass
class Processor(
private val codeGenerator: CodeGenerator
) : SymbolProcessor {
private val definitionClassName = ClassName("org.koin.core.definition", "Definition")
private val koinDefinitionClassName = ClassName("org.koin.core.definition", "KoinDefinition")
@OptIn(KspExperimental::class)
override fun process(resolver: Resolver): List<KSAnnotated> {
resolver.getSymbolsWithAnnotation(
GenerateKoinDefinition::class.qualifiedName!!
).filterIsInstance<KSFile>().forEach { ksFile ->
FileSpec.builder(
ksFile.packageName.asString(),
"GeneratedDefinitions${ksFile.fileName.removeSuffix(".kt")}"
).apply {
addFileComment(
"""
THIS CODE HAVE BEEN GENERATED AUTOMATICALLY
TO REGENERATE IT JUST DELETE FILE
ORIGINAL FILE: ${ksFile.fileName}
""".trimIndent()
)
ksFile.getAnnotationsByType(GenerateKoinDefinition::class).forEach {
val type = runCatching {
it.type.asTypeName()
}.getOrElse { e ->
if (e is KSTypeNotPresentException) {
e.ksType.toClassName()
} else {
throw e
}
}
val targetType = runCatching {
type.parameterizedBy(*(it.typeArgs.takeIf { it.isNotEmpty() } ?.map { it.asTypeName() } ?.toTypedArray() ?: return@runCatching type))
}.getOrElse { e ->
when (e) {
is KSTypeNotPresentException -> e.ksType.toClassName()
}
if (e is KSTypesNotPresentException) {
type.parameterizedBy(*e.ksTypes.map { it.toTypeName() }.toTypedArray())
} else {
throw e
}
}.copy(
nullable = it.nullable
)
fun addGetterProperty(
receiver: KClass<*>
) {
addProperty(
PropertySpec.builder(
it.name,
targetType,
).apply {
addKdoc(
"""
@return Definition by key "${it.name}"
""".trimIndent()
)
2023-02-21 19:34:42 +00:00
getter(
FunSpec.getterBuilder().apply {
addCode(
"return " + (if (it.nullable) {
"getOrNull"
} else {
"get"
}) + "(named(\"${it.name}\"))"
)
}.build()
)
receiver(receiver)
}.build()
)
}
addGetterProperty(Scope::class)
addGetterProperty(Koin::class)
2023-06-18 17:38:36 +00:00
val parametersDefinitionClassName = ClassName(
"org.koin.core.parameter",
"ParametersDefinition"
)
fun addGetterMethod(
receiver: KClass<*>
) {
2023-02-21 19:34:42 +00:00
addFunction(
2023-06-18 17:38:36 +00:00
FunSpec.builder(
it.name
).apply {
addKdoc(
"""
2023-06-18 17:38:36 +00:00
@return Definition by key "${it.name}" with [parameters]
""".trimIndent()
)
2023-06-18 17:38:36 +00:00
receiver(receiver)
2023-02-21 19:34:42 +00:00
addParameter(
2023-06-18 17:38:36 +00:00
"parameters",
parametersDefinitionClassName,
KModifier.NOINLINE
2023-02-21 19:34:42 +00:00
)
2023-06-18 17:38:36 +00:00
addModifiers(KModifier.INLINE)
2023-02-21 19:34:42 +00:00
addCode(
2023-06-18 17:38:36 +00:00
"return " + (if (it.nullable) {
"getOrNull"
} else {
"get"
}) + "(named(\"${it.name}\"), parameters)"
2023-02-21 19:34:42 +00:00
)
}.build()
)
}
2023-06-18 17:38:36 +00:00
addGetterMethod(Scope::class)
addGetterMethod(Koin::class)
if (it.generateSingle) {
fun FunSpec.Builder.configure(
useInstead: String? = null
) {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.single] and key "${it.name}"
""".trimIndent()
)
receiver(Module::class)
addParameter(
ParameterSpec.builder(
"createdAtStart",
Boolean::class
).apply {
defaultValue("false")
}.build()
)
addParameter(
ParameterSpec.builder(
"definition",
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
).build()
)
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return single(named(\"${it.name}\"), createdAtStart = createdAtStart, definition = definition)"
)
if (useInstead != null) {
addAnnotation(
AnnotationSpec.builder(
Deprecated::class
).apply {
addMember(
CodeBlock.of(
"""
"This definition is old style and should not be used anymore. Use $useInstead instead"
""".trimIndent()
)
)
addMember(CodeBlock.of("ReplaceWith(\"$useInstead\")"))
}.build()
)
}
}
val actualSingleName = "single${it.name.replaceFirstChar { it.uppercase() }}"
2023-02-21 19:34:42 +00:00
addFunction(
2023-06-18 17:38:36 +00:00
FunSpec.builder("${it.name}Single").apply { configure(actualSingleName) }.build()
)
addFunction(
FunSpec.builder(actualSingleName).apply { configure() }.build()
)
}
if (it.generateFactory) {
fun FunSpec.Builder.configure(
useInstead: String? = null
) {
addKdoc(
"""
Will register [definition] with [org.koin.core.module.Module.factory] and key "${it.name}"
""".trimIndent()
2023-06-18 17:38:36 +00:00
)
receiver(Module::class)
addParameter(
ParameterSpec.builder(
"definition",
definitionClassName.parameterizedBy(targetType.copy(nullable = false))
).build()
)
returns(koinDefinitionClassName.parameterizedBy(targetType.copy(nullable = false)))
addCode(
"return factory(named(\"${it.name}\"), definition = definition)"
)
if (useInstead != null) {
addAnnotation(
AnnotationSpec.builder(
Deprecated::class
).apply {
addMember(
CodeBlock.of(
"""
"This definition is old style and should not be used anymore. Use $useInstead instead"
""".trimIndent()
)
)
addMember(CodeBlock.of("ReplaceWith(\"$useInstead\")"))
}.build()
)
2023-06-18 17:38:36 +00:00
}
}
val actualFactoryName = "factory${it.name.replaceFirstChar { it.uppercase() }}"
addFunction(
FunSpec.builder("${it.name}Factory").apply { configure(useInstead = actualFactoryName) }.build()
)
addFunction(
FunSpec.builder(actualFactoryName).apply { configure() }.build()
2023-02-21 19:34:42 +00:00
)
}
addImport("org.koin.core.qualifier", "named")
}
}.build().let {
File(
File(ksFile.filePath).parent,
"GeneratedDefinitions${ksFile.fileName}"
).takeIf { !it.exists() } ?.apply {
parentFile.mkdirs()
writer().use { writer ->
it.writeTo(writer)
}
}
}
}
return emptyList()
}
}