From 4cf934e9c86cf6793305957c130abb92d0fed91d Mon Sep 17 00:00:00 2001 From: Arnaud Giuliani Date: Tue, 13 Aug 2024 15:00:43 +0200 Subject: [PATCH 1/3] Clean up & naming --- .../org/koin/compiler/BuilderProcessor.kt | 10 +- ...igVerification.kt => KoinConfigChecker.kt} | 93 +++++-------------- .../org/koin/compiler/verify/KoinTagWriter.kt | 72 ++++++++++++++ .../verify/{Ignored.kt => TypeWhiteList.kt} | 2 +- 4 files changed, 100 insertions(+), 77 deletions(-) rename projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/{KoinConfigVerification.kt => KoinConfigChecker.kt} (53%) create mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt rename projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/{Ignored.kt => TypeWhiteList.kt} (88%) diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt index a863a21..465e5e4 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt @@ -21,7 +21,8 @@ import org.koin.compiler.KspOptions.* import org.koin.compiler.generator.KoinGenerator import org.koin.compiler.metadata.KoinMetaData import org.koin.compiler.scanner.KoinMetaDataScanner -import org.koin.compiler.verify.KoinConfigVerification +import org.koin.compiler.verify.KoinConfigChecker +import org.koin.compiler.verify.KoinTagWriter class BuilderProcessor( private val codeGenerator: CodeGenerator, @@ -31,7 +32,8 @@ class BuilderProcessor( private val koinCodeGenerator = KoinGenerator(codeGenerator, logger, isComposeViewModelActive() || isKoinComposeViewModelActive()) private val koinMetaDataScanner = KoinMetaDataScanner(logger) - private val koinConfigVerification = KoinConfigVerification(codeGenerator, logger) + private val koinTagWriter = KoinTagWriter(codeGenerator,logger) + private val koinConfigChecker = KoinConfigChecker(codeGenerator, logger, koinTagWriter) override fun process(resolver: Resolver): List { logger.logging("Scan symbols ...") @@ -56,8 +58,8 @@ class BuilderProcessor( if (isConfigCheckActive()) { logger.warn("Koin Configuration Check") - koinConfigVerification.verifyDefinitionDeclarations(moduleList + defaultModule, resolver) - koinConfigVerification.verifyModuleIncludes(moduleList + defaultModule, resolver) + koinConfigChecker.verifyDefinitionDeclarations(moduleList + defaultModule, resolver) + koinConfigChecker.verifyModuleIncludes(moduleList + defaultModule, resolver) } return emptyList() } diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigVerification.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigChecker.kt similarity index 53% rename from projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigVerification.kt rename to projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigChecker.kt index 51ef2d6..580b586 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigVerification.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigChecker.kt @@ -15,99 +15,49 @@ */ package org.koin.compiler.verify -import appendText import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.symbol.KSDeclaration -import org.koin.compiler.generator.getNewFile import org.koin.compiler.metadata.KoinMetaData -import java.io.OutputStream -private val classPrefix = "KoinDef" -private val generationPackage = "org.koin.ksp.generated" +const val codeGenerationPackage = "org.koin.ksp.generated" /** * Koin Configuration Checker */ -class KoinConfigVerification(val codeGenerator: CodeGenerator, val logger: KSPLogger) { +class KoinConfigChecker(val codeGenerator: CodeGenerator, val logger: KSPLogger, val koinTagWriter: KoinTagWriter) { fun verifyDefinitionDeclarations( moduleList: List, resolver: Resolver ) { val isAlreadyGenerated = codeGenerator.generatedFile.isEmpty() - val alreadyDeclaredTags = arrayListOf() - val allDefinitions = moduleList.flatMap { it.definitions } if (!isAlreadyGenerated) { - val tagFileName = "DefinitionTags-${hashCode()}" - val tagFileStream = writeDefinitionTagFile(tagFileName) - allDefinitions.forEach { def -> - writeDefinitionTag(tagFileStream, def, alreadyDeclaredTags) - } + koinTagWriter.writeTags(allDefinitions) } else { - allDefinitions.forEach { def -> - def.parameters - .filterIsInstance() - .forEach { param -> - if (!param.hasDefault && !param.isNullable && !param.alreadyProvided) { - checkDependencyIsDefined(param, resolver, def) - } - //TODO Check Cycle - } - } + verifyTags(allDefinitions, resolver) } } - private fun writeDefinitionTagFile(tagFileName: String): OutputStream { - val fileStream = codeGenerator.getNewFile(fileName = tagFileName) - fileStream.appendText("package $generationPackage") - return fileStream - } - - private fun writeDefinitionTag( - fileStream: OutputStream, - def: KoinMetaData.Definition, - alreadyDeclared: ArrayList - ) { - fileStream.appendText("\n") - - writeClassTag(def, alreadyDeclared, fileStream) - def.bindings.forEach { writeDefinitionBindingTag(it, alreadyDeclared, fileStream) } - } - - private fun writeClassTag( - def: KoinMetaData.Definition, - alreadyDeclared: java.util.ArrayList, - fileStream: OutputStream + private fun verifyTags( + allDefinitions: List, + resolver: Resolver ) { - val cn = def.packageCamelCase() + def.label.capitalize() - - if (cn !in alreadyDeclared) { - val write = "public class $classPrefix$cn" - - fileStream.appendText("\n$write") - alreadyDeclared.add(cn) + allDefinitions.forEach { def -> + def.parameters + .filterIsInstance() + .forEach { param -> + if (!param.hasDefault && !param.isNullable && !param.alreadyProvided) { + checkDependencyIsDefined(param, resolver, def) + } + //TODO Check Cycle + } } } - private fun writeDefinitionBindingTag( - binding: KSDeclaration, - alreadyDeclared: ArrayList, - fileStream: OutputStream - ) { - if (binding.qualifiedName?.asString() !in ignored) { - val cn = binding.qualifiedNameCamelCase() - - if (cn !in alreadyDeclared && cn != null) { - val write = "public class $classPrefix$cn" - fileStream.appendText("\n$write") - alreadyDeclared.add(cn) - } - } - } private fun checkDependencyIsDefined( dependencyToCheck: KoinMetaData.DefinitionParameter.Dependency, @@ -124,9 +74,9 @@ class KoinConfigVerification(val codeGenerator: CodeGenerator, val logger: KSPLo } val parameterFullName = targetTypeToCheck.qualifiedName?.asString() - if (parameterFullName !in ignored && parameterFullName != null) { + if (parameterFullName !in typeWhiteList && parameterFullName != null) { val cn = targetTypeToCheck.qualifiedNameCamelCase() - val className = "$generationPackage.$classPrefix$cn" + val className = "$codeGenerationPackage.$tagPrefix$cn" val resolution = resolver.getClassDeclarationByName(resolver.getKSNameFromString(className)) val isNotScopeType = scope != parameterFullName if (resolution == null && isNotScopeType) { @@ -142,7 +92,7 @@ class KoinConfigVerification(val codeGenerator: CodeGenerator, val logger: KSPLo val mn = m.packageName + "." + m.name m.includes?.forEach { inc -> val cn = inc.qualifiedName?.asString()?.replace(".", "_") - val ksn = resolver.getKSNameFromString("$generationPackage.$cn") + val ksn = resolver.getKSNameFromString("$codeGenerationPackage.$cn") val prop = resolver.getPropertyDeclarationByName(ksn, includeTopLevel = true) if (prop == null) { logger.error("--> Module Undefined :'${inc.qualifiedName?.asString()}' included in '$mn'. Fix your configuration: add @Module annotation on '${inc.simpleName.asString()}' class.") @@ -153,6 +103,5 @@ class KoinConfigVerification(val codeGenerator: CodeGenerator, val logger: KSPLo } } -private fun KoinMetaData.Definition.packageCamelCase() = packageName.split(".").joinToString("") { it.capitalize() } -private fun KSDeclaration.qualifiedNameCamelCase() = - qualifiedName?.asString()?.split(".")?.joinToString(separator = "") { it.capitalize() } +internal fun KoinMetaData.Definition.packageCamelCase() = packageName.split(".").joinToString("") { it.capitalize() } +internal fun KSDeclaration.qualifiedNameCamelCase() = qualifiedName?.asString()?.split(".")?.joinToString(separator = "") { it.capitalize() } diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt new file mode 100644 index 0000000..8cab05d --- /dev/null +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt @@ -0,0 +1,72 @@ +package org.koin.compiler.verify + +import appendText +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.symbol.KSDeclaration +import org.koin.compiler.generator.getNewFile +import org.koin.compiler.metadata.KoinMetaData +import java.io.OutputStream + +const val tagPrefix = "KoinDef" + +class KoinTagWriter(val codeGenerator: CodeGenerator, val logger: KSPLogger) { + + fun writeTags(allDefinitions: List) { + val alreadyDeclaredTags = arrayListOf() + val tagFileName = "KoinMeta-${hashCode()}" + val tagFileStream = writeTagFile(tagFileName) + + allDefinitions.forEach { def -> writeDefinitionTag(tagFileStream, def, alreadyDeclaredTags) } + } + + private fun writeTagFile(tagFileName: String): OutputStream { + val fileStream = codeGenerator.getNewFile(fileName = tagFileName) + fileStream.appendText("package $codeGenerationPackage\n") + return fileStream + } + + private fun writeDefinitionTag( + fileStream: OutputStream, + def: KoinMetaData.Definition, + alreadyDeclared: ArrayList + ) { + writeClassTag(def, alreadyDeclared, fileStream) + def.bindings.forEach { writeDefinitionBindingTag(it, alreadyDeclared, fileStream) } + } + + private fun writeClassTag( + def: KoinMetaData.Definition, + alreadyDeclared: java.util.ArrayList, + fileStream: OutputStream + ) { + val className = def.packageCamelCase() + def.label.capitalize() + if (className !in alreadyDeclared) { + writeTagLine(className, fileStream, alreadyDeclared) + } + } + + private fun writeTagLine( + className: String, + fileStream: OutputStream, + alreadyDeclared: java.util.ArrayList + ) { + val tag = "public class $tagPrefix$className" + fileStream.appendText("\n$tag") + alreadyDeclared.add(className) + } + + private fun writeDefinitionBindingTag( + binding: KSDeclaration, + alreadyDeclared: ArrayList, + fileStream: OutputStream + ) { + val name = binding.qualifiedName?.asString() + if (name !in typeWhiteList) { + val className = binding.qualifiedNameCamelCase() + if (className !in alreadyDeclared && className != null) { + writeTagLine(className, fileStream, alreadyDeclared) + } + } + } +} \ No newline at end of file diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/Ignored.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/TypeWhiteList.kt similarity index 88% rename from projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/Ignored.kt rename to projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/TypeWhiteList.kt index ec01ef8..b1842c0 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/Ignored.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/TypeWhiteList.kt @@ -1,7 +1,7 @@ package org.koin.compiler.verify -internal val ignored = listOf( +internal val typeWhiteList = listOf( "kotlin.Any", "android.content.Context", "android.app.Application", From 4844d9e145560504ed3da72fa81bd96458a45abf Mon Sep 17 00:00:00 2001 From: Arnaud Giuliani Date: Tue, 13 Aug 2024 15:04:35 +0200 Subject: [PATCH 2/3] always write tags --- .../kotlin/org/koin/compiler/BuilderProcessor.kt | 9 ++++++--- .../org/koin/compiler/verify/KoinConfigChecker.kt | 6 ++---- .../org/koin/compiler/verify/KoinTagWriter.kt | 15 ++++++++++++++- 3 files changed, 22 insertions(+), 8 deletions(-) diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt index 465e5e4..e063c25 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt @@ -33,7 +33,7 @@ class BuilderProcessor( private val koinCodeGenerator = KoinGenerator(codeGenerator, logger, isComposeViewModelActive() || isKoinComposeViewModelActive()) private val koinMetaDataScanner = KoinMetaDataScanner(logger) private val koinTagWriter = KoinTagWriter(codeGenerator,logger) - private val koinConfigChecker = KoinConfigChecker(codeGenerator, logger, koinTagWriter) + private val koinConfigChecker = KoinConfigChecker(codeGenerator, logger) override fun process(resolver: Resolver): List { logger.logging("Scan symbols ...") @@ -56,10 +56,13 @@ class BuilderProcessor( logger.logging("Generate code ...") koinCodeGenerator.generateModules(moduleList, defaultModule, isDefaultModuleActive()) + val allModules = moduleList + defaultModule + koinTagWriter.writeAllTags(allModules) + if (isConfigCheckActive()) { logger.warn("Koin Configuration Check") - koinConfigChecker.verifyDefinitionDeclarations(moduleList + defaultModule, resolver) - koinConfigChecker.verifyModuleIncludes(moduleList + defaultModule, resolver) + koinConfigChecker.verifyDefinitionDeclarations(allModules, resolver) + koinConfigChecker.verifyModuleIncludes(allModules, resolver) } return emptyList() } diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigChecker.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigChecker.kt index 580b586..2183401 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigChecker.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigChecker.kt @@ -26,7 +26,7 @@ const val codeGenerationPackage = "org.koin.ksp.generated" /** * Koin Configuration Checker */ -class KoinConfigChecker(val codeGenerator: CodeGenerator, val logger: KSPLogger, val koinTagWriter: KoinTagWriter) { +class KoinConfigChecker(val codeGenerator: CodeGenerator, val logger: KSPLogger) { fun verifyDefinitionDeclarations( moduleList: List, @@ -35,9 +35,7 @@ class KoinConfigChecker(val codeGenerator: CodeGenerator, val logger: KSPLogger, val isAlreadyGenerated = codeGenerator.generatedFile.isEmpty() val allDefinitions = moduleList.flatMap { it.definitions } - if (!isAlreadyGenerated) { - koinTagWriter.writeTags(allDefinitions) - } else { + if (isAlreadyGenerated) { verifyTags(allDefinitions, resolver) } } diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt index 8cab05d..41ee561 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt @@ -12,7 +12,20 @@ const val tagPrefix = "KoinDef" class KoinTagWriter(val codeGenerator: CodeGenerator, val logger: KSPLogger) { - fun writeTags(allDefinitions: List) { + fun writeAllTags( + moduleList: List + ) { + + val isAlreadyGenerated = codeGenerator.generatedFile.isEmpty() + val allDefinitions = moduleList.flatMap { it.definitions } + + if (!isAlreadyGenerated) { + logger.warn("Koin Tags Generation ...") + writeTags(allDefinitions) + } + } + + private fun writeTags(allDefinitions: List) { val alreadyDeclaredTags = arrayListOf() val tagFileName = "KoinMeta-${hashCode()}" val tagFileStream = writeTagFile(tagFileName) From 4b18c2b18847b007844041f199b83935621e38b2 Mon Sep 17 00:00:00 2001 From: Arnaud Giuliani Date: Tue, 13 Aug 2024 15:12:40 +0200 Subject: [PATCH 3/3] 1.4.0-RC4 context receiver cleaning clean up clean clean up check class tag tagging ok - need modules wip - debug fix ok - remain default wip ok - still ned check config KMP avoid skip actual defs Detect includes & better generation add ... clean up refacto #1 wip wip - ok wip clean up ok --- .../android-coffee-maker/build.gradle.kts | 2 +- .../koin/sample/androidx/app/MyPresenter.kt | 5 +- .../src/test/java/AndroidModuleTest.kt | 3 +- examples/android-library/build.gradle.kts | 2 +- examples/gradle/libs.versions.toml | 2 +- projects/gradle.properties | 2 +- projects/koin-ksp-compiler/build.gradle.kts | 6 + .../org/koin/compiler/BuilderProcessor.kt | 24 +- .../compiler/generator/ClassModuleWriter.kt | 33 +++ .../compiler/generator/DefaultModuleWriter.kt | 36 +++ .../generator/DefinitionGenerationExt.kt | 211 ----------------- .../compiler/generator/DefinitionWriter.kt | 190 +++++++++++++++ .../generator/DefinitionWriterFactory.kt | 55 +++++ .../compiler/generator/KoinCodeGenerator.kt | 85 +++++++ .../koin/compiler/generator/KoinGenerator.kt | 109 --------- .../compiler/generator/ModuleGenerationExt.kt | 197 ---------------- .../koin/compiler/generator/ModuleWriter.kt | 216 ++++++++++++++++++ .../org/koin/compiler/generator/Templates.kt | 26 +-- .../generator/ext/CodeGeneratorExt.kt | 17 ++ .../compiler/generator/{ => ext}/Utils.kt | 2 +- .../compiler/metadata/AnnotationMetadata.kt | 1 - .../koin/compiler/metadata/KoinMetaData.kt | 42 +++- .../compiler/scanner/ClassComponentScanner.kt | 4 + .../scanner/FunctionComponentScanner.kt | 4 + .../koin/compiler/scanner/FunctionScanner.kt | 2 + .../compiler/scanner/KoinMetaDataScanner.kt | 25 +- .../koin/compiler/scanner/ModuleScanner.kt | 18 +- .../koin/compiler/scanner/{ => ext}/KspExt.kt | 2 +- .../koin/compiler/verify/KoinConfigChecker.kt | 29 ++- .../org/koin/compiler/verify/KoinTagWriter.kt | 98 +++++--- .../koin/compiler/verify/ext/ResolverExt.kt | 24 ++ 31 files changed, 853 insertions(+), 619 deletions(-) create mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ClassModuleWriter.kt create mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefaultModuleWriter.kt delete mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionGenerationExt.kt create mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionWriter.kt create mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionWriterFactory.kt create mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/KoinCodeGenerator.kt delete mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/KoinGenerator.kt delete mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ModuleGenerationExt.kt create mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ModuleWriter.kt create mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ext/CodeGeneratorExt.kt rename projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/{ => ext}/Utils.kt (96%) rename projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/{ => ext}/KspExt.kt (99%) create mode 100644 projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/ext/ResolverExt.kt diff --git a/examples/android-coffee-maker/build.gradle.kts b/examples/android-coffee-maker/build.gradle.kts index 5b26aa4..3b7ef49 100644 --- a/examples/android-coffee-maker/build.gradle.kts +++ b/examples/android-coffee-maker/build.gradle.kts @@ -48,5 +48,5 @@ dependencies { ksp { arg("KOIN_CONFIG_CHECK","true") - arg("KOIN_DEFAULT_MODULE","false") + arg("KOIN_DEFAULT_MODULE","true") } \ No newline at end of file diff --git a/examples/android-coffee-maker/src/main/java/org/koin/sample/androidx/app/MyPresenter.kt b/examples/android-coffee-maker/src/main/java/org/koin/sample/androidx/app/MyPresenter.kt index 964d772..316aafb 100644 --- a/examples/android-coffee-maker/src/main/java/org/koin/sample/androidx/app/MyPresenter.kt +++ b/examples/android-coffee-maker/src/main/java/org/koin/sample/androidx/app/MyPresenter.kt @@ -10,4 +10,7 @@ import org.koin.sample.androidx.data.ProvidedComponent class MyProvidedComponent @Factory -class MyPresenter(@InjectedParam val mainActivity: MainActivity, val context: Context, @Provided val provided : MyProvidedComponent) \ No newline at end of file +class MyPresenter(@InjectedParam val mainActivity: MainActivity, val context: Context, @Provided val provided : MyProvidedComponent) + +@Factory +class MyPresenterHolder(val mp : MyPresenter) \ No newline at end of file diff --git a/examples/android-coffee-maker/src/test/java/AndroidModuleTest.kt b/examples/android-coffee-maker/src/test/java/AndroidModuleTest.kt index 5c27d25..37b08e0 100644 --- a/examples/android-coffee-maker/src/test/java/AndroidModuleTest.kt +++ b/examples/android-coffee-maker/src/test/java/AndroidModuleTest.kt @@ -1,9 +1,9 @@ package org.koin.sample.androidx import org.junit.Test -import org.koin.android.ext.android.getKoin import org.koin.core.context.startKoin import org.koin.core.context.stopKoin +import org.koin.core.parameter.parametersOf import org.koin.ksp.generated.module import org.koin.sample.android.library.CommonRepository import org.koin.sample.android.library.MyScope @@ -11,7 +11,6 @@ import org.koin.sample.androidx.app.MyPresenter import org.koin.sample.androidx.app.ScopedStuff import org.koin.sample.androidx.data.DataConsumer import org.koin.sample.androidx.data.MyDataConsumer -import org.koin.sample.androidx.data.ProvidedComponent import org.koin.sample.androidx.di.AppModule import org.koin.sample.androidx.di.DataModule import org.koin.sample.androidx.repository.RepositoryModule diff --git a/examples/android-library/build.gradle.kts b/examples/android-library/build.gradle.kts index 0b29f28..ee1f4a2 100644 --- a/examples/android-library/build.gradle.kts +++ b/examples/android-library/build.gradle.kts @@ -35,5 +35,5 @@ dependencies { ksp { arg("KOIN_CONFIG_CHECK","true") - arg("KOIN_DEFAULT_MODULE","false") + arg("KOIN_DEFAULT_MODULE","true") } \ No newline at end of file diff --git a/examples/gradle/libs.versions.toml b/examples/gradle/libs.versions.toml index 40eea53..b0375b4 100644 --- a/examples/gradle/libs.versions.toml +++ b/examples/gradle/libs.versions.toml @@ -5,7 +5,7 @@ # Core kotlin = "1.9.24" koin = "3.5.6" -koinAnnotations = "1.4.0-RC3" +koinAnnotations = "1.4.0-RC4" ksp = "1.9.24-1.0.20" junit = "4.13.2" # Android diff --git a/projects/gradle.properties b/projects/gradle.properties index 4ec0ac5..85b6a64 100644 --- a/projects/gradle.properties +++ b/projects/gradle.properties @@ -7,7 +7,7 @@ org.gradle.parallel=true #Kotlin kotlin.code.style=official #Koin -koinAnnotationsVersion=1.4.0-RC3 +koinAnnotationsVersion=1.4.0-RC4 #Android android.useAndroidX=true androidMinSDK=14 diff --git a/projects/koin-ksp-compiler/build.gradle.kts b/projects/koin-ksp-compiler/build.gradle.kts index c969a37..8ab9275 100644 --- a/projects/koin-ksp-compiler/build.gradle.kts +++ b/projects/koin-ksp-compiler/build.gradle.kts @@ -20,4 +20,10 @@ kotlin { } } +tasks.withType { + kotlinOptions { + freeCompilerArgs += listOf("-Xcontext-receivers") + } +} + apply(from = file("../gradle/publish.gradle.kts")) diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt index e063c25..95c2223 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/BuilderProcessor.kt @@ -18,24 +18,27 @@ package org.koin.compiler import com.google.devtools.ksp.processing.* import com.google.devtools.ksp.symbol.KSAnnotated import org.koin.compiler.KspOptions.* -import org.koin.compiler.generator.KoinGenerator +import org.koin.compiler.generator.KoinCodeGenerator import org.koin.compiler.metadata.KoinMetaData import org.koin.compiler.scanner.KoinMetaDataScanner import org.koin.compiler.verify.KoinConfigChecker import org.koin.compiler.verify.KoinTagWriter class BuilderProcessor( - private val codeGenerator: CodeGenerator, + codeGenerator: CodeGenerator, private val logger: KSPLogger, private val options: Map ) : SymbolProcessor { - private val koinCodeGenerator = KoinGenerator(codeGenerator, logger, isComposeViewModelActive() || isKoinComposeViewModelActive()) + private val isComposeViewModelActive = isComposeViewModelActive() || isKoinComposeViewModelActive() + private val koinCodeGenerator = KoinCodeGenerator(codeGenerator, logger, isComposeViewModelActive) private val koinMetaDataScanner = KoinMetaDataScanner(logger) private val koinTagWriter = KoinTagWriter(codeGenerator,logger) private val koinConfigChecker = KoinConfigChecker(codeGenerator, logger) override fun process(resolver: Resolver): List { + initComponents(resolver) + logger.logging("Scan symbols ...") val invalidSymbols = koinMetaDataScanner.scanSymbols(resolver) @@ -50,23 +53,28 @@ class BuilderProcessor( isDefault = true, ) - logger.logging("Scan metadata ...") + logger.logging("Build metadata ...") val moduleList = koinMetaDataScanner.scanKoinModules(defaultModule) logger.logging("Generate code ...") koinCodeGenerator.generateModules(moduleList, defaultModule, isDefaultModuleActive()) val allModules = moduleList + defaultModule - koinTagWriter.writeAllTags(allModules) + koinTagWriter.writeAllTags(moduleList, defaultModule) if (isConfigCheckActive()) { - logger.warn("Koin Configuration Check") + logger.warn("Check Configuration ...") koinConfigChecker.verifyDefinitionDeclarations(allModules, resolver) koinConfigChecker.verifyModuleIncludes(allModules, resolver) } return emptyList() } + private fun initComponents(resolver: Resolver) { + koinCodeGenerator.resolver = resolver + koinTagWriter.resolver = resolver + } + private fun isConfigCheckActive(): Boolean { return options.getOrDefault(KOIN_CONFIG_CHECK.name, "false") == true.toString() } @@ -74,12 +82,12 @@ class BuilderProcessor( //TODO Use Koin 4.0 ViewModel DSL @Deprecated("use isKoinComposeViewModelActive") private fun isComposeViewModelActive(): Boolean { - logger.warn("[Deprecated] Please use KOIN_USE_COMPOSE_VIEWMODEL arg") + logger.warn("[Deprecated] 'USE_COMPOSE_VIEWMODEL' arg is deprecated. Please use 'KOIN_USE_COMPOSE_VIEWMODEL'") return options.getOrDefault(USE_COMPOSE_VIEWMODEL.name, "false") == true.toString() } private fun isKoinComposeViewModelActive(): Boolean { - logger.warn("Use Compose ViewModel for @KoinViewModel generation") + logger.warn("Activate Compose ViewModel for @KoinViewModel generation") return options.getOrDefault(KOIN_USE_COMPOSE_VIEWMODEL.name, "false") == true.toString() } diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ClassModuleWriter.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ClassModuleWriter.kt new file mode 100644 index 0000000..9fd206a --- /dev/null +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ClassModuleWriter.kt @@ -0,0 +1,33 @@ +/* + * Copyright 2017-present the original author or 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 org.koin.compiler.generator + +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Resolver +import org.koin.compiler.metadata.KoinMetaData + +class ClassModuleWriter( + codeGenerator: CodeGenerator, + resolver: Resolver, + module: KoinMetaData.Module +) : ModuleWriter(codeGenerator, resolver, module) { + + override val fileName : String = generateModuleFileName(module) + private fun generateModuleFileName(m: KoinMetaData.Module): String { + val extensionName = m.packageName("$") + return "${m.name}Gen${extensionName}" + } +} \ No newline at end of file diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefaultModuleWriter.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefaultModuleWriter.kt new file mode 100644 index 0000000..be1ea72 --- /dev/null +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefaultModuleWriter.kt @@ -0,0 +1,36 @@ +/* + * Copyright 2017-present the original author or 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 org.koin.compiler.generator + +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Resolver +import org.koin.compiler.metadata.KoinMetaData + +class DefaultModuleWriter( + codeGenerator: CodeGenerator, + resolver: Resolver, + defaultModule: KoinMetaData.Module, + generateDefaultModule: Boolean +) : ModuleWriter(codeGenerator, resolver, defaultModule) { + + override val fileName : String = "KoinDefault-${defaultModule.hashCode()}" + override val hasExternalDefinitions: Boolean = true + override val generateModuleBody: Boolean = generateDefaultModule + + override fun writeModuleFooter() { + writeln(DEFAULT_MODULE_FOOTER) + } +} \ No newline at end of file diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionGenerationExt.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionGenerationExt.kt deleted file mode 100644 index b689d70..0000000 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionGenerationExt.kt +++ /dev/null @@ -1,211 +0,0 @@ -/* - * Copyright 2017-present the original author or 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. - */ -import com.google.devtools.ksp.symbol.KSDeclaration -import org.koin.compiler.generator.KoinGenerator.Companion.LOGGER -import org.koin.compiler.metadata.KoinMetaData -import org.koin.compiler.metadata.KoinMetaData.Module.Companion.DEFINE_PREFIX -import org.koin.compiler.metadata.SINGLE -import org.koin.compiler.scanner.filterForbiddenKeywords -import java.io.OutputStream - -const val NEW_LINE = "\n\t" - - -fun OutputStream.generateDefinition(def: KoinMetaData.Definition, isExternalDefinition: Boolean = false, label: () -> String) { - if (def.isExpect.not()){ - LOGGER.logging("generate ${def.label} - $def") - val param = def.parameters.generateParamFunction() - val ctor = generateConstructor(def.parameters) - val binds = generateBindings(def.bindings) - val qualifier = def.qualifier.generateQualifier() - val createAtStart = if (def.isType(SINGLE) && def.isCreatedAtStart == true) { - if (qualifier == "") CREATED_AT_START else ",$CREATED_AT_START" - } else "" - val space = if (def.isScoped()) NEW_LINE + "\t" else NEW_LINE - - if (isExternalDefinition) { - writeExternalDefinitionFunction(def, qualifier, createAtStart, param, label, ctor, binds) - } - else { - writeDefinition(space, def, qualifier, createAtStart, param, label, ctor, binds) - } - } else { - LOGGER.warn("skip generate ${def.label} - isExpect") - appendText("// no definition for ${def.label} - isExpect") - } -} - -private fun OutputStream.writeDefinition( - space: String, - def: KoinMetaData.Definition, - qualifier: String, - createAtStart: String, - param: String, - label: () -> String, - ctor: String, - binds: String -) { - appendText("$space${def.keyword.keyword}($qualifier$createAtStart) { ${param}${label()}$ctor } $binds") -} - -private fun OutputStream.writeExternalDefinitionFunction( - def: KoinMetaData.Definition, - qualifier: String, - createAtStart: String, - param: String, - label: () -> String, - ctor: String, - binds: String -) { - appendText("\n@Definition(\"${def.packageName}\")\n") - appendText("public fun Module.$DEFINE_PREFIX${def.label}() : KoinDefinition<*> = ${def.keyword.keyword}($qualifier$createAtStart) { ${param}${label()}$ctor } $binds") -} - -fun OutputStream.generateModuleFunctionDeclarationDefinition(def: KoinMetaData.Definition.FunctionDefinition) { - generateDefinition(def) { "moduleInstance.${def.functionName}" } -} - -fun OutputStream.generateObjectModuleFunctionDeclarationDefinition( - def: KoinMetaData.Definition.FunctionDefinition, - modulePath: String -) { - generateDefinition(def ) { "$modulePath.${def.functionName}" } -} - -fun OutputStream.generateFunctionDeclarationDefinition(def: KoinMetaData.Definition.FunctionDefinition,isExternalDefinition: Boolean = false) { - generateDefinition(def,isExternalDefinition) { "${def.packageNamePrefix}${def.functionName}" } -} - -fun OutputStream.generateClassDeclarationDefinition( - def: KoinMetaData.Definition.ClassDefinition, - isExternalDefinition: Boolean = false -) { - generateDefinition(def,isExternalDefinition) { "${def.packageNamePrefix}${def.className}" } -} - -internal fun OutputStream.generateExternalDefinitionCalls(module: KoinMetaData.Module) { - generateExternalDefinitionCalls(module.externalDefinitions) -} - -internal fun OutputStream.generateExternalDefinitionCalls(externalDefinitions : List) { - this.appendText("${NEW_LINE}${externalDefinitions.joinToString(separator = "\n${NEW_LINE}") { "${it.name}()" }}") -} - -const val CREATED_AT_START = "createdAtStart=true" - -private fun List.generateParamFunction(): String { - return if (any { it is KoinMetaData.DefinitionParameter.ParameterInject }) "params -> " else "" -} - -private fun String?.generateQualifier(): String = when { - this == "\"null\"" -> "" - this == "null" -> "" - !this.isNullOrBlank() -> "qualifier=org.koin.core.qualifier.StringQualifier(\"$this\")" - else -> "" -} - -val BLOCKED_TYPES = listOf("Any", "ViewModel", "CoroutineWorker", "ListenableWorker") - -private fun generateBindings(bindings: List): String { - val validBindings = bindings.filter { - val clazzName = it.simpleName.asString() - clazzName !in BLOCKED_TYPES - } - - return when { - validBindings.isEmpty() -> "" - validBindings.size == 1 -> { - val generateBinding = generateBinding(validBindings.first()) - "bind($generateBinding)" - } - - else -> validBindings.joinToString(prefix = "binds(arrayOf(", separator = ",", postfix = "))") { - generateBinding(it) ?: "" - } - } -} - -private fun generateBinding(declaration: KSDeclaration): String { - val packageName = declaration.packageName.asString().filterForbiddenKeywords() - val className = declaration.simpleName.asString() - val parents = getParentDeclarations(declaration) - return if (parents.isNotEmpty()) { - val parentNames = parents.joinToString(".") { it.simpleName.asString() } - "$packageName.$parentNames.$className::class" - } else { - "$packageName.$className::class" - } -} - -private fun getParentDeclarations(declaration: KSDeclaration): List { - val parents = mutableListOf() - - var parent = declaration.parentDeclaration - while (parent != null) { - parents.add(parent) - parent = parent.parentDeclaration - } - - return parents.reversed() -} - -fun generateScope(scope: KoinMetaData.Scope): String { - return when (scope) { - is KoinMetaData.Scope.ClassScope -> { - val type = scope.type - val packageName = type.packageName.asString().filterForbiddenKeywords() - val className = type.simpleName.asString() - "${NEW_LINE}scope<$packageName.$className> {" - } - - is KoinMetaData.Scope.StringScope -> "${NEW_LINE}scope(org.koin.core.qualifier.StringQualifier(\"${scope.name}\")) {\n" - } -} - -fun generateScopeClosing(): String = "${NEW_LINE}}" - -private fun generateConstructor(constructorParameters: List): String { - val paramsWithoutDefaultValues = constructorParameters.filter { !it.hasDefault || it is KoinMetaData.DefinitionParameter.Property} - return paramsWithoutDefaultValues.joinToString(prefix = "(", separator = ",", postfix = ")") { ctorParam -> - val isNullable: Boolean = ctorParam.nullable - when (ctorParam) { - is KoinMetaData.DefinitionParameter.Dependency -> { - val scopeId = ctorParam.scopeId - when (ctorParam.kind) { - KoinMetaData.DependencyKind.List -> "getAll()" - else -> { - val keyword = when (ctorParam.kind) { - KoinMetaData.DependencyKind.Lazy -> "inject" - else -> "get" - } - val qualifier = - ctorParam.qualifier?.let { "qualifier=org.koin.core.qualifier.StringQualifier(\"${it}\")" } - ?: "" - val operator = if (!isNullable) "$keyword($qualifier)" else "${keyword}OrNull($qualifier)" - val scopeOperator = scopeId?.let { "getScope(\"$scopeId\").$operator" } ?: operator - if (ctorParam.name == null) scopeOperator else "${ctorParam.name}=$scopeOperator" - } - } - } - - is KoinMetaData.DefinitionParameter.ParameterInject -> if (!isNullable) "params.get()" else "params.getOrNull()" - is KoinMetaData.DefinitionParameter.Property -> { - val defaultValue = ctorParam.defaultValue?.let { ",${it.field}" } ?: "" - if (!isNullable) "getProperty(\"${ctorParam.value}\"$defaultValue)" else "getPropertyOrNull(\"${ctorParam.value}\",$defaultValue)" - } - } - } -} diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionWriter.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionWriter.kt new file mode 100644 index 0000000..ff1d64b --- /dev/null +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionWriter.kt @@ -0,0 +1,190 @@ +/* + * Copyright 2017-present the original author or 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 org.koin.compiler.generator + +import org.koin.compiler.generator.ext.appendText +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.KSDeclaration +import org.koin.compiler.generator.KoinCodeGenerator.Companion.LOGGER +import org.koin.compiler.metadata.KoinMetaData +import org.koin.compiler.metadata.KoinMetaData.Module.Companion.DEFINE_PREFIX +import org.koin.compiler.metadata.SINGLE +import org.koin.compiler.scanner.ext.filterForbiddenKeywords +import org.koin.compiler.verify.ext.getResolution +import java.io.OutputStream + +class DefinitionWriter( + val resolver: Resolver, + val fileStream: OutputStream +) { + private fun write(s: String) { fileStream.appendText(s) } + private fun writeln(s: String) = write("$s\n") + + fun writeDefinition(def: KoinMetaData.Definition, isExternalDefinition: Boolean = false, prefix: String) { + + if (def.alreadyGenerated == null){ + def.alreadyGenerated = canResolveType(def) + } + + if (def.alreadyGenerated == true){ + LOGGER.logging("skip ${def.label} -> ${def.getTagName()} - already generated") + } else { + if (def.isExpect.not()){ + LOGGER.logging("write definition ${def.label} ...") + + val param = def.parameters.generateParamFunction() + val ctor = generateConstructor(def.parameters) + val binds = generateBindings(def.bindings) + val qualifier = def.qualifier.generateQualifier() + val createAtStart = if (def.isType(SINGLE) && def.isCreatedAtStart == true) { + if (qualifier == "") CREATED_AT_START else ",$CREATED_AT_START" + } else "" + val space = if (def.isScoped()) TAB + TAB else TAB + + if (isExternalDefinition) { + writeExternalDefinitionFunction(def, qualifier, createAtStart, param, prefix, ctor, binds) + } + else { + writeDefinition(space, def, qualifier, createAtStart, param, prefix, ctor, binds) + } + } else { + LOGGER.logging("skip ${def.label} - isExpect") + } + } + } + + private fun canResolveType(def: KoinMetaData.Definition): Boolean = resolver.getResolution(def) != null + + private fun writeDefinition( + space: String, + def: KoinMetaData.Definition, + qualifier: String, + createAtStart: String, + param: String, + prefix: String, + ctor: String, + binds: String + ) { + writeln("$space${def.keyword.keyword}($qualifier$createAtStart) { ${param}${prefix}$ctor } $binds") + } + + private fun writeExternalDefinitionFunction( + def: KoinMetaData.Definition, + qualifier: String, + createAtStart: String, + param: String, + prefix: String, + ctor: String, + binds: String + ) { + writeln("@Definition(\"${def.packageName}\")") + writeln("public fun Module.$DEFINE_PREFIX${def.label}() : KoinDefinition<*> = ${def.keyword.keyword}($qualifier$createAtStart) { ${param}${prefix}$ctor } $binds") + } + + private fun List.generateParamFunction(): String { + return if (any { it is KoinMetaData.DefinitionParameter.ParameterInject }) "params -> " else "" + } + + private fun String?.generateQualifier(): String = when { + this == "\"null\"" -> "" + this == "null" -> "" + !this.isNullOrBlank() -> "qualifier=org.koin.core.qualifier.StringQualifier(\"$this\")" + else -> "" + } + + val BLOCKED_TYPES = listOf("Any", "ViewModel", "CoroutineWorker", "ListenableWorker") + + private fun generateBindings(bindings: List): String { + val validBindings = bindings.filter { + val clazzName = it.simpleName.asString() + clazzName !in BLOCKED_TYPES + } + + return when { + validBindings.isEmpty() -> "" + validBindings.size == 1 -> { + val generateBinding = generateBinding(validBindings.first()) + "bind($generateBinding)" + } + + else -> validBindings.joinToString(prefix = "binds(arrayOf(", separator = ",", postfix = "))") { + generateBinding(it) + } + } + } + + private fun generateBinding(declaration: KSDeclaration): String { + val packageName = declaration.packageName.asString().filterForbiddenKeywords() + val className = declaration.simpleName.asString() + val parents = getParentDeclarations(declaration) + return if (parents.isNotEmpty()) { + val parentNames = parents.joinToString(".") { it.simpleName.asString() } + "$packageName.$parentNames.$className::class" + } else { + "$packageName.$className::class" + } + } + + private fun getParentDeclarations(declaration: KSDeclaration): List { + val parents = mutableListOf() + + var parent = declaration.parentDeclaration + while (parent != null) { + parents.add(parent) + parent = parent.parentDeclaration + } + + return parents.reversed() + } + + private fun generateConstructor(constructorParameters: List): String { + val paramsWithoutDefaultValues = constructorParameters.filter { !it.hasDefault || it is KoinMetaData.DefinitionParameter.Property} + return paramsWithoutDefaultValues.joinToString(prefix = "(", separator = ",", postfix = ")") { ctorParam -> + val isNullable: Boolean = ctorParam.nullable + when (ctorParam) { + is KoinMetaData.DefinitionParameter.Dependency -> { + val scopeId = ctorParam.scopeId + when (ctorParam.kind) { + KoinMetaData.DependencyKind.List -> "getAll()" + else -> { + val keyword = when (ctorParam.kind) { + KoinMetaData.DependencyKind.Lazy -> "inject" + else -> "get" + } + val qualifier = + ctorParam.qualifier?.let { "qualifier=org.koin.core.qualifier.StringQualifier(\"${it}\")" } + ?: "" + val operator = if (!isNullable) "$keyword($qualifier)" else "${keyword}OrNull($qualifier)" + val scopeOperator = scopeId?.let { "getScope(\"$scopeId\").$operator" } ?: operator + if (ctorParam.name == null) scopeOperator else "${ctorParam.name}=$scopeOperator" + } + } + } + + is KoinMetaData.DefinitionParameter.ParameterInject -> if (!isNullable) "params.get()" else "params.getOrNull()" + is KoinMetaData.DefinitionParameter.Property -> { + val defaultValue = ctorParam.defaultValue?.let { ",${it.field}" } ?: "" + if (!isNullable) "getProperty(\"${ctorParam.value}\"$defaultValue)" else "getPropertyOrNull(\"${ctorParam.value}\",$defaultValue)" + } + } + } + } + + companion object { + const val TAB = "\t" + const val CREATED_AT_START = "createdAtStart=true" + } +} \ No newline at end of file diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionWriterFactory.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionWriterFactory.kt new file mode 100644 index 0000000..4435ad0 --- /dev/null +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/DefinitionWriterFactory.kt @@ -0,0 +1,55 @@ +/* + * Copyright 2017-present the original author or 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 org.koin.compiler.generator + +import com.google.devtools.ksp.processing.Resolver +import org.koin.compiler.generator.ModuleWriter.Companion.MODULE_INSTANCE +import org.koin.compiler.metadata.KoinMetaData +import java.io.OutputStream + +class DefinitionWriterFactory( + resolver: Resolver, + fileStream: OutputStream +) { + private val writer = DefinitionWriter(resolver, fileStream) + + fun writeDefinition( + definition : KoinMetaData.Definition, + module: KoinMetaData.Module? = null, + isExternal : Boolean? = null + ) { + return when (definition) { + is KoinMetaData.Definition.FunctionDefinition -> { + if (definition.isClassFunction) { + if (module?.type?.isObject == true) { + val modulePath = "${module.packageName}.${module.name}" + // Object Class function + writer.writeDefinition(definition, prefix = "$modulePath.${definition.functionName}", isExternalDefinition = isExternal ?: false) + } else { + // Module function + writer.writeDefinition(definition, prefix = "$MODULE_INSTANCE.${definition.functionName}") + } + } else { + // Pure Function + writer.writeDefinition(definition, prefix = "${definition.packageNamePrefix}${definition.functionName}", isExternalDefinition = isExternal ?: false) + } + } + // Class + is KoinMetaData.Definition.ClassDefinition -> writer.writeDefinition(definition, prefix = "${definition.packageNamePrefix}${definition.className}", isExternalDefinition = isExternal ?: false) + } + } + +} \ No newline at end of file diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/KoinCodeGenerator.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/KoinCodeGenerator.kt new file mode 100644 index 0000000..c14cb4f --- /dev/null +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/KoinCodeGenerator.kt @@ -0,0 +1,85 @@ +/* + * Copyright 2017-present the original author or 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 org.koin.compiler.generator + +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver +import org.koin.compiler.metadata.KoinMetaData +import org.koin.compiler.verify.ext.getResolution + +class KoinCodeGenerator( + val codeGenerator: CodeGenerator, + val logger: KSPLogger, + //TODO Remove isComposeViewModelActive with Koin 4 + val isComposeViewModelActive: Boolean +) { + lateinit var resolver: Resolver + + init { + LOGGER = logger + } + + fun generateModules( + moduleList: List, + defaultModule: KoinMetaData.Module, + generateDefaultModule : Boolean + ) { + logger.logging("generate ${moduleList.size} modules ...") + moduleList.forEach { generateModule(it) } + + if (defaultModule.definitions.isNotEmpty()) { + generateDefaultFile(defaultModule, generateDefaultModule) + } + } + + private fun generateDefaultFile( + defaultModule: KoinMetaData.Module, + generateDefaultModule: Boolean + ) { + logger.logging("generate default file ...") + + checkAlreadyGenerated(defaultModule) + val hasDefaultDefinitions = defaultModule.definitions.any { resolver.getResolution(it) == null } + + if (defaultModule.alreadyGenerated == false && hasDefaultDefinitions){ + defaultModule.setCurrentDefinitionsToExternals() + DefaultModuleWriter(codeGenerator, resolver, defaultModule, generateDefaultModule).writeModule() + } + } + + private fun generateModule(module: KoinMetaData.Module) { + logger.logging("generate module ${module.name}") + + checkAlreadyGenerated(module) + + if (module.alreadyGenerated == false && !module.isExpect){ + ClassModuleWriter(codeGenerator, resolver, module).writeModule(isComposeViewModelActive) + } + } + + private fun checkAlreadyGenerated(module: KoinMetaData.Module){ + if (module.alreadyGenerated == null){ + module.alreadyGenerated = resolver.getResolution(module) != null + } + } + + companion object { + lateinit var LOGGER: KSPLogger + private set + } +} + diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/KoinGenerator.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/KoinGenerator.kt deleted file mode 100644 index bfd0a7e..0000000 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/KoinGenerator.kt +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2017-present the original author or 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 org.koin.compiler.generator - -import com.google.devtools.ksp.processing.CodeGenerator -import com.google.devtools.ksp.processing.Dependencies -import com.google.devtools.ksp.processing.KSPLogger -import generateClassModule -import generateDefaultModuleFooter -import generateDefaultModuleFunction -import generateDefaultModuleHeader -import generateExternalDefinitionCalls -import generateFieldDefaultModule -import org.koin.compiler.metadata.KoinMetaData -import java.io.OutputStream - -class KoinGenerator( - val codeGenerator: CodeGenerator, - val logger: KSPLogger, - val isComposeViewModelActive: Boolean -) { - - init { - LOGGER = logger - } - - fun generateModules( - moduleList: List, - defaultModule: KoinMetaData.Module, - generateDefaultModule : Boolean - ) { - logger.logging("generate ${moduleList.size} modules ...") - moduleList.forEach { generateModule(it) } - - if (defaultModule.definitions.isNotEmpty()) { - generateDefaultFile(defaultModule, generateDefaultModule) - } - } - - private fun generateDefaultFile( - defaultModule: KoinMetaData.Module, - generateDefaultModule: Boolean - ) { - logger.logging("generate default file ...") - val defaultModuleFile = codeGenerator.getNewFile(fileName = "Default${defaultModule.hashCode()}") - defaultModuleFile.generateDefaultModuleHeader(defaultModule.definitions) - generateAllExternalDefinitions(defaultModule, defaultModuleFile) - - if (generateDefaultModule) { - generateDefaultModule(defaultModule, defaultModuleFile) - } - defaultModuleFile.close() - } - - private fun generateAllExternalDefinitions(defaultModule: KoinMetaData.Module, defaultModuleFile: OutputStream) { - defaultModuleFile.generateFieldDefaultModule(defaultModule.definitions, generateExternalDefinitions = true) - } - - private fun generateDefaultModule(defaultModule: KoinMetaData.Module, defaultModuleFile: OutputStream) { - with(defaultModuleFile){ - generateDefaultModuleFunction() - generateExternalDefinitionCalls(defaultModule.getDefinitionsAsExternals()) - generateDefaultModuleFooter() - } - } - - private fun generateModule(module: KoinMetaData.Module) { - logger.logging("generate $module - ${module.type}") - // generate class module - val moduleFile = codeGenerator.getNewFile(fileName = module.generateModuleFileName()) - //TODO Remove isComposeViewModelActive with Koin 4 - generateClassModule(moduleFile, module, isComposeViewModelActive) - } - - private fun KoinMetaData.Module.generateModuleFileName(): String { - val extensionName = packageName("$") - return "${name}Gen${extensionName}" - } - - companion object { - lateinit var LOGGER: KSPLogger - private set - } -} - -fun CodeGenerator.getNewFile(packageName: String = "org.koin.ksp.generated", fileName: String): OutputStream { - return try { - createNewFile( - Dependencies.ALL_FILES, - packageName, - fileName - ) - } catch (ex: FileAlreadyExistsException){ - ex.file.outputStream() - } -} diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ModuleGenerationExt.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ModuleGenerationExt.kt deleted file mode 100644 index 61f460b..0000000 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ModuleGenerationExt.kt +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright 2017-present the original author or 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. - */ -import com.google.devtools.ksp.symbol.KSDeclaration -import org.koin.compiler.generator.* -import org.koin.compiler.metadata.KOIN_VIEWMODEL -import org.koin.compiler.metadata.KOIN_VIEWMODEL_COMPOSE -import org.koin.compiler.metadata.KoinMetaData -import java.io.OutputStream - -fun OutputStream.generateFieldDefaultModule( - definitions: List, - generateExternalDefinitions: Boolean -) { - val standardDefinitions = definitions.filter { it.isNotScoped() }.toSet() - val scopeDefinitions = definitions.filter { it.isScoped() }.toSet() - - standardDefinitions.forEach { generateDefaultModuleDefinition(it, generateExternalDefinitions) } - //TODO Scope in function? - scopeDefinitions - .groupBy { it.scope } - .forEach { (scope, definitions) -> - appendText(generateScope(scope!!)) - definitions.forEach { definition -> - generateDefaultModuleDefinition(definition, generateExternalDefinitions) - } - appendText(generateScopeClosing()) - } -} - -fun OutputStream.generateDefaultModuleDefinition( - definition: KoinMetaData.Definition, - generateExternalDefinitions: Boolean -) { - if (definition is KoinMetaData.Definition.ClassDefinition) { - generateClassDeclarationDefinition(definition, isExternalDefinition = generateExternalDefinitions) - } else if (definition is KoinMetaData.Definition.FunctionDefinition && !definition.isClassFunction) { - generateFunctionDeclarationDefinition(definition, isExternalDefinition = generateExternalDefinitions) - } -} - -//TODO Remove isComposeViewModelActive once use Koin 4 ViewModel dsl -fun generateClassModule(classFile: OutputStream, module: KoinMetaData.Module, isComposeViewModelActive: Boolean) { - classFile.appendText(moduleHeader()) - classFile.appendText(module.definitions.generateImports(isComposeViewModelActive)) - - val generatedField = module.generateModuleField(classFile) - - val modulePath = "${module.packageName}.${module.name}" - - module.includes?.let { includes -> - if (includes.isNotEmpty()) { - generateIncludes(includes, classFile) - } - } - - if (module.definitions.isNotEmpty() && module.isExpect.not()) { - if (module.definitions.any { - // if any definition is a class function, we need to instantiate the module instance - // to able to call the function on this instance. - it is KoinMetaData.Definition.FunctionDefinition && - it.isClassFunction - } && !module.type.isObject) { - classFile.appendText("${NEW_LINE}val moduleInstance = $modulePath()") - } - - generateDefinitions(module, classFile) - } - - if (module.externalDefinitions.isNotEmpty() && module.isExpect.not()) { - classFile.generateExternalDefinitionCalls(module) - } - - if (module.isExpect){ - classFile.appendText("\n// empty module due to isExpect") - } - - classFile.appendText("\n}") - val visibilityString = module.visibility.toSourceString() - classFile.appendText( - "\n${visibilityString}val $modulePath.module : org.koin.core.module.Module get() = $generatedField" - ) - - classFile.flush() - classFile.close() -} - -private fun generateDefinitions( - module: KoinMetaData.Module, - classFile: OutputStream -) { - val standardDefinitions = module.definitions.filter { it.isNotScoped() } - standardDefinitions.forEach { it.generateTargetDefinition(module, classFile) } - - val scopeDefinitions = module.definitions.filter { it.isScoped() } - scopeDefinitions - .groupBy { it.scope } - .forEach { (scope, definitions) -> - classFile.appendText(generateScope(scope!!)) - definitions.forEach { - it.generateTargetDefinition(module, classFile) - } - // close scope - classFile.appendText("\n\t\t\t\t}") - } -} - -private fun KoinMetaData.Definition.generateTargetDefinition( - module: KoinMetaData.Module, - classFile: OutputStream -) { - when (this) { - is KoinMetaData.Definition.FunctionDefinition -> { - if (isClassFunction) { - if (module.type.isObject) { - val modulePath = "${module.packageName}.${module.name}" - classFile.generateObjectModuleFunctionDeclarationDefinition(this, modulePath) - } else { - classFile.generateModuleFunctionDeclarationDefinition(this) - } - } else { - classFile.generateFunctionDeclarationDefinition(this) - } - } - - is KoinMetaData.Definition.ClassDefinition -> classFile.generateClassDeclarationDefinition(this) - } -} - -private fun generateIncludes( - includeList: List, - classFile: OutputStream -) { - val generatedIncludes: String = includeList.generateModuleIncludes() - classFile.appendText("${NEW_LINE}includes($generatedIncludes)") -} - -private fun KoinMetaData.Module.generateModuleField( - classFile: OutputStream -): String { - val packageName = packageName("_") - val generatedField = "${packageName}_${name}" - val visibilityString = visibility.toSourceString() - val createdAtStartString = if (isCreatedAtStart != null && isCreatedAtStart) "($CREATED_AT_START)" else "" - classFile.appendText("\n${visibilityString}val $generatedField : Module get() = module$createdAtStartString {") - return generatedField -} - -fun OutputStream.generateDefaultModuleHeader(definitions: List) { - appendText(DEFAULT_MODULE_HEADER) - appendText(definitions.generateImports()) -} - -fun OutputStream.generateDefaultModuleFunction() { - appendText("\n\n") - appendText(DEFAULT_MODULE_FUNCTION) -} - -fun OutputStream.generateDefaultModuleFooter() { - appendText(DEFAULT_MODULE_FOOTER) -} - -//TODO Remove isComposeViewModelActive with Koin 4 -private fun List.generateImports(isComposeViewModelActive: Boolean = false): String { - return map { definition -> definition.keyword } - .toSet() - .mapNotNull { keyword -> - if (isComposeViewModelActive && keyword == KOIN_VIEWMODEL){ - KOIN_VIEWMODEL_COMPOSE.import.let { "import $it" } - } else { - keyword.import?.let { "import $it" } - } - } - .joinToString(separator = "\n", postfix = "\n") -} - -private fun List.generateModuleIncludes(): String { - return joinToString { it.generateModuleInclude() } -} - -private fun KSDeclaration.generateModuleInclude(): String { - val packageName: String = packageName.asString() - val className = simpleName.asString() - return "$packageName.$className().module" -} diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ModuleWriter.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ModuleWriter.kt new file mode 100644 index 0000000..eee5c8a --- /dev/null +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ModuleWriter.kt @@ -0,0 +1,216 @@ +/* + * Copyright 2017-present the original author or 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 org.koin.compiler.generator + +import org.koin.compiler.generator.ext.appendText +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Resolver +import org.koin.compiler.generator.DefinitionWriter.Companion.CREATED_AT_START +import org.koin.compiler.generator.DefinitionWriter.Companion.TAB +import org.koin.compiler.generator.ext.getNewFile +import org.koin.compiler.metadata.KOIN_VIEWMODEL +import org.koin.compiler.metadata.KOIN_VIEWMODEL_COMPOSE +import org.koin.compiler.metadata.KoinMetaData +import org.koin.compiler.scanner.ext.filterForbiddenKeywords +import org.koin.compiler.generator.ext.toSourceString +import java.io.OutputStream + +abstract class ModuleWriter( + val codeGenerator: CodeGenerator, + val resolver: Resolver, + val module: KoinMetaData.Module, +) { + abstract val fileName: String + + open val hasExternalDefinitions: Boolean = false + open val generateModuleBody: Boolean = true + + private fun createFileStream(): OutputStream = codeGenerator.getNewFile(fileName = fileName) + private var fileStream: OutputStream? = null + protected fun write(s: String) { fileStream?.appendText(s) } + protected fun writeln(s: String) = write("$s\n") + protected fun writeEmptyLine() = writeln("") + private lateinit var definitionFactory : DefinitionWriterFactory + + private val modulePath = "${module.packageName}.${module.name}" + private val generatedField = "${module.packageName("_")}_${module.name}" + + //TODO Remove isComposeViewModelActive with Koin 4 + fun writeModule(isComposeViewModelActive: Boolean = false) { + fileStream = createFileStream() + definitionFactory = DefinitionWriterFactory(resolver, fileStream!!) + + writeHeader() + writeHeaderImports(isComposeViewModelActive) + + if (hasExternalDefinitions) { + writeExternalDefinitionImports() + writeExternalDefinitions() + } + + writeEmptyLine() + + if (generateModuleBody){ + writeModuleFunction() + writeModuleInstance() + writeModuleIncludes() + if (!hasExternalDefinitions) { + writeDefinitions() + } + writeExternalDefinitionCalls() + writeModuleFooter() + } + + onFinishWriteModule() + } + + private fun writeExternalDefinitionImports() { + writeln(""" + import org.koin.core.annotation.Definition + import org.koin.core.definition.KoinDefinition + """.trimIndent()) + } + + open fun writeHeader() { + write(MODULE_HEADER) + } + + open fun writeHeaderImports(isComposeViewModelActive: Boolean) { + write(generateImports(module.definitions, isComposeViewModelActive)) + } + + private fun generateImports( + definitions: List, + isComposeViewModelActive: Boolean + ): String { + return definitions.map { definition -> definition.keyword } + .toSet() + .mapNotNull { keyword -> + if (isComposeViewModelActive && keyword == KOIN_VIEWMODEL) { + KOIN_VIEWMODEL_COMPOSE.import.let { "import $it" } + } else { + keyword.import?.let { "import $it" } + } + } + .joinToString(separator = "\n", postfix = "\n") + } + + open fun writeExternalDefinitions() { + //Reduce external defs to ClassDefinition for now + val standardDefinitions = module.definitions.filter { it.isNotScoped() } + standardDefinitions.forEach { definitionFactory.writeDefinition(it, module, isExternal = true) } + } + + open fun writeModuleFunction() { + // Module definition + // TODO generate Extensions + writeln(generateModuleField(module)) + } + + open fun writeModuleInstance() { + // if any definition is a class function, we need to instantiate the module instance + // to able to call the function on this instance. + val needModuleInstance = module + .definitions.any { it is KoinMetaData.Definition.FunctionDefinition && it.isClassFunction } + && !module.type.isObject + + if (needModuleInstance) { + writeln("${TAB}val $MODULE_INSTANCE = $modulePath()") + } + } + + private fun generateModuleField( + module: KoinMetaData.Module + ): String { + with(module) { + val visibilityString = visibility.toSourceString() + val createdAtStartString = if (isCreatedAtStart != null && isCreatedAtStart) "($CREATED_AT_START)" else "" + return "${visibilityString}val $generatedField : Module get() = module$createdAtStartString {" + } + } + + open fun writeModuleIncludes() { + if (module.includes?.isNotEmpty() == true){ + generateIncludes()?.let { writeln("${TAB}includes($it)") } + } + } + + private fun generateIncludes(): String? { + return module.includes?.joinToString(separator = ",") { "${it.packageName}.${it.className}().module" } + } + + open fun writeDefinitions() { + val (standardDefinitions, scopeDefinitions) = module.definitions.partition { it.isNotScoped() } + + standardDefinitions.forEach { definitionFactory.writeDefinition(it, module) } + + scopeDefinitions + .groupBy { it.scope } + .forEach { (scope, scopeDefinitions) -> + scope?.let { writeScope(scope, scopeDefinitions) } + } + } + + open fun writeScope( + scope: KoinMetaData.Scope, + scopeDefinitions: List + ) { + writeln(generateScopeHeader(scope)) + scopeDefinitions.forEach { definitionFactory.writeDefinition(it, module) } + writeln(generateScopeFooter()) + } + + internal fun generateScopeHeader(scope: KoinMetaData.Scope): String { + return when (scope) { + is KoinMetaData.Scope.ClassScope -> { + val type = scope.type + val packageName = type.packageName.asString().filterForbiddenKeywords() + val className = type.simpleName.asString() + "${TAB}scope<$packageName.$className> {" + } + + is KoinMetaData.Scope.StringScope -> "${TAB}scope(org.koin.core.qualifier.StringQualifier(\"${scope.name}\")) {" + } + } + + private fun generateScopeFooter(): String = "${TAB}}" + + open fun writeExternalDefinitionCalls() { + if (module.externalDefinitions.isNotEmpty()){ + writeln(TAB+generateExternalDefinitionCalls()) + } + } + + private fun generateExternalDefinitionCalls(): String = + module.externalDefinitions.joinToString(separator = "\n${TAB}") { "${it.name}()" } + + open fun writeModuleFooter() { + writeln(MODULE_FOOTER) + val visibilityString = module.visibility.toSourceString() + writeln("${visibilityString}val $modulePath.module : org.koin.core.module.Module get() = $generatedField") + } + + open fun onFinishWriteModule() { + fileStream?.apply{ + flush() + close() + } + } + + companion object { + val MODULE_INSTANCE = "moduleInstance" + } +} \ No newline at end of file diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/Templates.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/Templates.kt index b0ea937..7c8f72c 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/Templates.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/Templates.kt @@ -15,31 +15,17 @@ */ package org.koin.compiler.generator -val DEFAULT_MODULE_HEADER = """ - package org.koin.ksp.generated - - import org.koin.core.KoinApplication - import org.koin.core.module.Module - import org.koin.core.annotation.Definition - import org.koin.core.definition.KoinDefinition - import org.koin.dsl.* - - """.trimIndent() - -val DEFAULT_MODULE_FUNCTION = """ - public fun KoinApplication.defaultModule(): KoinApplication = modules(defaultModule) - public val defaultModule : Module = module { - """.trimIndent() +val MODULE_FOOTER = "}" val DEFAULT_MODULE_FOOTER = """ - - } - """.trimIndent() + } + public val defaultModule : org.koin.core.module.Module get() = _defaultModule + public fun org.koin.core.KoinApplication.defaultModule(): org.koin.core.KoinApplication = modules(defaultModule) +""".trimIndent() -fun moduleHeader() = """ +val MODULE_HEADER = """ package org.koin.ksp.generated import org.koin.core.module.Module import org.koin.dsl.* - """.trimIndent() diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ext/CodeGeneratorExt.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ext/CodeGeneratorExt.kt new file mode 100644 index 0000000..c74c3a6 --- /dev/null +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ext/CodeGeneratorExt.kt @@ -0,0 +1,17 @@ +package org.koin.compiler.generator.ext + +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Dependencies +import java.io.OutputStream + +fun CodeGenerator.getNewFile(packageName: String = "org.koin.ksp.generated", fileName: String): OutputStream { + return try { + createNewFile( + Dependencies.ALL_FILES, + packageName, + fileName + ) + } catch (ex: FileAlreadyExistsException){ + ex.file.outputStream() + } +} \ No newline at end of file diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/Utils.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ext/Utils.kt similarity index 96% rename from projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/Utils.kt rename to projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ext/Utils.kt index c61a2d2..e2d7352 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/Utils.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/generator/ext/Utils.kt @@ -1,4 +1,4 @@ -/* +package org.koin.compiler.generator.ext/* * Copyright 2017-present the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/AnnotationMetadata.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/AnnotationMetadata.kt index f0b22b9..c082198 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/AnnotationMetadata.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/AnnotationMetadata.kt @@ -20,7 +20,6 @@ import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSType import org.koin.android.annotation.KoinViewModel import org.koin.android.annotation.KoinWorker -import org.koin.compiler.generator.KoinGenerator.Companion.LOGGER import org.koin.core.annotation.* import java.util.* import kotlin.reflect.KClass diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt index f4e3d7d..39203ef 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/metadata/KoinMetaData.kt @@ -20,25 +20,36 @@ import com.google.devtools.ksp.symbol.KSType import com.google.devtools.ksp.symbol.Visibility import java.util.* +typealias PackageName = String +fun PackageName.camelCase() = split(".").joinToString("") { it.capitalize() } + sealed class KoinMetaData { + data class Module( - val packageName: String, + val packageName: PackageName, val name: String, val definitions: MutableList = mutableListOf(), val externalDefinitions: MutableList = mutableListOf(), val type: ModuleType = ModuleType.FIELD, val componentScan: ComponentScan? = null, - val includes: List? = null, + val includes: List? = null, val isCreatedAtStart: Boolean? = null, val visibility: Visibility = Visibility.PUBLIC, val isDefault: Boolean = false, val isExpect : Boolean = false ) : KoinMetaData() { + var alreadyGenerated : Boolean? = null + + fun getTagName() = packageName.camelCase() + name.capitalize() + if (isExpect) "Exp" else "" + fun packageName(separator: String): String { - val default = Locale.getDefault() - return packageName.split(".").joinToString(separator) { it.lowercase(default) } + return if (isDefault) "" + else { + val default = Locale.getDefault() + packageName.split(".").joinToString(separator) { it.lowercase(default) } + } } data class ComponentScan(val packageName: String = "") @@ -56,8 +67,12 @@ sealed class KoinMetaData { } } - fun getDefinitionsAsExternals(): List { - return definitions.map { ExternalDefinition(it.packageName, "$DEFINE_PREFIX${it.label}") } + fun setCurrentDefinitionsToExternals() { + val externals = definitions + .filter { !it.isExpect } + .map { ExternalDefinition(it.packageName, "$DEFINE_PREFIX${it.label}") } + + externalDefinitions.addAll(externals) } companion object { @@ -65,6 +80,14 @@ sealed class KoinMetaData { } } + data class ModuleInclude( + val packageName: PackageName, + val className : String, + val isExpect : Boolean + ){ + fun getTagName() = packageName.camelCase() + className.capitalize() + if (isExpect) "Exp" else "" + } + enum class ModuleType { FIELD, CLASS, OBJECT; @@ -115,7 +138,7 @@ sealed class KoinMetaData { sealed class Definition( val label: String, val parameters: List, - val packageName: String, + val packageName: PackageName, val qualifier: String? = null, val isCreatedAtStart: Boolean? = null, val keyword: DefinitionAnnotation, @@ -124,12 +147,16 @@ sealed class KoinMetaData { val isExpect : Boolean ) : KoinMetaData() { + var alreadyGenerated : Boolean? = null + fun isScoped(): Boolean = scope != null fun isNotScoped(): Boolean = !isScoped() fun isType(keyword: DefinitionAnnotation): Boolean = this.keyword == keyword val packageNamePrefix : String = if (packageName.isEmpty()) "" else "${packageName}." + fun getTagName() = packageName.camelCase() + label.capitalize() + if (isExpect) "Exp" else "" + override fun equals(other: Any?): Boolean { if (this === other) return true if (javaClass != other?.javaClass) return false @@ -139,6 +166,7 @@ sealed class KoinMetaData { if (label != other.label) return false if (packageName != other.packageName) return false if (scope != other.scope) return false + if (isExpect != other.isExpect) return false return true } diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ClassComponentScanner.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ClassComponentScanner.kt index e2eb97b..89f800d 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ClassComponentScanner.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ClassComponentScanner.kt @@ -18,6 +18,7 @@ package org.koin.compiler.scanner import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.symbol.* import org.koin.compiler.metadata.* +import org.koin.compiler.scanner.ext.* class ClassComponentScanner( val logger: KSPLogger, @@ -53,7 +54,10 @@ class ClassComponentScanner( val defaultBindings = ksClassDeclaration.superTypes.map { it.resolve().declaration }.toList() val allBindings: List = if (declaredBindings?.hasDefaultUnitValue() == false) declaredBindings else defaultBindings val ctorParams = ksClassDeclaration.primaryConstructor?.parameters?.getParameters() + val isExpect = ksClassDeclaration.isExpect +// val isActual = ksClassDeclaration.isActual +// LOGGER.info("definition - $packageName $className - isExpect:$isExpect isActual:$isActual") return when (annotationName) { SINGLE.annotationName -> { diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/FunctionComponentScanner.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/FunctionComponentScanner.kt index 3fd7466..b9112a9 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/FunctionComponentScanner.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/FunctionComponentScanner.kt @@ -18,6 +18,10 @@ package org.koin.compiler.scanner import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.symbol.* import org.koin.compiler.metadata.* +import org.koin.compiler.scanner.ext.filterForbiddenKeywords +import org.koin.compiler.scanner.ext.getKoinAnnotations +import org.koin.compiler.scanner.ext.getQualifier +import org.koin.compiler.scanner.ext.getScopeAnnotation class FunctionComponentScanner( val logger: KSPLogger diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/FunctionScanner.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/FunctionScanner.kt index e42ceec..6eec89f 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/FunctionScanner.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/FunctionScanner.kt @@ -19,6 +19,8 @@ import com.google.devtools.ksp.symbol.KSAnnotation import com.google.devtools.ksp.symbol.KSDeclaration import com.google.devtools.ksp.symbol.KSFunctionDeclaration import org.koin.compiler.metadata.* +import org.koin.compiler.scanner.ext.getParameters +import org.koin.compiler.scanner.ext.getScope /** * Scan for Koin function component metadata diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt index c8d21a2..5f885f5 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KoinMetaDataScanner.kt @@ -27,7 +27,6 @@ import org.koin.compiler.metadata.DEFINITION_ANNOTATION_LIST_TYPES import org.koin.compiler.metadata.KoinMetaData import org.koin.core.annotation.Definition import org.koin.core.annotation.Module -import org.koin.core.annotation.Property import org.koin.core.annotation.PropertyValue class KoinMetaDataScanner( @@ -43,7 +42,6 @@ class KoinMetaDataScanner( private var defaultProperties = mutableListOf() private var externalDefinitions = listOf() - private val definitionAnnotationName = Definition::class.simpleName @OptIn(KspExperimental::class) fun scanSymbols(resolver: Resolver): List { @@ -68,15 +66,9 @@ class KoinMetaDataScanner( defaultProperties.addAll(propertyValueSymbols.filter { it.validate() }) externalDefinitions = resolver.getDeclarationsFromPackage("org.koin.ksp.generated") - .filter { it.annotations.any { it.shortName.asString() == definitionAnnotationName } } + .filter { a -> a.annotations.any { it.shortName.asString() == DEFINITION_ANNOTATION } } .toList() - if (externalDefinitions.isNotEmpty()) { - logger.logging("external definitions: ${externalDefinitions.size}") - } else { - logger.logging("no external definition") - } - return emptyList() } @@ -117,15 +109,15 @@ class KoinMetaDataScanner( private fun scanExternalDefinitions(index: List) { externalDefinitions - .mapNotNull { def -> - def.annotations - .first { it.shortName.asString() == definitionAnnotationName }.arguments.first().value?.toString() - ?.let { - KoinMetaData.ExternalDefinition(targetPackage = it, name = def.simpleName.asString()) + .filter { !it.isExpect } + .mapNotNull { definitionDeclaration -> + definitionDeclaration.annotations + .first { it.shortName.asString() == DEFINITION_ANNOTATION }.arguments.first().value?.toString() + ?.let { packageValue -> + KoinMetaData.ExternalDefinition(targetPackage = packageValue, name = definitionDeclaration.simpleName.asString()) } } .forEach { extDef -> - // add to first module that accept val module = index.firstOrNull { it.acceptDefinition(extDef.targetPackage) } module?.externalDefinitions?.add(extDef) } @@ -208,4 +200,7 @@ class KoinMetaDataScanner( private fun logInvalidEntities(classDeclarationList: List) { classDeclarationList.forEach { logger.logging("Invalid entity: $it") } } + companion object { + private val DEFINITION_ANNOTATION = Definition::class.simpleName + } } diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ModuleScanner.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ModuleScanner.kt index af631fc..279cb13 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ModuleScanner.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ModuleScanner.kt @@ -19,6 +19,7 @@ import com.google.devtools.ksp.getVisibility import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.symbol.* import org.koin.compiler.metadata.* +import org.koin.compiler.scanner.ext.* class ModuleScanner( val logger: KSPLogger @@ -45,7 +46,7 @@ class ModuleScanner( name = name, type = type, componentScan = componentScan, - includes = includes, + includes = includes.toModuleIncludes(), isCreatedAtStart = isCreatedAtStart, visibility = declaration.getVisibility(), isExpect = isExpect @@ -101,4 +102,17 @@ class ModuleScanner( } } } -} \ No newline at end of file + + private fun List?.toModuleIncludes(): List? { + return this?.map { + it.mapModuleInclude() + } + } + + private fun KSDeclaration.mapModuleInclude(): KoinMetaData.ModuleInclude { + val packageName: String = packageName.asString() + val className = simpleName.asString() + return KoinMetaData.ModuleInclude(packageName, className, isExpect) + } +} + diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KspExt.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ext/KspExt.kt similarity index 99% rename from projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KspExt.kt rename to projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ext/KspExt.kt index 55e12c2..15ba836 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/KspExt.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/scanner/ext/KspExt.kt @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.koin.compiler.scanner +package org.koin.compiler.scanner.ext import com.google.devtools.ksp.symbol.* import org.koin.compiler.metadata.KoinMetaData diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigChecker.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigChecker.kt index 2183401..97906dc 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigChecker.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinConfigChecker.kt @@ -20,6 +20,7 @@ import com.google.devtools.ksp.processing.KSPLogger import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.symbol.KSDeclaration import org.koin.compiler.metadata.KoinMetaData +import org.koin.compiler.verify.ext.getResolutionForTag const val codeGenerationPackage = "org.koin.ksp.generated" @@ -36,11 +37,11 @@ class KoinConfigChecker(val codeGenerator: CodeGenerator, val logger: KSPLogger) val allDefinitions = moduleList.flatMap { it.definitions } if (isAlreadyGenerated) { - verifyTags(allDefinitions, resolver) + verifyDependencies(allDefinitions, resolver) } } - private fun verifyTags( + private fun verifyDependencies( allDefinitions: List, resolver: Resolver ) { @@ -48,14 +49,22 @@ class KoinConfigChecker(val codeGenerator: CodeGenerator, val logger: KSPLogger) def.parameters .filterIsInstance() .forEach { param -> - if (!param.hasDefault && !param.isNullable && !param.alreadyProvided) { - checkDependencyIsDefined(param, resolver, def) - } + checkDependency(param, resolver, def) //TODO Check Cycle } } } + private fun checkDependency( + param: KoinMetaData.DefinitionParameter.Dependency, + resolver: Resolver, + def: KoinMetaData.Definition + ) { + if (!param.hasDefault && !param.isNullable && !param.alreadyProvided) { + checkDependencyIsDefined(param, resolver, def) + } + } + private fun checkDependencyIsDefined( dependencyToCheck: KoinMetaData.DefinitionParameter.Dependency, @@ -74,8 +83,7 @@ class KoinConfigChecker(val codeGenerator: CodeGenerator, val logger: KSPLogger) val parameterFullName = targetTypeToCheck.qualifiedName?.asString() if (parameterFullName !in typeWhiteList && parameterFullName != null) { val cn = targetTypeToCheck.qualifiedNameCamelCase() - val className = "$codeGenerationPackage.$tagPrefix$cn" - val resolution = resolver.getClassDeclarationByName(resolver.getKSNameFromString(className)) + val resolution = resolver.getResolutionForTag(cn) val isNotScopeType = scope != parameterFullName if (resolution == null && isNotScopeType) { logger.error("--> Missing Definition type '$parameterFullName' for '${definition.packageName}.$label'. Fix your configuration to define type '${targetTypeToCheck.simpleName.asString()}'.") @@ -89,11 +97,9 @@ class KoinConfigChecker(val codeGenerator: CodeGenerator, val logger: KSPLogger) modules.forEach { m -> val mn = m.packageName + "." + m.name m.includes?.forEach { inc -> - val cn = inc.qualifiedName?.asString()?.replace(".", "_") - val ksn = resolver.getKSNameFromString("$codeGenerationPackage.$cn") - val prop = resolver.getPropertyDeclarationByName(ksn, includeTopLevel = true) + val prop = resolver.getResolutionForTag(inc.getTagName()) if (prop == null) { - logger.error("--> Module Undefined :'${inc.qualifiedName?.asString()}' included in '$mn'. Fix your configuration: add @Module annotation on '${inc.simpleName.asString()}' class.") + logger.error("--> Module Undefined :'${inc.className}' included in '$mn'. Fix your configuration: add @Module annotation on '${inc.className}' class.") } } } @@ -101,5 +107,4 @@ class KoinConfigChecker(val codeGenerator: CodeGenerator, val logger: KSPLogger) } } -internal fun KoinMetaData.Definition.packageCamelCase() = packageName.split(".").joinToString("") { it.capitalize() } internal fun KSDeclaration.qualifiedNameCamelCase() = qualifiedName?.asString()?.split(".")?.joinToString(separator = "") { it.capitalize() } diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt index 41ee561..0d6e2e2 100644 --- a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/KoinTagWriter.kt @@ -1,34 +1,48 @@ package org.koin.compiler.verify -import appendText +import org.koin.compiler.generator.ext.appendText import com.google.devtools.ksp.processing.CodeGenerator import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver import com.google.devtools.ksp.symbol.KSDeclaration -import org.koin.compiler.generator.getNewFile +import org.koin.compiler.generator.KoinCodeGenerator.Companion.LOGGER +import org.koin.compiler.generator.ext.getNewFile import org.koin.compiler.metadata.KoinMetaData +import org.koin.compiler.verify.ext.getResolution +import org.koin.compiler.verify.ext.getResolutionForTag import java.io.OutputStream const val tagPrefix = "KoinDef" class KoinTagWriter(val codeGenerator: CodeGenerator, val logger: KSPLogger) { + lateinit var resolver: Resolver + fun writeAllTags( - moduleList: List + moduleList: List, + default : KoinMetaData.Module ) { val isAlreadyGenerated = codeGenerator.generatedFile.isEmpty() - val allDefinitions = moduleList.flatMap { it.definitions } + val allDefinitions = (moduleList + default).flatMap { it.definitions } if (!isAlreadyGenerated) { - logger.warn("Koin Tags Generation ...") - writeTags(allDefinitions) + logger.logging("Koin Tags Generation ...") + val tagFileName = "KoinMeta-${hashCode()}" + val tagFileStream = writeTagFile(tagFileName) + + writeModuleTags(moduleList,tagFileStream) + writeDefinitionsTags(allDefinitions,tagFileStream) } } - private fun writeTags(allDefinitions: List) { + private fun writeModuleTags(allModules: List, tagFileStream : OutputStream) { + val alreadyDeclaredTags = arrayListOf() + allModules.forEach { m -> writeModuleTag(tagFileStream,m,alreadyDeclaredTags) } + } + + private fun writeDefinitionsTags(allDefinitions: List, tagFileStream : OutputStream) { val alreadyDeclaredTags = arrayListOf() - val tagFileName = "KoinMeta-${hashCode()}" - val tagFileStream = writeTagFile(tagFileName) allDefinitions.forEach { def -> writeDefinitionTag(tagFileStream, def, alreadyDeclaredTags) } } @@ -39,13 +53,31 @@ class KoinTagWriter(val codeGenerator: CodeGenerator, val logger: KSPLogger) { return fileStream } + private fun writeModuleTag( + fileStream: OutputStream, + mod: KoinMetaData.Module, + alreadyDeclared: ArrayList + ) { + logger.logging("writeModuleTag? ${mod.name}") + if (mod.alreadyGenerated == null){ + mod.alreadyGenerated = resolver.getResolution(mod) != null + } + + if (mod.alreadyGenerated == false){ + val className = mod.getTagName() + if (className !in alreadyDeclared) { + writeTagLine(className, fileStream, alreadyDeclared) + } + } + } + private fun writeDefinitionTag( fileStream: OutputStream, def: KoinMetaData.Definition, alreadyDeclared: ArrayList ) { writeClassTag(def, alreadyDeclared, fileStream) - def.bindings.forEach { writeDefinitionBindingTag(it, alreadyDeclared, fileStream) } + def.bindings.forEach { writeBindingTag(it, alreadyDeclared, fileStream) } } private fun writeClassTag( @@ -53,33 +85,43 @@ class KoinTagWriter(val codeGenerator: CodeGenerator, val logger: KSPLogger) { alreadyDeclared: java.util.ArrayList, fileStream: OutputStream ) { - val className = def.packageCamelCase() + def.label.capitalize() - if (className !in alreadyDeclared) { - writeTagLine(className, fileStream, alreadyDeclared) + if (def.alreadyGenerated == null){ + def.alreadyGenerated = resolver.getResolution(def) != null } - } - private fun writeTagLine( - className: String, - fileStream: OutputStream, - alreadyDeclared: java.util.ArrayList - ) { - val tag = "public class $tagPrefix$className" - fileStream.appendText("\n$tag") - alreadyDeclared.add(className) + if (!def.isExpect && def.alreadyGenerated == false){ + val className = def.getTagName() + if (className !in alreadyDeclared) { + writeTagLine(className, fileStream, alreadyDeclared) + } + } } - private fun writeDefinitionBindingTag( + private fun writeBindingTag( binding: KSDeclaration, alreadyDeclared: ArrayList, fileStream: OutputStream ) { - val name = binding.qualifiedName?.asString() - if (name !in typeWhiteList) { - val className = binding.qualifiedNameCamelCase() - if (className !in alreadyDeclared && className != null) { - writeTagLine(className, fileStream, alreadyDeclared) + binding.qualifiedName?.asString()?.let { name -> + if (name !in typeWhiteList) { + binding.qualifiedNameCamelCase()?.let { className -> + val alreadyGenerated = resolver.getResolutionForTag(className) != null + if (className !in alreadyDeclared && !alreadyGenerated) { + writeTagLine(className, fileStream, alreadyDeclared) + } + } } } } + + private fun writeTagLine( + tagName: String, + fileStream: OutputStream, + alreadyDeclared: java.util.ArrayList + ) { + LOGGER.logging("tag: $tagName") + val tag = "public class $tagPrefix$tagName" + fileStream.appendText("\n$tag") + alreadyDeclared.add(tagName) + } } \ No newline at end of file diff --git a/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/ext/ResolverExt.kt b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/ext/ResolverExt.kt new file mode 100644 index 0000000..bb3c0cc --- /dev/null +++ b/projects/koin-ksp-compiler/src/jvmMain/kotlin/org/koin/compiler/verify/ext/ResolverExt.kt @@ -0,0 +1,24 @@ +package org.koin.compiler.verify.ext + +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.symbol.KSDeclaration +import org.koin.compiler.metadata.KoinMetaData +import org.koin.compiler.verify.codeGenerationPackage +import org.koin.compiler.verify.tagPrefix + + +fun Resolver.getResolution(mod : KoinMetaData.Module) : KSDeclaration?{ + return getResolutionForTag(mod.getTagName()) +} + +fun Resolver.getResolution(def : KoinMetaData.Definition) : KSDeclaration?{ + return getResolutionForTag(def.getTagName()) +} + +fun Resolver.getResolutionForTag(tag : String?) : KSDeclaration?{ + return getResolutionForClass("$codeGenerationPackage.$tagPrefix$tag") +} + +fun Resolver.getResolutionForClass(name : String) : KSDeclaration?{ + return getClassDeclarationByName(getKSNameFromString(name)) +} \ No newline at end of file