diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/KxsTsGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/KxsTsGenerator.kt index 2ab1a9fb..015486c2 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/KxsTsGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/KxsTsGenerator.kt @@ -14,6 +14,7 @@ import dev.adamko.kxstsgen.core.TsTypeRefConverter import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.nullable +import kotlinx.serialization.modules.SerializersModule /** @@ -28,8 +29,8 @@ import kotlinx.serialization.descriptors.nullable */ open class KxsTsGenerator( open val config: KxsTsConfig = KxsTsConfig(), - open val sourceCodeGenerator: TsSourceCodeGenerator = TsSourceCodeGenerator.Default(config), + open val serializersModule: SerializersModule = SerializersModule { }, ) { @@ -60,7 +61,8 @@ open class KxsTsGenerator( open val descriptorsExtractor = object : SerializerDescriptorsExtractor { - val extractor: SerializerDescriptorsExtractor = SerializerDescriptorsExtractor.Default + val extractor: SerializerDescriptorsExtractor = + SerializerDescriptorsExtractor.default(serializersModule) val cache: MutableMap, Set> = mutableMapOf() override fun invoke(serializer: KSerializer<*>): Set = diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractor.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractor.kt index 32db4d08..e4e4df87 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractor.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractor.kt @@ -1,8 +1,9 @@ package dev.adamko.kxstsgen.core -import dev.adamko.kxstsgen.core.util.MutableMapWithDefaultPut import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.* +import kotlinx.serialization.modules.SerializersModule + /** @@ -14,8 +15,20 @@ fun interface SerializerDescriptorsExtractor { serializer: KSerializer<*> ): Set + companion object { + /** The default [SerializerDescriptorsExtractor], for easy use. */ + fun default( + serializersModule: SerializersModule, + ): SerializerDescriptorsExtractor { + return Default( + elementDescriptorsExtractor = TsElementDescriptorsExtractor.default(serializersModule) + ) + } + } - object Default : SerializerDescriptorsExtractor { + class Default( + private val elementDescriptorsExtractor: TsElementDescriptorsExtractor, + ) : SerializerDescriptorsExtractor { override operator fun invoke( serializer: KSerializer<*> @@ -25,7 +38,6 @@ fun interface SerializerDescriptorsExtractor { .toSet() } - private tailrec fun extractDescriptors( current: SerialDescriptor? = null, queue: ArrayDeque = ArrayDeque(), @@ -34,55 +46,63 @@ fun interface SerializerDescriptorsExtractor { return if (current == null) { extracted } else { - val currentDescriptors = elementDescriptors.getValue(current) + val currentDescriptors = elementDescriptorsExtractor.elementDescriptors(current) queue.addAll(currentDescriptors - extracted) extractDescriptors(queue.removeFirstOrNull(), queue, extracted + current) } } + } +} - private val elementDescriptors by MutableMapWithDefaultPut> { descriptor -> - when (descriptor.kind) { - SerialKind.ENUM -> emptyList() - - SerialKind.CONTEXTUAL -> emptyList() - - PrimitiveKind.BOOLEAN, - PrimitiveKind.BYTE, - PrimitiveKind.CHAR, - PrimitiveKind.SHORT, - PrimitiveKind.INT, - PrimitiveKind.LONG, - PrimitiveKind.FLOAT, - PrimitiveKind.DOUBLE, - PrimitiveKind.STRING -> emptyList() - - StructureKind.CLASS, - StructureKind.LIST, - StructureKind.MAP, - StructureKind.OBJECT -> descriptor.elementDescriptors - - PolymorphicKind.SEALED, - PolymorphicKind.OPEN -> - // Polymorphic descriptors have 2 elements, the 'type' and 'value' - we don't need either - // for generation, they're metadata that will be used later. - // The elements of 'value' are similarly unneeded, but their elements might contain new - // descriptors - so extract them - descriptor.elementDescriptors - .flatMap { it.elementDescriptors } - .flatMap { it.elementDescriptors } - - // Example: - // com.application.Polymorphic - // ├── 'type' descriptor (ignore / it's a String, so check its elements, it doesn't hurt) - // └── 'value' descriptor (check elements...) - // ├── com.application.Polymorphic (ignore) - // │ ├── Double (extract!) - // │ └── com.application.SomeOtherClass (extract!) - // └── com.application.Polymorphic (ignore) - // ├── UInt (extract!) - // └── List + + companion object { + + fun default(serializersModule: SerializersModule) = + TsElementDescriptorsExtractor { descriptor -> + when (descriptor.kind) { + SerialKind.ENUM -> emptyList() + + SerialKind.CONTEXTUAL -> emptyList() + + PrimitiveKind.BOOLEAN, + PrimitiveKind.BYTE, + PrimitiveKind.CHAR, + PrimitiveKind.SHORT, + PrimitiveKind.INT, + PrimitiveKind.LONG, + PrimitiveKind.FLOAT, + PrimitiveKind.DOUBLE, + PrimitiveKind.STRING -> emptyList() + + StructureKind.CLASS, + StructureKind.LIST, + StructureKind.MAP, + StructureKind.OBJECT -> descriptor.elementDescriptors + + PolymorphicKind.SEALED, + PolymorphicKind.OPEN -> + // Polymorphic descriptors have 2 elements, the 'type' and 'value' - we don't need either + // for generation, they're metadata that will be used later. + // The elements of 'value' are similarly unneeded, but their elements might contain new + // descriptors - so extract them + descriptor.elementDescriptors + .flatMap { it.elementDescriptors } + .flatMap { it.elementDescriptors } + + // Example: + // com.application.Polymorphic + // ├── 'type' descriptor (ignore / it's a String, so check its elements, it doesn't hurt) + // └── 'value' descriptor (check elements...) + // ├── com.application.Polymorphic (ignore) + // │ ├── Double (extract!) + // │ └── com.application.SomeOtherClass (extract!) + // └── com.application.Polymorphic (ignore) + // ├── UInt (extract!) + // └── List.shouldContainDescriptors(expected: Collection) { @@ -105,3 +142,27 @@ private object Example3 { val optional: SomeType?, ) } + + +@Suppress("unused") +private object Example4 { + + @Serializable + class SomeType(val a: String) + + @Serializable + class TypeHolder( + @kotlinx.serialization.Contextual + val required: SomeType, + ) +} + + +private object Example5 { + + @Serializable + sealed class Parent + + @Serializable + class SubClass(val x: String) : Parent() +}