Skip to content

Commit

Permalink
Do not repeat @OptIn annotation for generated class (#773)
Browse files Browse the repository at this point in the history
* Do not repeat @OptIn annotation  for generated class

* Add test for opt in marker class deduplication
  • Loading branch information
dewantawsif authored Feb 10, 2025
1 parent 399b659 commit 5d66ba5
Show file tree
Hide file tree
Showing 3 changed files with 76 additions and 19 deletions.
1 change: 1 addition & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ ktorfit{

## Fixed
- @Headers annotation produces unexpected newline in generated code by ksp plugin #752
- Generated code containing repeated @OptIn annotation #767

# [2.2.0]()
* Supported Kotlin version: 2.0.0; 2.0.10; 2.0.20, 2.1.0-Beta1; 2.0.21-RC, 2.0.21, 2.1.0-RC, 2.1.0-RC2, 2.1.0, 2.1.10
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package de.jensklingenberg.ktorfit.poetspec

import com.google.devtools.ksp.processing.Resolver
import com.google.devtools.ksp.symbol.KSAnnotation
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.KSType
import com.google.devtools.ksp.symbol.KSTypeReference
import com.squareup.kotlinpoet.AnnotationSpec
import com.squareup.kotlinpoet.ClassName
Expand All @@ -10,7 +12,7 @@ import com.squareup.kotlinpoet.FunSpec
import com.squareup.kotlinpoet.KModifier
import com.squareup.kotlinpoet.PropertySpec
import com.squareup.kotlinpoet.TypeSpec
import com.squareup.kotlinpoet.ksp.toAnnotationSpec
import com.squareup.kotlinpoet.ksp.toClassName
import com.squareup.kotlinpoet.ksp.toTypeName
import de.jensklingenberg.ktorfit.KtorfitOptions
import de.jensklingenberg.ktorfit.model.ClassData
Expand Down Expand Up @@ -56,25 +58,16 @@ private fun createImplClassTypeSpec(
implClassProperties: List<PropertySpec>,
funSpecs: List<FunSpec>
): TypeSpec {
val optInAnnotations =
classData.annotations.filter { it.shortName.getShortName() == "OptIn" }.map { it.toAnnotationSpec() }
val helperProperty =
PropertySpec
.builder(converterHelper.objectName, converterHelper.toClassName())
.initializer("${converterHelper.name}(${ktorfitClass.objectName})")
.addModifiers(KModifier.PRIVATE)
.build()

val internalApiAnnotation =
AnnotationSpec
.builder(ClassName("kotlin", "OptIn"))
.addMember(
"%T::class",
internalApi,
).build()
return TypeSpec
.classBuilder(implClassName)
.addAnnotations(optInAnnotations + internalApiAnnotation)
.addAnnotation(getOptInAnnotation(classData.annotations))
.addModifiers(classData.modifiers)
.primaryConstructor(
FunSpec
Expand All @@ -94,6 +87,27 @@ private fun createImplClassTypeSpec(
.build()
}

private fun getOptInAnnotation(annotations: List<KSAnnotation>): AnnotationSpec {
val markerClasses =
annotations
.filter { it.shortName.getShortName() == "OptIn" }
.flatMap { annotation ->
@Suppress("UNCHECKED_CAST")
(annotation.arguments[0].value as? List<KSType>)
?.map { it.toClassName() }
.orEmpty()
}
.plus(internalApi)
.toTypedArray<Any>()

val format = (1..markerClasses.size).joinToString { "%T::class" }

return AnnotationSpec
.builder(ClassName("kotlin", "OptIn"))
.addMember(format, *markerClasses)
.build()
}

private fun propertySpec(property: KSPropertyDeclaration): PropertySpec {
val propBuilder =
PropertySpec
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,16 @@ import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
@OptIn(ExperimentalCompilerApi::class)
interface TestService {
@Headers(value = ["x:y","a:b"])
@GET("posts")
@OptIn(ExperimentalCompilerApi::class)
suspend fun test(@Header("testHeader") testParameterNonNullable: String?, @Header("testHeader") testParameterNullable: String?, @HeaderMap("testHeaderMap") testParameter2: Map<String,String>): String
@Headers(value = ["x:y","a:b"])
@GET("posts")
@OptIn(ExperimentalCompilerApi::class)
suspend fun test(@Header("testHeader") testParameterNonNullable: String?, @Header("testHeader") testParameterNullable: String?, @HeaderMap("testHeaderMap") testParameter2: Map<String,String>): String
}
""",
)

val expectedHeadersArgumentText =
"""@OptIn(ExperimentalCompilerApi::class)
@OptIn(InternalKtorfitApi::class)
"""@OptIn(ExperimentalCompilerApi::class, InternalKtorfitApi::class)
public class _TestServiceImpl(
private val _ktorfit: Ktorfit,
) : TestService {
Expand All @@ -41,9 +40,52 @@ public class _TestServiceImpl(
override suspend fun test("""

val compilation = getCompilation(listOf(source))
val result = compilation.compile()

val generatedSourcesDir = compilation.kspSourcesDir
val generatedSourcesDir = compilation.apply { compile() }.kspSourcesDir
val generatedFile =
File(
generatedSourcesDir,
"/kotlin/com/example/api/_TestServiceImpl.kt",
)

val actualSource = generatedFile.readText()
assertTrue(actualSource.contains(expectedHeadersArgumentText))
}

@Test
fun `when OptIn annotation are add to the implementation class do not repeat marker classes`() {
val source =
SourceFile.kotlin(
"Source.kt",
"""package com.example.api
import de.jensklingenberg.ktorfit.http.GET
import de.jensklingenberg.ktorfit.http.Headers
import de.jensklingenberg.ktorfit.http.Header
import de.jensklingenberg.ktorfit.http.HeaderMap
import org.jetbrains.kotlin.compiler.plugin.ExperimentalCompilerApi
@OptIn(ExperimentalCompilerApi::class)
interface TestService {
@GET("posts")
@OptIn(ExperimentalCompilerApi::class)
suspend fun test1()
@GET("posts")
@OptIn(ExperimentalCompilerApi::class)
suspend fun test2()
}
""",
)

val expectedHeadersArgumentText =
"""@OptIn(ExperimentalCompilerApi::class, InternalKtorfitApi::class)
public class _TestServiceImpl(
private val _ktorfit: Ktorfit,
) : TestService {"""

val compilation = getCompilation(listOf(source))

val generatedSourcesDir = compilation.apply { compile() }.kspSourcesDir
val generatedFile =
File(
generatedSourcesDir,
Expand Down

0 comments on commit 5d66ba5

Please sign in to comment.