From f807f2beeb38b83fcff3cabfd29de3b1058b37ea Mon Sep 17 00:00:00 2001 From: InsanusMokrassar Date: Tue, 11 Feb 2025 13:50:58 +0600 Subject: [PATCH] start add variations generator --- .../src/main/kotlin/FilesWorkaround.kt | 12 +- ksp/variations/build.gradle | 7 + ksp/variations/generator/build.gradle | 21 +++ .../generator/src/main/kotlin/Processor.kt | 148 ++++++++++++++++++ .../generator/src/main/kotlin/Provider.kt | 11 ++ ...ols.ksp.processing.SymbolProcessorProvider | 1 + ksp/variations/generator/test/build.gradle | 27 ++++ .../test/src/commonMain/kotlin/SampleFun.kt | 16 ++ .../commonMain/kotlin/GenerateVariations.kt | 7 + .../commonMain/kotlin/GenerationVariant.kt | 13 ++ settings.gradle | 4 + 11 files changed, 263 insertions(+), 4 deletions(-) create mode 100644 ksp/variations/build.gradle create mode 100644 ksp/variations/generator/build.gradle create mode 100644 ksp/variations/generator/src/main/kotlin/Processor.kt create mode 100644 ksp/variations/generator/src/main/kotlin/Provider.kt create mode 100644 ksp/variations/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider create mode 100644 ksp/variations/generator/test/build.gradle create mode 100644 ksp/variations/generator/test/src/commonMain/kotlin/SampleFun.kt create mode 100644 ksp/variations/src/commonMain/kotlin/GenerateVariations.kt create mode 100644 ksp/variations/src/commonMain/kotlin/GenerationVariant.kt diff --git a/ksp/generator/src/main/kotlin/FilesWorkaround.kt b/ksp/generator/src/main/kotlin/FilesWorkaround.kt index 17bbabc22b4..2dedf08884e 100644 --- a/ksp/generator/src/main/kotlin/FilesWorkaround.kt +++ b/ksp/generator/src/main/kotlin/FilesWorkaround.kt @@ -1,11 +1,13 @@ package dev.inmo.micro_ksp.generator import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSFile +import com.google.devtools.ksp.symbol.KSFunctionDeclaration import com.squareup.kotlinpoet.FileSpec import java.io.File -fun KSClassDeclaration.writeFile( +fun KSDeclaration.writeFile( prefix: String = "", suffix: String = "", relatedPath: String = "", @@ -21,8 +23,9 @@ fun KSClassDeclaration.writeFile( "$prefix${simpleName.asString()}$suffix.kt" ).takeIf { force || !it.exists() } ?.apply { parentFile.mkdirs() + val fileSpec = fileSpecBuilder() writer().use { writer -> - fileSpecBuilder().writeTo(writer) + fileSpec.writeTo(writer) } } } @@ -42,8 +45,9 @@ fun KSFile.writeFile( "$prefix${fileName.dropLastWhile { it != '.' }.removeSuffix(".")}$suffix.kt" ).takeIf { force || !it.exists() } ?.apply { parentFile.mkdirs() + val fileSpec = fileSpecBuilder() writer().use { writer -> - fileSpecBuilder().writeTo(writer) + fileSpec.writeTo(writer) } } -} \ No newline at end of file +} diff --git a/ksp/variations/build.gradle b/ksp/variations/build.gradle new file mode 100644 index 00000000000..9e169e84113 --- /dev/null +++ b/ksp/variations/build.gradle @@ -0,0 +1,7 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" +} + +apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project" diff --git a/ksp/variations/generator/build.gradle b/ksp/variations/generator/build.gradle new file mode 100644 index 00000000000..c951b3a648a --- /dev/null +++ b/ksp/variations/generator/build.gradle @@ -0,0 +1,21 @@ +plugins { + id "org.jetbrains.kotlin.jvm" +} + +apply from: "$publish_jvm" + +repositories { + mavenCentral() +} + +dependencies { + api project(":micro_utils.ksp.generator") + api project(":micro_utils.ksp.variations") + api libs.kotlin.poet + api libs.ksp +} + +java { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 +} diff --git a/ksp/variations/generator/src/main/kotlin/Processor.kt b/ksp/variations/generator/src/main/kotlin/Processor.kt new file mode 100644 index 00000000000..f62b0fd1410 --- /dev/null +++ b/ksp/variations/generator/src/main/kotlin/Processor.kt @@ -0,0 +1,148 @@ +package dev.inmo.micro_utils.ksp.variations.generator + +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.* +import com.squareup.kotlinpoet.* +import com.squareup.kotlinpoet.ParameterizedTypeName.Companion.parameterizedBy +import com.squareup.kotlinpoet.ksp.toClassName +import com.squareup.kotlinpoet.ksp.toKModifier +import com.squareup.kotlinpoet.ksp.toTypeName +import dev.inmo.micro_ksp.generator.companion +import dev.inmo.micro_ksp.generator.findSubClasses +import dev.inmo.micro_ksp.generator.writeFile +import dev.inmo.micro_utils.ksp.variations.GenerateVariations +import dev.inmo.micro_utils.ksp.variations.GenerationVariant + +class Processor( + private val codeGenerator: CodeGenerator +) : SymbolProcessor { + private fun KSClassDeclaration.findSealedConnection(potentialSealedParent: KSClassDeclaration): Boolean { + val targetClassname = potentialSealedParent.qualifiedName ?.asString() + return superTypes.any { + val itAsDeclaration = it.resolve().declaration as? KSClassDeclaration ?: return@any false + targetClassname == (itAsDeclaration.qualifiedName ?.asString()) || (itAsDeclaration.getSealedSubclasses().any() && itAsDeclaration.findSealedConnection(potentialSealedParent)) + } + } + + private fun KSClassDeclaration.resolveSubclasses( + searchIn: Sequence, + allowNonSealed: Boolean + ): Sequence { + return findSubClasses(searchIn).let { + if (allowNonSealed) { + it + } else { + it.filter { + it.findSealedConnection(this) + } + } + } + } + + @OptIn(KspExperimental::class) + private fun FileSpec.Builder.generateVariations( + ksFunctionDeclaration: KSFunctionDeclaration, + resolver: Resolver + ) { + val annotation = ksFunctionDeclaration.getAnnotationsByType(GenerateVariations::class).first() + val variations: List, KSValueParameter>> = ksFunctionDeclaration.parameters.mapNotNull { + val variationAnnotations = it.getAnnotationsByType(GenerationVariant::class).toList().ifEmpty { return@mapNotNull null } + variationAnnotations to it + } + val accumulatedGenerations = mutableSetOf() + variations.forEach { (variations, parameter) -> + if (accumulatedGenerations.isEmpty()) { + variations.forEach { variation -> + accumulatedGenerations.add( + FunSpec.builder(ksFunctionDeclaration.simpleName.asString()).apply { + modifiers.addAll(ksFunctionDeclaration.modifiers.mapNotNull { it.toKModifier() }) + ksFunctionDeclaration.parameters.forEach { + parameters.add( + (if (it == parameter) { + ParameterSpec + .builder( + variation.argName, + if (variation.varargTypes.isEmpty()) { + variation.type.asTypeName() + } else { + variation.type.parameterizedBy(*variation.varargTypes) + } + ) + .apply { + val name = it.name ?.asString() ?: "this" + if (it.isVararg) { + defaultValue( + """ + *$name.map { it.${variation.conversion} }.toTypedArray() + """.trimIndent() + ) + } else { + defaultValue("$name.${variation.conversion}") + } + } + } else { + ParameterSpec + .builder( + it.name ?.asString() ?: return@forEach, + it.type.toTypeName(), + ) + }) + .apply { + if (it.isCrossInline) { + addModifiers(KModifier.CROSSINLINE) + } + if (it.isVal) { + addModifiers(KModifier.VALUE) + } + if (it.isNoInline) { + addModifiers(KModifier.NOINLINE) + } + if (it.isVararg) { + addModifiers(KModifier.VARARG) + } + } + .build() + ) + } + }.build() + ) + } + } else { + + } + } + accumulatedGenerations.forEach { + addFunction(it) + } + } + + @OptIn(KspExperimental::class) + override fun process(resolver: Resolver): List { + (resolver.getSymbolsWithAnnotation(GenerateVariations::class.qualifiedName!!)).filterIsInstance().forEach { + val prefix = (it.getAnnotationsByType(GenerateVariations::class)).firstOrNull() ?.prefix ?.takeIf { + it.isNotEmpty() + } ?: it.simpleName.asString().replaceFirst(it.simpleName.asString(), "") + it.writeFile(prefix = prefix, suffix = "GeneratedVariation") { + FileSpec.builder( + it.packageName.asString(), + "${it.simpleName.getShortName()}GeneratedVariation" + ).apply { + addFileComment( + """ + THIS CODE HAVE BEEN GENERATED AUTOMATICALLY + TO REGENERATE IT JUST DELETE FILE + ORIGINAL FILE: ${it.containingFile ?.fileName} + """.trimIndent() + ) + generateVariations(it, resolver) + }.build() + } + } + + return emptyList() + } +} diff --git a/ksp/variations/generator/src/main/kotlin/Provider.kt b/ksp/variations/generator/src/main/kotlin/Provider.kt new file mode 100644 index 00000000000..e3721a6b7d0 --- /dev/null +++ b/ksp/variations/generator/src/main/kotlin/Provider.kt @@ -0,0 +1,11 @@ +package dev.inmo.micro_utils.ksp.variations.generator + +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider + +class Provider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor = Processor( + environment.codeGenerator + ) +} diff --git a/ksp/variations/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider b/ksp/variations/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider new file mode 100644 index 00000000000..443f688865d --- /dev/null +++ b/ksp/variations/generator/src/main/resources/META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider @@ -0,0 +1 @@ +dev.inmo.micro_utils.ksp.variations.generator.Provider diff --git a/ksp/variations/generator/test/build.gradle b/ksp/variations/generator/test/build.gradle new file mode 100644 index 00000000000..f95f5c7bf64 --- /dev/null +++ b/ksp/variations/generator/test/build.gradle @@ -0,0 +1,27 @@ +plugins { + id "org.jetbrains.kotlin.multiplatform" + id "org.jetbrains.kotlin.plugin.serialization" + id "com.android.library" + id "com.google.devtools.ksp" +} + +apply from: "$mppJvmJsAndroidLinuxMingwLinuxArm64Project" + + +kotlin { + sourceSets { + commonMain { + dependencies { + api project(":micro_utils.ksp.variations") + } + } + } +} + + +dependencies { + add("kspCommonMainMetadata", project(":micro_utils.ksp.variations.generator")) +} + +ksp { +} diff --git a/ksp/variations/generator/test/src/commonMain/kotlin/SampleFun.kt b/ksp/variations/generator/test/src/commonMain/kotlin/SampleFun.kt new file mode 100644 index 00000000000..60d0b9e3da0 --- /dev/null +++ b/ksp/variations/generator/test/src/commonMain/kotlin/SampleFun.kt @@ -0,0 +1,16 @@ +import dev.inmo.micro_utils.ksp.variations.GenerateVariations +import dev.inmo.micro_utils.ksp.variations.GenerationVariant + +data class Sample( + val value: String +) + +@GenerateVariations +fun sample( + @GenerationVariant( + "example", + Sample::class, + "value" + ) + example: String = "12" +) = println(example) diff --git a/ksp/variations/src/commonMain/kotlin/GenerateVariations.kt b/ksp/variations/src/commonMain/kotlin/GenerateVariations.kt new file mode 100644 index 00000000000..91122da4bee --- /dev/null +++ b/ksp/variations/src/commonMain/kotlin/GenerateVariations.kt @@ -0,0 +1,7 @@ +package dev.inmo.micro_utils.ksp.variations + +@Retention(AnnotationRetention.BINARY) +@Target(AnnotationTarget.FUNCTION) +annotation class GenerateVariations( + val prefix: String = "" +) diff --git a/ksp/variations/src/commonMain/kotlin/GenerationVariant.kt b/ksp/variations/src/commonMain/kotlin/GenerationVariant.kt new file mode 100644 index 00000000000..93628ece414 --- /dev/null +++ b/ksp/variations/src/commonMain/kotlin/GenerationVariant.kt @@ -0,0 +1,13 @@ +package dev.inmo.micro_utils.ksp.variations + +import kotlin.reflect.KClass + +@Retention(AnnotationRetention.BINARY) +@Repeatable +@Target(AnnotationTarget.TYPE_PARAMETER, AnnotationTarget.VALUE_PARAMETER) +annotation class GenerationVariant( + val argName: String, + val type: KClass<*>, + val conversion: String, + vararg val varargTypes: KClass<*> +) diff --git a/settings.gradle b/settings.gradle index 3c8ea281c61..6379141d8cb 100644 --- a/settings.gradle +++ b/settings.gradle @@ -62,6 +62,10 @@ String[] includes = [ ":ksp:classcasts:generator", ":ksp:classcasts:generator:test", + ":ksp:variations", + ":ksp:variations:generator", + ":ksp:variations:generator:test", + ":dokka" ]