Skip to content

Commit

Permalink
fix(#49): mapping for nested classes
Browse files Browse the repository at this point in the history
  • Loading branch information
mcarleio committed Feb 25, 2024
1 parent fe6989c commit 6d4447f
Show file tree
Hide file tree
Showing 7 changed files with 304 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.mcarle.konvert.processor.codegen

import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.google.devtools.ksp.symbol.KSFunctionDeclaration
import com.google.devtools.ksp.symbol.KSPropertyDeclaration
import com.google.devtools.ksp.symbol.KSType
Expand All @@ -8,6 +9,7 @@ import com.google.devtools.ksp.symbol.KSValueParameter
import com.google.devtools.ksp.symbol.Origin
import com.squareup.kotlinpoet.CodeBlock
import com.squareup.kotlinpoet.joinToCode
import com.squareup.kotlinpoet.ksp.toClassName
import io.mcarle.konvert.converter.api.TypeConverterRegistry
import io.mcarle.konvert.converter.api.config.Configuration
import io.mcarle.konvert.converter.api.config.enableConverters
Expand All @@ -29,14 +31,18 @@ class MappingCodeGenerator {
targetClassImportName: String?,
targetProperties: List<KSPropertyDeclaration>
): CodeBlock {
val typeName = targetClassImportName ?: constructor.parentDeclaration?.qualifiedName!!.asString()
val className = constructor.parentDeclaration!!.simpleName.asString()
val constructorCode = constructorCode(typeName, constructor, sourceProperties)
val constructorCode = constructorCode(
className = targetClassImportName,
classDeclaration = constructor.parentDeclaration as? KSClassDeclaration,
constructor = constructor,
sourceProperties = sourceProperties
)
val propertyCode = propertyCode(
className,
functionParamName,
sourceProperties,
targetProperties
className = className,
functionParamName = functionParamName,
sourceProperties = sourceProperties,
targetProperties = targetProperties
)
return if (source.isNullable()) {
// source can only be nullable in case of @Konverter/@Konvert which require a functionParamName
Expand All @@ -56,18 +62,34 @@ class MappingCodeGenerator {
}

private fun constructorCode(
className: String,
className: String?,
classDeclaration: KSClassDeclaration?,
constructor: KSFunctionDeclaration,
sourceProperties: List<PropertyMappingInfo>
): CodeBlock {
if (className == null) {
return if (constructor.parameters.isEmpty()) {
CodeBlock.of("%T()", classDeclaration?.toClassName())
} else {
CodeBlock.of(
"""
%T(${"\n%L"}
⇤)
""".trimIndent(),
classDeclaration?.toClassName(),
constructorParamsCode(constructor = constructor, sourceProperties = sourceProperties)
)
}
}
return if (constructor.parameters.isEmpty()) {
CodeBlock.of("$className()")
} else {
CodeBlock.of(
"""
$className(${"\n%L"}
⇤)
""".trimIndent(), constructorParamsCode(constructor = constructor, sourceProperties = sourceProperties)
""".trimIndent(),
constructorParamsCode(constructor = constructor, sourceProperties = sourceProperties)
)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,18 +39,17 @@ object KonverterCodeGenerator {
withIsolatedConfiguration(konvertData.annotationData.options) {
if (isAlias(konvertData.sourceTypeReference, konvertData.sourceType)) {
// @Konverter annotated interface used alias for source, so the implementation should also use the same alias
codeBuilder.addImport(konvertData.sourceType, konvertData.sourceTypeReference.toString())}
codeBuilder.addImport(konvertData.sourceType, konvertData.sourceTypeReference.toString())
}

val targetClassImportName =
if (isAlias(konvertData.targetTypeReference, konvertData.targetType)) {
// @Konverter annotated interface used alias for target, so the implementation should also use the same alias
val alias = konvertData.targetTypeReference.toString()
codeBuilder.addImport(konvertData.targetType, alias)
alias
} else if (konvertData.sourceTypeReference.toString() == konvertData.targetTypeReference.toString()) {
null
} else {
konvertData.targetClassDeclaration.simpleName.asString()
null
}

codeBuilder.addFunction(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ object KonvertFromCodeGenerator {
data.annotationData.mappings.validated(data.sourceClassDeclaration, logger),
data.annotationData.constructor,
data.paramName,
data.targetClassDeclaration.simpleName.asString(),
null,
data.sourceClassDeclaration.asStarProjectedType(),
data.targetClassDeclaration.asStarProjectedType(),
data.targetCompanionDeclaration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,6 @@ object KonvertToCodeGenerator {
data.sourceClassDeclaration.simpleName.asString(),
)

val targetClassImportName =
if (data.sourceClassDeclaration.simpleName.asString() != data.targetClassDeclaration.simpleName.asString()) {
data.targetClassDeclaration.simpleName.asString()
} else {
null
}

fileSpecBuilder.addFunction(
funBuilder = FunSpec.builder(data.mapFunctionName)
.returns(data.targetClassDeclaration.asStarProjectedType().toTypeName())
Expand All @@ -38,7 +31,7 @@ object KonvertToCodeGenerator {
data.annotationData.mappings.validated(data.sourceClassDeclaration, logger),
data.annotationData.constructor,
null,
targetClassImportName,
null,
data.sourceClassDeclaration.asStarProjectedType(),
data.targetClassDeclaration.asStarProjectedType(),
data.sourceClassDeclaration,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -651,7 +651,7 @@ interface SomeConverter {
import b.SomeClass as BSomeClass
public object SomeConverterImpl : SomeConverter {
override fun toSomeClass(source: ASomeClass): BSomeClass = b.SomeClass().also { someClass ->
override fun toSomeClass(source: ASomeClass): BSomeClass = BSomeClass().also { someClass ->
someClass.property = source.property
}
}
Expand Down Expand Up @@ -711,7 +711,7 @@ interface SomeConverter {
import b.SomeClass as BSomeClass
public object SomeConverterImpl : SomeConverter {
override fun toSomeClass(source: ASomeClass): BSomeClass? = b.SomeClass().also { someClass ->
override fun toSomeClass(source: ASomeClass): BSomeClass? = BSomeClass().also { someClass ->
someClass.property = source.property
}
}
Expand Down Expand Up @@ -1520,6 +1520,125 @@ interface OtherMapper {

assertContains(mapperCode, "Konverter.get<OtherMapper>().toTarget(")
}

@Test
fun nestedClass() {
val (compilation) = compileWith(
enabledConverters = listOf(SameTypeConverter()),
expectResultCode = KotlinCompilation.ExitCode.OK,
code = arrayOf(
SourceFile.kotlin(
name = "a/Person.kt",
contents =
"""
package a
data class Person(val firstName: String, val lastName: String, val age: Int, val address: Address) {
data class Address(val address1: String, val address2: String)
}
""".trimIndent()
),
SourceFile.kotlin(
name = "b/PersonDto.kt",
contents =
"""
package b
data class PersonDto(val firstName: String, val lastName: String, val age: Int, val address: AddressDto) {
data class AddressDto(val address1: String, val address2: String)
}
""".trimIndent()
),
SourceFile.kotlin(
name = "c/DomainMapper.kt",
contents =
"""
package c
import io.mcarle.konvert.api.Konverter
import a.Person
import b.PersonDto
@Konverter
interface DomainMapper {
fun toAddressDto(address: Person.Address): PersonDto.AddressDto
}
""".trimIndent()
),
SourceFile.kotlin(
name = "d/DtoMapper.kt",
contents =
"""
package d
import io.mcarle.konvert.api.Konverter
import a.Person.Address as AddressDomain
import b.PersonDto
@Konverter
interface DtoMapper {
fun toAddress(address: PersonDto.AddressDto): AddressDomain
}
""".trimIndent()
),
)
)
val domainMapperCode = compilation.generatedSourceFor("DomainMapperKonverter.kt")
println(domainMapperCode)
val dtoMapperCode = compilation.generatedSourceFor("DtoMapperKonverter.kt")
println(dtoMapperCode)

assertSourceEquals(
"""
package c
import a.Person
import b.PersonDto
public object DomainMapperImpl : DomainMapper {
override fun toAddressDto(address: Person.Address): PersonDto.AddressDto = PersonDto.AddressDto(
address1 = address.address1,
address2 = address.address2
)
}
""".trimIndent(),
domainMapperCode
)
assertSourceEquals(
"""
package d
import a.Person
import b.PersonDto
import a.Person.Address as AddressDomain
public object DtoMapperImpl : DtoMapper {
override fun toAddress(address: PersonDto.AddressDto): a.Person.Address = AddressDomain(
address1 = address.address1,
address2 = address.address2
)
}
""".trimIndent(),
dtoMapperCode
)
// TODO: After https://github.com/square/kotlinpoet/issues/1838 is fixed, the following assertion should succeed
// assertSourceEquals(
// """
// package d
//
// import b.PersonDto
// import a.Person.Address as AddressDomain
//
// public object DtoMapperImpl : DtoMapper {
// override fun toAddress(address: PersonDto.AddressDto): AddressDomain = AddressDomain(
// address1 = address.address1,
// address2 = address.address2
// )
// }
// """.trimIndent(),
// dtoMapperCode
// )
}
}

private fun Konverter.Companion.getWithClassLoader(classFQN: String, classLoader: ClassLoader): Any {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -498,4 +498,81 @@ class TargetClass(val children: List<TargetClass>) {
)
}

@Test
fun nestedClass() {
val (compilation) = compileWith(
enabledConverters = listOf(SameTypeConverter()),
expectResultCode = KotlinCompilation.ExitCode.OK,
code = arrayOf(
SourceFile.kotlin(
name = "a/Person.kt",
contents =
"""
package a
import io.mcarle.konvert.api.KonvertFrom
import b.PersonDto
data class Person(val firstName: String, val lastName: String, val age: Int, val address: Address) {
data class Address(val address1: String, val address2: String) {
@KonvertFrom(PersonDto.AddressDto::class)
companion object
}
}
""".trimIndent()
),
SourceFile.kotlin(
name = "b/PersonDto.kt",
contents =
"""
package b
import io.mcarle.konvert.api.KonvertFrom
import a.Person.Address as AddressDomain
data class PersonDto(val firstName: String, val lastName: String, val age: Int, val address: AddressDto) {
data class AddressDto(val address1: String, val address2: String) {
@KonvertFrom(AddressDomain::class)
companion object
}
}
""".trimIndent()
)
)
)
val addressExtensionFunctionCode = compilation.generatedSourceFor("AddressKonverter.kt")
println(addressExtensionFunctionCode)
val addressDtoExtensionFunctionCode = compilation.generatedSourceFor("AddressDtoKonverter.kt")
println(addressDtoExtensionFunctionCode)

assertSourceEquals(
"""
package a
import b.PersonDto
public fun Person.Address.Companion.fromAddressDto(addressDto: PersonDto.AddressDto): Person.Address
= Person.Address(
address1 = addressDto.address1,
address2 = addressDto.address2
)
""".trimIndent(),
addressExtensionFunctionCode
)
assertSourceEquals(
"""
package b
import a.Person
public fun PersonDto.AddressDto.Companion.fromAddress(address: Person.Address): PersonDto.AddressDto
= PersonDto.AddressDto(
address1 = address.address1,
address2 = address.address2
)
""".trimIndent(),
addressDtoExtensionFunctionCode
)
}

}
Loading

0 comments on commit 6d4447f

Please sign in to comment.