Coverage Summary for Class: GhostPlugin (com.ghost.gradle)

Class Method, % Branch, % Line, % Instruction, %
GhostPlugin 90.9% (10/11) 63.9% (23/36) 88.1% (59/67) 92.6% (410/443)
GhostPlugin$apply$1 100% (1/1) 100% (1/1) 100% (7/7)
GhostPlugin$apply$2 100% (1/1) 100% (2/2) 100% (15/15)
GhostPlugin$apply$3$1 100% (1/1) 50% (1/2) 100% (4/4) 100% (23/23)
GhostPlugin$apply$4 100% (1/1) 91.7% (11/12) 100% (7/7) 100% (77/77)
GhostPlugin$apply$configureKspDependencies$1 100% (1/1) 66.7% (4/6) 100% (6/6) 97.1% (68/70)
GhostPlugin$apply$configureKspDependencies$compilerDep$1 100% (1/1) 100% (1/1) 100% (2/2)
GhostPlugin$Companion
GhostPlugin$setupAndroidOrJvmCore$apiDep$1 100% (1/1) 100% (1/1) 100% (2/2)
GhostPlugin$setupAndroidOrJvmCore$runtimeDep$1 100% (1/1) 100% (1/1) 100% (2/2)
GhostPlugin$setupKmp$apiDep$1 100% (1/1) 100% (1/1) 100% (2/2)
GhostPlugin$setupKmp$runtimeDep$1 100% (1/1) 100% (1/1) 100% (2/2)
Total 95.2% (20/21) 69.6% (39/56) 91.3% (84/92) 94.6% (610/645)


 package com.ghost.gradle
 
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.provider.Provider
 import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
 
 class GhostPlugin : Plugin<Project> {
 
     override fun apply(project: Project) {
         val extension = createExtension(project)
         val ghostVersion = extension.version
 
         // 1. Core & KSP (Reactive)
         var kspSetupDone = false
         fun configureKspDependencies() {
             if (kspSetupDone) return
             if (!project.plugins.hasPlugin(PLUGIN_KSP)) return
 
             val compilerDep = ghostVersion.map { "$GROUP_ID:$ARTIFACT_COMPILER:$it" }
             if (project.plugins.hasPlugin(PLUGIN_KMP)) {
                 val kotlinExtension = project
                     .extensions
                     .findByType(KotlinMultiplatformExtension::class.java)
 
                 kotlinExtension?.targets?.configureEach {
                     if (name == TARGET_METADATA) {
                         project.dependencies.add(CONFIG_KSP_COMMON, compilerDep)
                     } else {
                         val capitalizedTarget = name.replaceFirstChar { it.uppercase() }
                         project.dependencies.add(
                             "$PREFIX_KSP$capitalizedTarget",
                             compilerDep
                         )
                     }
                 }
                 kspSetupDone = true
             } else if (project.plugins.hasPlugin(PLUGIN_ANDROID_APP) ||
                 project.plugins.hasPlugin(PLUGIN_ANDROID_LIB) ||
                 project.plugins.hasPlugin(PLUGIN_JVM)
             ) {
                 project.dependencies.add(
                     PREFIX_KSP,
                     compilerDep
                 )
                 kspSetupDone = true
             }
         }
 
         project.plugins.withId(PLUGIN_KSP) { configureKspDependencies() }
         project.plugins.withId(PLUGIN_KMP) {
             setupKmp(project, ghostVersion)
             configureKspDependencies()
         }
         
         var coreApplied = false
         listOf(PLUGIN_ANDROID_APP, PLUGIN_ANDROID_LIB, PLUGIN_JVM).forEach { pluginId ->
             project.plugins.withId(pluginId) {
                 if (!coreApplied) {
                     setupAndroidOrJvmCore(project, ghostVersion)
                     coreApplied = true
                 }
                 configureKspDependencies()
             }
         }
 
         // 2. Network Adapters (Safe afterEvaluate)
         // We use afterEvaluate to perform the check ONCE after all dependencies are declared,
         // avoiding the circularity of providers and the variant resolution issues of configurations.all.
         project.afterEvaluate {
             val version = ghostVersion.get()
             if (extension.autoInjectKtor.get() && hasKtorDependency(project)) {
                 injectNetworkDependency(project, "$GROUP_ID:$ARTIFACT_KTOR:$version")
             }
             if (extension.autoInjectRetrofit.get() && hasRetrofitDependency(project)) {
                 injectNetworkDependency(project, "$GROUP_ID:$ARTIFACT_RETROFIT:$version")
             }
             if (extension.autoInjectProtobuf.get() && hasProtobufDependency(project)) {
                 injectNetworkDependency(project, "$GROUP_ID:$ARTIFACT_PROTOBUF:$version")
             }
         }
     }
 
     private fun createExtension(project: Project): GhostExtension {
         return project.extensions.create(EXTENSION_NAME, GhostExtension::class.java).apply {
             autoInjectKtor.convention(true)
             autoInjectRetrofit.convention(true)
             autoInjectProtobuf.convention(true)
             version.convention(DEFAULT_VERSION)
         }
     }
 
     private fun setupKmp(project: Project, version: Provider<String>) {
         val runtimeDep = version.map { "$GROUP_ID:$ARTIFACT_RUNTIME:$it" }
         val apiDep = version.map { "$GROUP_ID:$ARTIFACT_API:$it" }
         
         project.dependencies.add(CONFIG_COMMON_MAIN_IMPL, runtimeDep)
         project.dependencies.add(CONFIG_COMMON_MAIN_IMPL, apiDep)
     }
 
     private fun setupAndroidOrJvmCore(project: Project, version: Provider<String>) {
         val runtimeDep = version.map { "$GROUP_ID:$ARTIFACT_RUNTIME:$it" }
         val apiDep = version.map { "$GROUP_ID:$ARTIFACT_API:$it" }
         project.dependencies.add(CONFIG_IMPL, runtimeDep)
         project.dependencies.add(CONFIG_IMPL, apiDep)
     }
 
     private fun isMultiplatform(project: Project): Boolean {
         return project.pluginManager.hasPlugin(PLUGIN_KMP)
     }
 
     private fun hasKtorDependency(project: Project): Boolean {
         return listOf(CONFIG_IMPL, "api", CONFIG_COMMON_MAIN_IMPL).any { name ->
             val config = project.configurations.findByName(name)
             config?.dependencies?.any {
                 it.group == GROUP_KTOR &&
                         it.name.startsWith(PREFIX_KTOR_CLIENT)
             } ?: false
         }
     }
 
     private fun hasRetrofitDependency(project: Project): Boolean {
         return listOf(CONFIG_IMPL, "api", CONFIG_COMMON_MAIN_IMPL).any { name ->
             val config = project.configurations.findByName(name)
             config?.dependencies?.any {
                 it.group == GROUP_RETROFIT &&
                         it.name == NAME_RETROFIT
             } ?: false
         }
     }
 
     private fun hasProtobufDependency(project: Project): Boolean {
         if (project.plugins.hasPlugin(PLUGIN_PROTOBUF)) return true
         return listOf(CONFIG_IMPL, "api", CONFIG_COMMON_MAIN_IMPL).any { name ->
             val config = project.configurations.findByName(name)
             config?.dependencies?.any {
                 it.group == GROUP_PROTOBUF
             } ?: false
         }
     }
 
     private fun injectNetworkDependency(project: Project, dep: String) {
         if (project.pluginManager.hasPlugin(PLUGIN_KMP)) {
             project.dependencies.add(
                 CONFIG_COMMON_MAIN_IMPL,
                 dep
             )
         } else {
             project.dependencies.add(
                 CONFIG_IMPL,
                 dep
             )
         }
     }
 
     companion object {
         private const val EXTENSION_NAME = "ghost"
         private const val DEFAULT_VERSION = "1.2.7"
 
         private const val PLUGIN_KSP = "com.google.devtools.ksp"
         private const val PLUGIN_KMP = "org.jetbrains.kotlin.multiplatform"
         private const val PLUGIN_ANDROID_APP = "com.android.application"
         private const val PLUGIN_ANDROID_LIB = "com.android.library"
         private const val PLUGIN_JVM = "org.jetbrains.kotlin.jvm"
         private const val PLUGIN_PROTOBUF = "com.google.protobuf"
 
         private const val GROUP_ID = "com.ghostserializer"
         private const val ARTIFACT_COMPILER = "ghost-compiler"
         private const val ARTIFACT_RUNTIME = "ghost-serialization"
         private const val ARTIFACT_API = "ghost-api"
         private const val ARTIFACT_KTOR = "ghost-ktor"
         private const val ARTIFACT_RETROFIT = "ghost-retrofit"
         private const val ARTIFACT_PROTOBUF = "ghost-protobuf"
 
         private const val CONFIG_COMMON_MAIN_IMPL = "commonMainImplementation"
         private const val CONFIG_IMPL = "implementation"
         private const val CONFIG_KSP_COMMON = "kspCommonMainMetadata"
         private const val PREFIX_KSP = "ksp"
 
         private const val TARGET_METADATA = "metadata"
 
         private const val GROUP_KTOR = "io.ktor"
         private const val PREFIX_KTOR_CLIENT = "ktor-client"
         private const val GROUP_RETROFIT = "com.squareup.retrofit2"
         private const val NAME_RETROFIT = "retrofit"
         private const val GROUP_PROTOBUF = "com.google.protobuf"
     }
 }