From 4f7429751575f80a9bda815f17ff41addb967295 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 6 Apr 2022 23:21:03 +0200 Subject: [PATCH 1/8] tidy up polymorphism Polymorphism doesn't really exist as a TsElement, it's more like metadata. So remove it and directly convert sealed polymorphic descriptors in TsElementConverter.kt To do this it requires making TsElementConverter.kt return multiple elements... but I guess that's more flexible, so it's an improvement --- .../core/KxsTsSourceCodeGenerator.kt | 142 +++--------------- .../core/TsElementConverter.kt | 142 ++++++++++++------ .../dev.adamko.kxstsgen/core/tsElements.kt | 26 ---- 3 files changed, 121 insertions(+), 189 deletions(-) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt index 996fa4f5..e9a4ee4a 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt @@ -84,131 +84,37 @@ abstract class KxsTsSourceCodeGenerator( override fun generateInterface(element: TsDeclaration.TsInterface): String { - return when (element.polymorphism) { - is TsPolymorphism.Sealed -> generatePolyClosed(element, element.polymorphism) - is TsPolymorphism.Open -> generatePolyOpen(element, element.polymorphism) - null -> { - val properties = element - .properties - .joinToString(separator = "\n") { property -> - val separator = when (property) { - is TsProperty.Optional -> "?: " - is TsProperty.Required -> ": " - } - val propertyType = generateTypeReference(property.typeRef) - // generate ` name: Type;` - // or ` name:? Type;` - "${property.name}${separator}${propertyType};" - } - - buildString { - appendLine("export interface ${element.id.name} {") - if (properties.isNotBlank()) { - appendLine(properties.prependIndent(config.indent)) - } - append("}") - } - } - } - } - + val properties = element.properties + .joinToString(separator = "\n") { generateInterfaceProperty(it) } - // note: this isn't used because at present poly-open descriptors are converted to 'any' - private fun generatePolyOpen( - element: TsDeclaration.TsInterface, - polymorphism: TsPolymorphism.Open, - ): String { - val namespaceId = element.id - val namespaceRef = TsTypeRef.Declaration(namespaceId, null, false) - - val subInterfaceRefs = polymorphism.subclasses.map { - TsTypeRef.Declaration(it.id, namespaceRef, false) - }.toSet() - - val discriminatorProperty = TsProperty.Required( - polymorphism.discriminatorName, - TsTypeRef.Literal(TsLiteral.Primitive.TsString, false), - ) - - val subInterfaces = polymorphism - .subclasses - .map { it.copy(properties = it.properties + discriminatorProperty) } - .toSet() - - val namespace = TsDeclaration.TsNamespace( - element.id, - subInterfaces, - ) - - val subInterfaceTypeUnion = TsDeclaration.TsTypeAlias( - element.id, - subInterfaceRefs - ) - - return listOf(subInterfaceTypeUnion, namespace).joinToString("\n\n") { - generateDeclaration(it) + return buildString { + appendLine("export interface ${element.id.name} {") + if (properties.isNotBlank()) { + appendLine(properties.prependIndent(config.indent)) + } + append("}") } } /** - * Generate a 'sealed class' equivalent. - * - * * type union of all subclasses - * * a namespace with contains - * * a 'Type' enum - * * subclasses, each with an additional 'Type' property that has a literal 'Type' enum value + * Generate + * ```typescript + * name: Type; + * ``` + * or + * ```typescript + * name:? Type; + * ``` */ - private fun generatePolyClosed( - element: TsDeclaration.TsInterface, - polymorphism: TsPolymorphism.Sealed, + open fun generateInterfaceProperty( + property: TsProperty ): String { - val namespaceId = element.id - val namespaceRef = TsTypeRef.Declaration(namespaceId, null, false) - - val subInterfaceRefs: Map = - polymorphism.subclasses.associateBy { subclass -> - val subclassId = TsElementId(namespaceId.toString() + "." + subclass.id.name) - TsTypeRef.Declaration(subclassId, namespaceRef, false) - } - - val discriminatorEnum = TsDeclaration.TsEnum( - TsElementId("${element.id.namespace}.${polymorphism.discriminatorName.replaceFirstChar { it.uppercaseChar() }}"), - subInterfaceRefs.keys.map { it.id.name }.toSet(), - ) - - val discriminatorEnumRef = TsTypeRef.Declaration(discriminatorEnum.id, namespaceRef, false) - - val subInterfacesWithTypeProp = subInterfaceRefs.map { (subInterfaceRef, subclass) -> - val typePropId = TsElementId( - """ - |${discriminatorEnum.id.name}.${subInterfaceRef.id.name} - """.trimMargin() - ) - - val typeProp = TsProperty.Required( - polymorphism.discriminatorName, - TsTypeRef.Declaration(typePropId, discriminatorEnumRef, false), - ) - - subclass.copy(properties = setOf(typeProp) + subclass.properties) - } - - val subInterfaceTypeUnion = TsDeclaration.TsTypeAlias( - element.id, - subInterfaceRefs.keys - ) - - val namespace = TsDeclaration.TsNamespace( - namespaceId, - buildSet { - add(discriminatorEnum) - addAll(subInterfacesWithTypeProp) - } - ) - - return listOf(subInterfaceTypeUnion, namespace).joinToString("\n\n") { - generateDeclaration(it) + val separator = when (property) { + is TsProperty.Optional -> "?: " + is TsProperty.Required -> ": " } + val propertyType = generateTypeReference(property.typeRef) + return "${property.name}${separator}${propertyType};" } @@ -248,7 +154,6 @@ abstract class KxsTsSourceCodeGenerator( * constraint. */ override fun generateTypeReference( -// rootId: TsElementId?, typeRef: TsTypeRef, ): String { val plainType: String = when (typeRef) { @@ -301,7 +206,6 @@ abstract class KxsTsSourceCodeGenerator( override fun generateMapTypeReference( -// rootId: TsElementId?, tsMap: TsLiteral.TsMap ): String { val keyTypeRef = generateTypeReference(tsMap.keyTypeRef) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt index dfe29b38..6f9cab2a 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt @@ -13,7 +13,7 @@ fun interface TsElementConverter { operator fun invoke( descriptor: SerialDescriptor, - ): TsElement + ): Set open class Default( @@ -24,78 +24,123 @@ fun interface TsElementConverter { override operator fun invoke( descriptor: SerialDescriptor, - ): TsElement { + ): Set { return when (descriptor.kind) { - SerialKind.ENUM -> convertEnum(descriptor) + SerialKind.ENUM -> setOf(convertEnum(descriptor)) - PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean + PrimitiveKind.BOOLEAN -> setOf(TsLiteral.Primitive.TsBoolean) PrimitiveKind.CHAR, - PrimitiveKind.STRING -> TsLiteral.Primitive.TsString + PrimitiveKind.STRING -> setOf(TsLiteral.Primitive.TsString) PrimitiveKind.BYTE, PrimitiveKind.SHORT, PrimitiveKind.INT, PrimitiveKind.LONG, PrimitiveKind.FLOAT, - PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber + PrimitiveKind.DOUBLE -> setOf(TsLiteral.Primitive.TsNumber) - StructureKind.LIST -> convertList(descriptor) - StructureKind.MAP -> convertMap(descriptor) + StructureKind.LIST -> setOf(convertList(descriptor)) + StructureKind.MAP -> setOf(convertMap(descriptor)) StructureKind.CLASS, - StructureKind.OBJECT -> when { - descriptor.isInline -> convertTypeAlias(descriptor) - else -> convertInterface(descriptor, null) - } + StructureKind.OBJECT -> setOf( + when { + descriptor.isInline -> convertTypeAlias(descriptor) + else -> convertInterface(descriptor) + } + ) - PolymorphicKind.SEALED -> convertPolymorphic(descriptor) + PolymorphicKind.SEALED -> convertDiscriminatedInterface(descriptor) // TODO handle contextual // TODO handle polymorphic open SerialKind.CONTEXTUAL, - PolymorphicKind.OPEN -> { - val resultId = elementIdConverter(descriptor) - val fieldTypeRef = TsTypeRef.Literal(TsLiteral.Primitive.TsAny, false) - TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) - } + PolymorphicKind.OPEN -> setOf(createTypeAliasAny(descriptor)) } } - fun convertPolymorphic( + /** + * Handle sealed-polymorphic descriptors. + * + * Generate + * + * 1. a namespace that contains + * a. a 'type' enum, for each subclass + * b. the subclasses, as [TsDeclaration.TsInterface], with an additional 'type' field + * 2. a type union of all subclasses + */ + open fun convertDiscriminatedInterface( descriptor: SerialDescriptor, - ): TsDeclaration { - - if (descriptor.elementsCount == 0) { - return TsDeclaration.TsTypeAlias( - elementIdConverter(descriptor), - TsTypeRef.Literal(TsLiteral.Primitive.TsAny, false) - ) - } + ): Set { + // namespace details + val namespaceId = elementIdConverter(descriptor) + val namespaceRef = TsTypeRef.Declaration(namespaceId, null, false) + // discriminator name val discriminatorIndex = descriptor.elementDescriptors .indexOfFirst { it.kind == PrimitiveKind.STRING } - val discriminatorName = descriptor.getElementName(discriminatorIndex) + val discriminatorName = descriptor.elementNames.elementAtOrNull(discriminatorIndex) - val subclasses = descriptor - .elementDescriptors - .first { it.kind == SerialKind.CONTEXTUAL } + // subclasses details + val subclassInterfaces = descriptor .elementDescriptors + .firstOrNull { it.kind == SerialKind.CONTEXTUAL } + ?.elementDescriptors + ?.flatMap { this(it) } + ?.filterIsInstance() + ?.map { it.copy(id = TsElementId("${descriptor.serialName}.${it.id.name}")) } + ?.toSet() + ?: emptySet() + + val subInterfaceRefs: Map = + subclassInterfaces.associateBy { subclass -> + val subclassId = TsElementId(namespaceId.toString() + "." + subclass.id.name) + TsTypeRef.Declaration(subclassId, namespaceRef, false) + } - val subclassInterfaces = subclasses - .map { this(it) } - .filterIsInstance() - .map { it.copy(id = TsElementId("${descriptor.serialName}.${it.id.name}")) } - .toSet() + // verify a discriminated interface can be created + if (subInterfaceRefs.isEmpty() || discriminatorName.isNullOrBlank()) { + return setOf(createTypeAliasAny(descriptor)) + } else { + // discriminator enum + val discriminatorEnum = TsDeclaration.TsEnum( + TsElementId("${namespaceId.namespace}.${discriminatorName.replaceFirstChar { it.uppercaseChar() }}"), + subInterfaceRefs.keys.map { it.id.name }.toSet(), + ) + val discriminatorEnumRef = TsTypeRef.Declaration(discriminatorEnum.id, namespaceRef, false) - val polymorphism = when (descriptor.kind) { - PolymorphicKind.SEALED -> TsPolymorphism.Sealed(discriminatorName, subclassInterfaces) - PolymorphicKind.OPEN -> TsPolymorphism.Open(discriminatorName, subclassInterfaces) - else -> error("Can't convert non-polymorphic SerialKind ${descriptor.kind} to polymorphic interface") - } + // add discriminator property to subclasses + val subInterfacesWithTypeProp = subInterfaceRefs.map { (subInterfaceRef, subclass) -> - return convertInterface(descriptor, polymorphism) + val literalTypeRef = TsTypeRef.Declaration( + TsElementId("${discriminatorEnum.id.name}.${subInterfaceRef.id.name}"), + discriminatorEnumRef, + false, + ) + + val literalTypeProperty = TsProperty.Required(discriminatorName, literalTypeRef) + + subclass.copy(properties = setOf(literalTypeProperty) + subclass.properties) + } + + // create type union and namespace + val subInterfaceTypeUnion = TsDeclaration.TsTypeAlias( + namespaceId, + subInterfaceRefs.keys + ) + + val namespace = TsDeclaration.TsNamespace( + namespaceId, + buildSet { + add(discriminatorEnum) + addAll(subInterfacesWithTypeProp) + } + ) + + return setOf(subInterfaceTypeUnion, namespace) + } } @@ -111,7 +156,6 @@ fun interface TsElementConverter { fun convertInterface( descriptor: SerialDescriptor, - polymorphism: TsPolymorphism?, ): TsDeclaration { val resultId = elementIdConverter(descriptor) @@ -123,7 +167,8 @@ fun interface TsElementConverter { else -> TsProperty.Required(name, fieldTypeRef) } }.toSet() - return TsDeclaration.TsInterface(resultId, properties, polymorphism) + + return TsDeclaration.TsInterface(resultId, properties) } @@ -157,5 +202,14 @@ fun interface TsElementConverter { return TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) } + + + open fun createTypeAliasAny( + descriptor: SerialDescriptor, + ): TsDeclaration.TsTypeAlias { + val resultId = elementIdConverter(descriptor) + val fieldTypeRef = TsTypeRef.Literal(TsLiteral.Primitive.TsAny, false) + return TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) + } } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt index e1dfc318..634003aa 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt @@ -51,7 +51,6 @@ sealed interface TsDeclaration : TsElement { data class TsInterface( override val id: TsElementId, val properties: Set, - val polymorphism: TsPolymorphism?, ) : TsDeclaration @@ -161,28 +160,3 @@ sealed interface TsProperty { override val typeRef: TsTypeRef, ) : TsProperty } - - -/** - * Meta-data about the polymorphism of a [TsDeclaration.TsInterface]. - */ -sealed interface TsPolymorphism { - - /** The name of the field used to discriminate between [subclasses]. */ - val discriminatorName: String - val subclasses: Set - - - data class Sealed( - override val discriminatorName: String, - override val subclasses: Set, - ) : TsPolymorphism - - - /** Note: [Open] is not implemented correctly */ - @UnimplementedKxTsGenApi - data class Open( - override val discriminatorName: String, - override val subclasses: Set, - ) : TsPolymorphism -} From 5aeb4a1361f2e846b697747695c0ba86681fd184 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 6 Apr 2022 23:21:33 +0200 Subject: [PATCH 2/8] prevent warnings by making funs open, and some code tidying --- .../kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt | 1 + .../dev.adamko.kxstsgen/core/TsElementConverter.kt | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt index 666e95d8..5f0d83fe 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt @@ -1,5 +1,6 @@ package dev.adamko.kxstsgen +import dev.adamko.kxstsgen.core.TsDeclaration import dev.adamko.kxstsgen.core.UnimplementedKxTsGenApi import dev.adamko.kxstsgen.core.util.MutableMapWithDefaultPut import kotlin.jvm.JvmInline diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt index 6f9cab2a..f864a4e1 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt @@ -144,7 +144,7 @@ fun interface TsElementConverter { } - fun convertTypeAlias( + open fun convertTypeAlias( structDescriptor: SerialDescriptor, ): TsDeclaration { val resultId = elementIdConverter(structDescriptor) @@ -154,7 +154,7 @@ fun interface TsElementConverter { } - fun convertInterface( + open fun convertInterface( descriptor: SerialDescriptor, ): TsDeclaration { val resultId = elementIdConverter(descriptor) @@ -172,7 +172,7 @@ fun interface TsElementConverter { } - fun convertEnum( + open fun convertEnum( enumDescriptor: SerialDescriptor, ): TsDeclaration.TsEnum { val resultId = elementIdConverter(enumDescriptor) @@ -180,7 +180,7 @@ fun interface TsElementConverter { } - fun convertList( + open fun convertList( listDescriptor: SerialDescriptor, ): TsLiteral.TsList { val elementDescriptor = listDescriptor.elementDescriptors.first() @@ -189,7 +189,7 @@ fun interface TsElementConverter { } - fun convertMap( + open fun convertMap( mapDescriptor: SerialDescriptor, ): TsLiteral.TsMap { From 8935ef1301247fd4d8cb4078f1a05894895b658f Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Wed, 6 Apr 2022 23:22:51 +0200 Subject: [PATCH 3/8] 'memoize' some generator code, and add kdoc, and make things open to prevent warnings --- .../dev.adamko.kxstsgen/KxsTsGenerator.kt | 64 +++++++++++++++---- 1 file changed, 53 insertions(+), 11 deletions(-) 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 6b03ed67..12320fa4 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 @@ -3,42 +3,84 @@ package dev.adamko.kxstsgen import dev.adamko.kxstsgen.core.KxsTsSourceCodeGenerator import dev.adamko.kxstsgen.core.SerializerDescriptorsExtractor import dev.adamko.kxstsgen.core.TsDeclaration +import dev.adamko.kxstsgen.core.TsElement import dev.adamko.kxstsgen.core.TsElementConverter +import dev.adamko.kxstsgen.core.TsElementId import dev.adamko.kxstsgen.core.TsElementIdConverter import dev.adamko.kxstsgen.core.TsMapTypeConverter +import dev.adamko.kxstsgen.core.TsTypeRef import dev.adamko.kxstsgen.core.TsTypeRefConverter import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +/** + * Generate TypeScript from [`@Serializable`][Serializable] Kotlin. + * + * The output can be controlled by the settings in [config], + * or by setting hardcoded values in [serializerDescriptors] or [descriptorElements], + * or changed by overriding any converter. + * + * @param[config] General settings that affect how KxTsGen works + * @param[descriptorsExtractor] Given a [KSerializer], extract all [SerialDescriptor]s + * @param[elementIdConverter] Create an [TsElementId] from a [SerialDescriptor] + * @param[mapTypeConverter] Decides how [Map]s should be converted + * @param[typeRefConverter] Creates [TsTypeRef]s + * @param[elementConverter] Converts [SerialDescriptor]s to [TsElement]s + * @param[sourceCodeGenerator] Convert [TsElement]s to TypeScript source code + */ open class KxsTsGenerator( - val config: KxsTsConfig = KxsTsConfig(), + open val config: KxsTsConfig = KxsTsConfig(), - val descriptorsExtractor: SerializerDescriptorsExtractor = SerializerDescriptorsExtractor.Default, + open val descriptorsExtractor: SerializerDescriptorsExtractor = SerializerDescriptorsExtractor.Default, - val elementIdConverter: TsElementIdConverter = TsElementIdConverter.Default, + open val elementIdConverter: TsElementIdConverter = TsElementIdConverter.Default, - val mapTypeConverter: TsMapTypeConverter = TsMapTypeConverter.Default, + open val mapTypeConverter: TsMapTypeConverter = TsMapTypeConverter.Default, - val typeRefConverter: TsTypeRefConverter = + open val typeRefConverter: TsTypeRefConverter = TsTypeRefConverter.Default(elementIdConverter, mapTypeConverter), - val elementConverter: TsElementConverter = + open val elementConverter: TsElementConverter = TsElementConverter.Default( elementIdConverter, mapTypeConverter, typeRefConverter, ), - val sourceCodeGenerator: KxsTsSourceCodeGenerator = KxsTsSourceCodeGenerator.Default(config) + open val sourceCodeGenerator: KxsTsSourceCodeGenerator = KxsTsSourceCodeGenerator.Default(config), ) { - fun generate(vararg serializers: KSerializer<*>): String { + /** + * Stateful cache of all [descriptors][SerialDescriptor] extracted from a + * [serializer][KSerializer]. + * + * To customise the descriptors that a serializer produces, set value into this map. + */ + open val serializerDescriptors: MutableMap, Set> = mutableMapOf() - val descriptors = serializers.flatMap { descriptorsExtractor(it) }.toSet() + /** + * Cache of all [elements][TsElement] that are created from any [descriptor][SerialDescriptor]. + * + * To customise the elements that a descriptor produces, set value into this map. + */ + open val descriptorElements: MutableMap> = mutableMapOf() - val elements = descriptors.map { elementConverter(it) } + open fun generate(vararg serializers: KSerializer<*>): String { + return serializers + + // 1. get all SerialDescriptors from a KSerializer + .flatMap { serializer -> + serializerDescriptors.getOrPut(serializer) { descriptorsExtractor(serializer) } + } + .toSet() + + // 2. convert each SerialDescriptor to some TsElements + .flatMap { descriptor -> + descriptorElements.getOrPut(descriptor) { elementConverter(descriptor) } + } + .toSet() - return elements .groupBy { element -> sourceCodeGenerator.groupElementsBy(element) } .mapValues { (_, elements) -> elements From f4c6b2c62d0fb37b559033254f9df5e913f56268 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Thu, 7 Apr 2022 00:21:03 +0200 Subject: [PATCH 4/8] #9 initial Tuple implementation --- docs/code/example/example-enum-class-01.kt | 2 - docs/code/example/example-enum-class-02.kt | 2 - docs/code/example/example-format-tuple-01.kt | 21 +++++++++ docs/code/test/TsExportFormatTest.kt | 24 ++++++++++ docs/enums.md | 5 -- docs/export-formats.md | 46 +++++++++++++++++++ .../kotlin/dev.adamko.kxstsgen/TsExport.kt | 16 +++++++ .../core/KxsTsSourceCodeGenerator.kt | 12 +++++ .../core/TsElementConverter.kt | 40 ++++++++++++---- .../dev.adamko.kxstsgen/core/_annotations.kt | 2 +- .../dev.adamko.kxstsgen/core/tsElements.kt | 9 ++++ 11 files changed, 159 insertions(+), 20 deletions(-) create mode 100644 docs/code/example/example-format-tuple-01.kt create mode 100644 docs/code/test/TsExportFormatTest.kt create mode 100644 docs/export-formats.md create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsExport.kt diff --git a/docs/code/example/example-enum-class-01.kt b/docs/code/example/example-enum-class-01.kt index f21a7eab..d0bd0878 100644 --- a/docs/code/example/example-enum-class-01.kt +++ b/docs/code/example/example-enum-class-01.kt @@ -2,8 +2,6 @@ @file:Suppress("PackageDirectoryMismatch", "unused") package dev.adamko.kxstsgen.example.exampleEnumClass01 -import kotlinx.serialization.* -import dev.adamko.kxstsgen.* import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/code/example/example-enum-class-02.kt b/docs/code/example/example-enum-class-02.kt index 26b662a6..4e6eebf1 100644 --- a/docs/code/example/example-enum-class-02.kt +++ b/docs/code/example/example-enum-class-02.kt @@ -2,8 +2,6 @@ @file:Suppress("PackageDirectoryMismatch", "unused") package dev.adamko.kxstsgen.example.exampleEnumClass02 -import kotlinx.serialization.* -import dev.adamko.kxstsgen.* import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/code/example/example-format-tuple-01.kt b/docs/code/example/example-format-tuple-01.kt new file mode 100644 index 00000000..ba38a5f7 --- /dev/null +++ b/docs/code/example/example-format-tuple-01.kt @@ -0,0 +1,21 @@ +// This file was automatically generated from export-formats.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleFormatTuple01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +@TsExport(format = TsExport.Format.TUPLE) +class SimpleTypes( + val aString: String, + var anInt: Int, + val aDouble: Double, + val bool: Boolean, + private val privateMember: String, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(SimpleTypes.serializer())) +} diff --git a/docs/code/test/TsExportFormatTest.kt b/docs/code/test/TsExportFormatTest.kt new file mode 100644 index 00000000..8a1936ce --- /dev/null +++ b/docs/code/test/TsExportFormatTest.kt @@ -0,0 +1,24 @@ +// This file was automatically generated from export-formats.md by Knit tool. Do not edit. +@file:Suppress("JSUnusedLocalSymbols") +package dev.adamko.kxstsgen.example.test + +import io.kotest.matchers.* +import kotlinx.knit.test.* +import org.junit.jupiter.api.Test +import dev.adamko.kxstsgen.util.* + +class TsExportFormatTest { + @Test + fun testExampleFormatTuple01() { + captureOutput("ExampleFormatTuple01") { + dev.adamko.kxstsgen.example.exampleFormatTuple01.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export type SimpleTypes = [string, number, number, boolean, string]; + """.trimMargin() + .normalize() + ) + } +} diff --git a/docs/enums.md b/docs/enums.md index 68d9a152..1acd2831 100644 --- a/docs/enums.md +++ b/docs/enums.md @@ -21,11 +21,6 @@ import dev.adamko.kxstsgen.* ### Simple enum - - ```kotlin @Serializable enum class SomeType { diff --git a/docs/export-formats.md b/docs/export-formats.md new file mode 100644 index 00000000..d9de704f --- /dev/null +++ b/docs/export-formats.md @@ -0,0 +1,46 @@ + + + +**Table of contents** + + + +* [Introduction](#introduction) + * [Tuple](#tuple) + + + + + + +## Introduction + +### Tuple + +```kotlin +@Serializable +@TsExport(format = TsExport.Format.TUPLE) +class SimpleTypes( + val aString: String, + var anInt: Int, + val aDouble: Double, + val bool: Boolean, + private val privateMember: String, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(SimpleTypes.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-format-tuple-01.kt). + +```typescript +export type SimpleTypes = [string, number, number, boolean, string]; +``` + + diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsExport.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsExport.kt new file mode 100644 index 00000000..562fac1d --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsExport.kt @@ -0,0 +1,16 @@ +package dev.adamko.kxstsgen + +import kotlinx.serialization.SerialInfo + + +@Target(AnnotationTarget.CLASS) +@SerialInfo +@MustBeDocumented +annotation class TsExport( +// val name: String = "", + val format: Format, +) { + enum class Format { + TUPLE, + } +} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt index e9a4ee4a..c4bd890c 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt @@ -18,6 +18,7 @@ abstract class KxsTsSourceCodeGenerator( is TsDeclaration.TsInterface -> generateInterface(element) is TsDeclaration.TsNamespace -> generateNamespace(element) is TsDeclaration.TsTypeAlias -> generateType(element) + is TsDeclaration.TsTuple -> generateTuple(element) } } @@ -25,6 +26,7 @@ abstract class KxsTsSourceCodeGenerator( abstract fun generateInterface(element: TsDeclaration.TsInterface): String abstract fun generateNamespace(namespace: TsDeclaration.TsNamespace): String abstract fun generateType(element: TsDeclaration.TsTypeAlias): String + abstract fun generateTuple(element: TsDeclaration.TsTuple): String abstract fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String @@ -148,6 +150,16 @@ abstract class KxsTsSourceCodeGenerator( } } + override fun generateTuple(element: TsDeclaration.TsTuple): String { + val types = element.typeRefs + .map { generateTypeReference(it) } + .joinToString(separator = ", ", prefix = "[", postfix = "]") + + return """ + |export type ${element.id.name} = $types; + """.trimMargin() + } + /** * A type-reference, be it for the field of an interface, a type alias, or a generic type diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt index f864a4e1..44ed05f6 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt @@ -1,5 +1,6 @@ package dev.adamko.kxstsgen.core +import dev.adamko.kxstsgen.TsExport import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor @@ -25,6 +26,16 @@ fun interface TsElementConverter { override operator fun invoke( descriptor: SerialDescriptor, ): Set { + + descriptor.annotations + .filterIsInstance() + .map { it.format } + .firstOrNull { format -> + return when (format) { + TsExport.Format.TUPLE -> setOf(convertTuple(descriptor)) + } + } + return when (descriptor.kind) { SerialKind.ENUM -> setOf(convertEnum(descriptor)) @@ -157,18 +168,27 @@ fun interface TsElementConverter { open fun convertInterface( descriptor: SerialDescriptor, ): TsDeclaration { - val resultId = elementIdConverter(descriptor) + val resultId = elementIdConverter(descriptor) - val properties = descriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> - val name = descriptor.getElementName(index) - val fieldTypeRef = typeRefConverter(fieldDescriptor) - when { - descriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeRef) - else -> TsProperty.Required(name, fieldTypeRef) - } - }.toSet() + val properties = descriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> + val name = descriptor.getElementName(index) + val fieldTypeRef = typeRefConverter(fieldDescriptor) + when { + descriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeRef) + else -> TsProperty.Required(name, fieldTypeRef) + } + }.toSet() + + return TsDeclaration.TsInterface(resultId, properties) + } - return TsDeclaration.TsInterface(resultId, properties) + + open fun convertTuple( + descriptor: SerialDescriptor, + ): TsDeclaration.TsTuple { + val resultId = elementIdConverter(descriptor) + val typeRefs = descriptor.elementDescriptors.map{typeRefConverter(it)} + return TsDeclaration.TsTuple(resultId, typeRefs) } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/_annotations.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/_annotations.kt index 56af3080..3b9b0ae8 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/_annotations.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/_annotations.kt @@ -5,7 +5,7 @@ package dev.adamko.kxstsgen.core AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, AnnotationTarget.FUNCTION, - AnnotationTarget.TYPEALIAS + AnnotationTarget.TYPEALIAS, ) @RequiresOptIn(level = RequiresOptIn.Level.WARNING) @MustBeDocumented diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt index 634003aa..9654f72a 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt @@ -48,6 +48,15 @@ sealed interface TsDeclaration : TsElement { } + data class TsTuple( + override val id: TsElementId, + val typeRefs: List, + ) : TsDeclaration { + constructor(id: TsElementId, typeRef: TsTypeRef, vararg typeRefs: TsTypeRef) : + this(id, listOf(typeRef) + typeRefs.toList()) + } + + data class TsInterface( override val id: TsElementId, val properties: Set, From b22f8f76b47bbc78153ced86cd6c9347dc64a80c Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Thu, 7 Apr 2022 20:10:39 +0200 Subject: [PATCH 5/8] code tidy up --- .../dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt | 8 ++++---- .../core/{_annotations.kt => annotations.kt} | 0 2 files changed, 4 insertions(+), 4 deletions(-) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/{_annotations.kt => annotations.kt} (100%) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt index c4bd890c..1a6a0a3e 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt @@ -151,12 +151,12 @@ abstract class KxsTsSourceCodeGenerator( } override fun generateTuple(element: TsDeclaration.TsTuple): String { - val types = element.typeRefs - .map { generateTypeReference(it) } - .joinToString(separator = ", ", prefix = "[", postfix = "]") + val types = element.typeRefs.joinToString(separator = ", ") { + generateTypeReference(it) + } return """ - |export type ${element.id.name} = $types; + |export type ${element.id.name} = [$types]; """.trimMargin() } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/_annotations.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/annotations.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/_annotations.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/annotations.kt From 8b07ff446218f197aca8af93e69a48b3a2cd97c6 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 9 Apr 2022 00:07:02 +0200 Subject: [PATCH 6/8] finish Tuple implementation - create tuple serializer builder - tuple tests - re-use TsProperty for tuples (make 'optional' a boolean field, two data classes wasn't needed) - remove TsExport (TypeScript generation can be controlled entirely through Kxs descriptors) --- docs/code/example/example-format-tuple-01.kt | 37 +++- docs/code/example/example-format-tuple-02.kt | 40 ++++ docs/code/example/example-format-tuple-03.kt | 36 ++++ docs/code/example/example-format-tuple-04.kt | 21 ++ docs/code/test/TsExportFormatTest.kt | 50 ++++- docs/export-formats.md | 186 +++++++++++++++++- .../kotlin/dev.adamko.kxstsgen/TsExport.kt | 16 -- .../core/KxsTsSourceCodeGenerator.kt | 19 +- .../core/TsElementConverter.kt | 49 +++-- .../core/TsTypeRefConverter.kt | 5 +- .../core/experiments/tuple.kt | 130 ++++++++++++ .../dev.adamko.kxstsgen/core/tsElements.kt | 42 ++-- 12 files changed, 546 insertions(+), 85 deletions(-) create mode 100644 docs/code/example/example-format-tuple-02.kt create mode 100644 docs/code/example/example-format-tuple-03.kt create mode 100644 docs/code/example/example-format-tuple-04.kt delete mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsExport.kt create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/experiments/tuple.kt diff --git a/docs/code/example/example-format-tuple-01.kt b/docs/code/example/example-format-tuple-01.kt index ba38a5f7..b1cde9f7 100644 --- a/docs/code/example/example-format-tuple-01.kt +++ b/docs/code/example/example-format-tuple-01.kt @@ -2,18 +2,43 @@ @file:Suppress("PackageDirectoryMismatch", "unused") package dev.adamko.kxstsgen.example.exampleFormatTuple01 -import kotlinx.serialization.* import dev.adamko.kxstsgen.* +import dev.adamko.kxstsgen.core.experiments.TupleSerializer +import kotlinx.serialization.* -@Serializable -@TsExport(format = TsExport.Format.TUPLE) -class SimpleTypes( +@Serializable(with = SimpleTypes.SimpleTypesSerializer::class) +data class SimpleTypes( val aString: String, var anInt: Int, - val aDouble: Double, + val aDouble: Double?, val bool: Boolean, private val privateMember: String, -) +) { + // Create `SimpleTypesSerializer` inside `SimpleTypes`, so it + // has access to the private property `privateMember`. + object SimpleTypesSerializer : TupleSerializer( + "SimpleTypes", + { + // Provide all tuple elements, in order, using the 'elements' helper method. + element(SimpleTypes::aString) + element(SimpleTypes::anInt) + element(SimpleTypes::aDouble) + element(SimpleTypes::bool) + element(SimpleTypes::privateMember) + } + ) { + override fun tupleConstructor(elements: List<*>): SimpleTypes { + // When deserializing, the elements will be available as a list, in the order defined + return SimpleTypes( + elements[0] as String, + elements[1] as Int, + elements[2] as Double, + elements[3] as Boolean, + elements[4] as String, + ) + } + } +} fun main() { val tsGenerator = KxsTsGenerator() diff --git a/docs/code/example/example-format-tuple-02.kt b/docs/code/example/example-format-tuple-02.kt new file mode 100644 index 00000000..2c5152cd --- /dev/null +++ b/docs/code/example/example-format-tuple-02.kt @@ -0,0 +1,40 @@ +// This file was automatically generated from export-formats.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleFormatTuple02 + +import dev.adamko.kxstsgen.* +import dev.adamko.kxstsgen.core.experiments.TupleSerializer +import kotlinx.serialization.* + +@Serializable(with = OptionalFields.Serializer::class) +data class OptionalFields( + val requiredString: String, + val optionalString: String = "", + val nullableString: String?, + val nullableOptionalString: String? = "", +) { + object Serializer : TupleSerializer( + "OptionalFields", + { + element(OptionalFields::requiredString) + element(OptionalFields::optionalString) + element(OptionalFields::nullableString) + element(OptionalFields::nullableOptionalString) + } + ) { + override fun tupleConstructor(elements: List<*>): OptionalFields { + val iter = elements.iterator() + return OptionalFields( + iter.next() as String, + iter.next() as String, + iter.next() as String, + iter.next() as String, + ) + } + } +} + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(OptionalFields.serializer())) +} diff --git a/docs/code/example/example-format-tuple-03.kt b/docs/code/example/example-format-tuple-03.kt new file mode 100644 index 00000000..a465d7ad --- /dev/null +++ b/docs/code/example/example-format-tuple-03.kt @@ -0,0 +1,36 @@ +// This file was automatically generated from export-formats.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleFormatTuple03 + +import dev.adamko.kxstsgen.* +import dev.adamko.kxstsgen.core.experiments.TupleSerializer +import kotlinx.serialization.* + +@Serializable(with = Coordinates.Serializer::class) +data class Coordinates( + val x: Int, + val y: Int, + val z: Int, +) { + object Serializer : TupleSerializer( + "Coordinates", + { + element(Coordinates::x) + element(Coordinates::y) + element(Coordinates::z) + } + ) { + override fun tupleConstructor(elements: List<*>): Coordinates { + return Coordinates( + elements[0] as Int, + elements[1] as Int, + elements[2] as Int, + ) + } + } +} + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Coordinates.serializer())) +} diff --git a/docs/code/example/example-format-tuple-04.kt b/docs/code/example/example-format-tuple-04.kt new file mode 100644 index 00000000..0df86e2e --- /dev/null +++ b/docs/code/example/example-format-tuple-04.kt @@ -0,0 +1,21 @@ +// This file was automatically generated from export-formats.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleFormatTuple04 + +import dev.adamko.kxstsgen.* +import dev.adamko.kxstsgen.core.experiments.TupleSerializer +import kotlinx.serialization.* + +import dev.adamko.kxstsgen.example.exampleFormatTuple03.Coordinates + +@Serializable +class GameLocations( + val homeLocation: Coordinates, + val allLocations: List, + val namedLocations: Map, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(GameLocations.serializer())) +} diff --git a/docs/code/test/TsExportFormatTest.kt b/docs/code/test/TsExportFormatTest.kt index 8a1936ce..443a5ce6 100644 --- a/docs/code/test/TsExportFormatTest.kt +++ b/docs/code/test/TsExportFormatTest.kt @@ -16,7 +16,55 @@ class TsExportFormatTest { .shouldBe( // language=TypeScript """ - |export type SimpleTypes = [string, number, number, boolean, string]; + |export type SimpleTypes = [string, number, number | null, boolean, string]; + """.trimMargin() + .normalize() + ) + } + + @Test + fun testExampleFormatTuple02() { + captureOutput("ExampleFormatTuple02") { + dev.adamko.kxstsgen.example.exampleFormatTuple02.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export type OptionalFields = [string, string, string | null, string | null]; + """.trimMargin() + .normalize() + ) + } + + @Test + fun testExampleFormatTuple03() { + captureOutput("ExampleFormatTuple03") { + dev.adamko.kxstsgen.example.exampleFormatTuple03.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export type Coordinates = [number, number, number]; + """.trimMargin() + .normalize() + ) + } + + @Test + fun testExampleFormatTuple04() { + captureOutput("ExampleFormatTuple04") { + dev.adamko.kxstsgen.example.exampleFormatTuple04.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export interface GameLocations { + | homeLocation: Coordinates; + | allLocations: Coordinates[]; + | namedLocations: { [key: string]: Coordinates }; + |} + | + |export type Coordinates = [number, number, number]; """.trimMargin() .normalize() ) diff --git a/docs/export-formats.md b/docs/export-formats.md index d9de704f..a9866175 100644 --- a/docs/export-formats.md +++ b/docs/export-formats.md @@ -6,30 +6,73 @@ * [Introduction](#introduction) - * [Tuple](#tuple) + * [Tuples](#tuples) + * [Tuple example](#tuple-example) + * [Optional elements in tuples](#optional-elements-in-tuples) + * [Properties all the same type](#properties-all-the-same-type) + * [Tuples as interface properties](#tuples-as-interface-properties) ## Introduction -### Tuple +### Tuples + +In TypeScript, tuples are a compact format for data structures. They're like fixed-length arrays +that only contain the type. This is especially useful when using JSON, as including property names +means the messages are much larger. + +Tuples are a bit difficult to create in Kotlinx Serialization, but KxTsGen includes +[TupleSerializer](../modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen) +which can help. It requires a name, an ordered list of elements, and a constructor for +deserializing. + +#### Tuple example + +Let's say we have a class, `SimpleTypes`, that we want to serializer. We need to create a bespoke +tuple serializer for it, so override the plugin-generated serializer. ```kotlin -@Serializable -@TsExport(format = TsExport.Format.TUPLE) -class SimpleTypes( +@Serializable(with = SimpleTypes.SimpleTypesSerializer::class) +data class SimpleTypes( val aString: String, var anInt: Int, - val aDouble: Double, + val aDouble: Double?, val bool: Boolean, private val privateMember: String, -) +) { + // Create `SimpleTypesSerializer` inside `SimpleTypes`, so it + // has access to the private property `privateMember`. + object SimpleTypesSerializer : TupleSerializer( + "SimpleTypes", + { + // Provide all tuple elements, in order, using the 'elements' helper method. + element(SimpleTypes::aString) + element(SimpleTypes::anInt) + element(SimpleTypes::aDouble) + element(SimpleTypes::bool) + element(SimpleTypes::privateMember) + } + ) { + override fun tupleConstructor(elements: List<*>): SimpleTypes { + // When deserializing, the elements will be available as a list, in the order defined + return SimpleTypes( + elements[0] as String, + elements[1] as Int, + elements[2] as Double, + elements[3] as Boolean, + elements[4] as String, + ) + } + } +} fun main() { val tsGenerator = KxsTsGenerator() @@ -40,7 +83,132 @@ fun main() { > You can get the full code [here](./code/example/example-format-tuple-01.kt). ```typescript -export type SimpleTypes = [string, number, number, boolean, string]; +export type SimpleTypes = [string, number, number | null, boolean, string]; +``` + + + +#### Optional elements in tuples + +`TupleSerializer` does not consider whether a field is optional. This is intentional, partly because +it's quite complicated to set up already, and more options won't help! Also, tuples require that +optional elements must come at the end, which again would make defining tuples more complicated. + +If optional tuple elements is important to you, please make an issue and explaining your usecase, +requirements, and potential solutions or outcomes. + +```kotlin +@Serializable(with = OptionalFields.Serializer::class) +data class OptionalFields( + val requiredString: String, + val optionalString: String = "", + val nullableString: String?, + val nullableOptionalString: String? = "", +) { + object Serializer : TupleSerializer( + "OptionalFields", + { + element(OptionalFields::requiredString) + element(OptionalFields::optionalString) + element(OptionalFields::nullableString) + element(OptionalFields::nullableOptionalString) + } + ) { + override fun tupleConstructor(elements: List<*>): OptionalFields { + val iter = elements.iterator() + return OptionalFields( + iter.next() as String, + iter.next() as String, + iter.next() as String, + iter.next() as String, + ) + } + } +} + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(OptionalFields.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-format-tuple-02.kt). + +```typescript +export type OptionalFields = [string, string, string | null, string | null]; +``` + + + +#### Properties all the same type + +```kotlin +@Serializable(with = Coordinates.Serializer::class) +data class Coordinates( + val x: Int, + val y: Int, + val z: Int, +) { + object Serializer : TupleSerializer( + "Coordinates", + { + element(Coordinates::x) + element(Coordinates::y) + element(Coordinates::z) + } + ) { + override fun tupleConstructor(elements: List<*>): Coordinates { + return Coordinates( + elements[0] as Int, + elements[1] as Int, + elements[2] as Int, + ) + } + } +} + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Coordinates.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-format-tuple-03.kt). + +```typescript +export type Coordinates = [number, number, number]; +``` + + + +#### Tuples as interface properties + +```kotlin +import dev.adamko.kxstsgen.example.exampleFormatTuple03.Coordinates + +@Serializable +class GameLocations( + val homeLocation: Coordinates, + val allLocations: List, + val namedLocations: Map, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(GameLocations.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-format-tuple-04.kt). + +```typescript +export interface GameLocations { + homeLocation: Coordinates; + allLocations: Coordinates[]; + namedLocations: { [key: string]: Coordinates }; +} + +export type Coordinates = [number, number, number]; ``` diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsExport.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsExport.kt deleted file mode 100644 index 562fac1d..00000000 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsExport.kt +++ /dev/null @@ -1,16 +0,0 @@ -package dev.adamko.kxstsgen - -import kotlinx.serialization.SerialInfo - - -@Target(AnnotationTarget.CLASS) -@SerialInfo -@MustBeDocumented -annotation class TsExport( -// val name: String = "", - val format: Format, -) { - enum class Format { - TUPLE, - } -} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt index 1a6a0a3e..45801db1 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt @@ -26,7 +26,7 @@ abstract class KxsTsSourceCodeGenerator( abstract fun generateInterface(element: TsDeclaration.TsInterface): String abstract fun generateNamespace(namespace: TsDeclaration.TsNamespace): String abstract fun generateType(element: TsDeclaration.TsTypeAlias): String - abstract fun generateTuple(element: TsDeclaration.TsTuple): String + abstract fun generateTuple(tuple: TsDeclaration.TsTuple): String abstract fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String @@ -109,12 +109,9 @@ abstract class KxsTsSourceCodeGenerator( * ``` */ open fun generateInterfaceProperty( - property: TsProperty + property: TsProperty.Named ): String { - val separator = when (property) { - is TsProperty.Optional -> "?: " - is TsProperty.Required -> ": " - } + val separator = if (property.optional) "?: " else ": " val propertyType = generateTypeReference(property.typeRef) return "${property.name}${separator}${propertyType};" } @@ -150,13 +147,15 @@ abstract class KxsTsSourceCodeGenerator( } } - override fun generateTuple(element: TsDeclaration.TsTuple): String { - val types = element.typeRefs.joinToString(separator = ", ") { - generateTypeReference(it) + override fun generateTuple(tuple: TsDeclaration.TsTuple): String { + + val types = tuple.elements.joinToString(separator = ", ") { + val optionalMarker = if (it.optional) "?" else "" + generateTypeReference(it.typeRef) + optionalMarker } return """ - |export type ${element.id.name} = [$types]; + |export type ${tuple.id.name} = [$types]; """.trimMargin() } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt index 44ed05f6..2d55c9b2 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt @@ -1,6 +1,5 @@ package dev.adamko.kxstsgen.core -import dev.adamko.kxstsgen.TsExport import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor @@ -26,16 +25,6 @@ fun interface TsElementConverter { override operator fun invoke( descriptor: SerialDescriptor, ): Set { - - descriptor.annotations - .filterIsInstance() - .map { it.format } - .firstOrNull { format -> - return when (format) { - TsExport.Format.TUPLE -> setOf(convertTuple(descriptor)) - } - } - return when (descriptor.kind) { SerialKind.ENUM -> setOf(convertEnum(descriptor)) @@ -51,7 +40,13 @@ fun interface TsElementConverter { PrimitiveKind.FLOAT, PrimitiveKind.DOUBLE -> setOf(TsLiteral.Primitive.TsNumber) - StructureKind.LIST -> setOf(convertList(descriptor)) + StructureKind.LIST -> setOf( + when { + descriptor.elementDescriptors.count() > 1 -> convertTuple(descriptor) + else -> convertList(descriptor) + } + ) + StructureKind.MAP -> setOf(convertMap(descriptor)) StructureKind.CLASS, @@ -131,7 +126,7 @@ fun interface TsElementConverter { false, ) - val literalTypeProperty = TsProperty.Required(discriminatorName, literalTypeRef) + val literalTypeProperty = TsProperty.Named(discriminatorName, false, literalTypeRef) subclass.copy(properties = setOf(literalTypeProperty) + subclass.properties) } @@ -168,18 +163,16 @@ fun interface TsElementConverter { open fun convertInterface( descriptor: SerialDescriptor, ): TsDeclaration { - val resultId = elementIdConverter(descriptor) + val resultId = elementIdConverter(descriptor) - val properties = descriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> - val name = descriptor.getElementName(index) - val fieldTypeRef = typeRefConverter(fieldDescriptor) - when { - descriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeRef) - else -> TsProperty.Required(name, fieldTypeRef) - } - }.toSet() + val properties = descriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> + val name = descriptor.getElementName(index) + val fieldTypeRef = typeRefConverter(fieldDescriptor) + val optional = descriptor.isElementOptional(index) + TsProperty.Named(name, optional, fieldTypeRef) + }.toSet() - return TsDeclaration.TsInterface(resultId, properties) + return TsDeclaration.TsInterface(resultId, properties) } @@ -187,8 +180,14 @@ fun interface TsElementConverter { descriptor: SerialDescriptor, ): TsDeclaration.TsTuple { val resultId = elementIdConverter(descriptor) - val typeRefs = descriptor.elementDescriptors.map{typeRefConverter(it)} - return TsDeclaration.TsTuple(resultId, typeRefs) + + val properties = descriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> + val fieldTypeRef = typeRefConverter(fieldDescriptor) + val optional = descriptor.isElementOptional(index) + TsProperty.Unnamed(optional, fieldTypeRef) + } + + return TsDeclaration.TsTuple(resultId, properties) } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsTypeRefConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsTypeRefConverter.kt index d1d5f74d..ea23ea4f 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsTypeRefConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsTypeRefConverter.kt @@ -24,7 +24,10 @@ fun interface TsTypeRefConverter { return when (val descriptorKind = descriptor.kind) { is PrimitiveKind -> primitiveTypeRef(descriptor, descriptorKind) - StructureKind.LIST -> listTypeRef(descriptor) + StructureKind.LIST -> when { + descriptor.elementDescriptors.count() > 1 -> declarationTypeRef(descriptor) + else -> listTypeRef(descriptor) + } StructureKind.MAP -> mapTypeRef(descriptor) SerialKind.CONTEXTUAL, diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/experiments/tuple.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/experiments/tuple.kt new file mode 100644 index 00000000..aa93aae8 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/experiments/tuple.kt @@ -0,0 +1,130 @@ +@file:OptIn(InternalSerializationApi::class) + +package dev.adamko.kxstsgen.core.experiments + +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.descriptors.buildSerialDescriptor +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.CompositeEncoder +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import kotlinx.serialization.encoding.decodeStructure +import kotlinx.serialization.encoding.encodeCollection +import kotlinx.serialization.serializer + + +open class TupleElement( + open val index: Int, + open val elementSerializer: KSerializer, + open val elementAccessor: T.() -> E, +) { + val descriptor: SerialDescriptor + get() = elementSerializer.descriptor + + open fun encodeElement(encoder: CompositeEncoder, value: T) { + encoder.encodeSerializableElement( + descriptor, + index, + elementSerializer, + value.elementAccessor() + ) + } + + open fun decodeElement(decoder: CompositeDecoder): E { + return decoder.decodeSerializableElement( + descriptor, + index, + elementSerializer, + ) + } +} + + +inline fun tupleElement( + index: Int, + noinline elementAccessor: T.() -> E, + serializer: KSerializer = serializer(), +): TupleElement { + return TupleElement( + index, + serializer, + elementAccessor, + ) +} + + +fun tupleElements( + builder: TupleElementsBuilder.() -> Unit +): List> { + val tupleElementsBuilder = TupleElementsBuilder() + tupleElementsBuilder.builder() + return tupleElementsBuilder.elements +} + + +class TupleElementsBuilder { + + private val _elements: ArrayDeque> = ArrayDeque() + val elements: List> + get() = _elements.toList() + val elementsSize by _elements::size + + inline fun element( + noinline elementAccessor: T.() -> E, + ) { + element(tupleElement(elementsSize, elementAccessor)) + } + + fun element(element: TupleElement) { + _elements.addLast(element) + } +} + + +abstract class TupleSerializer( + serialName: String, + buildElements: TupleElementsBuilder.() -> Unit +) : KSerializer { + + val tupleElements: List> = run { + val tupleElementsBuilder = TupleElementsBuilder() + tupleElementsBuilder.buildElements() + tupleElementsBuilder.elements.sortedBy { it.index } + } + private val indexedTupleElements = tupleElements.associateBy { it.index } + + abstract fun tupleConstructor(elements: List<*>): T + + override val descriptor: SerialDescriptor = buildSerialDescriptor( + serialName = serialName, + kind = StructureKind.LIST, + typeParameters = emptyArray(), + ) { + tupleElements + .sortedBy { it.index } + .forEach { tupleElement -> + element("element${tupleElement.index}", tupleElement.descriptor) + } + } + + override fun serialize(encoder: Encoder, value: T) { + encoder.encodeCollection(descriptor, tupleElements.size) { + tupleElements.forEach { tupleElement -> + tupleElement.encodeElement(this, value) + } + } + } + + override fun deserialize(decoder: Decoder): T { + val elements = decoder.decodeStructure(descriptor) { + generateSequence { + val index = decodeElementIndex(descriptor) + indexedTupleElements[index]?.decodeElement(this) + }.toList() + } + return tupleConstructor(elements) + } +} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt index 9654f72a..92b2e0e8 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt @@ -48,18 +48,16 @@ sealed interface TsDeclaration : TsElement { } + /** A [tuple type](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types). */ data class TsTuple( override val id: TsElementId, - val typeRefs: List, - ) : TsDeclaration { - constructor(id: TsElementId, typeRef: TsTypeRef, vararg typeRefs: TsTypeRef) : - this(id, listOf(typeRef) + typeRefs.toList()) - } + val elements: List, + ) : TsDeclaration data class TsInterface( override val id: TsElementId, - val properties: Set, + val properties: Set, ) : TsDeclaration @@ -128,6 +126,12 @@ sealed interface TsLiteral : TsElement { * This helps prevent circular dependencies causing a lock. */ sealed interface TsTypeRef { + + /** + * Defines whether this reference may 'unset', and set to `null`. + * + * Nullability is different to optionality, which is determined by [TsProperty.optional]. + */ val nullable: Boolean @@ -148,24 +152,28 @@ sealed interface TsTypeRef { /** * A property within an [interface][TsDeclaration.TsInterface] - * - * In property may be [required][TsProperty.Required] or [optional][TsProperty.Optional]. - * See the TypeScript docs: - * ['Optional Properties'](https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties) + * or [tuple][TsDeclaration.TsTuple]. */ sealed interface TsProperty { - val name: String val typeRef: TsTypeRef - - - data class Required( - override val name: String, + /** + * A property may be required or optional. See the TypeScript docs: + * ['Optional Properties'](https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties) + * + * Optionality is different to nullability, which is defined by [TsTypeRef.nullable]. + */ + val optional: Boolean + + + data class Named( + val name: String, + override val optional: Boolean, override val typeRef: TsTypeRef, ) : TsProperty - data class Optional( - override val name: String, + data class Unnamed( + override val optional: Boolean, override val typeRef: TsTypeRef, ) : TsProperty } From 7205e6c5f0350f0e354f659500a17bebc3ead199 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 9 Apr 2022 00:15:34 +0200 Subject: [PATCH 7/8] =?UTF-8?q?move=20files=20to=20correct=20directory=20?= =?UTF-8?q?=F0=9F=A4=A6=E2=80=8D=E2=99=80=EF=B8=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/KxsTsConfig.kt | 0 .../adamko/kxstsgen}/KxsTsGenerator.kt | 0 .../adamko/kxstsgen}/core/KxsTsSourceCodeGenerator.kt | 0 .../adamko/kxstsgen}/core/SerializerDescriptorsExtractor.kt | 0 .../adamko/kxstsgen}/core/TsElementConverter.kt | 0 .../adamko/kxstsgen}/core/TsElementIdConverter.kt | 0 .../adamko/kxstsgen}/core/TsMapTypeConverter.kt | 0 .../adamko/kxstsgen}/core/TsTypeRefConverter.kt | 0 .../adamko/kxstsgen}/core/annotations.kt | 0 .../adamko/kxstsgen}/core/experiments/serializerExtractors.kt | 0 .../adamko/kxstsgen}/core/experiments/tuple.kt | 0 .../adamko/kxstsgen}/core/tsElements.kt | 0 .../adamko/kxstsgen}/core/util/mapWithDefaultDelegate.kt | 0 13 files changed, 0 insertions(+), 0 deletions(-) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/KxsTsConfig.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/KxsTsGenerator.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/core/KxsTsSourceCodeGenerator.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/core/SerializerDescriptorsExtractor.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/core/TsElementConverter.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/core/TsElementIdConverter.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/core/TsMapTypeConverter.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/core/TsTypeRefConverter.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/core/annotations.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/core/experiments/serializerExtractors.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/core/experiments/tuple.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/core/tsElements.kt (100%) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/{dev.adamko.kxstsgen => dev/adamko/kxstsgen}/core/util/mapWithDefaultDelegate.kt (100%) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/KxsTsConfig.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/KxsTsConfig.kt 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 similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsGenerator.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/KxsTsGenerator.kt diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/KxsTsSourceCodeGenerator.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt 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 similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/SerializerDescriptorsExtractor.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractor.kt diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementConverter.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementIdConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementIdConverter.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsElementIdConverter.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementIdConverter.kt diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsMapTypeConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsMapTypeConverter.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsMapTypeConverter.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsMapTypeConverter.kt diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsTypeRefConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsTypeRefConverter.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/TsTypeRefConverter.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsTypeRefConverter.kt diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/annotations.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/annotations.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/annotations.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/annotations.kt diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/experiments/serializerExtractors.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/serializerExtractors.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/experiments/serializerExtractors.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/serializerExtractors.kt diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/experiments/tuple.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/tuple.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/experiments/tuple.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/tuple.kt diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/tsElements.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/util/mapWithDefaultDelegate.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/util/mapWithDefaultDelegate.kt similarity index 100% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/core/util/mapWithDefaultDelegate.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/util/mapWithDefaultDelegate.kt From 177d5bfbf8c672936d05b5d30a0477dd82848590 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 9 Apr 2022 00:23:23 +0200 Subject: [PATCH 8/8] rename 'tuple' tests --- ...format-tuple-01.kt => example-tuple-01.kt} | 4 +-- ...format-tuple-02.kt => example-tuple-02.kt} | 4 +-- ...format-tuple-03.kt => example-tuple-03.kt} | 4 +-- ...format-tuple-04.kt => example-tuple-04.kt} | 6 ++-- .../{TsExportFormatTest.kt => TuplesTest.kt} | 28 ++++++++-------- docs/{export-formats.md => tuples.md} | 33 +++++++++---------- 6 files changed, 38 insertions(+), 41 deletions(-) rename docs/code/example/{example-format-tuple-01.kt => example-tuple-01.kt} (90%) rename docs/code/example/{example-format-tuple-02.kt => example-tuple-02.kt} (87%) rename docs/code/example/{example-format-tuple-03.kt => example-tuple-03.kt} (84%) rename docs/code/example/{example-format-tuple-04.kt => example-tuple-04.kt} (67%) rename docs/code/test/{TsExportFormatTest.kt => TuplesTest.kt} (65%) rename docs/{export-formats.md => tuples.md} (86%) diff --git a/docs/code/example/example-format-tuple-01.kt b/docs/code/example/example-tuple-01.kt similarity index 90% rename from docs/code/example/example-format-tuple-01.kt rename to docs/code/example/example-tuple-01.kt index b1cde9f7..cba8b0c0 100644 --- a/docs/code/example/example-format-tuple-01.kt +++ b/docs/code/example/example-tuple-01.kt @@ -1,6 +1,6 @@ -// This file was automatically generated from export-formats.md by Knit tool. Do not edit. +// This file was automatically generated from tuples.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package dev.adamko.kxstsgen.example.exampleFormatTuple01 +package dev.adamko.kxstsgen.example.exampleTuple01 import dev.adamko.kxstsgen.* import dev.adamko.kxstsgen.core.experiments.TupleSerializer diff --git a/docs/code/example/example-format-tuple-02.kt b/docs/code/example/example-tuple-02.kt similarity index 87% rename from docs/code/example/example-format-tuple-02.kt rename to docs/code/example/example-tuple-02.kt index 2c5152cd..6416fd3d 100644 --- a/docs/code/example/example-format-tuple-02.kt +++ b/docs/code/example/example-tuple-02.kt @@ -1,6 +1,6 @@ -// This file was automatically generated from export-formats.md by Knit tool. Do not edit. +// This file was automatically generated from tuples.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package dev.adamko.kxstsgen.example.exampleFormatTuple02 +package dev.adamko.kxstsgen.example.exampleTuple02 import dev.adamko.kxstsgen.* import dev.adamko.kxstsgen.core.experiments.TupleSerializer diff --git a/docs/code/example/example-format-tuple-03.kt b/docs/code/example/example-tuple-03.kt similarity index 84% rename from docs/code/example/example-format-tuple-03.kt rename to docs/code/example/example-tuple-03.kt index a465d7ad..b81d7ae4 100644 --- a/docs/code/example/example-format-tuple-03.kt +++ b/docs/code/example/example-tuple-03.kt @@ -1,6 +1,6 @@ -// This file was automatically generated from export-formats.md by Knit tool. Do not edit. +// This file was automatically generated from tuples.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package dev.adamko.kxstsgen.example.exampleFormatTuple03 +package dev.adamko.kxstsgen.example.exampleTuple03 import dev.adamko.kxstsgen.* import dev.adamko.kxstsgen.core.experiments.TupleSerializer diff --git a/docs/code/example/example-format-tuple-04.kt b/docs/code/example/example-tuple-04.kt similarity index 67% rename from docs/code/example/example-format-tuple-04.kt rename to docs/code/example/example-tuple-04.kt index 0df86e2e..bae81b03 100644 --- a/docs/code/example/example-format-tuple-04.kt +++ b/docs/code/example/example-tuple-04.kt @@ -1,12 +1,12 @@ -// This file was automatically generated from export-formats.md by Knit tool. Do not edit. +// This file was automatically generated from tuples.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package dev.adamko.kxstsgen.example.exampleFormatTuple04 +package dev.adamko.kxstsgen.example.exampleTuple04 import dev.adamko.kxstsgen.* import dev.adamko.kxstsgen.core.experiments.TupleSerializer import kotlinx.serialization.* -import dev.adamko.kxstsgen.example.exampleFormatTuple03.Coordinates +import dev.adamko.kxstsgen.example.exampleTuple03.Coordinates @Serializable class GameLocations( diff --git a/docs/code/test/TsExportFormatTest.kt b/docs/code/test/TuplesTest.kt similarity index 65% rename from docs/code/test/TsExportFormatTest.kt rename to docs/code/test/TuplesTest.kt index 443a5ce6..9b237478 100644 --- a/docs/code/test/TsExportFormatTest.kt +++ b/docs/code/test/TuplesTest.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from export-formats.md by Knit tool. Do not edit. +// This file was automatically generated from tuples.md by Knit tool. Do not edit. @file:Suppress("JSUnusedLocalSymbols") package dev.adamko.kxstsgen.example.test @@ -7,11 +7,11 @@ import kotlinx.knit.test.* import org.junit.jupiter.api.Test import dev.adamko.kxstsgen.util.* -class TsExportFormatTest { +class TuplesTest { @Test - fun testExampleFormatTuple01() { - captureOutput("ExampleFormatTuple01") { - dev.adamko.kxstsgen.example.exampleFormatTuple01.main() + fun testExampleTuple01() { + captureOutput("ExampleTuple01") { + dev.adamko.kxstsgen.example.exampleTuple01.main() }.normalizeJoin() .shouldBe( // language=TypeScript @@ -23,9 +23,9 @@ class TsExportFormatTest { } @Test - fun testExampleFormatTuple02() { - captureOutput("ExampleFormatTuple02") { - dev.adamko.kxstsgen.example.exampleFormatTuple02.main() + fun testExampleTuple02() { + captureOutput("ExampleTuple02") { + dev.adamko.kxstsgen.example.exampleTuple02.main() }.normalizeJoin() .shouldBe( // language=TypeScript @@ -37,9 +37,9 @@ class TsExportFormatTest { } @Test - fun testExampleFormatTuple03() { - captureOutput("ExampleFormatTuple03") { - dev.adamko.kxstsgen.example.exampleFormatTuple03.main() + fun testExampleTuple03() { + captureOutput("ExampleTuple03") { + dev.adamko.kxstsgen.example.exampleTuple03.main() }.normalizeJoin() .shouldBe( // language=TypeScript @@ -51,9 +51,9 @@ class TsExportFormatTest { } @Test - fun testExampleFormatTuple04() { - captureOutput("ExampleFormatTuple04") { - dev.adamko.kxstsgen.example.exampleFormatTuple04.main() + fun testExampleTuple04() { + captureOutput("ExampleTuple04") { + dev.adamko.kxstsgen.example.exampleTuple04.main() }.normalizeJoin() .shouldBe( // language=TypeScript diff --git a/docs/export-formats.md b/docs/tuples.md similarity index 86% rename from docs/export-formats.md rename to docs/tuples.md index a9866175..81042d7b 100644 --- a/docs/export-formats.md +++ b/docs/tuples.md @@ -1,15 +1,14 @@ - + **Table of contents** -* [Introduction](#introduction) - * [Tuples](#tuples) - * [Tuple example](#tuple-example) - * [Optional elements in tuples](#optional-elements-in-tuples) - * [Properties all the same type](#properties-all-the-same-type) +* [Tuples](#tuples) + * [Tuple example](#tuple-example) + * [Optional elements in tuples](#optional-elements-in-tuples) + * [Properties all the same type](#properties-all-the-same-type) * [Tuples as interface properties](#tuples-as-interface-properties) @@ -21,20 +20,18 @@ import dev.adamko.kxstsgen.core.experiments.TupleSerializer import kotlinx.serialization.* --> -## Introduction - -### Tuples +## Tuples In TypeScript, tuples are a compact format for data structures. They're like fixed-length arrays that only contain the type. This is especially useful when using JSON, as including property names means the messages are much larger. Tuples are a bit difficult to create in Kotlinx Serialization, but KxTsGen includes -[TupleSerializer](../modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen) +[TupleSerializer](../modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/tuple.kt) which can help. It requires a name, an ordered list of elements, and a constructor for deserializing. -#### Tuple example +### Tuple example Let's say we have a class, `SimpleTypes`, that we want to serializer. We need to create a bespoke tuple serializer for it, so override the plugin-generated serializer. @@ -80,7 +77,7 @@ fun main() { } ``` -> You can get the full code [here](./code/example/example-format-tuple-01.kt). +> You can get the full code [here](./code/example/example-tuple-01.kt). ```typescript export type SimpleTypes = [string, number, number | null, boolean, string]; @@ -88,7 +85,7 @@ export type SimpleTypes = [string, number, number | null, boolean, string]; -#### Optional elements in tuples +### Optional elements in tuples `TupleSerializer` does not consider whether a field is optional. This is intentional, partly because it's quite complicated to set up already, and more options won't help! Also, tuples require that @@ -132,7 +129,7 @@ fun main() { } ``` -> You can get the full code [here](./code/example/example-format-tuple-02.kt). +> You can get the full code [here](./code/example/example-tuple-02.kt). ```typescript export type OptionalFields = [string, string, string | null, string | null]; @@ -140,7 +137,7 @@ export type OptionalFields = [string, string, string | null, string | null]; -#### Properties all the same type +### Properties all the same type ```kotlin @Serializable(with = Coordinates.Serializer::class) @@ -173,7 +170,7 @@ fun main() { } ``` -> You can get the full code [here](./code/example/example-format-tuple-03.kt). +> You can get the full code [here](./code/example/example-tuple-03.kt). ```typescript export type Coordinates = [number, number, number]; @@ -184,7 +181,7 @@ export type Coordinates = [number, number, number]; #### Tuples as interface properties ```kotlin -import dev.adamko.kxstsgen.example.exampleFormatTuple03.Coordinates +import dev.adamko.kxstsgen.example.exampleTuple03.Coordinates @Serializable class GameLocations( @@ -199,7 +196,7 @@ fun main() { } ``` -> You can get the full code [here](./code/example/example-format-tuple-04.kt). +> You can get the full code [here](./code/example/example-tuple-04.kt). ```typescript export interface GameLocations {