From a651496b4c2ddb54dae21d4aeef1e14b49494231 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 4 Mar 2022 08:37:12 +0100 Subject: [PATCH 01/46] - #1 basic lists implementation - #2 Basic maps implementation - #7 quick implementation of brand typing for value classes --- docs/abstract-classes.md | 28 ++++++- ...xample-abstract-class-abstract-field-01.kt | 16 ++++ docs/knit/example/example-map-primitive-01.kt | 6 +- docs/knit/example/example-value-classes-03.kt | 17 ++-- docs/knit/example/example-value-classes-04.kt | 15 ++++ docs/knit/test/AbstractClassesTest.kt | 11 +++ docs/knit/test/ListsTests.kt | 2 +- docs/knit/test/ValueClassesTest.kt | 9 ++ docs/lists.md | 2 +- docs/maps.md | 11 +-- docs/value-classes.md | 42 +++++++++- .../kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt | 12 ++- .../dev.adamko.kxstsgen/KxsTsGenerator.kt | 47 +++++------ .../KxsTsSourceCodeGenerator.kt | 84 +++++++++++-------- .../kotlin/dev.adamko.kxstsgen/tsElements.kt | 40 +++------ 15 files changed, 228 insertions(+), 114 deletions(-) create mode 100644 docs/knit/example/example-abstract-class-abstract-field-01.kt create mode 100644 docs/knit/example/example-value-classes-04.kt diff --git a/docs/abstract-classes.md b/docs/abstract-classes.md index 18f8ba50..ab593a3a 100644 --- a/docs/abstract-classes.md +++ b/docs/abstract-classes.md @@ -1,12 +1,12 @@ -### Abstract class with a single field - +### Abstract class with a single field + ```kotlin @Serializable abstract class Color(val rgb: Int) @@ -58,3 +58,27 @@ interface SimpleTypes { ``` + +### Abstract class, abstract value + +```kotlin +@Serializable +abstract class Color { + abstract val rgb: Int +} + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Color.serializer().descriptor)) +} +``` + +> You can get the full code [here](./knit/example/example-abstract-class-abstract-field-01.kt). + +```typescript +interface Color { + rgb: number; +} +``` + + diff --git a/docs/knit/example/example-abstract-class-abstract-field-01.kt b/docs/knit/example/example-abstract-class-abstract-field-01.kt new file mode 100644 index 00000000..5f2a2073 --- /dev/null +++ b/docs/knit/example/example-abstract-class-abstract-field-01.kt @@ -0,0 +1,16 @@ +// This file was automatically generated from abstract-classes.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.exampleAbstractClassAbstractField01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +abstract class Color { + abstract val rgb: Int +} + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Color.serializer().descriptor)) +} diff --git a/docs/knit/example/example-map-primitive-01.kt b/docs/knit/example/example-map-primitive-01.kt index a588f0ad..779861af 100644 --- a/docs/knit/example/example-map-primitive-01.kt +++ b/docs/knit/example/example-map-primitive-01.kt @@ -6,9 +6,9 @@ import kotlinx.serialization.* import dev.adamko.kxstsgen.* @Serializable -class Config { - val properties: Map = mapOf() -} +data class Config( + val properties: Map +) fun main() { val tsGenerator = KxsTsGenerator() diff --git a/docs/knit/example/example-value-classes-03.kt b/docs/knit/example/example-value-classes-03.kt index 95c5d22f..751af227 100644 --- a/docs/knit/example/example-value-classes-03.kt +++ b/docs/knit/example/example-value-classes-03.kt @@ -5,11 +5,18 @@ package example.exampleValueClasses03 import kotlinx.serialization.* import dev.adamko.kxstsgen.* -@Serializable -@JvmInline -value class UserCount(private val count: UInt) +import kotlinx.serialization.builtins.serializer +import dev.adamko.kxstsgen.KxsTsConfig.TypeAliasTypingConfig.BrandTyping + fun main() { - val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(UserCount.serializer().descriptor)) + + val tsConfig = KxsTsConfig(typeAliasTyping = BrandTyping) + + val tsGenerator = KxsTsGenerator(config = tsConfig) + println( + tsGenerator.generate( + ULong.serializer().descriptor, + ) + ) } diff --git a/docs/knit/example/example-value-classes-04.kt b/docs/knit/example/example-value-classes-04.kt new file mode 100644 index 00000000..0fef58aa --- /dev/null +++ b/docs/knit/example/example-value-classes-04.kt @@ -0,0 +1,15 @@ +// This file was automatically generated from value-classes.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.exampleValueClasses04 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +@JvmInline +value class UserCount(private val count: UInt) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(UserCount.serializer().descriptor)) +} diff --git a/docs/knit/test/AbstractClassesTest.kt b/docs/knit/test/AbstractClassesTest.kt index 03406432..c1b16c00 100644 --- a/docs/knit/test/AbstractClassesTest.kt +++ b/docs/knit/test/AbstractClassesTest.kt @@ -30,4 +30,15 @@ class AbstractClassesTest { "}" ) } + + @Test + fun testExampleAbstractClassAbstractField01() { + captureOutput("ExampleAbstractClassAbstractField01") { + example.exampleAbstractClassAbstractField01.main() + }.verifyOutputLines( + "interface Color {", + " rgb: number;", + "}" + ) + } } diff --git a/docs/knit/test/ListsTests.kt b/docs/knit/test/ListsTests.kt index 8b4cc83c..22278b78 100644 --- a/docs/knit/test/ListsTests.kt +++ b/docs/knit/test/ListsTests.kt @@ -11,7 +11,7 @@ class ListsTests { example.exampleListPrimitive01.main() }.verifyOutputLines( "interface CalendarEvent {", - " attendeeNames: string[]", + " attendeeNames: string[];", "}" ) } diff --git a/docs/knit/test/ValueClassesTest.kt b/docs/knit/test/ValueClassesTest.kt index b5d5604e..2362a241 100644 --- a/docs/knit/test/ValueClassesTest.kt +++ b/docs/knit/test/ValueClassesTest.kt @@ -33,6 +33,15 @@ class ValueClassesTest { fun testExampleValueClasses03() { captureOutput("ExampleValueClasses03") { example.exampleValueClasses03.main() + }.verifyOutputLines( + "type ULong = number & { __ULong__: void };" + ) + } + + @Test + fun testExampleValueClasses04() { + captureOutput("ExampleValueClasses04") { + example.exampleValueClasses04.main() }.verifyOutputLines( "type UInt = number;", "", diff --git a/docs/lists.md b/docs/lists.md index 1a4dd69d..2e3adae4 100644 --- a/docs/lists.md +++ b/docs/lists.md @@ -23,7 +23,7 @@ fun main() { ```typescript interface CalendarEvent { - attendeeNames: string[] + attendeeNames: string[]; } ``` diff --git a/docs/maps.md b/docs/maps.md index f01c85ee..7cce2f3d 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -1,17 +1,18 @@ -### Primitive lists - +### Primitive lists + + ```kotlin @Serializable -class Config { - val properties: Map = mapOf() -} +data class Config( + val properties: Map +) fun main() { val tsGenerator = KxsTsGenerator() diff --git a/docs/value-classes.md b/docs/value-classes.md index f59a9071..f1f98ee6 100644 --- a/docs/value-classes.md +++ b/docs/value-classes.md @@ -62,11 +62,47 @@ type UInt = number; type ULong = number; ``` -(At present this is not very useful as Typescript will make no distinction between any of these -numbers, even though they are distinct in Kotlin. 'Brand typing' might be introduced in the future.) + + + +### Brand typing + +To make value classes a little more strict, we can use brand typing + + +```kotlin +import kotlinx.serialization.builtins.serializer +import dev.adamko.kxstsgen.KxsTsConfig.TypeAliasTypingConfig.BrandTyping +``` + + + +```kotlin + +fun main() { + + val tsConfig = KxsTsConfig(typeAliasTyping = BrandTyping) + + val tsGenerator = KxsTsGenerator(config = tsConfig) + println( + tsGenerator.generate( + ULong.serializer().descriptor, + ) + ) +} +``` + + + +> You can get the full code [here](./knit/example/example-value-classes-03.kt). + +```typescript +type ULong = number & { __ULong__: void }; +``` + ### Nested value classes If the value class contains another value class, then the outer class will be aliased to other value @@ -83,7 +119,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-value-classes-03.kt). +> You can get the full code [here](./knit/example/example-value-classes-04.kt). ```typescript type UInt = number; 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 ad3c7d64..7c3f805e 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 @@ -5,16 +5,22 @@ import kotlinx.serialization.descriptors.SerialDescriptor data class KxsTsConfig( val indent: String = " ", - val namespaceConfig: NamespaceConfig = NamespaceConfig.None, + val namespaceConfig: NamespaceConfig = NamespaceConfig.Disabled, + val typeAliasTyping: TypeAliasTypingConfig = TypeAliasTypingConfig.None, ) { sealed interface NamespaceConfig { /** Use the prefix of the [SerialDescriptor] */ object UseDescriptorNamePrefix : NamespaceConfig /** don't generate a namespace */ - object None : NamespaceConfig + object Disabled : NamespaceConfig @JvmInline - value class Hardcoded(val namespace: String) : NamespaceConfig + value class Static(val namespace: String) : NamespaceConfig + } + + sealed interface TypeAliasTypingConfig { + object None : TypeAliasTypingConfig + object BrandTyping : TypeAliasTypingConfig } } 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 452b1654..d2fac077 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 @@ -62,7 +62,7 @@ class TsConverter( convertToTsElement(null, target) } - private fun convertToTsElement(requestor: TsElementId?, target: SerialDescriptor): TsElementId { + private fun convertToTsElement(requestor: TsElementId?, target: SerialDescriptor): TsElement { val targetId = TsElementId(target.serialName.removeSuffix("?")) @@ -112,7 +112,7 @@ class TsConverter( } result - }.id + } } @@ -123,18 +123,18 @@ class TsConverter( if (structDescriptor.isInline) { val fieldDescriptor = structDescriptor.elementDescriptors.first() - val typeId = convertToTsElement(targetId, fieldDescriptor) - val typeReference = TsTypeReference(typeId, fieldDescriptor.isNullable) - return TsTypeAlias(targetId, setOf(typeReference)) + val fieldType = convertToTsElement(targetId, fieldDescriptor) + val fieldTyping = TsTyping(fieldType, fieldDescriptor.isNullable) + return TsTypeAlias(targetId, fieldTyping) } else { - val properties = structDescriptor.elementDescriptors.mapIndexed { index, field -> + val properties = structDescriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> val name = structDescriptor.getElementName(index) - val fieldTypeId = convertToTsElement(targetId, field) - val fieldTypeReference = TsTypeReference(fieldTypeId, field.isNullable) + val fieldType = convertToTsElement(targetId, fieldDescriptor) + val fieldTyping = TsTyping(fieldType, fieldDescriptor.isNullable) when { - structDescriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeReference) - else -> TsProperty.Required(name, fieldTypeReference) + structDescriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTyping) + else -> TsProperty.Required(name, fieldTyping) } } @@ -146,10 +146,6 @@ class TsConverter( targetId: TsElementId, enumDescriptor: SerialDescriptor, ): TsElement { -// if (descriptor.elementsCount > 0) { -// convertStructure(descriptor) -// } - return TsStructure.TsEnum( targetId, enumDescriptor.elementNames.toSet(), @@ -160,12 +156,10 @@ class TsConverter( targetId: TsElementId, listDescriptor: SerialDescriptor, ): TsStructure.TsList { - - val typeDescriptor = listDescriptor.elementDescriptors.first() - val typeId = - TsTypeReference(convertToTsElement(targetId, typeDescriptor), typeDescriptor.isNullable) - - return TsStructure.TsList(targetId, typeId) + val elementDescriptor = listDescriptor.elementDescriptors.first() + val elementType = convertToTsElement(targetId, elementDescriptor) + val elementTyping = TsTyping(elementType, elementDescriptor.isNullable) + return TsStructure.TsList(targetId, elementTyping) } private fun convertMap( @@ -174,10 +168,13 @@ class TsConverter( ): TsStructure.TsMap { val (keyDescriptor, valueDescriptor) = mapDescriptor.elementDescriptors.toList() - val keyType = - TsTypeReference(convertToTsElement(targetId, keyDescriptor), keyDescriptor.isNullable) - val valueType = - TsTypeReference(convertToTsElement(targetId, valueDescriptor), valueDescriptor.isNullable) - return TsStructure.TsMap(targetId, keyType, valueType) + + val keyType = convertToTsElement(targetId, keyDescriptor) + val keyTyping = TsTyping(keyType, keyDescriptor.isNullable) + + val valueType = convertToTsElement(targetId, valueDescriptor) + val valueTyping = TsTyping(valueType, valueDescriptor.isNullable) + + return TsStructure.TsMap(targetId, keyTyping, valueTyping) } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index c387f014..3a893dea 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -12,8 +12,8 @@ class KxsTsSourceCodeGenerator( return elements .groupBy { when (config.namespaceConfig) { - is KxsTsConfig.NamespaceConfig.Hardcoded -> config.namespaceConfig.namespace - KxsTsConfig.NamespaceConfig.None -> "" + is KxsTsConfig.NamespaceConfig.Static -> config.namespaceConfig.namespace + KxsTsConfig.NamespaceConfig.Disabled -> "" KxsTsConfig.NamespaceConfig.UseDescriptorNamePrefix -> it.id.namespace } } @@ -21,11 +21,11 @@ class KxsTsSourceCodeGenerator( elements.mapNotNull { element -> when (element) { + is TsStructure.TsMap, + is TsStructure.TsList, is TsPrimitive -> null is TsStructure.TsEnum -> generateEnum(element) is TsStructure.TsInterface -> generateInterface(element) - is TsStructure.TsMap -> generateMap(element) - is TsStructure.TsList -> generateList(element) is TsTypeAlias -> generateTypeAlias(element) } } @@ -69,7 +69,7 @@ class KxsTsSourceCodeGenerator( is TsProperty.Optional -> "?: " is TsProperty.Required -> ": " } - val propertyType = generateTypeReference(property.typeReference) + val propertyType = generateTypeReference(property.typing) // generate ` name: Type;` // or ` name:? Type;` "${indent}${property.name}${separator}${propertyType};" @@ -83,43 +83,55 @@ class KxsTsSourceCodeGenerator( } private fun generateTypeAlias(element: TsTypeAlias): String { - val aliases = generateTypeReference(element.types) - return """ - |type ${element.id.name} = ${aliases}; - """.trimMargin() + val aliases = generateTypeReference(element.type) + + return when (config.typeAliasTyping) { + KxsTsConfig.TypeAliasTypingConfig.None -> + """ + |type ${element.id.name} = ${aliases}; + """.trimMargin() + KxsTsConfig.TypeAliasTypingConfig.BrandTyping -> { + + val brandType = element.id.name + .replace(".", "_") + .filter { it.isLetter() || it == '_' } + + """ + |type ${element.id.name} = $aliases & { __${brandType}__: void }; + """.trimMargin() + } + } } - private fun generateTypeReference(typeRefs: Collection) = - generateTypeReference(*typeRefs.toTypedArray()) +// private fun generateTypeReference(typeRefs: Collection) = +// generateTypeReference(*typeRefs.toTypedArray()) /** * A type-reference, be it for the field of an interface, a type alias, or a generic type * constraint. */ - private fun generateTypeReference(vararg typeRefs: TsTypeReference): String { - - val includeNull = typeRefs.any { it.nullable } - - return typeRefs - .map { it.id.name } - .sorted() - .plus(if (includeNull) "null" else null) - .filterNotNull() - .joinToString(separator = " | ") - } - - private fun generateList(element: TsStructure.TsList): String { - val elementsTypeRef = generateTypeReference(element.elementsTsType) - return """ - |${elementsTypeRef}[] - """.trimMargin() - } - - private fun generateMap(element: TsStructure.TsMap): String { - val keyTypeRef = generateTypeReference(element.keyTsType) - val valueTypeRef = generateTypeReference(element.keyTsType) - return """ - |{ [key: $keyTypeRef]: $valueTypeRef } - """.trimMargin() + private fun generateTypeReference(typing: TsTyping): String { + return buildString { + when (typing.type) { + is TsStructure.TsEnum, + is TsStructure.TsInterface, + is TsTypeAlias, + is TsPrimitive -> { + append(typing.type.id.name) + if (typing.nullable) { + append(" | " + TsPrimitive.TsNull.id.name) + } + } + is TsStructure.TsList -> { + append(generateTypeReference(typing.type.elementsTyping)) + append("[]") + } + is TsStructure.TsMap -> { + val keyTypeRef = generateTypeReference(typing.type.keyTyping) + val valueTypeRef = generateTypeReference(typing.type.valueTyping) + append("{ [key: $keyTypeRef]: $valueTypeRef }") + } + } + } } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt index 973a3dee..81a61bc4 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt @@ -5,18 +5,15 @@ import kotlin.jvm.JvmInline @JvmInline value class TsElementId(private val id: String) { - val namespace: String get() = id.substringBeforeLast(".") val name: String get() = id.substringAfterLast(".") - val filename: String - get() = namespace.lowercase().replace('.', '-') + ".d.ts" } + sealed interface TsElement { val id: TsElementId -// val namespace: String } @@ -42,40 +39,29 @@ sealed class TsPrimitive( data class TsTypeAlias( override val id: TsElementId, - val types: Set, + val type: TsTyping, ) : TsElement -//{ -// constructor(id: TsElementId, vararg types: TsElementId) -// : this(id, types.map { it.id }.toSet()) -//} -data class TsTypeReference( - val id: TsElementId, +data class TsTyping( + val type: TsElement, val nullable: Boolean, ) sealed interface TsProperty { val name: String - val typeReference: TsTypeReference -// val optional: Boolean + val typing: TsTyping data class Required( override val name: String, - override val typeReference: TsTypeReference, + override val typing: TsTyping, ) : TsProperty -// { -// override val optional: Boolean = false -// } data class Optional( override val name: String, - override val typeReference: TsTypeReference, + override val typing: TsTyping, ) : TsProperty -// { -// override val optional: Boolean = true -// } } @@ -93,22 +79,16 @@ sealed interface TsStructure : TsElement { data class TsList( override val id: TsElementId, - val elementsTsType: TsTypeReference, + val elementsTyping: TsTyping, ) : TsStructure data class TsMap( override val id: TsElementId, - val keyTsType: TsTypeReference, - val valueTsType: TsTypeReference, + val keyTyping: TsTyping, + val valueTyping: TsTyping, ) : TsStructure } -//@JvmInline -//value class TsProperties( -// private val properties: List, -//) { -// override fun toString(): String = properties.joinToString("\n") { it.output + ";" } -//} sealed interface TsPolymorphicDiscriminator { sealed interface Closed From 63ec8d09988247a1617fdcc555a3aed37d815752 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 4 Mar 2022 09:08:06 +0100 Subject: [PATCH 02/46] map tests #2 #3 --- docs/knit/example/example-map-complex-01.kt | 24 +++++++ docs/knit/example/example-map-primitive-02.kt | 16 +++++ docs/knit/test/MapsTests.kt | 31 +++++++++ docs/maps.md | 68 ++++++++++++++++++- .../KxsTsSourceCodeGenerator.kt | 2 - 5 files changed, 137 insertions(+), 4 deletions(-) create mode 100644 docs/knit/example/example-map-complex-01.kt create mode 100644 docs/knit/example/example-map-primitive-02.kt diff --git a/docs/knit/example/example-map-complex-01.kt b/docs/knit/example/example-map-complex-01.kt new file mode 100644 index 00000000..8c35e154 --- /dev/null +++ b/docs/knit/example/example-map-complex-01.kt @@ -0,0 +1,24 @@ +// This file was automatically generated from maps.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.exampleMapComplex01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +data class Colour( + val r: UByte, + val g: UByte, + val b: UByte, + val a: UByte, +) + +@Serializable +data class CanvasProperties( + val colourNames: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(CanvasProperties.serializer().descriptor)) +} diff --git a/docs/knit/example/example-map-primitive-02.kt b/docs/knit/example/example-map-primitive-02.kt new file mode 100644 index 00000000..d509ee56 --- /dev/null +++ b/docs/knit/example/example-map-primitive-02.kt @@ -0,0 +1,16 @@ +// This file was automatically generated from maps.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.exampleMapPrimitive02 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +data class Config( + val properties: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Config.serializer().descriptor)) +} diff --git a/docs/knit/test/MapsTests.kt b/docs/knit/test/MapsTests.kt index 86b270ce..da6760f1 100644 --- a/docs/knit/test/MapsTests.kt +++ b/docs/knit/test/MapsTests.kt @@ -15,4 +15,35 @@ class MapsTests { "}" ) } + + @Test + fun testExampleMapPrimitive02() { + captureOutput("ExampleMapPrimitive02") { + example.exampleMapPrimitive02.main() + }.verifyOutputLines( + "interface Config {", + " properties: { [key: string | null]: string | null };", + "}" + ) + } + + @Test + fun testExampleMapComplex01() { + captureOutput("ExampleMapComplex01") { + example.exampleMapComplex01.main() + }.verifyOutputLines( + "type UByte = number;", + "", + "interface Colour {", + " r: UByte;", + " g: UByte;", + " b: UByte;", + " a: UByte;", + "}", + "", + "interface CanvasProperties {", + " colourNames: { [key: Colour]: string };", + "}" + ) + } } diff --git a/docs/maps.md b/docs/maps.md index 7cce2f3d..dfda986f 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -5,8 +5,7 @@ import kotlinx.serialization.* import dev.adamko.kxstsgen.* --> -### Primitive lists - +### Primitive maps ```kotlin @Serializable @@ -29,3 +28,68 @@ interface Config { ``` + +### Nullable keys and values + +```kotlin +@Serializable +data class Config( + val properties: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Config.serializer().descriptor)) +} +``` + +> You can get the full code [here](./knit/example/example-map-primitive-02.kt). + +```typescript +interface Config { + properties: { [key: string | null]: string | null }; +} +``` + + + +### Maps with complex keys + +```kotlin +@Serializable +data class Colour( + val r: UByte, + val g: UByte, + val b: UByte, + val a: UByte, +) + +@Serializable +data class CanvasProperties( + val colourNames: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(CanvasProperties.serializer().descriptor)) +} +``` + +> You can get the full code [here](./knit/example/example-map-complex-01.kt). + +```typescript +type UByte = number; + +interface Colour { + r: UByte; + g: UByte; + b: UByte; + a: UByte; +} + +interface CanvasProperties { + colourNames: { [key: Colour]: string }; +} +``` + + diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index 3a893dea..ecc9b87d 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -103,8 +103,6 @@ class KxsTsSourceCodeGenerator( } } -// private fun generateTypeReference(typeRefs: Collection) = -// generateTypeReference(*typeRefs.toTypedArray()) /** * A type-reference, be it for the field of an interface, a type alias, or a generic type From 239c1b8688b314d5fc763da1bb01cc066d26ff40 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 4 Mar 2022 14:50:25 +0100 Subject: [PATCH 03/46] generate implementations and test improvements - add TOC to all docs - Map generation for complex and enum keys #2 #3 - update test template, for easier comparison after failures - tests for #10 --- docs/abstract-classes.md | 23 ++- docs/default-values.md | 14 ++ docs/enums.md | 18 ++- docs/knit.properties | 1 + docs/knit/build.gradle.kts | 3 +- ...xample-abstract-class-abstract-field-01.kt | 9 +- ...mple-abstract-class-primitive-fields-01.kt | 2 +- ...mple-closed-polymorphic-static-types-01.kt | 16 ++ ...mple-closed-polymorphic-static-types-02.kt | 19 +++ docs/knit/example/example-enum-class-01.kt | 2 + docs/knit/example/example-enum-class-02.kt | 2 + docs/knit/example/example-map-primitive-02.kt | 12 +- docs/knit/example/example-map-primitive-03.kt | 16 ++ ...phic-abstract-class-primitive-fields-01.kt | 20 +++ .../example-polymorphic-sealed-class-01.kt | 22 +++ .../example-polymorphic-static-types-01.kt | 16 ++ .../example-polymorphic-static-types-02.kt | 19 +++ docs/knit/knit-test.ftl | 15 +- docs/knit/test/AbstractClassesTest.kt | 53 ++++--- docs/knit/test/BasicClassesTest.kt | 61 +++++--- docs/knit/test/DefaultValuesTest.kt | 35 +++-- docs/knit/test/EnumClassTest.kt | 39 +++-- docs/knit/test/ListsTests.kt | 17 +- docs/knit/test/MapsTests.kt | 83 +++++++--- docs/knit/test/PolymorphismTest.kt | 99 +++++++++--- docs/knit/test/ValueClassesTest.kt | 59 ++++--- docs/lists.md | 15 +- docs/maps.md | 55 ++++++- docs/polymorphism.md | 148 ++++++++++++++---- docs/value-classes.md | 16 ++ .../KxsTsSourceCodeGenerator.kt | 36 ++++- 31 files changed, 743 insertions(+), 202 deletions(-) create mode 100644 docs/knit/example/example-closed-polymorphic-static-types-01.kt create mode 100644 docs/knit/example/example-closed-polymorphic-static-types-02.kt create mode 100644 docs/knit/example/example-map-primitive-03.kt create mode 100644 docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt create mode 100644 docs/knit/example/example-polymorphic-sealed-class-01.kt create mode 100644 docs/knit/example/example-polymorphic-static-types-01.kt create mode 100644 docs/knit/example/example-polymorphic-static-types-02.kt diff --git a/docs/abstract-classes.md b/docs/abstract-classes.md index ab593a3a..3546d2ad 100644 --- a/docs/abstract-classes.md +++ b/docs/abstract-classes.md @@ -1,10 +1,24 @@ +**Table of contents** + + + +* [Introduction](#introduction) + * [Abstract class with a single field](#abstract-class-with-a-single-field) + * [Abstract class with primitive fields](#abstract-class-with-primitive-fields) + * [Abstract class, abstract value](#abstract-class-abstract-value) + + + + +## Introduction + ### Abstract class with a single field ```kotlin @@ -63,13 +77,16 @@ interface SimpleTypes { ```kotlin @Serializable -abstract class Color { - abstract val rgb: Int +abstract class AbstractSimpleTypes { + abstract val aString: String + abstract var anInt: Int + abstract val aDouble: Double + abstract val bool: Boolean } fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Color.serializer().descriptor)) + println(tsGenerator.generate(AbstractSimpleTypes.serializer().descriptor)) } ``` diff --git a/docs/default-values.md b/docs/default-values.md index 335c8a2d..c88bba89 100644 --- a/docs/default-values.md +++ b/docs/default-values.md @@ -1,10 +1,24 @@ +**Table of contents** + + + +* [Introduction](#introduction) + * [Default values](#default-values) + * [Default and nullable](#default-and-nullable) + + + + +## Introduction + + ### Default values If a value has a default value, then it is not required for creating an encoded message. Therefore, diff --git a/docs/enums.md b/docs/enums.md index 4bd54d30..80fe17c9 100644 --- a/docs/enums.md +++ b/docs/enums.md @@ -1,8 +1,24 @@ + +**Table of contents** + + + +* [Introduction](#introduction) + * [Plain class with a single field](#plain-class-with-a-single-field) + * [Plain class with primitive fields](#plain-class-with-primitive-fields) + + + + + + ## Introduction -Lorem ipsum... ### Plain class with a single field diff --git a/docs/knit.properties b/docs/knit.properties index 1a4114ba..0f2c3b66 100644 --- a/docs/knit.properties +++ b/docs/knit.properties @@ -6,3 +6,4 @@ test.package=example.test test.template=./knit/knit-test.ftl test.language=typescript knit.include=./knit/knit-include.ftl +test.mode.=joinToString(\"\\n\")\n .shouldBe diff --git a/docs/knit/build.gradle.kts b/docs/knit/build.gradle.kts index 7fe8e7ba..83d4555a 100644 --- a/docs/knit/build.gradle.kts +++ b/docs/knit/build.gradle.kts @@ -7,7 +7,6 @@ plugins { } - val kotlinxSerializationVersion = "1.3.2" dependencies { @@ -17,6 +16,8 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json") + implementation("org.jetbrains.kotlinx:kotlinx-knit:0.3.0") + testImplementation(kotlin("test")) testImplementation("org.jetbrains.kotlinx:kotlinx-knit-test:0.3.0") diff --git a/docs/knit/example/example-abstract-class-abstract-field-01.kt b/docs/knit/example/example-abstract-class-abstract-field-01.kt index 5f2a2073..f8a5bf49 100644 --- a/docs/knit/example/example-abstract-class-abstract-field-01.kt +++ b/docs/knit/example/example-abstract-class-abstract-field-01.kt @@ -6,11 +6,14 @@ import kotlinx.serialization.* import dev.adamko.kxstsgen.* @Serializable -abstract class Color { - abstract val rgb: Int +abstract class AbstractSimpleTypes { + abstract val aString: String + abstract var anInt: Int + abstract val aDouble: Double + abstract val bool: Boolean } fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Color.serializer().descriptor)) + println(tsGenerator.generate(AbstractSimpleTypes.serializer().descriptor)) } diff --git a/docs/knit/example/example-abstract-class-primitive-fields-01.kt b/docs/knit/example/example-abstract-class-primitive-fields-01.kt index 2e481d93..06f2af95 100644 --- a/docs/knit/example/example-abstract-class-primitive-fields-01.kt +++ b/docs/knit/example/example-abstract-class-primitive-fields-01.kt @@ -1,4 +1,4 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +// This file was automatically generated from abstract-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") package example.exampleAbstractClassPrimitiveFields01 diff --git a/docs/knit/example/example-closed-polymorphic-static-types-01.kt b/docs/knit/example/example-closed-polymorphic-static-types-01.kt new file mode 100644 index 00000000..9cd6e15c --- /dev/null +++ b/docs/knit/example/example-closed-polymorphic-static-types-01.kt @@ -0,0 +1,16 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.exampleClosedPolymorphicStaticTypes01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +open class Project(val name: String) + +class OwnedProject(name: String, val owner: String) : Project(name) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Project.serializer().descriptor)) +} diff --git a/docs/knit/example/example-closed-polymorphic-static-types-02.kt b/docs/knit/example/example-closed-polymorphic-static-types-02.kt new file mode 100644 index 00000000..c4b614b4 --- /dev/null +++ b/docs/knit/example/example-closed-polymorphic-static-types-02.kt @@ -0,0 +1,19 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.exampleClosedPolymorphicStaticTypes02 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +abstract class Project { + abstract val name: String +} + +@Serializable +class OwnedProject(override val name: String, val owner: String) : Project() + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(OwnedProject.serializer().descriptor)) +} diff --git a/docs/knit/example/example-enum-class-01.kt b/docs/knit/example/example-enum-class-01.kt index d6e96ed9..34e97596 100644 --- a/docs/knit/example/example-enum-class-01.kt +++ b/docs/knit/example/example-enum-class-01.kt @@ -2,6 +2,8 @@ @file:Suppress("PackageDirectoryMismatch", "unused") package example.exampleEnumClass01 +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-enum-class-02.kt b/docs/knit/example/example-enum-class-02.kt index fdb9ed8f..cb8c04f7 100644 --- a/docs/knit/example/example-enum-class-02.kt +++ b/docs/knit/example/example-enum-class-02.kt @@ -2,6 +2,8 @@ @file:Suppress("PackageDirectoryMismatch", "unused") package example.exampleEnumClass02 +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-map-primitive-02.kt b/docs/knit/example/example-map-primitive-02.kt index d509ee56..3bd4c93e 100644 --- a/docs/knit/example/example-map-primitive-02.kt +++ b/docs/knit/example/example-map-primitive-02.kt @@ -6,11 +6,17 @@ import kotlinx.serialization.* import dev.adamko.kxstsgen.* @Serializable -data class Config( - val properties: Map +enum class SettingKeys { + SCREEN_SIZE, + MAX_MEMORY, +} + +@Serializable +class Application( + val settings: Map ) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Config.serializer().descriptor)) + println(tsGenerator.generate(Application.serializer().descriptor)) } diff --git a/docs/knit/example/example-map-primitive-03.kt b/docs/knit/example/example-map-primitive-03.kt new file mode 100644 index 00000000..b0c88a76 --- /dev/null +++ b/docs/knit/example/example-map-primitive-03.kt @@ -0,0 +1,16 @@ +// This file was automatically generated from maps.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.exampleMapPrimitive03 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +data class Config( + val properties: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Config.serializer().descriptor)) +} diff --git a/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt b/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt new file mode 100644 index 00000000..73034e5a --- /dev/null +++ b/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt @@ -0,0 +1,20 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.examplePolymorphicAbstractClassPrimitiveFields01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +abstract 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().descriptor)) +} diff --git a/docs/knit/example/example-polymorphic-sealed-class-01.kt b/docs/knit/example/example-polymorphic-sealed-class-01.kt new file mode 100644 index 00000000..dc55f95f --- /dev/null +++ b/docs/knit/example/example-polymorphic-sealed-class-01.kt @@ -0,0 +1,22 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.examplePolymorphicSealedClass01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +sealed class Project { + abstract val name: String +} + +@Serializable +class OwnedProject(override val name: String, val owner: String) : Project() + +@Serializable +class DeprecatedProject(override val name: String, val reason: String) : Project() + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(OwnedProject.serializer().descriptor)) +} diff --git a/docs/knit/example/example-polymorphic-static-types-01.kt b/docs/knit/example/example-polymorphic-static-types-01.kt new file mode 100644 index 00000000..f3979915 --- /dev/null +++ b/docs/knit/example/example-polymorphic-static-types-01.kt @@ -0,0 +1,16 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.examplePolymorphicStaticTypes01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +open class Project(val name: String) + +class OwnedProject(name: String, val owner: String) : Project(name) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Project.serializer().descriptor)) +} diff --git a/docs/knit/example/example-polymorphic-static-types-02.kt b/docs/knit/example/example-polymorphic-static-types-02.kt new file mode 100644 index 00000000..3f9861cf --- /dev/null +++ b/docs/knit/example/example-polymorphic-static-types-02.kt @@ -0,0 +1,19 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.examplePolymorphicStaticTypes02 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +abstract class Project { + abstract val name: String +} + +@Serializable +class OwnedProject(override val name: String, val owner: String) : Project() + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(OwnedProject.serializer().descriptor)) +} diff --git a/docs/knit/knit-test.ftl b/docs/knit/knit-test.ftl index e4ae1290..60a17413 100644 --- a/docs/knit/knit-test.ftl +++ b/docs/knit/knit-test.ftl @@ -1,11 +1,11 @@ -<#-- @ftlvariable name="test.name" type="String" --> -<#-- @ftlvariable name="test.package" type="String" --> -<#-- @ftlvariable name="file.name" type="String" --> +<#-- @ftlvariable name="test.name" type="java.lang.String" --> +<#-- @ftlvariable name="test.package" type="java.lang.String" --> // This file was automatically generated from ${file.name} by Knit tool. Do not edit. package ${test.package} -import org.junit.jupiter.api.Test +import io.kotest.matchers.* import kotlinx.knit.test.* +import org.junit.jupiter.api.Test class ${test.name} { <#list cases as case><#assign method = test["mode.${case.param}"]!"custom"> @@ -14,10 +14,13 @@ class ${test.name} { captureOutput("${case.name}") { ${case.knit.package}.${case.knit.name}.main() }<#if method != "custom">.${method}( + // language=TypeScript + """ <#list case.lines as line> - "${line?j_string}"<#sep>, + |${line} - ) + """.trimMargin() + ) <#else>.also { lines -> check(${case.param}) } diff --git a/docs/knit/test/AbstractClassesTest.kt b/docs/knit/test/AbstractClassesTest.kt index c1b16c00..a086c430 100644 --- a/docs/knit/test/AbstractClassesTest.kt +++ b/docs/knit/test/AbstractClassesTest.kt @@ -1,44 +1,57 @@ // This file was automatically generated from abstract-classes.md by Knit tool. Do not edit. package example.test -import org.junit.jupiter.api.Test +import io.kotest.matchers.* import kotlinx.knit.test.* +import org.junit.jupiter.api.Test class AbstractClassesTest { @Test fun testExampleAbstractClassSingleField01() { captureOutput("ExampleAbstractClassSingleField01") { example.exampleAbstractClassSingleField01.main() - }.verifyOutputLines( - "interface Color {", - " rgb: number;", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface Color { + | rgb: number; + |} + """.trimMargin() + ) } @Test fun testExampleAbstractClassPrimitiveFields01() { captureOutput("ExampleAbstractClassPrimitiveFields01") { example.exampleAbstractClassPrimitiveFields01.main() - }.verifyOutputLines( - "interface SimpleTypes {", - " aString: string;", - " anInt: number;", - " aDouble: number;", - " bool: boolean;", - " privateMember: string;", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface SimpleTypes { + | aString: string; + | anInt: number; + | aDouble: number; + | bool: boolean; + | privateMember: string; + |} + """.trimMargin() + ) } @Test fun testExampleAbstractClassAbstractField01() { captureOutput("ExampleAbstractClassAbstractField01") { example.exampleAbstractClassAbstractField01.main() - }.verifyOutputLines( - "interface Color {", - " rgb: number;", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface Color { + | rgb: number; + |} + """.trimMargin() + ) } } diff --git a/docs/knit/test/BasicClassesTest.kt b/docs/knit/test/BasicClassesTest.kt index 2b347bee..7a5373df 100644 --- a/docs/knit/test/BasicClassesTest.kt +++ b/docs/knit/test/BasicClassesTest.kt @@ -1,48 +1,61 @@ // This file was automatically generated from basic-classes.md by Knit tool. Do not edit. package example.test -import org.junit.jupiter.api.Test +import io.kotest.matchers.* import kotlinx.knit.test.* +import org.junit.jupiter.api.Test class BasicClassesTest { @Test fun testExamplePlainClassSingleField01() { captureOutput("ExamplePlainClassSingleField01") { example.examplePlainClassSingleField01.main() - }.verifyOutputLines( - "interface Color {", - " rgb: number;", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface Color { + | rgb: number; + |} + """.trimMargin() + ) } @Test fun testExamplePlainClassPrimitiveFields01() { captureOutput("ExamplePlainClassPrimitiveFields01") { example.examplePlainClassPrimitiveFields01.main() - }.verifyOutputLines( - "interface SimpleTypes {", - " aString: string;", - " anInt: number;", - " aDouble: number;", - " bool: boolean;", - " privateMember: string;", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface SimpleTypes { + | aString: string; + | anInt: number; + | aDouble: number; + | bool: boolean; + | privateMember: string; + |} + """.trimMargin() + ) } @Test fun testExamplePlainDataClass01() { captureOutput("ExamplePlainDataClass01") { example.examplePlainDataClass01.main() - }.verifyOutputLines( - "interface SomeDataClass {", - " aString: string;", - " anInt: number;", - " aDouble: number;", - " bool: boolean;", - " privateMember: string;", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface SomeDataClass { + | aString: string; + | anInt: number; + | aDouble: number; + | bool: boolean; + | privateMember: string; + |} + """.trimMargin() + ) } } diff --git a/docs/knit/test/DefaultValuesTest.kt b/docs/knit/test/DefaultValuesTest.kt index 257fc830..d0f07b10 100644 --- a/docs/knit/test/DefaultValuesTest.kt +++ b/docs/knit/test/DefaultValuesTest.kt @@ -1,31 +1,40 @@ // This file was automatically generated from default-values.md by Knit tool. Do not edit. package example.test -import org.junit.jupiter.api.Test +import io.kotest.matchers.* import kotlinx.knit.test.* +import org.junit.jupiter.api.Test class DefaultValuesTest { @Test fun testExampleDefaultValuesSingleField01() { captureOutput("ExampleDefaultValuesSingleField01") { example.exampleDefaultValuesSingleField01.main() - }.verifyOutputLines( - "interface Color {", - " rgb?: number;", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface Color { + | rgb?: number; + |} + """.trimMargin() + ) } @Test fun testExampleDefaultValuesPrimitiveFields01() { captureOutput("ExampleDefaultValuesPrimitiveFields01") { example.exampleDefaultValuesPrimitiveFields01.main() - }.verifyOutputLines( - "interface ContactDetails {", - " email: string | null;", - " phoneNumber?: string | null;", - " active?: boolean | null;", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface ContactDetails { + | email: string | null; + | phoneNumber?: string | null; + | active?: boolean | null; + |} + """.trimMargin() + ) } } diff --git a/docs/knit/test/EnumClassTest.kt b/docs/knit/test/EnumClassTest.kt index 88fa3b19..b772e718 100644 --- a/docs/knit/test/EnumClassTest.kt +++ b/docs/knit/test/EnumClassTest.kt @@ -1,33 +1,42 @@ // This file was automatically generated from enums.md by Knit tool. Do not edit. package example.test -import org.junit.jupiter.api.Test +import io.kotest.matchers.* import kotlinx.knit.test.* +import org.junit.jupiter.api.Test class EnumClassTest { @Test fun testExampleEnumClass01() { captureOutput("ExampleEnumClass01") { example.exampleEnumClass01.main() - }.verifyOutputLines( - "enum SomeType {", - " Alpha = \"Alpha\",", - " Beta = \"Beta\",", - " Gamma = \"Gamma\",", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |enum SomeType { + | Alpha = "Alpha", + | Beta = "Beta", + | Gamma = "Gamma", + |} + """.trimMargin() + ) } @Test fun testExampleEnumClass02() { captureOutput("ExampleEnumClass02") { example.exampleEnumClass02.main() - }.verifyOutputLines( - "enum SomeType2 {", - " Alpha = \"Alpha\",", - " Beta = \"Beta\",", - " Gamma = \"Gamma\",", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |enum SomeType2 { + | Alpha = "Alpha", + | Beta = "Beta", + | Gamma = "Gamma", + |} + """.trimMargin() + ) } } diff --git a/docs/knit/test/ListsTests.kt b/docs/knit/test/ListsTests.kt index 22278b78..68bf9f0f 100644 --- a/docs/knit/test/ListsTests.kt +++ b/docs/knit/test/ListsTests.kt @@ -1,18 +1,23 @@ // This file was automatically generated from lists.md by Knit tool. Do not edit. package example.test -import org.junit.jupiter.api.Test +import io.kotest.matchers.* import kotlinx.knit.test.* +import org.junit.jupiter.api.Test class ListsTests { @Test fun testExampleListPrimitive01() { captureOutput("ExampleListPrimitive01") { example.exampleListPrimitive01.main() - }.verifyOutputLines( - "interface CalendarEvent {", - " attendeeNames: string[];", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface CalendarEvent { + | attendeeNames: string[]; + |} + """.trimMargin() + ) } } diff --git a/docs/knit/test/MapsTests.kt b/docs/knit/test/MapsTests.kt index da6760f1..adfc0cd1 100644 --- a/docs/knit/test/MapsTests.kt +++ b/docs/knit/test/MapsTests.kt @@ -1,49 +1,82 @@ // This file was automatically generated from maps.md by Knit tool. Do not edit. package example.test -import org.junit.jupiter.api.Test +import io.kotest.matchers.* import kotlinx.knit.test.* +import org.junit.jupiter.api.Test class MapsTests { @Test fun testExampleMapPrimitive01() { captureOutput("ExampleMapPrimitive01") { example.exampleMapPrimitive01.main() - }.verifyOutputLines( - "interface Config {", - " properties: { [key: string]: string };", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface Config { + | properties: { [key: string]: string }; + |} + """.trimMargin() + ) } @Test fun testExampleMapPrimitive02() { captureOutput("ExampleMapPrimitive02") { example.exampleMapPrimitive02.main() - }.verifyOutputLines( - "interface Config {", - " properties: { [key: string | null]: string | null };", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |enum SettingKeys { + | SCREEN_SIZE = "SCREEN_SIZE", + | MAX_MEMORY = "MAX_MEMORY", + |} + | + |interface Application { + | settings: { [key in SettingKeys]: string }; + |} + """.trimMargin() + ) + } + + @Test + fun testExampleMapPrimitive03() { + captureOutput("ExampleMapPrimitive03") { + example.exampleMapPrimitive03.main() + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface Config { + | properties: { [key: string | null]: string | null }; + |} + """.trimMargin() + ) } @Test fun testExampleMapComplex01() { captureOutput("ExampleMapComplex01") { example.exampleMapComplex01.main() - }.verifyOutputLines( - "type UByte = number;", - "", - "interface Colour {", - " r: UByte;", - " g: UByte;", - " b: UByte;", - " a: UByte;", - "}", - "", - "interface CanvasProperties {", - " colourNames: { [key: Colour]: string };", - "}" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |type UByte = number; + | + |interface Colour { + | r: UByte; + | g: UByte; + | b: UByte; + | a: UByte; + |} + | + |interface CanvasProperties { + | colourNames: Map; + |} + """.trimMargin() + ) } } diff --git a/docs/knit/test/PolymorphismTest.kt b/docs/knit/test/PolymorphismTest.kt index 73dd1594..d8684398 100644 --- a/docs/knit/test/PolymorphismTest.kt +++ b/docs/knit/test/PolymorphismTest.kt @@ -1,33 +1,90 @@ // This file was automatically generated from polymorphism.md by Knit tool. Do not edit. package example.test -import org.junit.jupiter.api.Test +import io.kotest.matchers.* import kotlinx.knit.test.* +import org.junit.jupiter.api.Test class PolymorphismTest { @Test - fun testExamplePolymorphismSealed01() { - captureOutput("ExamplePolymorphismSealed01") { - example.examplePolymorphismSealed01.main() - }.verifyOutputLines( - "interface Color {", - " rgb: number;", - "}" - ) + fun testExamplePolymorphicAbstractClassPrimitiveFields01() { + captureOutput("ExamplePolymorphicAbstractClassPrimitiveFields01") { + example.examplePolymorphicAbstractClassPrimitiveFields01.main() + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface SimpleTypes { + | aString: string; + | anInt: number; + | aDouble: number; + | bool: boolean; + | privateMember: string; + |} + """.trimMargin() + ) + } + + @Test + fun testExamplePolymorphicStaticTypes01() { + captureOutput("ExamplePolymorphicStaticTypes01") { + example.examplePolymorphicStaticTypes01.main() + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface Project { + | name: string; + |} + """.trimMargin() + ) + } + + @Test + fun testExamplePolymorphicStaticTypes02() { + captureOutput("ExamplePolymorphicStaticTypes02") { + example.examplePolymorphicStaticTypes02.main() + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface OwnedProject { + | name: string; + | owner: string; + |} + """.trimMargin() + ) } @Test - fun testExampleAbstractClassPrimitiveFields01() { - captureOutput("ExampleAbstractClassPrimitiveFields01") { - example.exampleAbstractClassPrimitiveFields01.main() - }.verifyOutputLines( - "interface SimpleTypes {", - " aString: string;", - " anInt: number;", - " aDouble: number;", - " bool: boolean;", - " privateMember: string;", - "}" - ) + fun testExamplePolymorphicSealedClass01() { + captureOutput("ExamplePolymorphicSealedClass01") { + example.examplePolymorphicSealedClass01.main() + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |enum ProjectKind { + | OwnedProject, + | DeprecatedProject, + |} + | + |interface Project { + | type: ProjectKind; + |} + | + |interface OwnedProject { + | type: ProjectKind.OwnedProject; + | name: string; + | owner: string; + |} + | + |interface DeprecatedProject { + | type: ProjectKind.DeprecatedProject; + | name: string; + | reason: string; + |} + """.trimMargin() + ) } } diff --git a/docs/knit/test/ValueClassesTest.kt b/docs/knit/test/ValueClassesTest.kt index 2362a241..90252a6b 100644 --- a/docs/knit/test/ValueClassesTest.kt +++ b/docs/knit/test/ValueClassesTest.kt @@ -1,51 +1,68 @@ // This file was automatically generated from value-classes.md by Knit tool. Do not edit. package example.test -import org.junit.jupiter.api.Test +import io.kotest.matchers.* import kotlinx.knit.test.* +import org.junit.jupiter.api.Test class ValueClassesTest { @Test fun testExampleValueClasses01() { captureOutput("ExampleValueClasses01") { example.exampleValueClasses01.main() - }.verifyOutputLines( - "type AuthToken = string;" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |type AuthToken = string; + """.trimMargin() + ) } @Test fun testExampleValueClasses02() { captureOutput("ExampleValueClasses02") { example.exampleValueClasses02.main() - }.verifyOutputLines( - "type UByte = number;", - "", - "type UShort = number;", - "", - "type UInt = number;", - "", - "type ULong = number;" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |type UByte = number; + | + |type UShort = number; + | + |type UInt = number; + | + |type ULong = number; + """.trimMargin() + ) } @Test fun testExampleValueClasses03() { captureOutput("ExampleValueClasses03") { example.exampleValueClasses03.main() - }.verifyOutputLines( - "type ULong = number & { __ULong__: void };" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |type ULong = number & { __ULong__: void }; + """.trimMargin() + ) } @Test fun testExampleValueClasses04() { captureOutput("ExampleValueClasses04") { example.exampleValueClasses04.main() - }.verifyOutputLines( - "type UInt = number;", - "", - "type UserCount = UInt;" - ) + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |type UInt = number; + | + |type UserCount = UInt; + """.trimMargin() + ) } } diff --git a/docs/lists.md b/docs/lists.md index 2e3adae4..32d0a1b3 100644 --- a/docs/lists.md +++ b/docs/lists.md @@ -1,12 +1,25 @@ -### Primitive lists +**Table of contents** + + + +* [Introduction](#introduction) + * [Primitive lists](#primitive-lists) + + + +## Introduction + + +### Primitive lists + ```kotlin @Serializable data class CalendarEvent( diff --git a/docs/maps.md b/docs/maps.md index dfda986f..2c6f8af8 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -1,10 +1,26 @@ +**Table of contents** + + + +* [Introduction](#introduction) + * [Primitive maps](#primitive-maps) + * [Enum keys](#enum-keys) + * [Nullable keys and values](#nullable-keys-and-values) + * [Maps with complex keys](#maps-with-complex-keys) + + + + +## Introduction + + ### Primitive maps ```kotlin @@ -29,6 +45,41 @@ interface Config { +### Enum keys + +```kotlin +@Serializable +enum class SettingKeys { + SCREEN_SIZE, + MAX_MEMORY, +} + +@Serializable +class Application( + val settings: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Application.serializer().descriptor)) +} +``` + +> You can get the full code [here](./knit/example/example-map-primitive-02.kt). + +```typescript +enum SettingKeys { + SCREEN_SIZE = "SCREEN_SIZE", + MAX_MEMORY = "MAX_MEMORY", +} + +interface Application { + settings: { [key in SettingKeys]: string }; +} +``` + + + ### Nullable keys and values ```kotlin @@ -43,7 +94,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-map-primitive-02.kt). +> You can get the full code [here](./knit/example/example-map-primitive-03.kt). ```typescript interface Config { @@ -88,7 +139,7 @@ interface Colour { } interface CanvasProperties { - colourNames: { [key: Colour]: string }; + colourNames: Map; } ``` diff --git a/docs/polymorphism.md b/docs/polymorphism.md index 39ac4dad..3ff9e291 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -1,72 +1,158 @@ -### Sealed classes +**Table of contents** + + + +* [Introduction](#introduction) + * [Abstract class with primitive fields](#abstract-class-with-primitive-fields) +* [Closed Polymorphism](#closed-polymorphism) + * [Static types](#static-types) + * [Sealed classes](#sealed-classes) + + + +## Introduction + + + +### Abstract class with primitive fields + ```kotlin @Serializable -sealed class Project { +abstract 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().descriptor)) +} +``` + +> You can get the full code [here](./knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt). + +```typescript +interface SimpleTypes { + aString: string; + anInt: number; + aDouble: number; + bool: boolean; + privateMember: string; +} +``` + + + +## Closed Polymorphism + +https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/polymorphism.md#closed-polymorphism + +### Static types + +```kotlin +@Serializable +open class Project(val name: String) + +class OwnedProject(name: String, val owner: String) : Project(name) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Project.serializer().descriptor)) +} +``` + +> You can get the full code [here](./knit/example/example-polymorphic-static-types-01.kt). + +Only the Project class properties are generated. + +```typescript +interface Project { + name: string; +} +``` + + + +```kotlin +@Serializable +abstract class Project { abstract val name: String } @Serializable -data class OwnedProject( - override val name: String, - val owner: String, -) : Project() +class OwnedProject(override val name: String, val owner: String) : Project() fun main() { val tsGenerator = KxsTsGenerator() - println( - tsGenerator.generate( - Project.serializer().descriptor, - OwnedProject.serializer().descriptor, - ) - ) + println(tsGenerator.generate(OwnedProject.serializer().descriptor)) } ``` -> You can get the full code [here](./knit/example/example-polymorphism-sealed-01.kt). +> You can get the full code [here](./knit/example/example-polymorphic-static-types-02.kt). ```typescript -interface Color { - rgb: number; +interface OwnedProject { + name: string; + owner: string; } ``` -### Abstract class with primitive fields +### Sealed classes ```kotlin @Serializable -abstract class SimpleTypes( - val aString: String, - var anInt: Int, - val aDouble: Double, - val bool: Boolean, - private val privateMember: String, -) +sealed class Project { + abstract val name: String +} + +@Serializable +class OwnedProject(override val name: String, val owner: String) : Project() + +@Serializable +class DeprecatedProject(override val name: String, val reason: String) : Project() fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SimpleTypes.serializer().descriptor)) + println(tsGenerator.generate(OwnedProject.serializer().descriptor)) } ``` -> You can get the full code [here](./knit/example/example-abstract-class-primitive-fields-01.kt). +> You can get the full code [here](./knit/example/example-polymorphic-sealed-class-01.kt). + ```typescript -interface SimpleTypes { - aString: string; - anInt: number; - aDouble: number; - bool: boolean; - privateMember: string; +enum ProjectKind { + OwnedProject, + DeprecatedProject, +} + +interface Project { + type: ProjectKind; +} + +interface OwnedProject { + type: ProjectKind.OwnedProject; + name: string; + owner: string; +} + +interface DeprecatedProject { + type: ProjectKind.DeprecatedProject; + name: string; + reason: string; } ``` diff --git a/docs/value-classes.md b/docs/value-classes.md index f1f98ee6..6ab4053a 100644 --- a/docs/value-classes.md +++ b/docs/value-classes.md @@ -1,10 +1,26 @@ + +**Table of contents** + + + +* [Introduction](#introduction) + * [Inline value classes](#inline-value-classes) + * [Brand typing](#brand-typing) + * [Nested value classes](#nested-value-classes) + + + + +## Introduction + + ### Inline value classes Value classes are transformed to type aliases. The type of the value class is used. diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index ecc9b87d..182cb973 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -124,12 +124,38 @@ class KxsTsSourceCodeGenerator( append(generateTypeReference(typing.type.elementsTyping)) append("[]") } - is TsStructure.TsMap -> { - val keyTypeRef = generateTypeReference(typing.type.keyTyping) - val valueTypeRef = generateTypeReference(typing.type.valueTyping) - append("{ [key: $keyTypeRef]: $valueTypeRef }") - } + is TsStructure.TsMap -> append(mapTypeReference(typing)) } } } + + private fun mapTypeReference(typing: TsTyping): String { + require(typing.type is TsStructure.TsMap) + + val keyTypeRef = generateTypeReference(typing.type.keyTyping) + val valueTypeRef = generateTypeReference(typing.type.valueTyping) + + return when (typing.type.keyTyping.type) { + + TsPrimitive.TsString, + TsPrimitive.TsNumber -> "{ [key: $keyTypeRef]: $valueTypeRef }" + + is TsStructure.TsEnum -> "{ [key in $keyTypeRef]: $valueTypeRef }" + + TsPrimitive.TsBoolean, + TsPrimitive.TsObject, + TsPrimitive.TsAny, + TsPrimitive.TsNever, + TsPrimitive.TsNull, + TsPrimitive.TsUndefined, + TsPrimitive.TsUnknown, + TsPrimitive.TsVoid, + is TsTypeAlias, + is TsStructure.TsInterface, + is TsStructure.TsList, + is TsStructure.TsMap -> "Map<$keyTypeRef, $valueTypeRef>" + + } + } + } From dbdca26343e4c58a0778916072768d872a16488a Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 4 Mar 2022 14:58:30 +0100 Subject: [PATCH 04/46] suppress warning in tests --- docs/knit/knit-test.ftl | 1 + docs/knit/test/AbstractClassesTest.kt | 1 + docs/knit/test/BasicClassesTest.kt | 1 + docs/knit/test/DefaultValuesTest.kt | 1 + docs/knit/test/EnumClassTest.kt | 1 + docs/knit/test/ListsTests.kt | 1 + docs/knit/test/MapsTests.kt | 1 + docs/knit/test/PolymorphismTest.kt | 1 + docs/knit/test/ValueClassesTest.kt | 1 + 9 files changed, 9 insertions(+) diff --git a/docs/knit/knit-test.ftl b/docs/knit/knit-test.ftl index 60a17413..9d910b67 100644 --- a/docs/knit/knit-test.ftl +++ b/docs/knit/knit-test.ftl @@ -1,6 +1,7 @@ <#-- @ftlvariable name="test.name" type="java.lang.String" --> <#-- @ftlvariable name="test.package" type="java.lang.String" --> // This file was automatically generated from ${file.name} by Knit tool. Do not edit. +@file:Suppress("JSUnusedLocalSymbols") package ${test.package} import io.kotest.matchers.* diff --git a/docs/knit/test/AbstractClassesTest.kt b/docs/knit/test/AbstractClassesTest.kt index a086c430..525486d3 100644 --- a/docs/knit/test/AbstractClassesTest.kt +++ b/docs/knit/test/AbstractClassesTest.kt @@ -1,4 +1,5 @@ // This file was automatically generated from abstract-classes.md by Knit tool. Do not edit. +@file:Suppress("JSUnusedLocalSymbols") package example.test import io.kotest.matchers.* diff --git a/docs/knit/test/BasicClassesTest.kt b/docs/knit/test/BasicClassesTest.kt index 7a5373df..aab163e1 100644 --- a/docs/knit/test/BasicClassesTest.kt +++ b/docs/knit/test/BasicClassesTest.kt @@ -1,4 +1,5 @@ // This file was automatically generated from basic-classes.md by Knit tool. Do not edit. +@file:Suppress("JSUnusedLocalSymbols") package example.test import io.kotest.matchers.* diff --git a/docs/knit/test/DefaultValuesTest.kt b/docs/knit/test/DefaultValuesTest.kt index d0f07b10..b32a2f70 100644 --- a/docs/knit/test/DefaultValuesTest.kt +++ b/docs/knit/test/DefaultValuesTest.kt @@ -1,4 +1,5 @@ // This file was automatically generated from default-values.md by Knit tool. Do not edit. +@file:Suppress("JSUnusedLocalSymbols") package example.test import io.kotest.matchers.* diff --git a/docs/knit/test/EnumClassTest.kt b/docs/knit/test/EnumClassTest.kt index b772e718..0c5a9ad2 100644 --- a/docs/knit/test/EnumClassTest.kt +++ b/docs/knit/test/EnumClassTest.kt @@ -1,4 +1,5 @@ // This file was automatically generated from enums.md by Knit tool. Do not edit. +@file:Suppress("JSUnusedLocalSymbols") package example.test import io.kotest.matchers.* diff --git a/docs/knit/test/ListsTests.kt b/docs/knit/test/ListsTests.kt index 68bf9f0f..a8530b6d 100644 --- a/docs/knit/test/ListsTests.kt +++ b/docs/knit/test/ListsTests.kt @@ -1,4 +1,5 @@ // This file was automatically generated from lists.md by Knit tool. Do not edit. +@file:Suppress("JSUnusedLocalSymbols") package example.test import io.kotest.matchers.* diff --git a/docs/knit/test/MapsTests.kt b/docs/knit/test/MapsTests.kt index adfc0cd1..85895901 100644 --- a/docs/knit/test/MapsTests.kt +++ b/docs/knit/test/MapsTests.kt @@ -1,4 +1,5 @@ // This file was automatically generated from maps.md by Knit tool. Do not edit. +@file:Suppress("JSUnusedLocalSymbols") package example.test import io.kotest.matchers.* diff --git a/docs/knit/test/PolymorphismTest.kt b/docs/knit/test/PolymorphismTest.kt index d8684398..4f91dd06 100644 --- a/docs/knit/test/PolymorphismTest.kt +++ b/docs/knit/test/PolymorphismTest.kt @@ -1,4 +1,5 @@ // This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +@file:Suppress("JSUnusedLocalSymbols") package example.test import io.kotest.matchers.* diff --git a/docs/knit/test/ValueClassesTest.kt b/docs/knit/test/ValueClassesTest.kt index 90252a6b..76c36e59 100644 --- a/docs/knit/test/ValueClassesTest.kt +++ b/docs/knit/test/ValueClassesTest.kt @@ -1,4 +1,5 @@ // This file was automatically generated from value-classes.md by Knit tool. Do not edit. +@file:Suppress("JSUnusedLocalSymbols") package example.test import io.kotest.matchers.* From ec52c3fc3165eca2889d13bb692a3766aef4657b Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 4 Mar 2022 16:50:48 +0100 Subject: [PATCH 05/46] `export enum` -> valid for .d.ts files --- docs/enums.md | 15 ++++++++------- docs/knit/test/EnumClassTest.kt | 4 ++-- docs/knit/test/MapsTests.kt | 2 +- docs/maps.md | 2 +- .../KxsTsSourceCodeGenerator.kt | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) diff --git a/docs/enums.md b/docs/enums.md index 80fe17c9..eb85b32f 100644 --- a/docs/enums.md +++ b/docs/enums.md @@ -6,8 +6,8 @@ * [Introduction](#introduction) - * [Plain class with a single field](#plain-class-with-a-single-field) - * [Plain class with primitive fields](#plain-class-with-primitive-fields) + * [Simple enum](#simple-enum) + * [Enum with properties](#enum-with-properties) @@ -19,8 +19,7 @@ import dev.adamko.kxstsgen.* ## Introduction - -### Plain class with a single field +### Simple enum -### Plain class with primitive fields +### Enum with properties + +Because enums are static, fields aren't converted. ```kotlin @Serializable @@ -74,7 +75,7 @@ fun main() { > You can get the full code [here](./knit/example/example-enum-class-02.kt). ```typescript -enum SomeType2 { +export enum SomeType2 { Alpha = "Alpha", Beta = "Beta", Gamma = "Gamma", diff --git a/docs/knit/test/EnumClassTest.kt b/docs/knit/test/EnumClassTest.kt index 0c5a9ad2..78430b92 100644 --- a/docs/knit/test/EnumClassTest.kt +++ b/docs/knit/test/EnumClassTest.kt @@ -15,7 +15,7 @@ class EnumClassTest { .shouldBe( // language=TypeScript """ - |enum SomeType { + |export enum SomeType { | Alpha = "Alpha", | Beta = "Beta", | Gamma = "Gamma", @@ -32,7 +32,7 @@ class EnumClassTest { .shouldBe( // language=TypeScript """ - |enum SomeType2 { + |export enum SomeType2 { | Alpha = "Alpha", | Beta = "Beta", | Gamma = "Gamma", diff --git a/docs/knit/test/MapsTests.kt b/docs/knit/test/MapsTests.kt index 85895901..43c0d9fd 100644 --- a/docs/knit/test/MapsTests.kt +++ b/docs/knit/test/MapsTests.kt @@ -30,7 +30,7 @@ class MapsTests { .shouldBe( // language=TypeScript """ - |enum SettingKeys { + |export enum SettingKeys { | SCREEN_SIZE = "SCREEN_SIZE", | MAX_MEMORY = "MAX_MEMORY", |} diff --git a/docs/maps.md b/docs/maps.md index 2c6f8af8..ec9dadb6 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -68,7 +68,7 @@ fun main() { > You can get the full code [here](./knit/example/example-map-primitive-02.kt). ```typescript -enum SettingKeys { +export enum SettingKeys { SCREEN_SIZE = "SCREEN_SIZE", MAX_MEMORY = "MAX_MEMORY", } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index 182cb973..b93c28fb 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -56,7 +56,7 @@ class KxsTsSourceCodeGenerator( } return """ - |enum ${enum.id.name} { + |export enum ${enum.id.name} { |${enumMembers} |} """.trimMargin() From 220ba8e33615f48bf5e56b5f9d69b21510e62cf5 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 4 Mar 2022 16:52:39 +0100 Subject: [PATCH 06/46] test for ignoring fields with @Transient, and if an interface has no properties, don't have a blank line between {} --- docs/basic-classes.md | 37 +++++++++++++++++++ docs/knit/test/BasicClassesTest.kt | 14 +++++++ .../KxsTsSourceCodeGenerator.kt | 32 +++++++++------- 3 files changed, 69 insertions(+), 14 deletions(-) diff --git a/docs/basic-classes.md b/docs/basic-classes.md index d0e0425b..18c25068 100644 --- a/docs/basic-classes.md +++ b/docs/basic-classes.md @@ -8,6 +8,7 @@ * [Plain class with a single field](#plain-class-with-a-single-field) * [Plain class with primitive fields](#plain-class-with-primitive-fields) * [Data class with primitive fields](#data-class-with-primitive-fields) + * [Ignoring fields with `@Transitive`](#ignoring-fields-with-@transitive) @@ -105,3 +106,39 @@ interface SomeDataClass { ``` + +### Ignoring fields with `@Transitive` + +Just like in Kotlinx.Serialization, +[fields can be ignored](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/basic-serialization.md#transient-properties) + +> A property can be excluded from serialization by marking it with the +> [`@Transient`](https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization/-transient/index.html) +> annotation +> (don't confuse it with +> [`kotlin.jvm.Transient`](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.jvm/-transient/)). +> Transient properties must have a default value. + +```kotlin +import kotlinx.serialization.Transient + +@Serializable +class SimpleTypes( + @Transient + val aString: String = "default-value" +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(SimpleTypes.serializer().descriptor)) +} +``` + +> You can get the full code [here](./knit/example/example-plain-class-primitive-fields-02.kt). + +```typescript +interface SimpleTypes { +} +``` + + diff --git a/docs/knit/test/BasicClassesTest.kt b/docs/knit/test/BasicClassesTest.kt index aab163e1..ac025a38 100644 --- a/docs/knit/test/BasicClassesTest.kt +++ b/docs/knit/test/BasicClassesTest.kt @@ -59,4 +59,18 @@ class BasicClassesTest { """.trimMargin() ) } + + @Test + fun testExamplePlainClassPrimitiveFields02() { + captureOutput("ExamplePlainClassPrimitiveFields02") { + example.examplePlainClassPrimitiveFields02.main() + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface SimpleTypes { + |} + """.trimMargin() + ) + } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index b93c28fb..4f38f1a2 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -64,22 +64,26 @@ class KxsTsSourceCodeGenerator( private fun generateInterface(element: TsStructure.TsInterface): String { - val properties = element.properties.joinToString("\n") { property -> - val separator = when (property) { - is TsProperty.Optional -> "?: " - is TsProperty.Required -> ": " + val properties = element + .properties + .joinToString(separator = "\n") { property -> + val separator = when (property) { + is TsProperty.Optional -> "?: " + is TsProperty.Required -> ": " + } + val propertyType = generateTypeReference(property.typing) + // generate ` name: Type;` + // or ` name:? Type;` + "${indent}${property.name}${separator}${propertyType};" } - val propertyType = generateTypeReference(property.typing) - // generate ` name: Type;` - // or ` name:? Type;` - "${indent}${property.name}${separator}${propertyType};" - } - return """ - |interface ${element.id.name} { - |${properties} - |} - """.trimMargin() + return buildString { + appendLine("interface ${element.id.name} {") + if (properties.isNotBlank()) { + appendLine(properties) + } + append("}") + } } private fun generateTypeAlias(element: TsTypeAlias): String { From 301cf889cd59f1f5ee2b5af736ccda60fa631620 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 4 Mar 2022 16:54:26 +0100 Subject: [PATCH 07/46] test for objects, and fix test expectations in polymophic classes, so they extend parent interfaces --- ...example-plain-class-primitive-fields-02.kt | 19 ++++++ .../example/example-polymorphic-objects-01.kt | 25 ++++++++ docs/knit/test/PolymorphismTest.kt | 39 +++++++++++- docs/polymorphism.md | 61 +++++++++++++++++-- 4 files changed, 135 insertions(+), 9 deletions(-) create mode 100644 docs/knit/example/example-plain-class-primitive-fields-02.kt create mode 100644 docs/knit/example/example-polymorphic-objects-01.kt diff --git a/docs/knit/example/example-plain-class-primitive-fields-02.kt b/docs/knit/example/example-plain-class-primitive-fields-02.kt new file mode 100644 index 00000000..47762669 --- /dev/null +++ b/docs/knit/example/example-plain-class-primitive-fields-02.kt @@ -0,0 +1,19 @@ +// This file was automatically generated from basic-classes.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.examplePlainClassPrimitiveFields02 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +import kotlinx.serialization.Transient + +@Serializable +class SimpleTypes( + @Transient + val aString: String = "default-value" +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(SimpleTypes.serializer().descriptor)) +} diff --git a/docs/knit/example/example-polymorphic-objects-01.kt b/docs/knit/example/example-polymorphic-objects-01.kt new file mode 100644 index 00000000..e4d41bb9 --- /dev/null +++ b/docs/knit/example/example-polymorphic-objects-01.kt @@ -0,0 +1,25 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package example.examplePolymorphicObjects01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +sealed class Response + +@Serializable +object EmptyResponse : Response() + +@Serializable +class TextResponse(val text: String) : Response() + +fun main() { + val tsGenerator = KxsTsGenerator() + println( + tsGenerator.generate( + EmptyResponse.serializer().descriptor, + TextResponse.serializer().descriptor, + ) + ) +} diff --git a/docs/knit/test/PolymorphismTest.kt b/docs/knit/test/PolymorphismTest.kt index 4f91dd06..90cd523e 100644 --- a/docs/knit/test/PolymorphismTest.kt +++ b/docs/knit/test/PolymorphismTest.kt @@ -49,7 +49,11 @@ class PolymorphismTest { .shouldBe( // language=TypeScript """ - |interface OwnedProject { + |interface Project { + | name: string; + |} + | + |interface OwnedProject extends Project { | name: string; | owner: string; |} @@ -74,13 +78,13 @@ class PolymorphismTest { | type: ProjectKind; |} | - |interface OwnedProject { + |interface OwnedProject extends Project { | type: ProjectKind.OwnedProject; | name: string; | owner: string; |} | - |interface DeprecatedProject { + |interface DeprecatedProject extends Project { | type: ProjectKind.DeprecatedProject; | name: string; | reason: string; @@ -88,4 +92,33 @@ class PolymorphismTest { """.trimMargin() ) } + + @Test + fun testExamplePolymorphicObjects01() { + captureOutput("ExamplePolymorphicObjects01") { + example.examplePolymorphicObjects01.main() + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |export enum ResponseKind { + | EmptyResponse = "EmptyResponse", + | TextResponse = "TextResponse", + |} + | + |interface Response { + | type: ResponseKind; + |} + | + |interface EmptyResponse extends Response { + | type: ResponseKind.EmptyResponse; + |} + | + |interface TextResponse extends Response { + | type: ResponseKind.TextResponse; + | text: string; + |} + """.trimMargin() + ) + } } diff --git a/docs/polymorphism.md b/docs/polymorphism.md index 3ff9e291..17e41e9b 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -9,6 +9,7 @@ * [Closed Polymorphism](#closed-polymorphism) * [Static types](#static-types) * [Sealed classes](#sealed-classes) + * [Objects](#objects) @@ -20,8 +21,6 @@ import dev.adamko.kxstsgen.* ## Introduction - - ### Abstract class with primitive fields ```kotlin @@ -102,7 +101,11 @@ fun main() { > You can get the full code [here](./knit/example/example-polymorphic-static-types-02.kt). ```typescript -interface OwnedProject { +interface Project { + name: string; +} + +interface OwnedProject extends Project { name: string; owner: string; } @@ -132,7 +135,6 @@ fun main() { > You can get the full code [here](./knit/example/example-polymorphic-sealed-class-01.kt). - ```typescript enum ProjectKind { OwnedProject, @@ -143,13 +145,13 @@ interface Project { type: ProjectKind; } -interface OwnedProject { +interface OwnedProject extends Project { type: ProjectKind.OwnedProject; name: string; owner: string; } -interface DeprecatedProject { +interface DeprecatedProject extends Project { type: ProjectKind.DeprecatedProject; name: string; reason: string; @@ -157,3 +159,50 @@ interface DeprecatedProject { ``` + +### Objects + +```kotlin +@Serializable +sealed class Response + +@Serializable +object EmptyResponse : Response() + +@Serializable +class TextResponse(val text: String) : Response() + +fun main() { + val tsGenerator = KxsTsGenerator() + println( + tsGenerator.generate( + EmptyResponse.serializer().descriptor, + TextResponse.serializer().descriptor, + ) + ) +} +``` + +> You can get the full code [here](./knit/example/example-polymorphic-objects-01.kt). + +```typescript +export enum ResponseKind { + EmptyResponse = "EmptyResponse", + TextResponse = "TextResponse", +} + +interface Response { + type: ResponseKind; +} + +interface EmptyResponse extends Response { + type: ResponseKind.EmptyResponse; +} + +interface TextResponse extends Response { + type: ResponseKind.TextResponse; + text: string; +} +``` + + From 5155ffe69f60f557d766fb9b8e9b142a1c261b1a Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 4 Mar 2022 16:54:40 +0100 Subject: [PATCH 08/46] try fixing knit gradle task warning --- docs/knit/build.gradle.kts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/docs/knit/build.gradle.kts b/docs/knit/build.gradle.kts index 83d4555a..2e259645 100644 --- a/docs/knit/build.gradle.kts +++ b/docs/knit/build.gradle.kts @@ -1,5 +1,3 @@ -import buildsrc.config.excludeGeneratedGradleDsl - plugins { buildsrc.convention.`kotlin-jvm` kotlin("plugin.serialization") @@ -33,13 +31,21 @@ sourceSets.test { java.srcDirs("example", "test") } -//knit { -// rootDir = layout.projectDirectory.asFile -// files = rootProject.fileTree("docs") -//} +knit { + val docsDir = rootProject.layout.projectDirectory.dir("docs") + rootDir = docsDir.asFile + files = project.fileTree(docsDir) { + include("*.md") + } +} tasks.test { - dependsOn(tasks.knit) + dependsOn(tasks.knitCheck) +// finalizedBy(tasks.knitCheck) } tasks.compileKotlin { mustRunAfter(tasks.knit) } + +//tasks.knitCheck { +// dependsOn(tasks.test) +//} From d23c4e128a0014a0eda6428ad7deacbf4734a340 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 4 Mar 2022 18:12:12 +0100 Subject: [PATCH 09/46] - replace jacoco with kotlinx.kover - clean up root build.gradle.kts - change package name of example code (so the coverage report can be package filtered, if needed) - create a kotlin.multiplatform convention plugin --- build.gradle.kts | 42 +------------------ buildSrc/build.gradle.kts | 10 +++-- .../convention/jacoco-aggregation.gradle.kts | 28 ------------- .../buildsrc/convention/kotlin-jvm.gradle.kts | 3 -- .../kotlin-multiplatform.gradle.kts | 20 +++++++++ docs/knit.properties | 4 +- docs/knit/build.gradle.kts | 2 +- ...xample-abstract-class-abstract-field-01.kt | 2 +- ...mple-abstract-class-primitive-fields-01.kt | 2 +- .../example-abstract-class-single-field-01.kt | 2 +- ...mple-closed-polymorphic-static-types-01.kt | 16 ------- ...mple-closed-polymorphic-static-types-02.kt | 19 --------- ...mple-default-values-primitive-fields-01.kt | 2 +- .../example-default-values-single-field-01.kt | 2 +- docs/knit/example/example-enum-class-01.kt | 2 +- docs/knit/example/example-enum-class-02.kt | 2 +- .../knit/example/example-list-primitive-01.kt | 2 +- docs/knit/example/example-map-complex-01.kt | 2 +- docs/knit/example/example-map-primitive-01.kt | 2 +- docs/knit/example/example-map-primitive-02.kt | 2 +- docs/knit/example/example-map-primitive-03.kt | 2 +- ...example-plain-class-primitive-fields-01.kt | 2 +- ...example-plain-class-primitive-fields-02.kt | 2 +- .../example-plain-class-single-field-01.kt | 2 +- .../example/example-plain-data-class-01.kt | 2 +- ...phic-abstract-class-primitive-fields-01.kt | 2 +- .../example/example-polymorphic-objects-01.kt | 2 +- .../example-polymorphic-sealed-class-01.kt | 2 +- .../example-polymorphic-static-types-01.kt | 2 +- .../example-polymorphic-static-types-02.kt | 2 +- .../example/example-polymorphism-sealed-01.kt | 27 ------------ docs/knit/example/example-value-classes-01.kt | 2 +- docs/knit/example/example-value-classes-02.kt | 2 +- docs/knit/example/example-value-classes-03.kt | 2 +- docs/knit/example/example-value-classes-04.kt | 2 +- docs/knit/test/AbstractClassesTest.kt | 8 ++-- docs/knit/test/BasicClassesTest.kt | 10 ++--- docs/knit/test/DefaultValuesTest.kt | 6 +-- docs/knit/test/EnumClassTest.kt | 6 +-- docs/knit/test/ListsTests.kt | 4 +- docs/knit/test/MapsTests.kt | 10 ++--- docs/knit/test/PolymorphismTest.kt | 12 +++--- docs/knit/test/ValueClassesTest.kt | 10 ++--- modules/kxs-ts-gen-core/build.gradle.kts | 3 +- 44 files changed, 90 insertions(+), 200 deletions(-) delete mode 100644 buildSrc/src/main/kotlin/buildsrc/convention/jacoco-aggregation.gradle.kts create mode 100644 buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts delete mode 100644 docs/knit/example/example-closed-polymorphic-static-types-01.kt delete mode 100644 docs/knit/example/example-closed-polymorphic-static-types-02.kt delete mode 100644 docs/knit/example/example-polymorphism-sealed-01.kt diff --git a/build.gradle.kts b/build.gradle.kts index e5fa5cc9..0a5e67c7 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -2,11 +2,9 @@ import buildsrc.config.excludeGeneratedGradleDsl plugins { base - id("me.qoomon.git-versioning") version "5.1.2" idea - buildsrc.convention.`kotlin-jvm` - kotlin("plugin.serialization") - id("org.jetbrains.kotlinx.knit") + id("me.qoomon.git-versioning") version "5.1.2" + id("org.jetbrains.kotlinx.kover") } @@ -40,39 +38,3 @@ idea { ) } } - - -val kotlinxSerializationVersion = "1.3.2" - -dependencies { - implementation(projects.modules.kxsTsGenCore) - - implementation(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:${kotlinxSerializationVersion}")) - implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json") - - testImplementation(kotlin("test")) - - testImplementation("org.jetbrains.kotlinx:kotlinx-knit-test:0.3.0") -} - -tasks.withType { - kotlinOptions.freeCompilerArgs += listOf( - "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", - ) -} - -sourceSets.test { - java.srcDirs("docs/example", "docs/test") -} - -//knit { -// rootDir = layout.projectDirectory.asFile -// files = rootProject.fileTree("docs") -//} - -tasks.test { - dependsOn(tasks.knit) -} - -tasks.compileKotlin { mustRunAfter(tasks.knit) } diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index ccde2aac..096d3196 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -10,11 +10,12 @@ plugins { object Versions { const val jvmTarget = "11" - const val kotlinTarget = "1.6" const val kotlin = "1.6.10" - const val ksp = "1.6.10-1.0.2" - const val kotlinxSerializationVersion = "1.3.2" + const val kotlinTarget = "1.6" const val kotlinxKnit = "0.3.0" + const val kotlinxKover = "0.5.0" + const val kotlinxSerialization = "1.3.2" + const val ksp = "1.6.10-1.0.4" const val kotest = "5.1.0" } @@ -28,11 +29,12 @@ dependencies { implementation("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:${Versions.ksp}") - implementation(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:${Versions.kotlinxSerializationVersion}")) + implementation(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:${Versions.kotlinxSerialization}")) implementation("io.kotest:kotest-framework-multiplatform-plugin-gradle:${Versions.kotest}") implementation("org.jetbrains.kotlinx:kotlinx-knit:${Versions.kotlinxKnit}") + implementation("org.jetbrains.kotlinx:kover:${Versions.kotlinxKover}") } diff --git a/buildSrc/src/main/kotlin/buildsrc/convention/jacoco-aggregation.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/convention/jacoco-aggregation.gradle.kts deleted file mode 100644 index 0ee26fa6..00000000 --- a/buildSrc/src/main/kotlin/buildsrc/convention/jacoco-aggregation.gradle.kts +++ /dev/null @@ -1,28 +0,0 @@ -package buildsrc.convention - -import org.gradle.kotlin.dsl.`jacoco-report-aggregation` -import org.gradle.kotlin.dsl.base -import org.gradle.kotlin.dsl.dependencies - -plugins { - base - `jacoco-report-aggregation` -} - - -dependencies { - subprojects - .filter { it.buildFile.exists() } - .forEach { jacocoAggregation(it) } -} - - -@Suppress("UnstableApiUsage") // jacoco-report-aggregation is incubating -val testCodeCoverageReport by reporting.reports.creating(JacocoCoverageReport::class) { - testType.set(TestSuiteType.UNIT_TEST) -} - - -tasks.check { - dependsOn(testCodeCoverageReport.reportTask) -} diff --git a/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-jvm.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-jvm.gradle.kts index 48a547c9..8b04c6be 100644 --- a/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-jvm.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-jvm.gradle.kts @@ -2,7 +2,6 @@ package buildsrc.convention import org.gradle.kotlin.dsl.`java-library` import org.gradle.kotlin.dsl.dependencies -import org.gradle.kotlin.dsl.jacoco import org.gradle.kotlin.dsl.kotlin import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.tasks.KotlinCompile @@ -11,7 +10,6 @@ plugins { id("buildsrc.convention.subproject") kotlin("jvm") `java-library` - jacoco } dependencies { @@ -53,5 +51,4 @@ tasks.compileTestKotlin { tasks.test { useJUnitPlatform() - finalizedBy(tasks.jacocoTestReport) } diff --git a/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts new file mode 100644 index 00000000..4769941c --- /dev/null +++ b/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts @@ -0,0 +1,20 @@ +package buildsrc.convention + +import org.gradle.kotlin.dsl.configure +import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension + + +plugins { + id("buildsrc.convention.subproject") + kotlin("multiplatform") + `java-library` +} + +// `kotlin-js` adds a directory in the root-dir for the Yarn lock. +// That's a bit annoying. It's a little neater if it's in the +// gradle dir, next to the version-catalog. +afterEvaluate { + rootProject.extensions.configure { + lockFileDirectory = project.rootDir.resolve("gradle/kotlin-js-store") + } +} diff --git a/docs/knit.properties b/docs/knit.properties index 0f2c3b66..928c4f6c 100644 --- a/docs/knit.properties +++ b/docs/knit.properties @@ -1,7 +1,7 @@ knit.dir=./knit/example/ test.dir=./knit/test/ -knit.package=example -test.package=example.test +knit.package=dev.adamko.kxstsgen.example +test.package=dev.adamko.kxstsgen.example.test # test.template=./knit/knit-test.ftl test.language=typescript diff --git a/docs/knit/build.gradle.kts b/docs/knit/build.gradle.kts index 2e259645..1b9d97ee 100644 --- a/docs/knit/build.gradle.kts +++ b/docs/knit/build.gradle.kts @@ -40,7 +40,7 @@ knit { } tasks.test { - dependsOn(tasks.knitCheck) + dependsOn(tasks.knit) // finalizedBy(tasks.knitCheck) } diff --git a/docs/knit/example/example-abstract-class-abstract-field-01.kt b/docs/knit/example/example-abstract-class-abstract-field-01.kt index f8a5bf49..d77c6932 100644 --- a/docs/knit/example/example-abstract-class-abstract-field-01.kt +++ b/docs/knit/example/example-abstract-class-abstract-field-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from abstract-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleAbstractClassAbstractField01 +package dev.adamko.kxstsgen.example.exampleAbstractClassAbstractField01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-abstract-class-primitive-fields-01.kt b/docs/knit/example/example-abstract-class-primitive-fields-01.kt index 06f2af95..355ee72f 100644 --- a/docs/knit/example/example-abstract-class-primitive-fields-01.kt +++ b/docs/knit/example/example-abstract-class-primitive-fields-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from abstract-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleAbstractClassPrimitiveFields01 +package dev.adamko.kxstsgen.example.exampleAbstractClassPrimitiveFields01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-abstract-class-single-field-01.kt b/docs/knit/example/example-abstract-class-single-field-01.kt index 98155d80..3099b6e1 100644 --- a/docs/knit/example/example-abstract-class-single-field-01.kt +++ b/docs/knit/example/example-abstract-class-single-field-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from abstract-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleAbstractClassSingleField01 +package dev.adamko.kxstsgen.example.exampleAbstractClassSingleField01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-closed-polymorphic-static-types-01.kt b/docs/knit/example/example-closed-polymorphic-static-types-01.kt deleted file mode 100644 index 9cd6e15c..00000000 --- a/docs/knit/example/example-closed-polymorphic-static-types-01.kt +++ /dev/null @@ -1,16 +0,0 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. -@file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleClosedPolymorphicStaticTypes01 - -import kotlinx.serialization.* -import dev.adamko.kxstsgen.* - -@Serializable -open class Project(val name: String) - -class OwnedProject(name: String, val owner: String) : Project(name) - -fun main() { - val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Project.serializer().descriptor)) -} diff --git a/docs/knit/example/example-closed-polymorphic-static-types-02.kt b/docs/knit/example/example-closed-polymorphic-static-types-02.kt deleted file mode 100644 index c4b614b4..00000000 --- a/docs/knit/example/example-closed-polymorphic-static-types-02.kt +++ /dev/null @@ -1,19 +0,0 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. -@file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleClosedPolymorphicStaticTypes02 - -import kotlinx.serialization.* -import dev.adamko.kxstsgen.* - -@Serializable -abstract class Project { - abstract val name: String -} - -@Serializable -class OwnedProject(override val name: String, val owner: String) : Project() - -fun main() { - val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(OwnedProject.serializer().descriptor)) -} diff --git a/docs/knit/example/example-default-values-primitive-fields-01.kt b/docs/knit/example/example-default-values-primitive-fields-01.kt index 9c1f8300..20d4292c 100644 --- a/docs/knit/example/example-default-values-primitive-fields-01.kt +++ b/docs/knit/example/example-default-values-primitive-fields-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from default-values.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleDefaultValuesPrimitiveFields01 +package dev.adamko.kxstsgen.example.exampleDefaultValuesPrimitiveFields01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-default-values-single-field-01.kt b/docs/knit/example/example-default-values-single-field-01.kt index 02a3897b..4df7d33d 100644 --- a/docs/knit/example/example-default-values-single-field-01.kt +++ b/docs/knit/example/example-default-values-single-field-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from default-values.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleDefaultValuesSingleField01 +package dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-enum-class-01.kt b/docs/knit/example/example-enum-class-01.kt index 34e97596..0bbcbba5 100644 --- a/docs/knit/example/example-enum-class-01.kt +++ b/docs/knit/example/example-enum-class-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from enums.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleEnumClass01 +package dev.adamko.kxstsgen.example.exampleEnumClass01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-enum-class-02.kt b/docs/knit/example/example-enum-class-02.kt index cb8c04f7..b0320424 100644 --- a/docs/knit/example/example-enum-class-02.kt +++ b/docs/knit/example/example-enum-class-02.kt @@ -1,6 +1,6 @@ // This file was automatically generated from enums.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleEnumClass02 +package dev.adamko.kxstsgen.example.exampleEnumClass02 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-list-primitive-01.kt b/docs/knit/example/example-list-primitive-01.kt index fa8c6e24..3505414e 100644 --- a/docs/knit/example/example-list-primitive-01.kt +++ b/docs/knit/example/example-list-primitive-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from lists.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleListPrimitive01 +package dev.adamko.kxstsgen.example.exampleListPrimitive01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-map-complex-01.kt b/docs/knit/example/example-map-complex-01.kt index 8c35e154..39cad1d8 100644 --- a/docs/knit/example/example-map-complex-01.kt +++ b/docs/knit/example/example-map-complex-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from maps.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleMapComplex01 +package dev.adamko.kxstsgen.example.exampleMapComplex01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-map-primitive-01.kt b/docs/knit/example/example-map-primitive-01.kt index 779861af..feccfd11 100644 --- a/docs/knit/example/example-map-primitive-01.kt +++ b/docs/knit/example/example-map-primitive-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from maps.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleMapPrimitive01 +package dev.adamko.kxstsgen.example.exampleMapPrimitive01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-map-primitive-02.kt b/docs/knit/example/example-map-primitive-02.kt index 3bd4c93e..681cc5cf 100644 --- a/docs/knit/example/example-map-primitive-02.kt +++ b/docs/knit/example/example-map-primitive-02.kt @@ -1,6 +1,6 @@ // This file was automatically generated from maps.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleMapPrimitive02 +package dev.adamko.kxstsgen.example.exampleMapPrimitive02 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-map-primitive-03.kt b/docs/knit/example/example-map-primitive-03.kt index b0c88a76..d2ff2f6c 100644 --- a/docs/knit/example/example-map-primitive-03.kt +++ b/docs/knit/example/example-map-primitive-03.kt @@ -1,6 +1,6 @@ // This file was automatically generated from maps.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleMapPrimitive03 +package dev.adamko.kxstsgen.example.exampleMapPrimitive03 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-plain-class-primitive-fields-01.kt b/docs/knit/example/example-plain-class-primitive-fields-01.kt index 21f562a5..ee8e103e 100644 --- a/docs/knit/example/example-plain-class-primitive-fields-01.kt +++ b/docs/knit/example/example-plain-class-primitive-fields-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from basic-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.examplePlainClassPrimitiveFields01 +package dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-plain-class-primitive-fields-02.kt b/docs/knit/example/example-plain-class-primitive-fields-02.kt index 47762669..d4787c96 100644 --- a/docs/knit/example/example-plain-class-primitive-fields-02.kt +++ b/docs/knit/example/example-plain-class-primitive-fields-02.kt @@ -1,6 +1,6 @@ // This file was automatically generated from basic-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.examplePlainClassPrimitiveFields02 +package dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields02 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-plain-class-single-field-01.kt b/docs/knit/example/example-plain-class-single-field-01.kt index 1fd466a2..237482f0 100644 --- a/docs/knit/example/example-plain-class-single-field-01.kt +++ b/docs/knit/example/example-plain-class-single-field-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from basic-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.examplePlainClassSingleField01 +package dev.adamko.kxstsgen.example.examplePlainClassSingleField01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-plain-data-class-01.kt b/docs/knit/example/example-plain-data-class-01.kt index 6130a228..6ea44223 100644 --- a/docs/knit/example/example-plain-data-class-01.kt +++ b/docs/knit/example/example-plain-data-class-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from basic-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.examplePlainDataClass01 +package dev.adamko.kxstsgen.example.examplePlainDataClass01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt b/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt index 73034e5a..0f0e7205 100644 --- a/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt +++ b/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from polymorphism.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.examplePolymorphicAbstractClassPrimitiveFields01 +package dev.adamko.kxstsgen.example.examplePolymorphicAbstractClassPrimitiveFields01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-polymorphic-objects-01.kt b/docs/knit/example/example-polymorphic-objects-01.kt index e4d41bb9..afc8643c 100644 --- a/docs/knit/example/example-polymorphic-objects-01.kt +++ b/docs/knit/example/example-polymorphic-objects-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from polymorphism.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.examplePolymorphicObjects01 +package dev.adamko.kxstsgen.example.examplePolymorphicObjects01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-polymorphic-sealed-class-01.kt b/docs/knit/example/example-polymorphic-sealed-class-01.kt index dc55f95f..f3199cc7 100644 --- a/docs/knit/example/example-polymorphic-sealed-class-01.kt +++ b/docs/knit/example/example-polymorphic-sealed-class-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from polymorphism.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.examplePolymorphicSealedClass01 +package dev.adamko.kxstsgen.example.examplePolymorphicSealedClass01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-polymorphic-static-types-01.kt b/docs/knit/example/example-polymorphic-static-types-01.kt index f3979915..3c29a727 100644 --- a/docs/knit/example/example-polymorphic-static-types-01.kt +++ b/docs/knit/example/example-polymorphic-static-types-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from polymorphism.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.examplePolymorphicStaticTypes01 +package dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-polymorphic-static-types-02.kt b/docs/knit/example/example-polymorphic-static-types-02.kt index 3f9861cf..3384c656 100644 --- a/docs/knit/example/example-polymorphic-static-types-02.kt +++ b/docs/knit/example/example-polymorphic-static-types-02.kt @@ -1,6 +1,6 @@ // This file was automatically generated from polymorphism.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.examplePolymorphicStaticTypes02 +package dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes02 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-polymorphism-sealed-01.kt b/docs/knit/example/example-polymorphism-sealed-01.kt deleted file mode 100644 index 0179a781..00000000 --- a/docs/knit/example/example-polymorphism-sealed-01.kt +++ /dev/null @@ -1,27 +0,0 @@ -// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. -@file:Suppress("PackageDirectoryMismatch", "unused") -package example.examplePolymorphismSealed01 - -import kotlinx.serialization.* -import dev.adamko.kxstsgen.* - -@Serializable -sealed class Project { - abstract val name: String -} - -@Serializable -data class OwnedProject( - override val name: String, - val owner: String, -) : Project() - -fun main() { - val tsGenerator = KxsTsGenerator() - println( - tsGenerator.generate( - Project.serializer().descriptor, - OwnedProject.serializer().descriptor, - ) - ) -} diff --git a/docs/knit/example/example-value-classes-01.kt b/docs/knit/example/example-value-classes-01.kt index bedab750..25860ac7 100644 --- a/docs/knit/example/example-value-classes-01.kt +++ b/docs/knit/example/example-value-classes-01.kt @@ -1,6 +1,6 @@ // This file was automatically generated from value-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleValueClasses01 +package dev.adamko.kxstsgen.example.exampleValueClasses01 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-value-classes-02.kt b/docs/knit/example/example-value-classes-02.kt index cf215d62..77c906b5 100644 --- a/docs/knit/example/example-value-classes-02.kt +++ b/docs/knit/example/example-value-classes-02.kt @@ -1,6 +1,6 @@ // This file was automatically generated from value-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleValueClasses02 +package dev.adamko.kxstsgen.example.exampleValueClasses02 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-value-classes-03.kt b/docs/knit/example/example-value-classes-03.kt index 751af227..a1e11b09 100644 --- a/docs/knit/example/example-value-classes-03.kt +++ b/docs/knit/example/example-value-classes-03.kt @@ -1,6 +1,6 @@ // This file was automatically generated from value-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleValueClasses03 +package dev.adamko.kxstsgen.example.exampleValueClasses03 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/example/example-value-classes-04.kt b/docs/knit/example/example-value-classes-04.kt index 0fef58aa..2645c3cc 100644 --- a/docs/knit/example/example-value-classes-04.kt +++ b/docs/knit/example/example-value-classes-04.kt @@ -1,6 +1,6 @@ // This file was automatically generated from value-classes.md by Knit tool. Do not edit. @file:Suppress("PackageDirectoryMismatch", "unused") -package example.exampleValueClasses04 +package dev.adamko.kxstsgen.example.exampleValueClasses04 import kotlinx.serialization.* import dev.adamko.kxstsgen.* diff --git a/docs/knit/test/AbstractClassesTest.kt b/docs/knit/test/AbstractClassesTest.kt index 525486d3..017868e9 100644 --- a/docs/knit/test/AbstractClassesTest.kt +++ b/docs/knit/test/AbstractClassesTest.kt @@ -1,6 +1,6 @@ // This file was automatically generated from abstract-classes.md by Knit tool. Do not edit. @file:Suppress("JSUnusedLocalSymbols") -package example.test +package dev.adamko.kxstsgen.example.test import io.kotest.matchers.* import kotlinx.knit.test.* @@ -10,7 +10,7 @@ class AbstractClassesTest { @Test fun testExampleAbstractClassSingleField01() { captureOutput("ExampleAbstractClassSingleField01") { - example.exampleAbstractClassSingleField01.main() + dev.adamko.kxstsgen.example.exampleAbstractClassSingleField01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -25,7 +25,7 @@ class AbstractClassesTest { @Test fun testExampleAbstractClassPrimitiveFields01() { captureOutput("ExampleAbstractClassPrimitiveFields01") { - example.exampleAbstractClassPrimitiveFields01.main() + dev.adamko.kxstsgen.example.exampleAbstractClassPrimitiveFields01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -44,7 +44,7 @@ class AbstractClassesTest { @Test fun testExampleAbstractClassAbstractField01() { captureOutput("ExampleAbstractClassAbstractField01") { - example.exampleAbstractClassAbstractField01.main() + dev.adamko.kxstsgen.example.exampleAbstractClassAbstractField01.main() }.joinToString("\n") .shouldBe( // language=TypeScript diff --git a/docs/knit/test/BasicClassesTest.kt b/docs/knit/test/BasicClassesTest.kt index ac025a38..f2006d5a 100644 --- a/docs/knit/test/BasicClassesTest.kt +++ b/docs/knit/test/BasicClassesTest.kt @@ -1,6 +1,6 @@ // This file was automatically generated from basic-classes.md by Knit tool. Do not edit. @file:Suppress("JSUnusedLocalSymbols") -package example.test +package dev.adamko.kxstsgen.example.test import io.kotest.matchers.* import kotlinx.knit.test.* @@ -10,7 +10,7 @@ class BasicClassesTest { @Test fun testExamplePlainClassSingleField01() { captureOutput("ExamplePlainClassSingleField01") { - example.examplePlainClassSingleField01.main() + dev.adamko.kxstsgen.example.examplePlainClassSingleField01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -25,7 +25,7 @@ class BasicClassesTest { @Test fun testExamplePlainClassPrimitiveFields01() { captureOutput("ExamplePlainClassPrimitiveFields01") { - example.examplePlainClassPrimitiveFields01.main() + dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -44,7 +44,7 @@ class BasicClassesTest { @Test fun testExamplePlainDataClass01() { captureOutput("ExamplePlainDataClass01") { - example.examplePlainDataClass01.main() + dev.adamko.kxstsgen.example.examplePlainDataClass01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -63,7 +63,7 @@ class BasicClassesTest { @Test fun testExamplePlainClassPrimitiveFields02() { captureOutput("ExamplePlainClassPrimitiveFields02") { - example.examplePlainClassPrimitiveFields02.main() + dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields02.main() }.joinToString("\n") .shouldBe( // language=TypeScript diff --git a/docs/knit/test/DefaultValuesTest.kt b/docs/knit/test/DefaultValuesTest.kt index b32a2f70..27a48be8 100644 --- a/docs/knit/test/DefaultValuesTest.kt +++ b/docs/knit/test/DefaultValuesTest.kt @@ -1,6 +1,6 @@ // This file was automatically generated from default-values.md by Knit tool. Do not edit. @file:Suppress("JSUnusedLocalSymbols") -package example.test +package dev.adamko.kxstsgen.example.test import io.kotest.matchers.* import kotlinx.knit.test.* @@ -10,7 +10,7 @@ class DefaultValuesTest { @Test fun testExampleDefaultValuesSingleField01() { captureOutput("ExampleDefaultValuesSingleField01") { - example.exampleDefaultValuesSingleField01.main() + dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -25,7 +25,7 @@ class DefaultValuesTest { @Test fun testExampleDefaultValuesPrimitiveFields01() { captureOutput("ExampleDefaultValuesPrimitiveFields01") { - example.exampleDefaultValuesPrimitiveFields01.main() + dev.adamko.kxstsgen.example.exampleDefaultValuesPrimitiveFields01.main() }.joinToString("\n") .shouldBe( // language=TypeScript diff --git a/docs/knit/test/EnumClassTest.kt b/docs/knit/test/EnumClassTest.kt index 78430b92..d1d94b1b 100644 --- a/docs/knit/test/EnumClassTest.kt +++ b/docs/knit/test/EnumClassTest.kt @@ -1,6 +1,6 @@ // This file was automatically generated from enums.md by Knit tool. Do not edit. @file:Suppress("JSUnusedLocalSymbols") -package example.test +package dev.adamko.kxstsgen.example.test import io.kotest.matchers.* import kotlinx.knit.test.* @@ -10,7 +10,7 @@ class EnumClassTest { @Test fun testExampleEnumClass01() { captureOutput("ExampleEnumClass01") { - example.exampleEnumClass01.main() + dev.adamko.kxstsgen.example.exampleEnumClass01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -27,7 +27,7 @@ class EnumClassTest { @Test fun testExampleEnumClass02() { captureOutput("ExampleEnumClass02") { - example.exampleEnumClass02.main() + dev.adamko.kxstsgen.example.exampleEnumClass02.main() }.joinToString("\n") .shouldBe( // language=TypeScript diff --git a/docs/knit/test/ListsTests.kt b/docs/knit/test/ListsTests.kt index a8530b6d..7975c23f 100644 --- a/docs/knit/test/ListsTests.kt +++ b/docs/knit/test/ListsTests.kt @@ -1,6 +1,6 @@ // This file was automatically generated from lists.md by Knit tool. Do not edit. @file:Suppress("JSUnusedLocalSymbols") -package example.test +package dev.adamko.kxstsgen.example.test import io.kotest.matchers.* import kotlinx.knit.test.* @@ -10,7 +10,7 @@ class ListsTests { @Test fun testExampleListPrimitive01() { captureOutput("ExampleListPrimitive01") { - example.exampleListPrimitive01.main() + dev.adamko.kxstsgen.example.exampleListPrimitive01.main() }.joinToString("\n") .shouldBe( // language=TypeScript diff --git a/docs/knit/test/MapsTests.kt b/docs/knit/test/MapsTests.kt index 43c0d9fd..ebe3eef4 100644 --- a/docs/knit/test/MapsTests.kt +++ b/docs/knit/test/MapsTests.kt @@ -1,6 +1,6 @@ // This file was automatically generated from maps.md by Knit tool. Do not edit. @file:Suppress("JSUnusedLocalSymbols") -package example.test +package dev.adamko.kxstsgen.example.test import io.kotest.matchers.* import kotlinx.knit.test.* @@ -10,7 +10,7 @@ class MapsTests { @Test fun testExampleMapPrimitive01() { captureOutput("ExampleMapPrimitive01") { - example.exampleMapPrimitive01.main() + dev.adamko.kxstsgen.example.exampleMapPrimitive01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -25,7 +25,7 @@ class MapsTests { @Test fun testExampleMapPrimitive02() { captureOutput("ExampleMapPrimitive02") { - example.exampleMapPrimitive02.main() + dev.adamko.kxstsgen.example.exampleMapPrimitive02.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -45,7 +45,7 @@ class MapsTests { @Test fun testExampleMapPrimitive03() { captureOutput("ExampleMapPrimitive03") { - example.exampleMapPrimitive03.main() + dev.adamko.kxstsgen.example.exampleMapPrimitive03.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -60,7 +60,7 @@ class MapsTests { @Test fun testExampleMapComplex01() { captureOutput("ExampleMapComplex01") { - example.exampleMapComplex01.main() + dev.adamko.kxstsgen.example.exampleMapComplex01.main() }.joinToString("\n") .shouldBe( // language=TypeScript diff --git a/docs/knit/test/PolymorphismTest.kt b/docs/knit/test/PolymorphismTest.kt index 90cd523e..8e647c2c 100644 --- a/docs/knit/test/PolymorphismTest.kt +++ b/docs/knit/test/PolymorphismTest.kt @@ -1,6 +1,6 @@ // This file was automatically generated from polymorphism.md by Knit tool. Do not edit. @file:Suppress("JSUnusedLocalSymbols") -package example.test +package dev.adamko.kxstsgen.example.test import io.kotest.matchers.* import kotlinx.knit.test.* @@ -10,7 +10,7 @@ class PolymorphismTest { @Test fun testExamplePolymorphicAbstractClassPrimitiveFields01() { captureOutput("ExamplePolymorphicAbstractClassPrimitiveFields01") { - example.examplePolymorphicAbstractClassPrimitiveFields01.main() + dev.adamko.kxstsgen.example.examplePolymorphicAbstractClassPrimitiveFields01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -29,7 +29,7 @@ class PolymorphismTest { @Test fun testExamplePolymorphicStaticTypes01() { captureOutput("ExamplePolymorphicStaticTypes01") { - example.examplePolymorphicStaticTypes01.main() + dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -44,7 +44,7 @@ class PolymorphismTest { @Test fun testExamplePolymorphicStaticTypes02() { captureOutput("ExamplePolymorphicStaticTypes02") { - example.examplePolymorphicStaticTypes02.main() + dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes02.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -64,7 +64,7 @@ class PolymorphismTest { @Test fun testExamplePolymorphicSealedClass01() { captureOutput("ExamplePolymorphicSealedClass01") { - example.examplePolymorphicSealedClass01.main() + dev.adamko.kxstsgen.example.examplePolymorphicSealedClass01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -96,7 +96,7 @@ class PolymorphismTest { @Test fun testExamplePolymorphicObjects01() { captureOutput("ExamplePolymorphicObjects01") { - example.examplePolymorphicObjects01.main() + dev.adamko.kxstsgen.example.examplePolymorphicObjects01.main() }.joinToString("\n") .shouldBe( // language=TypeScript diff --git a/docs/knit/test/ValueClassesTest.kt b/docs/knit/test/ValueClassesTest.kt index 76c36e59..d8fa63f4 100644 --- a/docs/knit/test/ValueClassesTest.kt +++ b/docs/knit/test/ValueClassesTest.kt @@ -1,6 +1,6 @@ // This file was automatically generated from value-classes.md by Knit tool. Do not edit. @file:Suppress("JSUnusedLocalSymbols") -package example.test +package dev.adamko.kxstsgen.example.test import io.kotest.matchers.* import kotlinx.knit.test.* @@ -10,7 +10,7 @@ class ValueClassesTest { @Test fun testExampleValueClasses01() { captureOutput("ExampleValueClasses01") { - example.exampleValueClasses01.main() + dev.adamko.kxstsgen.example.exampleValueClasses01.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -23,7 +23,7 @@ class ValueClassesTest { @Test fun testExampleValueClasses02() { captureOutput("ExampleValueClasses02") { - example.exampleValueClasses02.main() + dev.adamko.kxstsgen.example.exampleValueClasses02.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -42,7 +42,7 @@ class ValueClassesTest { @Test fun testExampleValueClasses03() { captureOutput("ExampleValueClasses03") { - example.exampleValueClasses03.main() + dev.adamko.kxstsgen.example.exampleValueClasses03.main() }.joinToString("\n") .shouldBe( // language=TypeScript @@ -55,7 +55,7 @@ class ValueClassesTest { @Test fun testExampleValueClasses04() { captureOutput("ExampleValueClasses04") { - example.exampleValueClasses04.main() + dev.adamko.kxstsgen.example.exampleValueClasses04.main() }.joinToString("\n") .shouldBe( // language=TypeScript diff --git a/modules/kxs-ts-gen-core/build.gradle.kts b/modules/kxs-ts-gen-core/build.gradle.kts index c5553719..47441e6a 100644 --- a/modules/kxs-ts-gen-core/build.gradle.kts +++ b/modules/kxs-ts-gen-core/build.gradle.kts @@ -1,6 +1,5 @@ plugins { - kotlin("multiplatform") - `java-library` + buildsrc.convention.`kotlin-multiplatform` kotlin("plugin.serialization") } From 29f40b03788522ef17bfcfb47386c70b5971cfab Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 26 Mar 2022 18:26:23 +0100 Subject: [PATCH 10/46] bump gradle version --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0a5e67c7..c5b56ab9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ gitVersioning.apply { tasks.wrapper { - gradleVersion = "7.4" + gradleVersion = "7.4.1" distributionType = Wrapper.DistributionType.ALL } From 995179f1b5bfb664c3794ef0a543ecd9deb12a07 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 26 Mar 2022 18:26:34 +0100 Subject: [PATCH 11/46] set max line length to 100 --- .editorconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/.editorconfig b/.editorconfig index 0f178672..bcb8a3c2 100644 --- a/.editorconfig +++ b/.editorconfig @@ -7,3 +7,4 @@ indent_size = 2 indent_style = space insert_final_newline = true trim_trailing_whitespace = true +max_line_length = 100 From 3c47a240cb1b8569fde747a9739ee6001eb19ec0 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 26 Mar 2022 18:28:06 +0100 Subject: [PATCH 12/46] mucking about with plugins and compilation processors --- buildSrc/build.gradle.kts | 4 + buildSrc/repositories.settings.gradle.kts | 15 ++ modules/kxs-ts-gen-processor/build.gradle.kts | 33 ++++ .../dev/adamko/kxstsgen/kxsTsGenProcessor.kt | 141 ++++++++++++++++++ settings.gradle.kts | 2 +- 5 files changed, 194 insertions(+), 1 deletion(-) create mode 100644 modules/kxs-ts-gen-processor/build.gradle.kts create mode 100644 modules/kxs-ts-gen-processor/src/main/kotlin/dev/adamko/kxstsgen/kxsTsGenProcessor.kt diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 096d3196..39aba186 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -35,6 +35,10 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-knit:${Versions.kotlinxKnit}") implementation("org.jetbrains.kotlinx:kover:${Versions.kotlinxKover}") + + implementation("org.jetbrains.reflekt:gradle-plugin:1.6.10-1-SNAPSHOT") { + isChanging = true + } } diff --git a/buildSrc/repositories.settings.gradle.kts b/buildSrc/repositories.settings.gradle.kts index bca6f2f5..3fd09331 100644 --- a/buildSrc/repositories.settings.gradle.kts +++ b/buildSrc/repositories.settings.gradle.kts @@ -2,6 +2,7 @@ dependencyResolutionManagement { repositories { + myMavenLocal() mavenCentral() jitpack() gradlePluginPortal() @@ -9,6 +10,7 @@ dependencyResolutionManagement { pluginManagement { repositories { + myMavenLocal() jitpack() gradlePluginPortal() mavenCentral() @@ -16,6 +18,19 @@ dependencyResolutionManagement { } } + fun RepositoryHandler.jitpack() { maven("https://jitpack.io") } + + +fun RepositoryHandler.myMavenLocal(enabled: Boolean = true) { + if (enabled) { + logger.lifecycle("Maven local is enabled") + mavenLocal { + content { + includeGroup("org.jetbrains.reflekt") + } + } + } +} diff --git a/modules/kxs-ts-gen-processor/build.gradle.kts b/modules/kxs-ts-gen-processor/build.gradle.kts new file mode 100644 index 00000000..d09bfefa --- /dev/null +++ b/modules/kxs-ts-gen-processor/build.gradle.kts @@ -0,0 +1,33 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + +plugins { + buildsrc.convention.`kotlin-jvm` +// kotlin("plugin.serialization") + +// id("org.jetbrains.reflekt") +} + +val kspVersion = "1.6.10-1.0.4" +val kotlinCompileTestingVersion = "1.4.7" +val kotlinxSerializationVersion = "1.3.2" // TODO put dependencies in libs.version.toml + +dependencies { + implementation("com.google.devtools.ksp:symbol-processing-api:$kspVersion") + + testImplementation("com.github.tschuchortdev:kotlin-compile-testing:$kotlinCompileTestingVersion") + testImplementation("com.github.tschuchortdev:kotlin-compile-testing-ksp:$kotlinCompileTestingVersion") + + implementation(projects.modules.kxsTsGenCore) + + implementation(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:${kotlinxSerializationVersion}")) + implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json") + + testImplementation(kotlin("test")) +} + +tasks.withType { + kotlinOptions.freeCompilerArgs += listOf( + "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", + ) +} diff --git a/modules/kxs-ts-gen-processor/src/main/kotlin/dev/adamko/kxstsgen/kxsTsGenProcessor.kt b/modules/kxs-ts-gen-processor/src/main/kotlin/dev/adamko/kxstsgen/kxsTsGenProcessor.kt new file mode 100644 index 00000000..4377e726 --- /dev/null +++ b/modules/kxs-ts-gen-processor/src/main/kotlin/dev/adamko/kxstsgen/kxsTsGenProcessor.kt @@ -0,0 +1,141 @@ +package dev.adamko.kxstsgen + +import com.google.devtools.ksp.processing.CodeGenerator +import com.google.devtools.ksp.processing.Dependencies +import com.google.devtools.ksp.processing.KSPLogger +import com.google.devtools.ksp.processing.Resolver +import com.google.devtools.ksp.processing.SymbolProcessor +import com.google.devtools.ksp.processing.SymbolProcessorEnvironment +import com.google.devtools.ksp.processing.SymbolProcessorProvider +import com.google.devtools.ksp.symbol.ClassKind +import com.google.devtools.ksp.symbol.KSAnnotated +import com.google.devtools.ksp.symbol.KSClassDeclaration +import com.google.devtools.ksp.symbol.KSNode +import com.google.devtools.ksp.visitor.KSTopDownVisitor +import java.io.OutputStreamWriter +import kotlinx.serialization.SerialInfo + +class KxsTsGenProcessorProvider : SymbolProcessorProvider { + override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor { + return KxsTsGenProcessor(environment.codeGenerator, environment.logger) + } +} + + +class KxsTsGenProcessor( + private val codeGenerator: CodeGenerator, + private val logger: KSPLogger, +) : SymbolProcessor { + private var invoked = false + + override fun process(resolver: Resolver): List { + + val allFiles = resolver.getAllFiles().map { it.fileName } + logger.warn(allFiles.toList().joinToString()) + if (invoked) { + logger.info("Already invoked") + return emptyList() + } + invoked = true + + codeGenerator + .createNewFile(Dependencies(false), "", "Foo", "d.ts") + .use { output -> + + OutputStreamWriter(output).use { writer -> + + val visitor = ClassVisitor(codeGenerator, resolver) + resolver + .getSymbolsWithAnnotation(TsExport::class.qualifiedName!!) + .filterIsInstance() + .filter { + when (it.classKind) { + ClassKind.CLASS, + ClassKind.ENUM_CLASS, + ClassKind.OBJECT, + ClassKind.INTERFACE -> true + ClassKind.ANNOTATION_CLASS, + ClassKind.ENUM_ENTRY -> false + } + } + .forEach { + logger.info("Visiting $it") + it.accept(visitor, writer) + } + + } + } + return emptyList() + } +} + +class ClassVisitor( + private val codeGenerator: CodeGenerator, + private val resolver: Resolver +) : KSTopDownVisitor() { + + override fun defaultHandler(node: KSNode, data: OutputStreamWriter) { + } + + override fun visitClassDeclaration( + classDeclaration: KSClassDeclaration, + data: OutputStreamWriter + ) { + super.visitClassDeclaration(classDeclaration, data) + + classDeclaration.classKind + + val containingFile = classDeclaration.containingFile!! + + + codeGenerator.createNewFile( + Dependencies(true, containingFile), + containingFile.packageName.toString(), + "asd", + ) + + + val symbolName = classDeclaration.simpleName.asString() + +// val constructor = classDeclaration.primaryConstructor ?: return +// +// data.write( +// buildString { +// append("interface ") +// append(symbolName) +// appendLine(" {") +// +// constructor.parameters +// .filter { it.name != null } +// .forEach { +// append(" ") +// append(it.name?.getShortName()) +// append(if (it.hasDefault) "?:" else ":") +// append(" ") +// append(tsPrimitive(it.type.resolve())) +// appendLine(";") +// } +// +// appendLine("}") +// } +// ) + } + +} +// +// +//data class KxsIntrospectionInfo( +// val ksType: KSType, +// val descriptorHashCode: KxsDescriptorHashCode, +// val sealedParent: SerialDescriptor? = null, +// val sealedSubclasses: Set? = null, +//) + + +/** + * Mark [Serializable] classes that will be converted to TypeScript. + */ +@Target(AnnotationTarget.CLASS) +@MustBeDocumented +@SerialInfo +annotation class TsExport diff --git a/settings.gradle.kts b/settings.gradle.kts index 6e218056..d75a6c4e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,7 +4,7 @@ apply(from = "./buildSrc/repositories.settings.gradle.kts") include( ":modules:kxs-ts-gen-core", - ":modules:kxs-ts-gen-plugin", + ":modules:kxs-ts-gen-processor", ":docs:knit", ) From 1ae7c9a1fd985b1a99afdcc7a65a6347f339f956 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 26 Mar 2022 18:28:59 +0100 Subject: [PATCH 13/46] tidy up gradle config --- .../src/main/kotlin/buildsrc/config/gradle.kt | 40 +++++++++++++++++++ .../buildsrc/convention/kotlin-jvm.gradle.kts | 4 +- .../kotlin-multiplatform.gradle.kts | 14 ++----- 3 files changed, 45 insertions(+), 13 deletions(-) diff --git a/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt b/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt index ff6aaefe..399ee0d2 100644 --- a/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt +++ b/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt @@ -1,7 +1,14 @@ package buildsrc.config +import org.gradle.api.GradleException +import org.gradle.api.Project import org.gradle.api.file.ProjectLayout +import org.gradle.kotlin.dsl.findByType import org.gradle.plugins.ide.idea.model.IdeaModule +import org.jetbrains.kotlin.gradle.dsl.KotlinTargetContainerWithPresetFunctions +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithHostTests +import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension + /** exclude generated Gradle code, so it doesn't clog up search results */ fun IdeaModule.excludeGeneratedGradleDsl(layout: ProjectLayout) { @@ -20,3 +27,36 @@ fun IdeaModule.excludeGeneratedGradleDsl(layout: ProjectLayout) { ) ) } + + +/** + * `kotlin-js` adds a directory in the root-dir for the Yarn lock. + * That's a bit annoying. It's a little neater if it's in the + * gradle dir, next to the version-catalog. + */ +fun Project.relocateKotlinJsStore() { + + afterEvaluate { + rootProject.extensions.findByType()?.apply { + lockFileDirectory = project.rootDir.resolve("gradle/kotlin-js-store") + } + } + +} + +fun KotlinTargetContainerWithPresetFunctions.currentHostTarget( + targetName: String = "native", + configure: KotlinNativeTargetWithHostTests.() -> Unit, +): KotlinNativeTargetWithHostTests { + val hostOs = System.getProperty("os.name") + val isMingwX64 = hostOs.startsWith("Windows") + val hostTarget = when { + hostOs == "Mac OS X" -> macosX64(targetName) + hostOs == "Linux" -> linuxX64(targetName) + isMingwX64 -> mingwX64(targetName) + else -> throw GradleException("Preset for host OS '$hostOs' is undefined") + } + println("Current host target ${hostTarget.targetName}/${hostTarget.preset?.name}") + hostTarget.configure() + return hostTarget +} diff --git a/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-jvm.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-jvm.gradle.kts index 8b04c6be..b3580edd 100644 --- a/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-jvm.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-jvm.gradle.kts @@ -13,7 +13,7 @@ plugins { } dependencies { - testImplementation(platform("io.kotest:kotest-bom:5.1.0")) + testImplementation(platform("io.kotest:kotest-bom:5.2.1")) testImplementation("io.kotest:kotest-runner-junit5") testImplementation("io.kotest:kotest-assertions-core") testImplementation("io.kotest:kotest-property") @@ -31,7 +31,7 @@ java { withSourcesJar() } -tasks.withType().configureEach { +tasks.withType { kotlinOptions { jvmTarget = "11" apiVersion = "1.6" diff --git a/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts index 4769941c..6dc154a1 100644 --- a/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts @@ -1,8 +1,6 @@ package buildsrc.convention -import org.gradle.kotlin.dsl.configure -import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension - +import buildsrc.config.relocateKotlinJsStore plugins { id("buildsrc.convention.subproject") @@ -10,11 +8,5 @@ plugins { `java-library` } -// `kotlin-js` adds a directory in the root-dir for the Yarn lock. -// That's a bit annoying. It's a little neater if it's in the -// gradle dir, next to the version-catalog. -afterEvaluate { - rootProject.extensions.configure { - lockFileDirectory = project.rootDir.resolve("gradle/kotlin-js-store") - } -} + +relocateKotlinJsStore() From 4e3a6f69288c6839fc32664cf607378a52818cfa Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 26 Mar 2022 18:31:49 +0100 Subject: [PATCH 14/46] refactor kxs generator --- modules/kxs-ts-gen-core/build.gradle.kts | 57 +-- .../kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt | 47 ++- .../dev.adamko.kxstsgen/KxsTsGenerator.kt | 379 +++++++++++++----- .../kotlin/dev.adamko.kxstsgen/KxsTsModule.kt | 66 +++ .../KxsTsSourceCodeGenerator.kt | 144 ++++--- .../serializerExtractors.kt | 54 +++ .../kotlin/dev.adamko.kxstsgen/tsElements.kt | 192 ++++++--- .../kxstsgen/serializerExtractorsJvm.kt | 52 +++ 8 files changed, 747 insertions(+), 244 deletions(-) create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsModule.kt create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/serializerExtractors.kt create mode 100644 modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/serializerExtractorsJvm.kt diff --git a/modules/kxs-ts-gen-core/build.gradle.kts b/modules/kxs-ts-gen-core/build.gradle.kts index 47441e6a..f1f98ea2 100644 --- a/modules/kxs-ts-gen-core/build.gradle.kts +++ b/modules/kxs-ts-gen-core/build.gradle.kts @@ -1,32 +1,36 @@ plugins { buildsrc.convention.`kotlin-multiplatform` kotlin("plugin.serialization") +// id("org.jetbrains.reflekt") } val kotlinxSerializationVersion = "1.3.2" kotlin { - val hostOs = System.getProperty("os.name") - val isMingwX64 = hostOs.startsWith("Windows") - val nativeTarget = when { - hostOs == "Mac OS X" -> macosX64("native") - hostOs == "Linux" -> linuxX64("native") - isMingwX64 -> mingwX64("native") - else -> throw GradleException("Host OS is not supported in Kotlin/Native.") - } - +// val hostOs = System.getProperty("os.name") +// val isMingwX64 = hostOs.startsWith("Windows") +// val nativeTarget = when { +// hostOs == "Mac OS X" -> macosX64("native") +// hostOs == "Linux" -> linuxX64("native") +// isMingwX64 -> mingwX64("native") +// else -> throw GradleException("Host OS is not supported in Kotlin/Native.") +// } - js(IR) { - binaries.executable() - browser { - commonWebpackConfig { - cssSupport.enabled = true - } - } - } +// js(IR) { +// binaries.executable() +// browser { +// commonWebpackConfig { +// cssSupport.enabled = true +// } +// } +// } jvm { compilations.all { - kotlinOptions.jvmTarget = "11" + kotlinOptions { + jvmTarget = "11" + languageVersion = "1.6" + apiVersion = "1.6" + } } withJava() testRuns["test"].executionTask.configure { @@ -53,6 +57,7 @@ kotlin { ) implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json") + implementation(kotlin("reflect")) } } val commonTest by getting { @@ -60,11 +65,15 @@ kotlin { implementation(kotlin("test")) } } - val nativeMain by getting - val nativeTest by getting - val jsMain by getting - val jsTest by getting - val jvmMain by getting - val jvmTest by getting +// val nativeMain by getting +// val nativeTest by getting +// val jsMain by getting +// val jsTest by getting + val jvmMain by getting { + dependencies { + implementation(kotlin("reflect")) + } + } +// val jvmTest by getting } } 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 7c3f805e..9624d0a2 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,17 +1,20 @@ package dev.adamko.kxstsgen import kotlin.jvm.JvmInline +import kotlin.reflect.KClass +import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.SerialDescriptor data class KxsTsConfig( val indent: String = " ", + val structureSeparator: String = "\n\n", val namespaceConfig: NamespaceConfig = NamespaceConfig.Disabled, val typeAliasTyping: TypeAliasTypingConfig = TypeAliasTypingConfig.None, ) { sealed interface NamespaceConfig { /** Use the prefix of the [SerialDescriptor] */ - object UseDescriptorNamePrefix : NamespaceConfig + object DescriptorNamePrefix : NamespaceConfig /** don't generate a namespace */ object Disabled : NamespaceConfig @JvmInline @@ -24,3 +27,45 @@ data class KxsTsConfig( } } + +// +// +//class SerializerClassMap { +// private val _map: MutableMap, KClass<*>> = mutableMapOf() +// +// val size: Int by _map::size +// +// fun containsKey(key: KSerializer<*>): Boolean = _map.containsKey(key) +// fun containsValue(value: KClass<*>): Boolean = _map.containsValue(value) +// +// +// fun isEmpty(): Boolean = _map.isEmpty() +// +// val keys: MutableSet> by _map::keys +// val values: MutableCollection> by _map::values +// +// fun clear(): Unit = _map.clear() +// +// @Suppress("UNCHECKED_CAST") +// fun get(key: KSerializer): KClass? = +// _map[key] as KClass? +// +// @Suppress("UNCHECKED_CAST") +// fun put(key: KSerializer, value: KClass): KClass? = +// _map.put(key, value) as KClass? +// +// @Suppress("UNCHECKED_CAST") +// fun remove(key: KSerializer): KClass? = _map.remove(key) as KClass? +// +// fun entries(): Set> = _map.map { Entry(it) }.toSet() +// +// data class Entry( +// val serializer: KSerializer, +// val kClass: KClass, +// ) { +// @Suppress("UNCHECKED_CAST") +// constructor(entry: Map.Entry<*, *>) +// : this(entry.key as KSerializer, entry.value as KClass) +// } +// +//} 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 d2fac077..fcb96e3c 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 @@ -1,5 +1,12 @@ +@file:OptIn(InternalSerializationApi::class) + package dev.adamko.kxstsgen +import kotlinx.serialization.ContextualSerializer +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.PolymorphicSerializer +import kotlinx.serialization.SealedClassSerializer import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor @@ -7,174 +14,334 @@ import kotlinx.serialization.descriptors.SerialKind import kotlinx.serialization.descriptors.StructureKind import kotlinx.serialization.descriptors.elementDescriptors import kotlinx.serialization.descriptors.elementNames -import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.descriptors.getPolymorphicDescriptors import kotlinx.serialization.modules.SerializersModule + class KxsTsGenerator( private val config: KxsTsConfig = KxsTsConfig(), - private val serializersModule: SerializersModule = EmptySerializersModule, + private val kxsTsModule: KxsTsModule = KxsTsModule(), ) { - private val codeGenerator: KxsTsSourceCodeGenerator = KxsTsSourceCodeGenerator(config) + constructor( + config: KxsTsConfig = KxsTsConfig(), + serializersModule: SerializersModule, + ) : this(config, KxsTsModule(serializersModule)) +// private val codeGenerator: KxsTsSourceCodeGenerator = KxsTsSourceCodeGenerator(config) - fun generate(vararg descriptors: SerialDescriptor): String { -// val allDescriptors = getAllDescriptors(descriptors, setOf()) +// private val descriptorSerializers: MutableMap> = mutableMapOf() + fun generate(vararg serializers: KSerializer<*>): String { -// serializersModule.getContextualDescriptor() -// serializersModule.getPolymorphicDescriptors() + val converter = TsConverter(kxsTsModule) - val elements = descriptors - .fold(mapOf()) { acc, descriptor -> - TsConverter(descriptor, acc).result - } - .values - .toSet() - - return codeGenerator.joinElementsToString(elements) - } + serializers.forEach { serializer -> converter(serializer) } + val codeGenerator = KxsTsSourceCodeGenerator(config, converter.convertedElements) -// private fun convertSealedPolymorphic( -// descriptor: SerialDescriptor, -// discriminator: TsStructure.Enum, -// ): TsElement { -// } + return codeGenerator.joinElementsToString() + } } + class TsConverter( - target: SerialDescriptor, - existingElements: Map, - private val serializersModule: SerializersModule = EmptySerializersModule, + private val kxsTsModule: KxsTsModule, + elementsState: Map = emptyMap(), ) { + private val elementsState: MutableMap = elementsState.toMutableMap() - private val existingElements = existingElements.toMutableMap() - val result: Map - get() = existingElements + private val descriptorExtractor = DescriptorExtractor(kxsTsModule) - private val _dependencies = mutableMapOf>() - private val dependencies: Map> - get() = _dependencies + val convertedElements: Set + get() = elementsState.values.toSet() - init { - convertToTsElement(null, target) + operator fun invoke(serializer: KSerializer<*>) { + val descriptors: Map = descriptorExtractor(serializer) + descriptors.keys.forEach { descriptor -> convertToTsElement(descriptor, descriptors) } } - private fun convertToTsElement(requestor: TsElementId?, target: SerialDescriptor): TsElement { - - val targetId = TsElementId(target.serialName.removeSuffix("?")) + private fun convertToTsElement( + descriptor: SerialDescriptor, + typeRefs: Map, + ): TsElement { + return elementsState.getOrPut(descriptor) { - return existingElements.getOrPut(targetId) { + when (descriptor.kind) { + SerialKind.ENUM -> convertEnum(descriptor) + SerialKind.CONTEXTUAL -> { + // TODO contextual + TsLiteral.Primitive.TsAny + } - val result: TsElement = when (target.kind) { + PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean - PrimitiveKind.BOOLEAN -> TsPrimitive.TsBoolean + PrimitiveKind.CHAR, + PrimitiveKind.STRING -> TsLiteral.Primitive.TsString PrimitiveKind.BYTE, PrimitiveKind.SHORT, PrimitiveKind.INT, PrimitiveKind.LONG, PrimitiveKind.FLOAT, - PrimitiveKind.DOUBLE -> TsPrimitive.TsNumber - - PrimitiveKind.CHAR, - PrimitiveKind.STRING -> TsPrimitive.TsString + PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber - StructureKind.LIST -> convertList(targetId, target) - StructureKind.MAP -> convertMap(targetId, target) + StructureKind.LIST -> convertList(descriptor, typeRefs) + StructureKind.MAP -> convertMap(descriptor, typeRefs) StructureKind.CLASS, - StructureKind.OBJECT -> convertStructure(targetId, target) - - SerialKind.ENUM -> convertEnum(targetId, target) - - PolymorphicKind.SEALED -> { - // TODO PolymorphicKind.SEALED - convertStructure(targetId, target) - } - PolymorphicKind.OPEN -> { - // TODO PolymorphicKind.SEALED - val openTargetId = TsElementId( - targetId.namespace + "." + targetId.name.substringAfter("<").substringBeforeLast(">") - ) - convertStructure(openTargetId, target) - } - SerialKind.CONTEXTUAL -> { - // TODO SerialKind.CONTEXTUAL - TsPrimitive.TsUnknown + StructureKind.OBJECT, + PolymorphicKind.SEALED, + PolymorphicKind.OPEN -> when { + descriptor.isInline -> convertTypeAlias(descriptor, typeRefs) + else -> convertInterface(descriptor, typeRefs) } } - if (requestor != null) { - dependencies.getOrElse(requestor) { mutableSetOf() }.plus(result) - } - - result } } - private fun convertStructure( - targetId: TsElementId, + private fun convertTypeAlias( structDescriptor: SerialDescriptor, - ): TsElement { + typeRefs: Map, + ): TsDeclaration { + val resultId = createElementId(structDescriptor) + val fieldDescriptor = structDescriptor.elementDescriptors.first() + val fieldTypeRef = typeRefs[fieldDescriptor] ?: TsTypeRef.Unknown + return TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) - if (structDescriptor.isInline) { - val fieldDescriptor = structDescriptor.elementDescriptors.first() - val fieldType = convertToTsElement(targetId, fieldDescriptor) - val fieldTyping = TsTyping(fieldType, fieldDescriptor.isNullable) - return TsTypeAlias(targetId, fieldTyping) - } else { - - val properties = structDescriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> - val name = structDescriptor.getElementName(index) - val fieldType = convertToTsElement(targetId, fieldDescriptor) - val fieldTyping = TsTyping(fieldType, fieldDescriptor.isNullable) - when { - structDescriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTyping) - else -> TsProperty.Required(name, fieldTyping) - } - } + } - return TsStructure.TsInterface(targetId, properties) - } + + private fun convertInterface( + structDescriptor: SerialDescriptor, + typeRefs: Map, + ): TsDeclaration { + val resultId = createElementId(structDescriptor) + + val properties = structDescriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> + val name = structDescriptor.getElementName(index) + val fieldTypeRef = typeRefs[fieldDescriptor] ?: TsTypeRef.Unknown + when { + structDescriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeRef) + else -> TsProperty.Required(name, fieldTypeRef) + } + }.toSet() + return TsDeclaration.TsInterface(resultId, properties, TsPolymorphicDiscriminator.Open) } + private fun convertEnum( - targetId: TsElementId, enumDescriptor: SerialDescriptor, - ): TsElement { - return TsStructure.TsEnum( - targetId, - enumDescriptor.elementNames.toSet(), - ) + ): TsDeclaration.TsEnum { + val resultId = createElementId(enumDescriptor) + return TsDeclaration.TsEnum(resultId, enumDescriptor.elementNames.toSet()) } + private fun convertList( - targetId: TsElementId, listDescriptor: SerialDescriptor, - ): TsStructure.TsList { + typeRefs: Map, + ): TsLiteral.TsList { val elementDescriptor = listDescriptor.elementDescriptors.first() - val elementType = convertToTsElement(targetId, elementDescriptor) - val elementTyping = TsTyping(elementType, elementDescriptor.isNullable) - return TsStructure.TsList(targetId, elementTyping) + val elementTypeRef = typeRefs[elementDescriptor] ?: TsTypeRef.Unknown + return TsLiteral.TsList(elementTypeRef) } + private fun convertMap( - targetId: TsElementId, mapDescriptor: SerialDescriptor, - ): TsStructure.TsMap { + typeRefs: Map, + ): TsLiteral.TsMap { val (keyDescriptor, valueDescriptor) = mapDescriptor.elementDescriptors.toList() - val keyType = convertToTsElement(targetId, keyDescriptor) - val keyTyping = TsTyping(keyType, keyDescriptor.isNullable) + val keyTypeRef = typeRefs[keyDescriptor] ?: TsTypeRef.Unknown + val valueTypeRef = typeRefs[valueDescriptor] ?: TsTypeRef.Unknown + + val type = convertMapType(keyDescriptor) + + return TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) + } + + +} + - val valueType = convertToTsElement(targetId, valueDescriptor) - val valueTyping = TsTyping(valueType, valueDescriptor.isNullable) +class DescriptorExtractor( + private val kxsTsModule: KxsTsModule +) { + + operator fun invoke(serializer: KSerializer<*>): Map { + return sequence { + when (serializer) { + is PolymorphicSerializer<*> -> { + yieldAll(kxsTsModule.serializersModule.getPolymorphicDescriptors(serializer.descriptor)) + } + is SealedClassSerializer<*> -> + yield(serializer.descriptor) + is ContextualSerializer<*> -> + yield(extractContextualSerializer(serializer, kxsTsModule)?.descriptor) + else -> + yield(serializer.descriptor) + } + }.filterNotNull() + .flatMap { descriptor -> extractAll(descriptor) } + .distinct() + .associateWith { descriptor -> + createTypeRef(descriptor) + } + } + + private fun extractAll(descriptor: SerialDescriptor): Sequence { + return sequence { + val seen = mutableSetOf() + val deque = ArrayDeque() + deque.addLast(descriptor) + while (deque.isNotEmpty()) { + val next = deque.removeFirst() + + val nextElementDescriptors = extractElementDescriptors(next) + + nextElementDescriptors + .filter { it !in seen } + .forEach { deque.addLast(it) } + + seen.add(next) + yield(next) + } + }.distinct() + } + + + private fun extractElementDescriptors(serialDescriptor: SerialDescriptor): Iterable { + return when (serialDescriptor.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 -> serialDescriptor.elementDescriptors + + PolymorphicKind.SEALED, + PolymorphicKind.OPEN -> serialDescriptor + .elementDescriptors + .filter { it.kind is PolymorphicKind } + .flatMap { it.elementDescriptors } + } + } + + + private fun createTypeRef(descriptor: SerialDescriptor): TsTypeRef { + return when (descriptor.kind) { + is PrimitiveKind -> { + val tsPrimitive = when (descriptor.kind as PrimitiveKind) { + PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean + + PrimitiveKind.BYTE, + PrimitiveKind.SHORT, + PrimitiveKind.INT, + PrimitiveKind.LONG, + PrimitiveKind.FLOAT, + PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber + + PrimitiveKind.CHAR, + PrimitiveKind.STRING -> TsLiteral.Primitive.TsString + } + TsTypeRef.Literal(tsPrimitive, descriptor.isNullable) + } + + StructureKind.LIST -> { + val elementDescriptor = descriptor.elementDescriptors.first() + val elementTypeRef = createTypeRef(elementDescriptor) + val listRef = TsLiteral.TsList(elementTypeRef) + TsTypeRef.Literal(listRef, descriptor.isNullable) + } + StructureKind.MAP -> { + val (keyDescriptor, valueDescriptor) = descriptor.elementDescriptors.toList() + val keyTypeRef = createTypeRef(keyDescriptor) + val valueTypeRef = createTypeRef(valueDescriptor) + val type = convertMapType(keyDescriptor) + val map = TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) + TsTypeRef.Literal(map, descriptor.isNullable) + } + + SerialKind.CONTEXTUAL, + PolymorphicKind.SEALED, + PolymorphicKind.OPEN, + SerialKind.ENUM, + StructureKind.CLASS, + StructureKind.OBJECT -> { + val id = createElementId(descriptor) + TsTypeRef.Named(id, descriptor.isNullable) + } + } + } + +} + + +private fun createElementId(descriptor: SerialDescriptor): TsElementId { + + val targetId = TsElementId(descriptor.serialName.removeSuffix("?")) + + return when (descriptor.kind) { + PolymorphicKind.OPEN -> TsElementId( + targetId.namespace + "." + targetId.name.substringAfter("<").substringBeforeLast(">") + ) + PolymorphicKind.SEALED, + PrimitiveKind.BOOLEAN, + PrimitiveKind.BYTE, + PrimitiveKind.CHAR, + PrimitiveKind.DOUBLE, + PrimitiveKind.FLOAT, + PrimitiveKind.INT, + PrimitiveKind.LONG, + PrimitiveKind.SHORT, + PrimitiveKind.STRING, + SerialKind.CONTEXTUAL, + SerialKind.ENUM, + StructureKind.CLASS, + StructureKind.LIST, + StructureKind.MAP, + StructureKind.OBJECT -> targetId + } +} - return TsStructure.TsMap(targetId, keyTyping, valueTyping) +private fun convertMapType(keyDescriptor: SerialDescriptor): TsLiteral.TsMap.Type { + return when (keyDescriptor.kind) { + SerialKind.ENUM -> TsLiteral.TsMap.Type.MAPPED_OBJECT + + PrimitiveKind.STRING -> TsLiteral.TsMap.Type.INDEX_SIGNATURE + + SerialKind.CONTEXTUAL, + PrimitiveKind.BOOLEAN, + PrimitiveKind.BYTE, + PrimitiveKind.CHAR, + PrimitiveKind.SHORT, + PrimitiveKind.INT, + PrimitiveKind.LONG, + PrimitiveKind.FLOAT, + PrimitiveKind.DOUBLE, + StructureKind.CLASS, + StructureKind.LIST, + StructureKind.MAP, + StructureKind.OBJECT, + PolymorphicKind.SEALED, + PolymorphicKind.OPEN -> TsLiteral.TsMap.Type.MAP } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsModule.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsModule.kt new file mode 100644 index 00000000..bb66ebe1 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsModule.kt @@ -0,0 +1,66 @@ +package dev.adamko.kxstsgen + +import kotlin.reflect.KClass +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.ExperimentalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.SerializersModuleCollector + + +/** Collects the contents of a [SerializersModule], so kxs-ts-gen can view registered classes. */ +class KxsTsModule( + val serializersModule: SerializersModule = EmptySerializersModule +) { + + private val contextualClasses: MutableSet> = mutableSetOf() + private val polymorphicClasses: MutableList, KClass<*>>> = mutableListOf() + + init { + serializersModule.dumpTo(Collector()) + } + + + data class Polymorphic( + val baseClass: KClass, + val actualClass: KClass, + val actualSerializer: KSerializer, + ) { + val actualDescriptor: SerialDescriptor by actualSerializer::descriptor + } + + private inner class Collector : SerializersModuleCollector { + + override fun contextual( + kClass: KClass, + provider: (typeArgumentsSerializers: List>) -> KSerializer<*> + ) { + contextualClasses + kClass + } + + override fun polymorphic( + baseClass: KClass, + actualClass: KClass, + actualSerializer: KSerializer, + ) { + } + + @ExperimentalSerializationApi + override fun polymorphicDefaultDeserializer( + baseClass: KClass, + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? + ) { + } + + @ExperimentalSerializationApi + override fun polymorphicDefaultSerializer( + baseClass: KClass, + defaultSerializerProvider: (value: Base) -> SerializationStrategy? + ) { + } + + } +} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index 4f38f1a2..8a0abc6e 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -1,39 +1,46 @@ package dev.adamko.kxstsgen +/** + * Writes [TsElement]s as TypeScript source code. + */ class KxsTsSourceCodeGenerator( - private val config: KxsTsConfig + private val config: KxsTsConfig, + private val elements: Set, ) { private val indent by config::indent - fun joinElementsToString(elements: Set): String { +// private val mapIdToElement: MutableMap = +// mutableMapOf().withDefault { req -> +// elements.first { it.id == req } +// } + + private val typeReferenceState: MutableMap = mutableMapOf() + + fun joinElementsToString(): String { return elements .groupBy { when (config.namespaceConfig) { - is KxsTsConfig.NamespaceConfig.Static -> config.namespaceConfig.namespace - KxsTsConfig.NamespaceConfig.Disabled -> "" - KxsTsConfig.NamespaceConfig.UseDescriptorNamePrefix -> it.id.namespace + is KxsTsConfig.NamespaceConfig.Static -> config.namespaceConfig.namespace + KxsTsConfig.NamespaceConfig.Disabled -> "" + KxsTsConfig.NamespaceConfig.DescriptorNamePrefix -> "it.id.namespace" } } .mapValues { (_, elements) -> - - elements.mapNotNull { element -> - when (element) { - is TsStructure.TsMap, - is TsStructure.TsList, - is TsPrimitive -> null - is TsStructure.TsEnum -> generateEnum(element) - is TsStructure.TsInterface -> generateInterface(element) - is TsTypeAlias -> generateTypeAlias(element) + elements + .filterIsInstance() + .joinToString(config.structureSeparator) { declaration -> + when (declaration) { + is TsDeclaration.TsEnum -> generateEnum(declaration) + is TsDeclaration.TsInterface -> generateInterface(declaration) + is TsDeclaration.TsTypeAlias -> generateTypeAlias(declaration) + } } - } - .joinToString("\n\n") - }.entries .filter { it.value.isNotBlank() } - .joinToString("\n\n") { (namespace, namespaceContent) -> + .joinToString(config.structureSeparator) { (namespace, namespaceContent) -> if (namespace.isBlank()) { namespaceContent @@ -47,7 +54,7 @@ class KxsTsSourceCodeGenerator( } } - private fun generateEnum(enum: TsStructure.TsEnum): String { + private fun generateEnum(enum: TsDeclaration.TsEnum): String { val enumMembers = enum.members.joinToString("\n") { member -> """ @@ -62,7 +69,7 @@ class KxsTsSourceCodeGenerator( """.trimMargin() } - private fun generateInterface(element: TsStructure.TsInterface): String { + private fun generateInterface(element: TsDeclaration.TsInterface): String { val properties = element .properties @@ -71,7 +78,7 @@ class KxsTsSourceCodeGenerator( is TsProperty.Optional -> "?: " is TsProperty.Required -> ": " } - val propertyType = generateTypeReference(property.typing) + val propertyType = generateTypeReference(property.typeRef) // generate ` name: Type;` // or ` name:? Type;` "${indent}${property.name}${separator}${propertyType};" @@ -86,8 +93,15 @@ class KxsTsSourceCodeGenerator( } } - private fun generateTypeAlias(element: TsTypeAlias): String { - val aliases = generateTypeReference(element.type) +// private fun generateSealedSubInterfaces(sealed: TsPolymorphicDiscriminator.Sealed): String { +// +// val enumDiscriminator = generateEnum(sealed.discriminator) +// +// val sealedType = sealed.children.map { it } +// } + + private fun generateTypeAlias(element: TsDeclaration.TsTypeAlias): String { + val aliases = generateTypeReference(element.typeRef) return when (config.typeAliasTyping) { KxsTsConfig.TypeAliasTypingConfig.None -> @@ -97,8 +111,13 @@ class KxsTsSourceCodeGenerator( KxsTsConfig.TypeAliasTypingConfig.BrandTyping -> { val brandType = element.id.name - .replace(".", "_") - .filter { it.isLetter() || it == '_' } + .mapNotNull { c -> + when { + c == '.' -> '_' + !c.isLetter() -> null + else -> c + } + }.joinToString("") """ |type ${element.id.name} = $aliases & { __${brandType}__: void }; @@ -112,53 +131,56 @@ class KxsTsSourceCodeGenerator( * A type-reference, be it for the field of an interface, a type alias, or a generic type * constraint. */ - private fun generateTypeReference(typing: TsTyping): String { - return buildString { - when (typing.type) { - is TsStructure.TsEnum, - is TsStructure.TsInterface, - is TsTypeAlias, - is TsPrimitive -> { - append(typing.type.id.name) - if (typing.nullable) { - append(" | " + TsPrimitive.TsNull.id.name) + private fun generateTypeReference(typeRef: TsTypeRef): String { + return typeReferenceState.getOrPut(typeRef) { + val plainType: String = when (typeRef) { + is TsTypeRef.Literal -> when (typeRef.element) { + is TsLiteral.Primitive -> primitiveReference(typeRef.element) + is TsLiteral.TsList -> { + val valueTypeRef = generateTypeReference(typeRef.element.valueTypeRef) + "$valueTypeRef[]" } + is TsLiteral.TsMap -> generateMapTypeReference(typeRef.element) } - is TsStructure.TsList -> { - append(generateTypeReference(typing.type.elementsTyping)) - append("[]") + is TsTypeRef.Named -> typeRef.id.name + is TsTypeRef.Unknown -> generateTypeReference(typeRef.ref) + } + + buildString { + append(plainType) + if (typeRef.nullable) { + append(" | ") + append(primitiveReference(TsLiteral.Primitive.TsNull)) } - is TsStructure.TsMap -> append(mapTypeReference(typing)) } } } - private fun mapTypeReference(typing: TsTyping): String { - require(typing.type is TsStructure.TsMap) - - val keyTypeRef = generateTypeReference(typing.type.keyTyping) - val valueTypeRef = generateTypeReference(typing.type.valueTyping) - return when (typing.type.keyTyping.type) { - - TsPrimitive.TsString, - TsPrimitive.TsNumber -> "{ [key: $keyTypeRef]: $valueTypeRef }" + private fun primitiveReference(primitive: TsLiteral.Primitive): String { + return when (primitive) { + TsLiteral.Primitive.TsString -> "string" + TsLiteral.Primitive.TsNumber -> "number" + TsLiteral.Primitive.TsBoolean -> "boolean" + TsLiteral.Primitive.TsObject -> "object" + TsLiteral.Primitive.TsAny -> "any" + TsLiteral.Primitive.TsNever -> "never" + TsLiteral.Primitive.TsNull -> "null" + TsLiteral.Primitive.TsUndefined -> "undefined" + TsLiteral.Primitive.TsUnknown -> "unknown" + TsLiteral.Primitive.TsVoid -> "void" + } + } - is TsStructure.TsEnum -> "{ [key in $keyTypeRef]: $valueTypeRef }" - TsPrimitive.TsBoolean, - TsPrimitive.TsObject, - TsPrimitive.TsAny, - TsPrimitive.TsNever, - TsPrimitive.TsNull, - TsPrimitive.TsUndefined, - TsPrimitive.TsUnknown, - TsPrimitive.TsVoid, - is TsTypeAlias, - is TsStructure.TsInterface, - is TsStructure.TsList, - is TsStructure.TsMap -> "Map<$keyTypeRef, $valueTypeRef>" + private fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String { + val keyTypeRef = generateTypeReference(tsMap.keyTypeRef) + val valueTypeRef = generateTypeReference(tsMap.valueTypeRef) + return when (tsMap.type) { + TsLiteral.TsMap.Type.INDEX_SIGNATURE -> "{ [key: $keyTypeRef]: $valueTypeRef }" + TsLiteral.TsMap.Type.MAPPED_OBJECT -> "{ [key in $keyTypeRef]: $valueTypeRef }" + TsLiteral.TsMap.Type.MAP -> "Map<$keyTypeRef, $valueTypeRef>" } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/serializerExtractors.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/serializerExtractors.kt new file mode 100644 index 00000000..930b589e --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/serializerExtractors.kt @@ -0,0 +1,54 @@ +@file:OptIn(InternalSerializationApi::class) + +package dev.adamko.kxstsgen + +import kotlinx.serialization.ContextualSerializer +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SealedClassSerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.AbstractDecoder +import kotlinx.serialization.modules.SerializersModule + + +// https://github.com/Kotlin/kotlinx.serialization/issues/1865 +expect fun extractSealedSubclassSerializers( + serializer: SealedClassSerializer +): Collection> + + +/** Hacky exploit to capture the [KSerializer] of a [ContextualSerializer]. */ +fun extractContextualSerializer( + serializer: ContextualSerializer<*>, + kxsTsModule: KxsTsModule, +): KSerializer<*>? { + return try { + val decoder = ContextualSerializerCaptorDecoder(kxsTsModule.serializersModule) + serializer.deserialize(decoder) + null // this should never be hit, decoder should always throw an exception + } catch (e: SerializerCaptorException) { + e.serializer + } catch (e: Throwable) { + null + } +} + +private class ContextualSerializerCaptorDecoder( + override val serializersModule: SerializersModule +) : AbstractDecoder() { + + override fun decodeElementIndex(descriptor: SerialDescriptor): Nothing = + error("intentionally unimplemented, I don't expect ContextualSerializer to call this method") + + override fun decodeSerializableValue(deserializer: DeserializationStrategy): Nothing = + throw SerializerCaptorException(deserializer as KSerializer) +} + +private class SerializerCaptorException(val serializer: KSerializer<*>) : Exception() + + +expect fun extractContextualDescriptor( + serializer: ContextualSerializer, + serializersModule: SerializersModule, +): KSerializer diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt index 81a61bc4..72a40215 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt @@ -12,85 +12,173 @@ value class TsElementId(private val id: String) { } -sealed interface TsElement { +sealed interface TsElement + + +sealed interface TsDeclaration : TsElement { val id: TsElementId + + data class TsTypeAlias( + override val id: TsElementId, + val typeRef: TsTypeRef, + ) : TsDeclaration + + + data class TsInterface( + override val id: TsElementId, + val properties: Set, + val polymorphism: TsPolymorphicDiscriminator, + ) : TsDeclaration + + + data class TsEnum( + override val id: TsElementId, + val members: Set, + ) : TsDeclaration + } -sealed class TsPrimitive( - type: String -) : TsElement { - final override val id: TsElementId = TsElementId(type) +sealed interface TsLiteral : TsElement { + + sealed interface Primitive : TsLiteral { - object TsString : TsPrimitive("string") - object TsNumber : TsPrimitive("number") - object TsBoolean : TsPrimitive("boolean") + object TsString : Primitive + object TsNumber : Primitive + object TsBoolean : Primitive + + object TsObject : Primitive + + object TsAny : Primitive + object TsNever : Primitive + object TsNull : Primitive + object TsUndefined : Primitive + object TsUnknown : Primitive + object TsVoid : Primitive + } + + data class TsList( + val valueTypeRef: TsTypeRef, + ) : TsLiteral - object TsObject : TsPrimitive("object") + data class TsMap( + val keyTypeRef: TsTypeRef, + val valueTypeRef: TsTypeRef, + val type: Type, + ) : TsLiteral { + enum class Type { + INDEX_SIGNATURE, + MAPPED_OBJECT, + MAP, + } + } - object TsAny : TsPrimitive("any") - object TsNever : TsPrimitive("never") - object TsNull : TsPrimitive("null") - object TsUndefined : TsPrimitive("undefined") - object TsUnknown : TsPrimitive("unknown") - object TsVoid : TsPrimitive("void") } -data class TsTypeAlias( - override val id: TsElementId, - val type: TsTyping, -) : TsElement +sealed interface TsTypeRef { + val nullable: Boolean + + data class Literal(val element: TsLiteral, override val nullable: Boolean) : TsTypeRef + data class Named(val id: TsElementId, override val nullable: Boolean) : TsTypeRef -data class TsTyping( - val type: TsElement, - val nullable: Boolean, -) + object Unknown : TsTypeRef { + val ref: Literal = Literal(TsLiteral.Primitive.TsUnknown, false) + override val nullable: Boolean by ref::nullable + } +} +// source: kxs sealed interface TsProperty { val name: String - val typing: TsTyping + val typeRef: TsTypeRef data class Required( override val name: String, - override val typing: TsTyping, + override val typeRef: TsTypeRef, ) : TsProperty data class Optional( override val name: String, - override val typing: TsTyping, + override val typeRef: TsTypeRef, ) : TsProperty } -sealed interface TsStructure : TsElement { - - data class TsInterface( - override val id: TsElementId, - val properties: List, - ) : TsStructure - - data class TsEnum( - override val id: TsElementId, - val members: Set - ) : TsStructure - - data class TsList( - override val id: TsElementId, - val elementsTyping: TsTyping, - ) : TsStructure - - data class TsMap( - override val id: TsElementId, - val keyTyping: TsTyping, - val valueTyping: TsTyping, - ) : TsStructure +// source: kxs & introspection +sealed interface TsPolymorphicDiscriminator { + // source: introspection + data class Sealed( + // source: kxs + val discriminator: TsDeclaration.TsEnum, + // source: introspection + val children: Set = setOf(), + ) : TsPolymorphicDiscriminator + + // source: introspection + object Open : TsPolymorphicDiscriminator } -sealed interface TsPolymorphicDiscriminator { - sealed interface Closed - sealed interface Open -} +/* +//fun TsTypeRef.toTsTypeParamConstant(): TsTypeParam.Constant = TsTypeParam.Constant(this) + +///** +// * A generic type parameter, either constant or generic. +// * +// * The output depends on whether it is used in a [TsStructure.TsInterface] or [TsProperty]. +// * +// * ```typescript +// * // T is 'TsTypeParam.Generic' +// * interface Box { value: T; } +// * interface Pet { pet: T; } +// * interface NamedBox { namedValue: T; } +// * interface NamedPetBox { pet: T; } +// * +// * // 'string' and 'Dog' are 'TsTypeParam.Constant' +// * interface Person { aliases: string[] } +// * interface DogBox extends PetBox { override pet: Dog } +// * ``` +// */ +//// source: introspection +//sealed interface TsTypeParam { +// +// data class Generic( +// val name: String, +// val constraints: Set? = null +// ) : TsTypeParam +// +// data class Constant( +// val typeRef: TsTypeRef +// ) : TsTypeParam +// +//} + + +///** Defines how many generic types can be accepted. */ +//// source: introspection +//sealed interface TsGenericSlot { +// +// /** Enums, primitives, properties can't have generic slots. */ +// object None : TsGenericSlot +// +// /** A single generic type, e.g. `List` */ +// data class Value( +// val valueTyping: TsTypeParam, +// ) : TsGenericSlot +// +// /** The generic types of a `Map` */ +// data class KeyValue( +// val keyTyping: TsTypeParam, +// val valueTyping: TsTypeParam, +// ) : TsGenericSlot +// +// /** Can have zero-to-many [TsTypeParam]s. */ +// data class Multiple( +// val typings: Set, +// ) : TsGenericSlot +// +//} +*/ diff --git a/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/serializerExtractorsJvm.kt b/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/serializerExtractorsJvm.kt new file mode 100644 index 00000000..fe39f2f4 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/serializerExtractorsJvm.kt @@ -0,0 +1,52 @@ +@file:OptIn(InternalSerializationApi::class) // TODO make GitHub issue +package dev.adamko.kxstsgen + +import kotlin.reflect.KClass +import kotlin.reflect.KProperty1 +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.full.functions +import kotlin.reflect.jvm.isAccessible +import kotlinx.serialization.ContextualSerializer +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.SealedClassSerializer +import kotlinx.serialization.modules.SerializersModule + + +actual fun extractSealedSubclassSerializers( + serializer: SealedClassSerializer +): Collection> { + @Suppress("UNCHECKED_CAST") + val class2Serializer = class2SerializerAccessor + .get(serializer) as Map, KSerializer> + return class2Serializer.values +} + + +/** Access the private `class2Serializer` field in [SealedClassSerializer] */ +private val class2SerializerAccessor: KProperty1, *> = + SealedClassSerializer::class + .declaredMemberProperties + .firstOrNull { it.name == "class2Serializer" } + ?.apply { isAccessible = true } + ?: error("Can't access ContextualSerializer.serializer()") + + +@Suppress("UNCHECKED_CAST") +actual fun extractContextualDescriptor( + serializer: ContextualSerializer, + serializersModule: SerializersModule, +): KSerializer { + return contextualSerializerAccessor(serializersModule) as KSerializer +} + + +/** Access the private `.serializer()` function in [ContextualSerializer] */ +@Suppress("UNCHECKED_CAST") +private val contextualSerializerAccessor: (SerializersModule) -> KSerializer<*> = + ContextualSerializer::class + .functions + .firstOrNull { it.name == "serializer" } + ?.apply { isAccessible = true } + as? (SerializersModule) -> KSerializer<*> + ?: error("Can't access ContextualSerializer.serializer()") From 7924e2a41b36acecb1bcc2ba5e90ccbbf0f17e80 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 26 Mar 2022 18:32:23 +0100 Subject: [PATCH 15/46] update tests, and add some more --- README.md | 2 +- docs/basic-classes.md | 8 +- docs/default-values.md | 4 +- docs/edgecases.md | 121 ++++++++++++ docs/enums.md | 4 +- docs/knit/build.gradle.kts | 3 + ...xample-abstract-class-abstract-field-01.kt | 2 +- ...mple-abstract-class-primitive-fields-01.kt | 2 +- .../example-abstract-class-single-field-01.kt | 2 +- ...mple-default-values-primitive-fields-01.kt | 2 +- .../example-default-values-single-field-01.kt | 2 +- ...xample-edgecase-recursive-references-01.kt | 17 ++ ...xample-edgecase-recursive-references-02.kt | 21 ++ ...xample-edgecase-recursive-references-03.kt | 21 ++ docs/knit/example/example-enum-class-01.kt | 2 +- docs/knit/example/example-enum-class-02.kt | 2 +- docs/knit/example/example-generics-01.kt | 23 +++ .../knit/example/example-list-primitive-01.kt | 8 +- docs/knit/example/example-map-complex-01.kt | 2 +- docs/knit/example/example-map-primitive-01.kt | 2 +- docs/knit/example/example-map-primitive-02.kt | 12 +- docs/knit/example/example-map-primitive-03.kt | 2 +- ...example-plain-class-primitive-fields-01.kt | 2 +- ...example-plain-class-primitive-fields-02.kt | 2 +- .../example-plain-class-single-field-01.kt | 2 +- .../example/example-plain-data-class-01.kt | 2 +- ...phic-abstract-class-primitive-fields-01.kt | 2 +- .../example/example-polymorphic-objects-01.kt | 4 +- .../example-polymorphic-sealed-class-01.kt | 3 +- .../example-polymorphic-sealed-class-02.kt | 38 ++++ .../example-polymorphic-static-types-01.kt | 2 +- .../example-polymorphic-static-types-02.kt | 12 +- docs/knit/example/example-value-classes-01.kt | 2 +- docs/knit/example/example-value-classes-02.kt | 8 +- docs/knit/example/example-value-classes-03.kt | 2 +- docs/knit/example/example-value-classes-04.kt | 2 +- docs/knit/test/EdgeCasesTest.kt | 66 +++++++ docs/knit/test/ListsTests.kt | 6 +- docs/knit/test/MapsTests.kt | 16 +- docs/knit/test/PolymorphismTest.kt | 89 +++++++-- docs/knit/test/ValueClassesTest.kt | 4 +- docs/lists.md | 15 +- docs/maps.md | 35 ++-- docs/polymorphism.md | 181 +++++++++++++++--- docs/value-classes.md | 18 +- 45 files changed, 641 insertions(+), 136 deletions(-) create mode 100644 docs/edgecases.md create mode 100644 docs/knit/example/example-edgecase-recursive-references-01.kt create mode 100644 docs/knit/example/example-edgecase-recursive-references-02.kt create mode 100644 docs/knit/example/example-edgecase-recursive-references-03.kt create mode 100644 docs/knit/example/example-generics-01.kt create mode 100644 docs/knit/example/example-polymorphic-sealed-class-02.kt create mode 100644 docs/knit/test/EdgeCasesTest.kt diff --git a/README.md b/README.md index 8f94b241..a26a1d85 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ data class PlayerDetails( ) println( - KxsTsGenerator().generate(Color.serializer().descriptor) + KxsTsGenerator().generate(Color.serializer()) ) ``` diff --git a/docs/basic-classes.md b/docs/basic-classes.md index 18c25068..211292f0 100644 --- a/docs/basic-classes.md +++ b/docs/basic-classes.md @@ -29,7 +29,7 @@ class Color(val rgb: Int) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Color.serializer().descriptor)) + println(tsGenerator.generate(Color.serializer())) } ``` @@ -57,7 +57,7 @@ class SimpleTypes( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SimpleTypes.serializer().descriptor)) + println(tsGenerator.generate(SimpleTypes.serializer())) } ``` @@ -89,7 +89,7 @@ data class SomeDataClass( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SomeDataClass.serializer().descriptor)) + println(tsGenerator.generate(SomeDataClass.serializer())) } ``` @@ -130,7 +130,7 @@ class SimpleTypes( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SimpleTypes.serializer().descriptor)) + println(tsGenerator.generate(SimpleTypes.serializer())) } ``` diff --git a/docs/default-values.md b/docs/default-values.md index c88bba89..69c541a4 100644 --- a/docs/default-values.md +++ b/docs/default-values.md @@ -30,7 +30,7 @@ class Color(val rgb: Int = 12345) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Color.serializer().descriptor)) + println(tsGenerator.generate(Color.serializer())) } ``` @@ -56,7 +56,7 @@ data class ContactDetails( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(ContactDetails.serializer().descriptor)) + println(tsGenerator.generate(ContactDetails.serializer())) } ``` diff --git a/docs/edgecases.md b/docs/edgecases.md new file mode 100644 index 00000000..809c7389 --- /dev/null +++ b/docs/edgecases.md @@ -0,0 +1,121 @@ + + +**Table of contents** + + + +* [Introduction](#introduction) + * [Recursive references](#recursive-references) + * [Classes](#classes) + * [Lists](#lists) + * [Map](#map) + + + + + +## Introduction + +Lorem ipsum... + +### Recursive references + +A references B which references A which references B... should be handled properly + +#### Classes + +```kotlin +@Serializable +class A(val b: B) + +@Serializable +class B(val a: A) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(A.serializer(), B.serializer())) +} +``` + +> You can get the full code [here](./knit/example/example-edgecase-recursive-references-01.kt). + +```typescript +interface A { + b: B; +} + +interface B { + a: A; +} +``` + + + +#### Lists + +```kotlin +@Serializable +class A( + val list: List +) + +@Serializable +class B( + val list: List +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(A.serializer(), B.serializer())) +} +``` + +> You can get the full code [here](./knit/example/example-edgecase-recursive-references-02.kt). + +```typescript +interface A { + list: B[]; +} + +interface B { + list: A[]; +} +``` + + + +#### Map + +```kotlin +@Serializable +class A( + val map: Map +) + +@Serializable +class B( + val map: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(A.serializer(), B.serializer())) +} +``` + +> You can get the full code [here](./knit/example/example-edgecase-recursive-references-03.kt). + +```typescript +interface A { + map: { [key: string]: B }; +} + +interface B { + map: { [key: string]: A }; +} +``` + + diff --git a/docs/enums.md b/docs/enums.md index eb85b32f..2bd3e656 100644 --- a/docs/enums.md +++ b/docs/enums.md @@ -36,7 +36,7 @@ enum class SomeType { fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SomeType.serializer().descriptor)) + println(tsGenerator.generate(SomeType.serializer())) } ``` @@ -68,7 +68,7 @@ enum class SomeType2(val coolName: String) { fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SomeType2.serializer().descriptor)) + println(tsGenerator.generate(SomeType2.serializer())) } ``` diff --git a/docs/knit/build.gradle.kts b/docs/knit/build.gradle.kts index 1b9d97ee..dd685be5 100644 --- a/docs/knit/build.gradle.kts +++ b/docs/knit/build.gradle.kts @@ -16,6 +16,9 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-knit:0.3.0") + implementation(kotlin("reflect")) + + testImplementation(kotlin("test")) testImplementation("org.jetbrains.kotlinx:kotlinx-knit-test:0.3.0") diff --git a/docs/knit/example/example-abstract-class-abstract-field-01.kt b/docs/knit/example/example-abstract-class-abstract-field-01.kt index d77c6932..7f734fa3 100644 --- a/docs/knit/example/example-abstract-class-abstract-field-01.kt +++ b/docs/knit/example/example-abstract-class-abstract-field-01.kt @@ -15,5 +15,5 @@ abstract class AbstractSimpleTypes { fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(AbstractSimpleTypes.serializer().descriptor)) + println(tsGenerator.generate(AbstractSimpleTypes.serializer())) } diff --git a/docs/knit/example/example-abstract-class-primitive-fields-01.kt b/docs/knit/example/example-abstract-class-primitive-fields-01.kt index 355ee72f..f9f812a6 100644 --- a/docs/knit/example/example-abstract-class-primitive-fields-01.kt +++ b/docs/knit/example/example-abstract-class-primitive-fields-01.kt @@ -16,5 +16,5 @@ abstract class SimpleTypes( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SimpleTypes.serializer().descriptor)) + println(tsGenerator.generate(SimpleTypes.serializer())) } diff --git a/docs/knit/example/example-abstract-class-single-field-01.kt b/docs/knit/example/example-abstract-class-single-field-01.kt index 3099b6e1..1322e995 100644 --- a/docs/knit/example/example-abstract-class-single-field-01.kt +++ b/docs/knit/example/example-abstract-class-single-field-01.kt @@ -10,5 +10,5 @@ abstract class Color(val rgb: Int) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Color.serializer().descriptor)) + println(tsGenerator.generate(Color.serializer())) } diff --git a/docs/knit/example/example-default-values-primitive-fields-01.kt b/docs/knit/example/example-default-values-primitive-fields-01.kt index 20d4292c..b11a011f 100644 --- a/docs/knit/example/example-default-values-primitive-fields-01.kt +++ b/docs/knit/example/example-default-values-primitive-fields-01.kt @@ -14,5 +14,5 @@ data class ContactDetails( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(ContactDetails.serializer().descriptor)) + println(tsGenerator.generate(ContactDetails.serializer())) } diff --git a/docs/knit/example/example-default-values-single-field-01.kt b/docs/knit/example/example-default-values-single-field-01.kt index 4df7d33d..bd226807 100644 --- a/docs/knit/example/example-default-values-single-field-01.kt +++ b/docs/knit/example/example-default-values-single-field-01.kt @@ -10,5 +10,5 @@ class Color(val rgb: Int = 12345) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Color.serializer().descriptor)) + println(tsGenerator.generate(Color.serializer())) } diff --git a/docs/knit/example/example-edgecase-recursive-references-01.kt b/docs/knit/example/example-edgecase-recursive-references-01.kt new file mode 100644 index 00000000..b6b814ab --- /dev/null +++ b/docs/knit/example/example-edgecase-recursive-references-01.kt @@ -0,0 +1,17 @@ +// This file was automatically generated from edgecases.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +class A(val b: B) + +@Serializable +class B(val a: A) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(A.serializer(), B.serializer())) +} diff --git a/docs/knit/example/example-edgecase-recursive-references-02.kt b/docs/knit/example/example-edgecase-recursive-references-02.kt new file mode 100644 index 00000000..1d3a6e49 --- /dev/null +++ b/docs/knit/example/example-edgecase-recursive-references-02.kt @@ -0,0 +1,21 @@ +// This file was automatically generated from edgecases.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences02 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +class A( + val list: List +) + +@Serializable +class B( + val list: List +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(A.serializer(), B.serializer())) +} diff --git a/docs/knit/example/example-edgecase-recursive-references-03.kt b/docs/knit/example/example-edgecase-recursive-references-03.kt new file mode 100644 index 00000000..38774986 --- /dev/null +++ b/docs/knit/example/example-edgecase-recursive-references-03.kt @@ -0,0 +1,21 @@ +// This file was automatically generated from edgecases.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences03 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +class A( + val map: Map +) + +@Serializable +class B( + val map: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(A.serializer(), B.serializer())) +} diff --git a/docs/knit/example/example-enum-class-01.kt b/docs/knit/example/example-enum-class-01.kt index 0bbcbba5..f21a7eab 100644 --- a/docs/knit/example/example-enum-class-01.kt +++ b/docs/knit/example/example-enum-class-01.kt @@ -16,5 +16,5 @@ enum class SomeType { fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SomeType.serializer().descriptor)) + println(tsGenerator.generate(SomeType.serializer())) } diff --git a/docs/knit/example/example-enum-class-02.kt b/docs/knit/example/example-enum-class-02.kt index b0320424..26b662a6 100644 --- a/docs/knit/example/example-enum-class-02.kt +++ b/docs/knit/example/example-enum-class-02.kt @@ -18,5 +18,5 @@ enum class SomeType2(val coolName: String) { fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SomeType2.serializer().descriptor)) + println(tsGenerator.generate(SomeType2.serializer())) } diff --git a/docs/knit/example/example-generics-01.kt b/docs/knit/example/example-generics-01.kt new file mode 100644 index 00000000..648137fa --- /dev/null +++ b/docs/knit/example/example-generics-01.kt @@ -0,0 +1,23 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleGenerics01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +import kotlinx.serialization.builtins.serializer + +@Serializable +class Box( + val value: T, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + + println( + tsGenerator.generate( + Box.serializer(Double.serializer()), + ) + ) +} diff --git a/docs/knit/example/example-list-primitive-01.kt b/docs/knit/example/example-list-primitive-01.kt index 3505414e..2ec7c29d 100644 --- a/docs/knit/example/example-list-primitive-01.kt +++ b/docs/knit/example/example-list-primitive-01.kt @@ -6,11 +6,13 @@ import kotlinx.serialization.* import dev.adamko.kxstsgen.* @Serializable -data class CalendarEvent( - val attendeeNames: List +data class MyLists( + val strings: List, + val ints: List, + val longs: List, ) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(CalendarEvent.serializer().descriptor)) + println(tsGenerator.generate(MyLists.serializer())) } diff --git a/docs/knit/example/example-map-complex-01.kt b/docs/knit/example/example-map-complex-01.kt index 39cad1d8..a0752498 100644 --- a/docs/knit/example/example-map-complex-01.kt +++ b/docs/knit/example/example-map-complex-01.kt @@ -20,5 +20,5 @@ data class CanvasProperties( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(CanvasProperties.serializer().descriptor)) + println(tsGenerator.generate(CanvasProperties.serializer())) } diff --git a/docs/knit/example/example-map-primitive-01.kt b/docs/knit/example/example-map-primitive-01.kt index feccfd11..94b78a35 100644 --- a/docs/knit/example/example-map-primitive-01.kt +++ b/docs/knit/example/example-map-primitive-01.kt @@ -12,5 +12,5 @@ data class Config( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Config.serializer().descriptor)) + println(tsGenerator.generate(Config.serializer())) } diff --git a/docs/knit/example/example-map-primitive-02.kt b/docs/knit/example/example-map-primitive-02.kt index 681cc5cf..e5179b31 100644 --- a/docs/knit/example/example-map-primitive-02.kt +++ b/docs/knit/example/example-map-primitive-02.kt @@ -5,18 +5,18 @@ package dev.adamko.kxstsgen.example.exampleMapPrimitive02 import kotlinx.serialization.* import dev.adamko.kxstsgen.* +@Serializable +class Application( + val settings: Map +) + @Serializable enum class SettingKeys { SCREEN_SIZE, MAX_MEMORY, } -@Serializable -class Application( - val settings: Map -) - fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Application.serializer().descriptor)) + println(tsGenerator.generate(Application.serializer())) } diff --git a/docs/knit/example/example-map-primitive-03.kt b/docs/knit/example/example-map-primitive-03.kt index d2ff2f6c..1fff4023 100644 --- a/docs/knit/example/example-map-primitive-03.kt +++ b/docs/knit/example/example-map-primitive-03.kt @@ -12,5 +12,5 @@ data class Config( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Config.serializer().descriptor)) + println(tsGenerator.generate(Config.serializer())) } diff --git a/docs/knit/example/example-plain-class-primitive-fields-01.kt b/docs/knit/example/example-plain-class-primitive-fields-01.kt index ee8e103e..1531eb16 100644 --- a/docs/knit/example/example-plain-class-primitive-fields-01.kt +++ b/docs/knit/example/example-plain-class-primitive-fields-01.kt @@ -16,5 +16,5 @@ class SimpleTypes( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SimpleTypes.serializer().descriptor)) + println(tsGenerator.generate(SimpleTypes.serializer())) } diff --git a/docs/knit/example/example-plain-class-primitive-fields-02.kt b/docs/knit/example/example-plain-class-primitive-fields-02.kt index d4787c96..950a573e 100644 --- a/docs/knit/example/example-plain-class-primitive-fields-02.kt +++ b/docs/knit/example/example-plain-class-primitive-fields-02.kt @@ -15,5 +15,5 @@ class SimpleTypes( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SimpleTypes.serializer().descriptor)) + println(tsGenerator.generate(SimpleTypes.serializer())) } diff --git a/docs/knit/example/example-plain-class-single-field-01.kt b/docs/knit/example/example-plain-class-single-field-01.kt index 237482f0..18fbb258 100644 --- a/docs/knit/example/example-plain-class-single-field-01.kt +++ b/docs/knit/example/example-plain-class-single-field-01.kt @@ -10,5 +10,5 @@ class Color(val rgb: Int) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Color.serializer().descriptor)) + println(tsGenerator.generate(Color.serializer())) } diff --git a/docs/knit/example/example-plain-data-class-01.kt b/docs/knit/example/example-plain-data-class-01.kt index 6ea44223..ab235280 100644 --- a/docs/knit/example/example-plain-data-class-01.kt +++ b/docs/knit/example/example-plain-data-class-01.kt @@ -16,5 +16,5 @@ data class SomeDataClass( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SomeDataClass.serializer().descriptor)) + println(tsGenerator.generate(SomeDataClass.serializer())) } diff --git a/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt b/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt index 0f0e7205..056ee370 100644 --- a/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt +++ b/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt @@ -16,5 +16,5 @@ abstract class SimpleTypes( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SimpleTypes.serializer().descriptor)) + println(tsGenerator.generate(SimpleTypes.serializer())) } diff --git a/docs/knit/example/example-polymorphic-objects-01.kt b/docs/knit/example/example-polymorphic-objects-01.kt index afc8643c..6cc50be3 100644 --- a/docs/knit/example/example-polymorphic-objects-01.kt +++ b/docs/knit/example/example-polymorphic-objects-01.kt @@ -18,8 +18,8 @@ fun main() { val tsGenerator = KxsTsGenerator() println( tsGenerator.generate( - EmptyResponse.serializer().descriptor, - TextResponse.serializer().descriptor, + EmptyResponse.serializer(), + TextResponse.serializer(), ) ) } diff --git a/docs/knit/example/example-polymorphic-sealed-class-01.kt b/docs/knit/example/example-polymorphic-sealed-class-01.kt index f3199cc7..d19dbd76 100644 --- a/docs/knit/example/example-polymorphic-sealed-class-01.kt +++ b/docs/knit/example/example-polymorphic-sealed-class-01.kt @@ -11,6 +11,7 @@ sealed class Project { } @Serializable +@SerialName("owned-project") class OwnedProject(override val name: String, val owner: String) : Project() @Serializable @@ -18,5 +19,5 @@ class DeprecatedProject(override val name: String, val reason: String) : Project fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(OwnedProject.serializer().descriptor)) + println(tsGenerator.generate(OwnedProject.serializer())) } diff --git a/docs/knit/example/example-polymorphic-sealed-class-02.kt b/docs/knit/example/example-polymorphic-sealed-class-02.kt new file mode 100644 index 00000000..dd93fbcc --- /dev/null +++ b/docs/knit/example/example-polymorphic-sealed-class-02.kt @@ -0,0 +1,38 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.examplePolymorphicSealedClass02 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +sealed class Dog { + abstract val name: String + + @Serializable + class Mutt(override val name: String, val loveable: Boolean = true) : Dog() + + @Serializable + sealed class Retriever : Dog() { + abstract val colour: String + + @Serializable + data class Golden( + override val name: String, + override val colour: String, + val cute: Boolean = true, + ) : Retriever() + + @Serializable + data class NovaScotia( + override val name: String, + override val colour: String, + val adorable: Boolean = true, + ) : Retriever() + } +} + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Dog.serializer())) +} diff --git a/docs/knit/example/example-polymorphic-static-types-01.kt b/docs/knit/example/example-polymorphic-static-types-01.kt index 3c29a727..bf9c10c5 100644 --- a/docs/knit/example/example-polymorphic-static-types-01.kt +++ b/docs/knit/example/example-polymorphic-static-types-01.kt @@ -12,5 +12,5 @@ class OwnedProject(name: String, val owner: String) : Project(name) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Project.serializer().descriptor)) + println(tsGenerator.generate(Project.serializer())) } diff --git a/docs/knit/example/example-polymorphic-static-types-02.kt b/docs/knit/example/example-polymorphic-static-types-02.kt index 3384c656..b0966a1d 100644 --- a/docs/knit/example/example-polymorphic-static-types-02.kt +++ b/docs/knit/example/example-polymorphic-static-types-02.kt @@ -5,6 +5,8 @@ package dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes02 import kotlinx.serialization.* import dev.adamko.kxstsgen.* +import kotlinx.serialization.modules.* + @Serializable abstract class Project { abstract val name: String @@ -13,7 +15,13 @@ abstract class Project { @Serializable class OwnedProject(override val name: String, val owner: String) : Project() +val module = SerializersModule { + polymorphic(Project::class) { + subclass(OwnedProject::class) + } +} + fun main() { - val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(OwnedProject.serializer().descriptor)) + val tsGenerator = KxsTsGenerator(serializersModule = module) + println(tsGenerator.generate(OwnedProject.serializer())) } diff --git a/docs/knit/example/example-value-classes-01.kt b/docs/knit/example/example-value-classes-01.kt index 25860ac7..d4c21bae 100644 --- a/docs/knit/example/example-value-classes-01.kt +++ b/docs/knit/example/example-value-classes-01.kt @@ -11,5 +11,5 @@ value class AuthToken(private val token: String) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(AuthToken.serializer().descriptor)) + println(tsGenerator.generate(AuthToken.serializer())) } diff --git a/docs/knit/example/example-value-classes-02.kt b/docs/knit/example/example-value-classes-02.kt index 77c906b5..44a052b6 100644 --- a/docs/knit/example/example-value-classes-02.kt +++ b/docs/knit/example/example-value-classes-02.kt @@ -11,10 +11,10 @@ fun main() { val tsGenerator = KxsTsGenerator() println( tsGenerator.generate( - UByte.serializer().descriptor, - UShort.serializer().descriptor, - UInt.serializer().descriptor, - ULong.serializer().descriptor, + UByte.serializer(), + UShort.serializer(), + UInt.serializer(), + ULong.serializer(), ) ) } diff --git a/docs/knit/example/example-value-classes-03.kt b/docs/knit/example/example-value-classes-03.kt index a1e11b09..90b33c8d 100644 --- a/docs/knit/example/example-value-classes-03.kt +++ b/docs/knit/example/example-value-classes-03.kt @@ -16,7 +16,7 @@ fun main() { val tsGenerator = KxsTsGenerator(config = tsConfig) println( tsGenerator.generate( - ULong.serializer().descriptor, + ULong.serializer(), ) ) } diff --git a/docs/knit/example/example-value-classes-04.kt b/docs/knit/example/example-value-classes-04.kt index 2645c3cc..6793a720 100644 --- a/docs/knit/example/example-value-classes-04.kt +++ b/docs/knit/example/example-value-classes-04.kt @@ -11,5 +11,5 @@ value class UserCount(private val count: UInt) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(UserCount.serializer().descriptor)) + println(tsGenerator.generate(UserCount.serializer())) } diff --git a/docs/knit/test/EdgeCasesTest.kt b/docs/knit/test/EdgeCasesTest.kt new file mode 100644 index 00000000..f34b293f --- /dev/null +++ b/docs/knit/test/EdgeCasesTest.kt @@ -0,0 +1,66 @@ +// This file was automatically generated from edgecases.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 + +class EdgeCasesTest { + @Test + fun testExampleEdgecaseRecursiveReferences01() { + captureOutput("ExampleEdgecaseRecursiveReferences01") { + dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences01.main() + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface A { + | b: B; + |} + | + |interface B { + | a: A; + |} + """.trimMargin() + ) + } + + @Test + fun testExampleEdgecaseRecursiveReferences02() { + captureOutput("ExampleEdgecaseRecursiveReferences02") { + dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences02.main() + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface A { + | list: B[]; + |} + | + |interface B { + | list: A[]; + |} + """.trimMargin() + ) + } + + @Test + fun testExampleEdgecaseRecursiveReferences03() { + captureOutput("ExampleEdgecaseRecursiveReferences03") { + dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences03.main() + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |interface A { + | map: { [key: string]: B }; + |} + | + |interface B { + | map: { [key: string]: A }; + |} + """.trimMargin() + ) + } +} diff --git a/docs/knit/test/ListsTests.kt b/docs/knit/test/ListsTests.kt index 7975c23f..208f7afb 100644 --- a/docs/knit/test/ListsTests.kt +++ b/docs/knit/test/ListsTests.kt @@ -15,8 +15,10 @@ class ListsTests { .shouldBe( // language=TypeScript """ - |interface CalendarEvent { - | attendeeNames: string[]; + |interface MyLists { + | strings: string[]; + | ints: number[]; + | longs: number[]; |} """.trimMargin() ) diff --git a/docs/knit/test/MapsTests.kt b/docs/knit/test/MapsTests.kt index ebe3eef4..adf6bda6 100644 --- a/docs/knit/test/MapsTests.kt +++ b/docs/knit/test/MapsTests.kt @@ -30,14 +30,14 @@ class MapsTests { .shouldBe( // language=TypeScript """ + |interface Application { + | settings: { [key in SettingKeys]: string }; + |} + | |export enum SettingKeys { | SCREEN_SIZE = "SCREEN_SIZE", | MAX_MEMORY = "MAX_MEMORY", |} - | - |interface Application { - | settings: { [key in SettingKeys]: string }; - |} """.trimMargin() ) } @@ -65,7 +65,9 @@ class MapsTests { .shouldBe( // language=TypeScript """ - |type UByte = number; + |interface CanvasProperties { + | colourNames: Map; + |} | |interface Colour { | r: UByte; @@ -74,9 +76,7 @@ class MapsTests { | a: UByte; |} | - |interface CanvasProperties { - | colourNames: Map; - |} + |type UByte = number; """.trimMargin() ) } diff --git a/docs/knit/test/PolymorphismTest.kt b/docs/knit/test/PolymorphismTest.kt index 8e647c2c..26ba68ce 100644 --- a/docs/knit/test/PolymorphismTest.kt +++ b/docs/knit/test/PolymorphismTest.kt @@ -69,25 +69,76 @@ class PolymorphismTest { .shouldBe( // language=TypeScript """ - |enum ProjectKind { - | OwnedProject, - | DeprecatedProject, - |} + |export type Project = Project.Owned | Project.Deprecated | - |interface Project { - | type: ProjectKind; - |} + |export namespace Project { + | + | export enum Type { + | Owned = "owned-project", + | Deprecated = "DeprecatedProject", + | } + | + | export interface Owned { + | type: Type.Owned; + | name: string; + | owner: string; + | } + | + | export interface Deprecated { + | type: Type.Deprecated; + | name: string; + | reason: string; + | } | - |interface OwnedProject extends Project { - | type: ProjectKind.OwnedProject; - | name: string; - | owner: string; |} + """.trimMargin() + ) + } + + @Test + fun testExamplePolymorphicSealedClass02() { + captureOutput("ExamplePolymorphicSealedClass02") { + dev.adamko.kxstsgen.example.examplePolymorphicSealedClass02.main() + }.joinToString("\n") + .shouldBe( + // language=TypeScript + """ + |export type Dog = Dog.Mutt | Dog.Retriever + | + |export namespace Dog { + | + | export enum Type { + | Mutt = "Mutt", + | } + | + | export interface Mutt { + | type: Type.Mutt; + | name: string; + | loveable?: boolean; + | } + | + | export type Retriever = Retriever.Golden | Retriever.NovaScotia + | + | export namespace Retriever { + | + | export enum Type { + | Golden = "Golden", + | NovaScotia = "NovaScotia", + | } + | + | export interface Golden { + | type: Type.Golden; + | name: string; + | cute?: boolean; + | } + | + | export interface NovaScotia { + | type: Type.NovaScotia; + | name: string; + | adorable?: boolean; + | } + | } | - |interface DeprecatedProject extends Project { - | type: ProjectKind.DeprecatedProject; - | name: string; - | reason: string; |} """.trimMargin() ) @@ -106,15 +157,13 @@ class PolymorphismTest { | TextResponse = "TextResponse", |} | - |interface Response { - | type: ResponseKind; - |} + |type Response = EmptyResponse | TextResponse | - |interface EmptyResponse extends Response { + |interface EmptyResponse { | type: ResponseKind.EmptyResponse; |} | - |interface TextResponse extends Response { + |interface TextResponse { | type: ResponseKind.TextResponse; | text: string; |} diff --git a/docs/knit/test/ValueClassesTest.kt b/docs/knit/test/ValueClassesTest.kt index d8fa63f4..bed0c591 100644 --- a/docs/knit/test/ValueClassesTest.kt +++ b/docs/knit/test/ValueClassesTest.kt @@ -60,9 +60,9 @@ class ValueClassesTest { .shouldBe( // language=TypeScript """ - |type UInt = number; - | |type UserCount = UInt; + | + |type UInt = number; """.trimMargin() ) } diff --git a/docs/lists.md b/docs/lists.md index 32d0a1b3..aff255c0 100644 --- a/docs/lists.md +++ b/docs/lists.md @@ -17,26 +17,29 @@ import dev.adamko.kxstsgen.* ## Introduction - ### Primitive lists ```kotlin @Serializable -data class CalendarEvent( - val attendeeNames: List +data class MyLists( + val strings: List, + val ints: List, + val longs: List, ) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(CalendarEvent.serializer().descriptor)) + println(tsGenerator.generate(MyLists.serializer())) } ``` > You can get the full code [here](./knit/example/example-list-primitive-01.kt). ```typescript -interface CalendarEvent { - attendeeNames: string[]; +interface MyLists { + strings: string[]; + ints: number[]; + longs: number[]; } ``` diff --git a/docs/maps.md b/docs/maps.md index ec9dadb6..d90f27ec 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -20,7 +20,6 @@ import dev.adamko.kxstsgen.* ## Introduction - ### Primitive maps ```kotlin @@ -31,7 +30,7 @@ data class Config( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Config.serializer().descriptor)) + println(tsGenerator.generate(Config.serializer())) } ``` @@ -48,34 +47,34 @@ interface Config { ### Enum keys ```kotlin +@Serializable +class Application( + val settings: Map +) + @Serializable enum class SettingKeys { SCREEN_SIZE, MAX_MEMORY, } -@Serializable -class Application( - val settings: Map -) - fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Application.serializer().descriptor)) + println(tsGenerator.generate(Application.serializer())) } ``` > You can get the full code [here](./knit/example/example-map-primitive-02.kt). ```typescript +interface Application { + settings: { [key in SettingKeys]: string }; +} + export enum SettingKeys { SCREEN_SIZE = "SCREEN_SIZE", MAX_MEMORY = "MAX_MEMORY", } - -interface Application { - settings: { [key in SettingKeys]: string }; -} ``` @@ -90,7 +89,7 @@ data class Config( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Config.serializer().descriptor)) + println(tsGenerator.generate(Config.serializer())) } ``` @@ -122,14 +121,16 @@ data class CanvasProperties( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(CanvasProperties.serializer().descriptor)) + println(tsGenerator.generate(CanvasProperties.serializer())) } ``` > You can get the full code [here](./knit/example/example-map-complex-01.kt). ```typescript -type UByte = number; +interface CanvasProperties { + colourNames: Map; +} interface Colour { r: UByte; @@ -138,9 +139,7 @@ interface Colour { a: UByte; } -interface CanvasProperties { - colourNames: Map; -} +type UByte = number; ``` diff --git a/docs/polymorphism.md b/docs/polymorphism.md index 17e41e9b..397da54e 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -9,7 +9,10 @@ * [Closed Polymorphism](#closed-polymorphism) * [Static types](#static-types) * [Sealed classes](#sealed-classes) + * [Nested sealed classes](#nested-sealed-classes) * [Objects](#objects) +* [Open Polymorphism](#open-polymorphism) + * [Generics](#generics) @@ -35,7 +38,7 @@ abstract class SimpleTypes( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SimpleTypes.serializer().descriptor)) + println(tsGenerator.generate(SimpleTypes.serializer())) } ``` @@ -67,7 +70,7 @@ class OwnedProject(name: String, val owner: String) : Project(name) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Project.serializer().descriptor)) + println(tsGenerator.generate(Project.serializer())) } ``` @@ -84,6 +87,8 @@ interface Project { ```kotlin +import kotlinx.serialization.modules.* + @Serializable abstract class Project { abstract val name: String @@ -92,9 +97,15 @@ abstract class Project { @Serializable class OwnedProject(override val name: String, val owner: String) : Project() +val module = SerializersModule { + polymorphic(Project::class) { + subclass(OwnedProject::class) + } +} + fun main() { - val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(OwnedProject.serializer().descriptor)) + val tsGenerator = KxsTsGenerator(serializersModule = module) + println(tsGenerator.generate(OwnedProject.serializer())) } ``` @@ -115,6 +126,8 @@ interface OwnedProject extends Project { ### Sealed classes +https://www.typescriptlang.org/docs/handbook/enums.html#union-enums-and-enum-member-types + ```kotlin @Serializable sealed class Project { @@ -122,6 +135,7 @@ sealed class Project { } @Serializable +@SerialName("owned-project") class OwnedProject(override val name: String, val owner: String) : Project() @Serializable @@ -129,32 +143,116 @@ class DeprecatedProject(override val name: String, val reason: String) : Project fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(OwnedProject.serializer().descriptor)) + println(tsGenerator.generate(OwnedProject.serializer())) } ``` > You can get the full code [here](./knit/example/example-polymorphic-sealed-class-01.kt). ```typescript -enum ProjectKind { - OwnedProject, - DeprecatedProject, +export type Project = Project.Owned | Project.Deprecated + +export namespace Project { + + export enum Type { + Owned = "owned-project", + Deprecated = "DeprecatedProject", + } + + export interface Owned { + type: Type.Owned; + name: string; + owner: string; + } + + export interface Deprecated { + type: Type.Deprecated; + name: string; + reason: string; + } + } +``` -interface Project { - type: ProjectKind; + + +### Nested sealed classes + +[Union enums and enum member types](https://www.typescriptlang.org/docs/handbook/enums.html#union-enums-and-enum-member-types) + +```kotlin +@Serializable +sealed class Dog { + abstract val name: String + + @Serializable + class Mutt(override val name: String, val loveable: Boolean = true) : Dog() + + @Serializable + sealed class Retriever : Dog() { + abstract val colour: String + + @Serializable + data class Golden( + override val name: String, + override val colour: String, + val cute: Boolean = true, + ) : Retriever() + + @Serializable + data class NovaScotia( + override val name: String, + override val colour: String, + val adorable: Boolean = true, + ) : Retriever() + } } -interface OwnedProject extends Project { - type: ProjectKind.OwnedProject; - name: string; - owner: string; +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Dog.serializer())) } +``` + +> You can get the full code [here](./knit/example/example-polymorphic-sealed-class-02.kt). + +```typescript +export type Dog = Dog.Mutt | Dog.Retriever + +export namespace Dog { + + export enum Type { + Mutt = "Mutt", + } + + export interface Mutt { + type: Type.Mutt; + name: string; + loveable?: boolean; + } + + export type Retriever = Retriever.Golden | Retriever.NovaScotia + + export namespace Retriever { + + export enum Type { + Golden = "Golden", + NovaScotia = "NovaScotia", + } + + export interface Golden { + type: Type.Golden; + name: string; + cute?: boolean; + } + + export interface NovaScotia { + type: Type.NovaScotia; + name: string; + adorable?: boolean; + } + } -interface DeprecatedProject extends Project { - type: ProjectKind.DeprecatedProject; - name: string; - reason: string; } ``` @@ -176,8 +274,8 @@ fun main() { val tsGenerator = KxsTsGenerator() println( tsGenerator.generate( - EmptyResponse.serializer().descriptor, - TextResponse.serializer().descriptor, + EmptyResponse.serializer(), + TextResponse.serializer(), ) ) } @@ -191,18 +289,51 @@ export enum ResponseKind { TextResponse = "TextResponse", } -interface Response { - type: ResponseKind; -} +type Response = EmptyResponse | TextResponse -interface EmptyResponse extends Response { +interface EmptyResponse { type: ResponseKind.EmptyResponse; } -interface TextResponse extends Response { +interface TextResponse { type: ResponseKind.TextResponse; text: string; } ``` + +## Open Polymorphism + +### Generics + + + +```kotlin +@Serializable +class Box( + val value: T, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + + println( + tsGenerator.generate( + Box.serializer(Double.serializer()), + ) + ) +} +``` + +> You can get the full code [here](./knit/example/example-generics-01.kt). + +```typescript +type Double = number & { __kotlin_Double__: void } + +interface Box { + value: Double +} +``` diff --git a/docs/value-classes.md b/docs/value-classes.md index 6ab4053a..91cf0205 100644 --- a/docs/value-classes.md +++ b/docs/value-classes.md @@ -32,7 +32,7 @@ value class AuthToken(private val token: String) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(AuthToken.serializer().descriptor)) + println(tsGenerator.generate(AuthToken.serializer())) } ``` @@ -55,10 +55,10 @@ fun main() { val tsGenerator = KxsTsGenerator() println( tsGenerator.generate( - UByte.serializer().descriptor, - UShort.serializer().descriptor, - UInt.serializer().descriptor, - ULong.serializer().descriptor, + UByte.serializer(), + UShort.serializer(), + UInt.serializer(), + ULong.serializer(), ) ) } @@ -102,7 +102,7 @@ fun main() { val tsGenerator = KxsTsGenerator(config = tsConfig) println( tsGenerator.generate( - ULong.serializer().descriptor, + ULong.serializer(), ) ) } @@ -131,16 +131,16 @@ value class UserCount(private val count: UInt) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(UserCount.serializer().descriptor)) + println(tsGenerator.generate(UserCount.serializer())) } ``` > You can get the full code [here](./knit/example/example-value-classes-04.kt). ```typescript -type UInt = number; - type UserCount = UInt; + +type UInt = number; ``` From 71bde73f6974681ef7567ee3301f8d0b5b3e45d1 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Fri, 1 Apr 2022 10:14:46 +0200 Subject: [PATCH 16/46] major refactor to get things more like a library, and steps towards handing open/closed polymorphism --- docs/abstract-classes.md | 6 +- .../example-polymorphic-static-types-02.kt | 2 +- docs/polymorphism.md | 2 +- .../kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt | 54 ++ .../KxsTsConvertorContext.kt | 54 ++ .../dev.adamko.kxstsgen/KxsTsGenerator.kt | 627 +++++++++--------- .../kotlin/dev.adamko.kxstsgen/KxsTsModule.kt | 66 -- .../dev.adamko.kxstsgen/KxsTsProcessor.kt | 61 ++ .../KxsTsSourceCodeGenerator.kt | 246 +++---- .../dev.adamko.kxstsgen/TsElementConverter.kt | 122 ++++ .../TsElementIdConverter.kt | 42 ++ .../dev.adamko.kxstsgen/TsMapTypeConverter.kt | 41 ++ .../dev.adamko.kxstsgen/TsTypeRefConverter.kt | 66 ++ .../dev.adamko.kxstsgen/processSerializers.kt | 164 +++++ .../serializerExtractors.kt | 5 +- .../kotlin/dev.adamko.kxstsgen/tsElements.kt | 69 +- .../util/mapWithDefaultDelegate.kt | 38 ++ 17 files changed, 1091 insertions(+), 574 deletions(-) create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt delete mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsModule.kt create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsMapTypeConverter.kt create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/util/mapWithDefaultDelegate.kt diff --git a/docs/abstract-classes.md b/docs/abstract-classes.md index 3546d2ad..ff76aa1b 100644 --- a/docs/abstract-classes.md +++ b/docs/abstract-classes.md @@ -27,7 +27,7 @@ abstract class Color(val rgb: Int) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Color.serializer().descriptor)) + println(tsGenerator.generate(Color.serializer())) } ``` @@ -55,7 +55,7 @@ abstract class SimpleTypes( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(SimpleTypes.serializer().descriptor)) + println(tsGenerator.generate(SimpleTypes.serializer())) } ``` @@ -86,7 +86,7 @@ abstract class AbstractSimpleTypes { fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(AbstractSimpleTypes.serializer().descriptor)) + println(tsGenerator.generate(AbstractSimpleTypes.serializer())) } ``` diff --git a/docs/knit/example/example-polymorphic-static-types-02.kt b/docs/knit/example/example-polymorphic-static-types-02.kt index b0966a1d..06066533 100644 --- a/docs/knit/example/example-polymorphic-static-types-02.kt +++ b/docs/knit/example/example-polymorphic-static-types-02.kt @@ -23,5 +23,5 @@ val module = SerializersModule { fun main() { val tsGenerator = KxsTsGenerator(serializersModule = module) - println(tsGenerator.generate(OwnedProject.serializer())) + println(tsGenerator.generate(Project.serializer())) } diff --git a/docs/polymorphism.md b/docs/polymorphism.md index 397da54e..b827e720 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -105,7 +105,7 @@ val module = SerializersModule { fun main() { val tsGenerator = KxsTsGenerator(serializersModule = module) - println(tsGenerator.generate(OwnedProject.serializer())) + println(tsGenerator.generate(Project.serializer())) } ``` 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 9624d0a2..599dd5ce 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,15 +1,23 @@ package dev.adamko.kxstsgen +import dev.adamko.kxstsgen.util.MutableMapWithDefaultPut import kotlin.jvm.JvmInline import kotlin.reflect.KClass +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.ExperimentalSerializationApi import kotlinx.serialization.KSerializer +import kotlinx.serialization.SerializationStrategy import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule +import kotlinx.serialization.modules.SerializersModuleCollector data class KxsTsConfig( val indent: String = " ", val structureSeparator: String = "\n\n", val namespaceConfig: NamespaceConfig = NamespaceConfig.Disabled, val typeAliasTyping: TypeAliasTypingConfig = TypeAliasTypingConfig.None, + val serializersModule: SerializersModule = EmptySerializersModule, ) { sealed interface NamespaceConfig { @@ -26,6 +34,52 @@ data class KxsTsConfig( object BrandTyping : TypeAliasTypingConfig } + private val contextualClasses: MutableSet> = mutableSetOf() + + private val _polymorphicDescriptors + by MutableMapWithDefaultPut, MutableSet> { mutableSetOf() } + + val polymorphicDescriptors: Map, Set> + get() = _polymorphicDescriptors.mapValues { it.value.toSet() }.toMap().withDefault { setOf() } + + init { + serializersModule.dumpTo(Collector()) + } + + + /** Collects the contents of a [SerializersModule], so kxs-ts-gen can view registered classes. */ + private inner class Collector : SerializersModuleCollector { + + override fun contextual( + kClass: KClass, + provider: (typeArgumentsSerializers: List>) -> KSerializer<*> + ) { + contextualClasses + kClass + } + + override fun polymorphic( + baseClass: KClass, + actualClass: KClass, + actualSerializer: KSerializer, + ) { + _polymorphicDescriptors.getValue(baseClass).add(actualSerializer.descriptor) + } + + @ExperimentalSerializationApi + override fun polymorphicDefaultDeserializer( + baseClass: KClass, + defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? + ) { + } + + @ExperimentalSerializationApi + override fun polymorphicDefaultSerializer( + baseClass: KClass, + defaultSerializerProvider: (value: Base) -> SerializationStrategy? + ) { + } + + } } // diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt new file mode 100644 index 00000000..5a67cf63 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt @@ -0,0 +1,54 @@ +package dev.adamko.kxstsgen + +import dev.adamko.kxstsgen.util.MutableMapWithDefaultPut +import kotlinx.serialization.descriptors.SerialDescriptor + +interface KxsTsConvertorContext { +// val config: KxsTsConfig + + fun elementId(descriptor: SerialDescriptor): TsElementId + + fun typeRef(descriptor: SerialDescriptor): TsTypeRef + + fun mapType(descriptor: SerialDescriptor): TsLiteral.TsMap.Type + + fun element(descriptor: SerialDescriptor): TsElement + + class Default( + elementIds: Map = mapOf(), + typeRefs: Map = mapOf(), + mapTypes: Map = mapOf(), + convertedElements: Map = mapOf(), + private val elementIdConverter: TsElementIdConverter = TsElementIdConverter.Default, + private val elementConverter: TsElementConverter = TsElementConverter.Default, + private val typeRefConverter: TsTypeRefConverter = TsTypeRefConverter.Default, + private val mapTypeConverter: TsMapTypeConverter = TsMapTypeConverter.Default, + ) : KxsTsConvertorContext { + + private val elementIds: MutableMap + by MutableMapWithDefaultPut(elementIds) { elementIdConverter(it) } + + private val typeRefs: MutableMap + by MutableMapWithDefaultPut(typeRefs) { typeRefConverter(this, it) } + + private val mapTypes: MutableMap + by MutableMapWithDefaultPut(mapTypes) { mapTypeConverter(it) } + + private val convertedElements: MutableMap + by MutableMapWithDefaultPut(convertedElements) { elementConverter(this, it) } + + override fun elementId(descriptor: SerialDescriptor): TsElementId = + elementIds.getValue(descriptor) + + override fun typeRef(descriptor: SerialDescriptor): TsTypeRef = + typeRefs.getValue(descriptor) + + override fun mapType(descriptor: SerialDescriptor): TsLiteral.TsMap.Type = + mapTypes.getValue(descriptor) + + override fun element(descriptor: SerialDescriptor): TsElement = + convertedElements.getValue(descriptor) + + } + +} 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 fcb96e3c..9879e890 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 @@ -2,346 +2,329 @@ package dev.adamko.kxstsgen -import kotlinx.serialization.ContextualSerializer import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer -import kotlinx.serialization.PolymorphicSerializer -import kotlinx.serialization.SealedClassSerializer -import kotlinx.serialization.descriptors.PolymorphicKind -import kotlinx.serialization.descriptors.PrimitiveKind -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.SerialKind -import kotlinx.serialization.descriptors.StructureKind -import kotlinx.serialization.descriptors.elementDescriptors -import kotlinx.serialization.descriptors.elementNames -import kotlinx.serialization.descriptors.getPolymorphicDescriptors import kotlinx.serialization.modules.SerializersModule class KxsTsGenerator( private val config: KxsTsConfig = KxsTsConfig(), - private val kxsTsModule: KxsTsModule = KxsTsModule(), ) { constructor( - config: KxsTsConfig = KxsTsConfig(), serializersModule: SerializersModule, - ) : this(config, KxsTsModule(serializersModule)) + ) : this(KxsTsConfig(serializersModule = serializersModule)) -// private val codeGenerator: KxsTsSourceCodeGenerator = KxsTsSourceCodeGenerator(config) - -// private val descriptorSerializers: MutableMap> = mutableMapOf() fun generate(vararg serializers: KSerializer<*>): String { - val converter = TsConverter(kxsTsModule) - - serializers.forEach { serializer -> converter(serializer) } - - val codeGenerator = KxsTsSourceCodeGenerator(config, converter.convertedElements) - - return codeGenerator.joinElementsToString() - } - -} - - -class TsConverter( - private val kxsTsModule: KxsTsModule, - elementsState: Map = emptyMap(), -) { - private val elementsState: MutableMap = elementsState.toMutableMap() - - private val descriptorExtractor = DescriptorExtractor(kxsTsModule) - - val convertedElements: Set - get() = elementsState.values.toSet() - - operator fun invoke(serializer: KSerializer<*>) { - val descriptors: Map = descriptorExtractor(serializer) - descriptors.keys.forEach { descriptor -> convertToTsElement(descriptor, descriptors) } - } - - private fun convertToTsElement( - descriptor: SerialDescriptor, - typeRefs: Map, - ): TsElement { - return elementsState.getOrPut(descriptor) { - - when (descriptor.kind) { - SerialKind.ENUM -> convertEnum(descriptor) - SerialKind.CONTEXTUAL -> { - // TODO contextual - TsLiteral.Primitive.TsAny - } - - PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean - - PrimitiveKind.CHAR, - PrimitiveKind.STRING -> TsLiteral.Primitive.TsString - - PrimitiveKind.BYTE, - PrimitiveKind.SHORT, - PrimitiveKind.INT, - PrimitiveKind.LONG, - PrimitiveKind.FLOAT, - PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber - - StructureKind.LIST -> convertList(descriptor, typeRefs) - StructureKind.MAP -> convertMap(descriptor, typeRefs) - - StructureKind.CLASS, - StructureKind.OBJECT, - PolymorphicKind.SEALED, - PolymorphicKind.OPEN -> when { - descriptor.isInline -> convertTypeAlias(descriptor, typeRefs) - else -> convertInterface(descriptor, typeRefs) - } - } - - } - } - - - private fun convertTypeAlias( - structDescriptor: SerialDescriptor, - typeRefs: Map, - ): TsDeclaration { - val resultId = createElementId(structDescriptor) - val fieldDescriptor = structDescriptor.elementDescriptors.first() - val fieldTypeRef = typeRefs[fieldDescriptor] ?: TsTypeRef.Unknown - return TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) - - } - - - private fun convertInterface( - structDescriptor: SerialDescriptor, - typeRefs: Map, - ): TsDeclaration { - val resultId = createElementId(structDescriptor) - - val properties = structDescriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> - val name = structDescriptor.getElementName(index) - val fieldTypeRef = typeRefs[fieldDescriptor] ?: TsTypeRef.Unknown - when { - structDescriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeRef) - else -> TsProperty.Required(name, fieldTypeRef) - } - }.toSet() - return TsDeclaration.TsInterface(resultId, properties, TsPolymorphicDiscriminator.Open) - } - - - private fun convertEnum( - enumDescriptor: SerialDescriptor, - ): TsDeclaration.TsEnum { - val resultId = createElementId(enumDescriptor) - return TsDeclaration.TsEnum(resultId, enumDescriptor.elementNames.toSet()) - } - - - private fun convertList( - listDescriptor: SerialDescriptor, - typeRefs: Map, - ): TsLiteral.TsList { - val elementDescriptor = listDescriptor.elementDescriptors.first() - val elementTypeRef = typeRefs[elementDescriptor] ?: TsTypeRef.Unknown - return TsLiteral.TsList(elementTypeRef) - } - - - private fun convertMap( - mapDescriptor: SerialDescriptor, - typeRefs: Map, - ): TsLiteral.TsMap { + val processor = KxsTsProcessor(config) - val (keyDescriptor, valueDescriptor) = mapDescriptor.elementDescriptors.toList() + serializers.forEach { processor.addSerializer(it) } - val keyTypeRef = typeRefs[keyDescriptor] ?: TsTypeRef.Unknown - val valueTypeRef = typeRefs[valueDescriptor] ?: TsTypeRef.Unknown - - val type = convertMapType(keyDescriptor) - - return TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) + return processor.process() } - - } -class DescriptorExtractor( - private val kxsTsModule: KxsTsModule -) { - - operator fun invoke(serializer: KSerializer<*>): Map { - return sequence { - when (serializer) { - is PolymorphicSerializer<*> -> { - yieldAll(kxsTsModule.serializersModule.getPolymorphicDescriptors(serializer.descriptor)) - } - is SealedClassSerializer<*> -> - yield(serializer.descriptor) - is ContextualSerializer<*> -> - yield(extractContextualSerializer(serializer, kxsTsModule)?.descriptor) - else -> - yield(serializer.descriptor) - } - }.filterNotNull() - .flatMap { descriptor -> extractAll(descriptor) } - .distinct() - .associateWith { descriptor -> - createTypeRef(descriptor) - } - } - - private fun extractAll(descriptor: SerialDescriptor): Sequence { - return sequence { - val seen = mutableSetOf() - val deque = ArrayDeque() - deque.addLast(descriptor) - while (deque.isNotEmpty()) { - val next = deque.removeFirst() - - val nextElementDescriptors = extractElementDescriptors(next) - - nextElementDescriptors - .filter { it !in seen } - .forEach { deque.addLast(it) } - - seen.add(next) - yield(next) - } - }.distinct() - } - - - private fun extractElementDescriptors(serialDescriptor: SerialDescriptor): Iterable { - return when (serialDescriptor.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 -> serialDescriptor.elementDescriptors - - PolymorphicKind.SEALED, - PolymorphicKind.OPEN -> serialDescriptor - .elementDescriptors - .filter { it.kind is PolymorphicKind } - .flatMap { it.elementDescriptors } - } - } - - - private fun createTypeRef(descriptor: SerialDescriptor): TsTypeRef { - return when (descriptor.kind) { - is PrimitiveKind -> { - val tsPrimitive = when (descriptor.kind as PrimitiveKind) { - PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean - - PrimitiveKind.BYTE, - PrimitiveKind.SHORT, - PrimitiveKind.INT, - PrimitiveKind.LONG, - PrimitiveKind.FLOAT, - PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber - - PrimitiveKind.CHAR, - PrimitiveKind.STRING -> TsLiteral.Primitive.TsString - } - TsTypeRef.Literal(tsPrimitive, descriptor.isNullable) - } - - StructureKind.LIST -> { - val elementDescriptor = descriptor.elementDescriptors.first() - val elementTypeRef = createTypeRef(elementDescriptor) - val listRef = TsLiteral.TsList(elementTypeRef) - TsTypeRef.Literal(listRef, descriptor.isNullable) - } - StructureKind.MAP -> { - val (keyDescriptor, valueDescriptor) = descriptor.elementDescriptors.toList() - val keyTypeRef = createTypeRef(keyDescriptor) - val valueTypeRef = createTypeRef(valueDescriptor) - val type = convertMapType(keyDescriptor) - val map = TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) - TsTypeRef.Literal(map, descriptor.isNullable) - } - - SerialKind.CONTEXTUAL, - PolymorphicKind.SEALED, - PolymorphicKind.OPEN, - SerialKind.ENUM, - StructureKind.CLASS, - StructureKind.OBJECT -> { - val id = createElementId(descriptor) - TsTypeRef.Named(id, descriptor.isNullable) - } - } - } - -} - - -private fun createElementId(descriptor: SerialDescriptor): TsElementId { - - val targetId = TsElementId(descriptor.serialName.removeSuffix("?")) - - return when (descriptor.kind) { - PolymorphicKind.OPEN -> TsElementId( - targetId.namespace + "." + targetId.name.substringAfter("<").substringBeforeLast(">") - ) - PolymorphicKind.SEALED, - PrimitiveKind.BOOLEAN, - PrimitiveKind.BYTE, - PrimitiveKind.CHAR, - PrimitiveKind.DOUBLE, - PrimitiveKind.FLOAT, - PrimitiveKind.INT, - PrimitiveKind.LONG, - PrimitiveKind.SHORT, - PrimitiveKind.STRING, - SerialKind.CONTEXTUAL, - SerialKind.ENUM, - StructureKind.CLASS, - StructureKind.LIST, - StructureKind.MAP, - StructureKind.OBJECT -> targetId - } -} - -private fun convertMapType(keyDescriptor: SerialDescriptor): TsLiteral.TsMap.Type { - return when (keyDescriptor.kind) { - SerialKind.ENUM -> TsLiteral.TsMap.Type.MAPPED_OBJECT - - PrimitiveKind.STRING -> TsLiteral.TsMap.Type.INDEX_SIGNATURE - - SerialKind.CONTEXTUAL, - PrimitiveKind.BOOLEAN, - PrimitiveKind.BYTE, - PrimitiveKind.CHAR, - PrimitiveKind.SHORT, - PrimitiveKind.INT, - PrimitiveKind.LONG, - PrimitiveKind.FLOAT, - PrimitiveKind.DOUBLE, - StructureKind.CLASS, - StructureKind.LIST, - StructureKind.MAP, - StructureKind.OBJECT, - PolymorphicKind.SEALED, - PolymorphicKind.OPEN -> TsLiteral.TsMap.Type.MAP - } -} +//class TsConverter( +// private val kxsTsConfig: KxsTsConfig, +// elementsState: Map = emptyMap(), +//) { +// private val elementsState: MutableMap = elementsState.toMutableMap() +// +// private val descriptorExtractor = DescriptorExtractor(kxsTsConfig) +// +// val convertedElements: Set +// get() = elementsState.values.toSet() +// +// operator fun invoke(serializer: KSerializer<*>) { +// val descriptors: Map = descriptorExtractor(serializer) +// descriptors.keys.forEach { descriptor -> convertToTsElement(descriptor, descriptors) } +// } +// +// private fun convertToTsElement( +// descriptor: SerialDescriptor, +// typeRefs: Map, +// ): TsElement { +// return elementsState.getOrPut(descriptor) { +// +// when (descriptor.kind) { +// SerialKind.ENUM -> convertEnum(descriptor) +// SerialKind.CONTEXTUAL -> { +// // TODO contextual +// TsLiteral.Primitive.TsAny +// } +// +// PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean +// +// PrimitiveKind.CHAR, +// PrimitiveKind.STRING -> TsLiteral.Primitive.TsString +// +// PrimitiveKind.BYTE, +// PrimitiveKind.SHORT, +// PrimitiveKind.INT, +// PrimitiveKind.LONG, +// PrimitiveKind.FLOAT, +// PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber +// +// StructureKind.LIST -> convertList(descriptor, typeRefs) +// StructureKind.MAP -> convertMap(descriptor, typeRefs) +// +// StructureKind.CLASS, +// StructureKind.OBJECT, +// PolymorphicKind.SEALED, +// PolymorphicKind.OPEN -> when { +// descriptor.isInline -> convertTypeAlias(descriptor, typeRefs) +// else -> convertInterface(descriptor, typeRefs) +// } +// } +// } +// } +// +// +// private fun convertTypeAlias( +// structDescriptor: SerialDescriptor, +// typeRefs: Map, +// ): TsDeclaration { +// val resultId = createElementId(structDescriptor) +// val fieldDescriptor = structDescriptor.elementDescriptors.first() +// val fieldTypeRef = typeRefs[fieldDescriptor] ?: TsTypeRef.Unknown +// return TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) +// +// } +// +// +// private fun convertInterface( +// structDescriptor: SerialDescriptor, +// typeRefs: Map, +// ): TsDeclaration { +// val resultId = createElementId(structDescriptor) +// +// val properties = structDescriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> +// val name = structDescriptor.getElementName(index) +// val fieldTypeRef = typeRefs[fieldDescriptor] ?: TsTypeRef.Unknown +// when { +// structDescriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeRef) +// else -> TsProperty.Required(name, fieldTypeRef) +// } +// }.toSet() +// return TsDeclaration.TsInterface(resultId, properties, TsPolymorphicDiscriminator.Open) +// } +// +// +// private fun convertEnum( +// enumDescriptor: SerialDescriptor, +// ): TsDeclaration.TsEnum { +// val resultId = createElementId(enumDescriptor) +// return TsDeclaration.TsEnum(resultId, enumDescriptor.elementNames.toSet()) +// } +// +// +// private fun convertList( +// listDescriptor: SerialDescriptor, +// typeRefs: Map, +// ): TsLiteral.TsList { +// val elementDescriptor = listDescriptor.elementDescriptors.first() +// val elementTypeRef = typeRefs[elementDescriptor] ?: TsTypeRef.Unknown +// return TsLiteral.TsList(elementTypeRef) +// } +// +// +// private fun convertMap( +// mapDescriptor: SerialDescriptor, +// typeRefs: Map, +// ): TsLiteral.TsMap { +// +// val (keyDescriptor, valueDescriptor) = mapDescriptor.elementDescriptors.toList() +// +// val keyTypeRef = typeRefs[keyDescriptor] ?: TsTypeRef.Unknown +// val valueTypeRef = typeRefs[valueDescriptor] ?: TsTypeRef.Unknown +// +// val type = convertMapType(keyDescriptor) +// +// return TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) +// } +// +// +//} +// +// +//class DescriptorExtractor( +// private val kxsTsConfig: KxsTsConfig, +//) { +// +// operator fun invoke(serializer: KSerializer<*>): Map { +// return sequence { +// when (serializer) { +// is PolymorphicSerializer<*> -> { +// val x = kxsTsConfig.serializersModule.getPolymorphicDescriptors(serializer.descriptor) +// yieldAll(x) +// } +// is SealedClassSerializer<*> -> +// yield(serializer.descriptor) +// is ContextualSerializer<*> -> +// yield(extractContextualSerializer(serializer, kxsTsConfig)?.descriptor) +// else -> +// yield(serializer.descriptor) +// } +// }.filterNotNull() +// .flatMap { descriptor -> extractAll(descriptor) } +// .distinct() +// .associateWith { descriptor -> +// createTypeRef(descriptor) +// } +// } +// +// private fun extractAll(descriptor: SerialDescriptor): Sequence { +// return sequence { +// val seen = mutableSetOf() +// val deque = ArrayDeque() +// deque.addLast(descriptor) +// while (deque.isNotEmpty()) { +// val next = deque.removeFirst() +// +// val nextElementDescriptors = extractElementDescriptors(next) +// +// nextElementDescriptors +// .filter { it !in seen } +// .forEach { deque.addLast(it) } +// +// seen.add(next) +// yield(next) +// } +// }.distinct() +// } +// +// +// private fun extractElementDescriptors(serialDescriptor: SerialDescriptor): Iterable { +// return when (serialDescriptor.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 -> serialDescriptor.elementDescriptors +// +// PolymorphicKind.SEALED, +// PolymorphicKind.OPEN -> serialDescriptor +// .elementDescriptors +// .filter { it.kind is PolymorphicKind } +// .flatMap { it.elementDescriptors } +// } +// } +// +// +// private fun createTypeRef(descriptor: SerialDescriptor): TsTypeRef { +// return when (descriptor.kind) { +// is PrimitiveKind -> { +// val tsPrimitive = when (descriptor.kind as PrimitiveKind) { +// PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean +// +// PrimitiveKind.BYTE, +// PrimitiveKind.SHORT, +// PrimitiveKind.INT, +// PrimitiveKind.LONG, +// PrimitiveKind.FLOAT, +// PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber +// +// PrimitiveKind.CHAR, +// PrimitiveKind.STRING -> TsLiteral.Primitive.TsString +// } +// TsTypeRef.Literal(tsPrimitive, descriptor.isNullable) +// } +// +// StructureKind.LIST -> { +// val elementDescriptor = descriptor.elementDescriptors.first() +// val elementTypeRef = createTypeRef(elementDescriptor) +// val listRef = TsLiteral.TsList(elementTypeRef) +// TsTypeRef.Literal(listRef, descriptor.isNullable) +// } +// StructureKind.MAP -> { +// val (keyDescriptor, valueDescriptor) = descriptor.elementDescriptors.toList() +// val keyTypeRef = createTypeRef(keyDescriptor) +// val valueTypeRef = createTypeRef(valueDescriptor) +// val type = convertMapType(keyDescriptor) +// val map = TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) +// TsTypeRef.Literal(map, descriptor.isNullable) +// } +// +// SerialKind.CONTEXTUAL, +// PolymorphicKind.SEALED, +// PolymorphicKind.OPEN, +// SerialKind.ENUM, +// StructureKind.CLASS, +// StructureKind.OBJECT -> { +// val id = createElementId(descriptor) +// TsTypeRef.Named(id, descriptor.isNullable) +// } +// } +// } +// +//} +// +// +// +// +//private fun createElementId(descriptor: SerialDescriptor): TsElementId { +// +// val targetId = TsElementId(descriptor.serialName.removeSuffix("?")) +// +// return when (descriptor.kind) { +// PolymorphicKind.OPEN -> TsElementId( +// targetId.namespace + "." + targetId.name.substringAfter("<").substringBeforeLast(">") +// ) +// PolymorphicKind.SEALED, +// PrimitiveKind.BOOLEAN, +// PrimitiveKind.BYTE, +// PrimitiveKind.CHAR, +// PrimitiveKind.DOUBLE, +// PrimitiveKind.FLOAT, +// PrimitiveKind.INT, +// PrimitiveKind.LONG, +// PrimitiveKind.SHORT, +// PrimitiveKind.STRING, +// SerialKind.CONTEXTUAL, +// SerialKind.ENUM, +// StructureKind.CLASS, +// StructureKind.LIST, +// StructureKind.MAP, +// StructureKind.OBJECT -> targetId +// } +//} +// +//private fun convertMapType(keyDescriptor: SerialDescriptor): TsLiteral.TsMap.Type { +// return when (keyDescriptor.kind) { +// SerialKind.ENUM -> TsLiteral.TsMap.Type.MAPPED_OBJECT +// +// PrimitiveKind.STRING -> TsLiteral.TsMap.Type.INDEX_SIGNATURE +// +// SerialKind.CONTEXTUAL, +// PrimitiveKind.BOOLEAN, +// PrimitiveKind.BYTE, +// PrimitiveKind.CHAR, +// PrimitiveKind.SHORT, +// PrimitiveKind.INT, +// PrimitiveKind.LONG, +// PrimitiveKind.FLOAT, +// PrimitiveKind.DOUBLE, +// StructureKind.CLASS, +// StructureKind.LIST, +// StructureKind.MAP, +// StructureKind.OBJECT, +// PolymorphicKind.SEALED, +// PolymorphicKind.OPEN -> TsLiteral.TsMap.Type.MAP +// } +//} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsModule.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsModule.kt deleted file mode 100644 index bb66ebe1..00000000 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsModule.kt +++ /dev/null @@ -1,66 +0,0 @@ -package dev.adamko.kxstsgen - -import kotlin.reflect.KClass -import kotlinx.serialization.DeserializationStrategy -import kotlinx.serialization.ExperimentalSerializationApi -import kotlinx.serialization.KSerializer -import kotlinx.serialization.SerializationStrategy -import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.modules.EmptySerializersModule -import kotlinx.serialization.modules.SerializersModule -import kotlinx.serialization.modules.SerializersModuleCollector - - -/** Collects the contents of a [SerializersModule], so kxs-ts-gen can view registered classes. */ -class KxsTsModule( - val serializersModule: SerializersModule = EmptySerializersModule -) { - - private val contextualClasses: MutableSet> = mutableSetOf() - private val polymorphicClasses: MutableList, KClass<*>>> = mutableListOf() - - init { - serializersModule.dumpTo(Collector()) - } - - - data class Polymorphic( - val baseClass: KClass, - val actualClass: KClass, - val actualSerializer: KSerializer, - ) { - val actualDescriptor: SerialDescriptor by actualSerializer::descriptor - } - - private inner class Collector : SerializersModuleCollector { - - override fun contextual( - kClass: KClass, - provider: (typeArgumentsSerializers: List>) -> KSerializer<*> - ) { - contextualClasses + kClass - } - - override fun polymorphic( - baseClass: KClass, - actualClass: KClass, - actualSerializer: KSerializer, - ) { - } - - @ExperimentalSerializationApi - override fun polymorphicDefaultDeserializer( - baseClass: KClass, - defaultDeserializerProvider: (className: String?) -> DeserializationStrategy? - ) { - } - - @ExperimentalSerializationApi - override fun polymorphicDefaultSerializer( - baseClass: KClass, - defaultSerializerProvider: (value: Base) -> SerializationStrategy? - ) { - } - - } -} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt new file mode 100644 index 00000000..5a0ede01 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt @@ -0,0 +1,61 @@ +package dev.adamko.kxstsgen + + +import dev.adamko.kxstsgen.util.MutableMapWithDefaultPut +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.SerialDescriptor + + +class KxsTsProcessor( + val config: KxsTsConfig, + val context: KxsTsConvertorContext = KxsTsConvertorContext.Default(), + + val descriptorDataProcessor: DescriptorDataProcessor = + DescriptorDataProcessor.Default, + + val serializerDescriptorsExtractor: SerializerDescriptorsExtractor = + SerializerDescriptorsExtractor.Default, + + val sourceCodeGenerator: KxsTsSourceCodeGenerator = + KxsTsSourceCodeGenerator.Default(config, context), + + ) { + + private val descriptorData by MutableMapWithDefaultPut, DescriptorData> { serializer -> + descriptorDataProcessor(context, config, serializer) + } + + private val serializers = mutableSetOf>() + + fun addSerializer(serializer: KSerializer<*>) { + serializers += serializer + } + + fun process(): String { + val allDescriptorData = serializers.associate { serializer -> + serializer.descriptor to descriptorData.getValue(serializer) + } + + val allSerialDescriptors: Set = + serializerDescriptorsExtractor(allDescriptorData.values) + + val allTsElements: List = + allSerialDescriptors.map { descriptor -> + TsElementConverter.Default(context, descriptor) + } + + return allTsElements + .groupBy { element -> sourceCodeGenerator.groupElementsBy(element) } + .mapValues { (_, elements) -> + elements + .filterIsInstance() + .map { element -> sourceCodeGenerator.generateDeclaration(element) } + .filter { it.isNotBlank() } + .joinToString(config.structureSeparator) + } + .values// TODO create namespaces + .joinToString(config.structureSeparator) + } + + +} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index 8a0abc6e..c6ef980d 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -4,94 +4,106 @@ package dev.adamko.kxstsgen /** * Writes [TsElement]s as TypeScript source code. */ -class KxsTsSourceCodeGenerator( - private val config: KxsTsConfig, - private val elements: Set, +abstract class KxsTsSourceCodeGenerator( + val config: KxsTsConfig, + val context: KxsTsConvertorContext, ) { - private val indent by config::indent + abstract fun groupElementsBy(element: TsElement): String? -// private val mapIdToElement: MutableMap = -// mutableMapOf().withDefault { req -> -// elements.first { it.id == req } -// } + open fun generateDeclaration(element: TsDeclaration): String { + return when (element) { + is TsDeclaration.TsEnum -> generateEnum(element) + is TsDeclaration.TsInterface -> generateInterface(element) + is TsDeclaration.TsNamespace -> generateNamespace(element) + is TsDeclaration.TsTypeAlias -> generateTypeAlias(element) + } + } - private val typeReferenceState: MutableMap = mutableMapOf() + abstract fun generateEnum(enum: TsDeclaration.TsEnum): String + abstract fun generateInterface(element: TsDeclaration.TsInterface): String + abstract fun generateNamespace(namespace: TsDeclaration.TsNamespace): String + abstract fun generateTypeAlias(element: TsDeclaration.TsTypeAlias): String + + abstract fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String + abstract fun generatePrimitive(primitive: TsLiteral.Primitive): String + abstract fun generateTypeReference(typeRef: TsTypeRef): String + + open class Default( + config: KxsTsConfig, + context: KxsTsConvertorContext, + ) : KxsTsSourceCodeGenerator(config, context) { + + + override fun groupElementsBy(element: TsElement): String { + return when (config.namespaceConfig) { + is KxsTsConfig.NamespaceConfig.Static -> config.namespaceConfig.namespace + KxsTsConfig.NamespaceConfig.Disabled -> "" + KxsTsConfig.NamespaceConfig.DescriptorNamePrefix -> + when (element) { + is TsLiteral -> "" + is TsDeclaration -> element.id.namespace + } + } + } - fun joinElementsToString(): String { - return elements - .groupBy { - when (config.namespaceConfig) { - is KxsTsConfig.NamespaceConfig.Static -> config.namespaceConfig.namespace - KxsTsConfig.NamespaceConfig.Disabled -> "" - KxsTsConfig.NamespaceConfig.DescriptorNamePrefix -> "it.id.namespace" - } - } - .mapValues { (_, elements) -> - elements - .filterIsInstance() + override fun generateNamespace(namespace: TsDeclaration.TsNamespace): String { + val namespaceContent = + namespace + .members .joinToString(config.structureSeparator) { declaration -> - when (declaration) { - is TsDeclaration.TsEnum -> generateEnum(declaration) - is TsDeclaration.TsInterface -> generateInterface(declaration) - is TsDeclaration.TsTypeAlias -> generateTypeAlias(declaration) - } + generateDeclaration(declaration) } - }.entries - .filter { it.value.isNotBlank() } - .joinToString(config.structureSeparator) { (namespace, namespaceContent) -> - if (namespace.isBlank()) { - namespaceContent - } else { - """ - |export namespace $namespace { - |${namespaceContent.prependIndent(config.indent)} - |} - """.trimMargin() + return buildString { + appendLine("export namespace ${namespace.id.name} {") + if (namespaceContent.isNotBlank()) { + appendLine(namespaceContent.prependIndent(config.indent)) } + append("}") } - } + } + - private fun generateEnum(enum: TsDeclaration.TsEnum): String { + override fun generateEnum(enum: TsDeclaration.TsEnum): String { - val enumMembers = enum.members.joinToString("\n") { member -> - """ - |${indent}$member = "$member", + val enumMembers = enum.members.joinToString("\n") { member -> + """ + |${config.indent}$member = "$member", + """.trimMargin() + } + + return """ + |export enum ${enum.id.name} { + |${enumMembers} + |} """.trimMargin() } - return """ - |export enum ${enum.id.name} { - |${enumMembers} - |} - """.trimMargin() - } - - private fun generateInterface(element: TsDeclaration.TsInterface): String { + override fun generateInterface(element: TsDeclaration.TsInterface): String { - val properties = element - .properties - .joinToString(separator = "\n") { property -> - val separator = when (property) { - is TsProperty.Optional -> "?: " - is TsProperty.Required -> ": " + 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};" } - val propertyType = generateTypeReference(property.typeRef) - // generate ` name: Type;` - // or ` name:? Type;` - "${indent}${property.name}${separator}${propertyType};" - } - return buildString { - appendLine("interface ${element.id.name} {") - if (properties.isNotBlank()) { - appendLine(properties) + return buildString { + appendLine("interface ${element.id.name} {") + if (properties.isNotBlank()) { + appendLine(properties.prependIndent(config.indent)) + } + append("}") } - append("}") } - } // private fun generateSealedSubInterfaces(sealed: TsPolymorphicDiscriminator.Sealed): String { // @@ -100,42 +112,41 @@ class KxsTsSourceCodeGenerator( // val sealedType = sealed.children.map { it } // } - private fun generateTypeAlias(element: TsDeclaration.TsTypeAlias): String { - val aliases = generateTypeReference(element.typeRef) + override fun generateTypeAlias(element: TsDeclaration.TsTypeAlias): String { + val aliases = generateTypeReference(element.typeRef) - return when (config.typeAliasTyping) { - KxsTsConfig.TypeAliasTypingConfig.None -> - """ - |type ${element.id.name} = ${aliases}; - """.trimMargin() - KxsTsConfig.TypeAliasTypingConfig.BrandTyping -> { + return when (config.typeAliasTyping) { + KxsTsConfig.TypeAliasTypingConfig.None -> + """ + |type ${element.id.name} = ${aliases}; + """.trimMargin() + KxsTsConfig.TypeAliasTypingConfig.BrandTyping -> { - val brandType = element.id.name - .mapNotNull { c -> - when { - c == '.' -> '_' - !c.isLetter() -> null - else -> c - } - }.joinToString("") + val brandType = element.id.name + .mapNotNull { c -> + when { + c == '.' -> '_' + !c.isLetter() -> null + else -> c + } + }.joinToString("") - """ - |type ${element.id.name} = $aliases & { __${brandType}__: void }; - """.trimMargin() + """ + |type ${element.id.name} = $aliases & { __${brandType}__: void }; + """.trimMargin() + } } } - } - /** - * A type-reference, be it for the field of an interface, a type alias, or a generic type - * constraint. - */ - private fun generateTypeReference(typeRef: TsTypeRef): String { - return typeReferenceState.getOrPut(typeRef) { + /** + * A type-reference, be it for the field of an interface, a type alias, or a generic type + * constraint. + */ + override fun generateTypeReference(typeRef: TsTypeRef): String { val plainType: String = when (typeRef) { is TsTypeRef.Literal -> when (typeRef.element) { - is TsLiteral.Primitive -> primitiveReference(typeRef.element) + is TsLiteral.Primitive -> generatePrimitive(typeRef.element) is TsLiteral.TsList -> { val valueTypeRef = generateTypeReference(typeRef.element.valueTypeRef) "$valueTypeRef[]" @@ -146,42 +157,43 @@ class KxsTsSourceCodeGenerator( is TsTypeRef.Unknown -> generateTypeReference(typeRef.ref) } - buildString { + return buildString { append(plainType) if (typeRef.nullable) { append(" | ") - append(primitiveReference(TsLiteral.Primitive.TsNull)) + append(generatePrimitive(TsLiteral.Primitive.TsNull)) } } } - } - private fun primitiveReference(primitive: TsLiteral.Primitive): String { - return when (primitive) { - TsLiteral.Primitive.TsString -> "string" - TsLiteral.Primitive.TsNumber -> "number" - TsLiteral.Primitive.TsBoolean -> "boolean" - TsLiteral.Primitive.TsObject -> "object" - TsLiteral.Primitive.TsAny -> "any" - TsLiteral.Primitive.TsNever -> "never" - TsLiteral.Primitive.TsNull -> "null" - TsLiteral.Primitive.TsUndefined -> "undefined" - TsLiteral.Primitive.TsUnknown -> "unknown" - TsLiteral.Primitive.TsVoid -> "void" + override fun generatePrimitive(primitive: TsLiteral.Primitive): String { + return when (primitive) { + TsLiteral.Primitive.TsString -> "string" + TsLiteral.Primitive.TsNumber -> "number" + TsLiteral.Primitive.TsBoolean -> "boolean" + TsLiteral.Primitive.TsObject -> "object" + TsLiteral.Primitive.TsAny -> "any" + TsLiteral.Primitive.TsNever -> "never" + TsLiteral.Primitive.TsNull -> "null" + TsLiteral.Primitive.TsUndefined -> "undefined" + TsLiteral.Primitive.TsUnknown -> "unknown" + TsLiteral.Primitive.TsVoid -> "void" + } } - } - private fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String { - val keyTypeRef = generateTypeReference(tsMap.keyTypeRef) - val valueTypeRef = generateTypeReference(tsMap.valueTypeRef) + override fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String { + val keyTypeRef = generateTypeReference(tsMap.keyTypeRef) + val valueTypeRef = generateTypeReference(tsMap.valueTypeRef) - return when (tsMap.type) { - TsLiteral.TsMap.Type.INDEX_SIGNATURE -> "{ [key: $keyTypeRef]: $valueTypeRef }" - TsLiteral.TsMap.Type.MAPPED_OBJECT -> "{ [key in $keyTypeRef]: $valueTypeRef }" - TsLiteral.TsMap.Type.MAP -> "Map<$keyTypeRef, $valueTypeRef>" + return when (tsMap.type) { + TsLiteral.TsMap.Type.INDEX_SIGNATURE -> "{ [key: $keyTypeRef]: $valueTypeRef }" + TsLiteral.TsMap.Type.MAPPED_OBJECT -> "{ [key in $keyTypeRef]: $valueTypeRef }" + TsLiteral.TsMap.Type.MAP -> "Map<$keyTypeRef, $valueTypeRef>" + } } + } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt new file mode 100644 index 00000000..109c22e4 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt @@ -0,0 +1,122 @@ +package dev.adamko.kxstsgen + + +import kotlinx.serialization.descriptors.PolymorphicKind +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.SerialKind +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.descriptors.elementDescriptors +import kotlinx.serialization.descriptors.elementNames + +fun interface TsElementConverter { + + operator fun invoke( + context: KxsTsConvertorContext, + descriptor: SerialDescriptor, + ): TsElement + + object Default : TsElementConverter { + + override operator fun invoke( + context: KxsTsConvertorContext, + descriptor: SerialDescriptor, + ): TsElement { + return when (descriptor.kind) { + SerialKind.ENUM -> convertEnum(context, descriptor) + SerialKind.CONTEXTUAL -> { + // TODO contextual + TsLiteral.Primitive.TsAny + } + + PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean + + PrimitiveKind.CHAR, + PrimitiveKind.STRING -> TsLiteral.Primitive.TsString + + PrimitiveKind.BYTE, + PrimitiveKind.SHORT, + PrimitiveKind.INT, + PrimitiveKind.LONG, + PrimitiveKind.FLOAT, + PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber + + StructureKind.LIST -> convertList(context, descriptor) + StructureKind.MAP -> convertMap(context, descriptor) + + StructureKind.CLASS, + StructureKind.OBJECT, + PolymorphicKind.SEALED, + PolymorphicKind.OPEN -> when { + descriptor.isInline -> convertTypeAlias(context, descriptor) + else -> convertInterface(context, descriptor) + } + } + } + + + private fun convertTypeAlias( + context: KxsTsConvertorContext, + structDescriptor: SerialDescriptor, + ): TsDeclaration { + val resultId = context.elementId(structDescriptor) + val fieldDescriptor = structDescriptor.elementDescriptors.first() + val fieldTypeRef = context.typeRef(fieldDescriptor) + return TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) + + } + + + private fun convertInterface( + context: KxsTsConvertorContext, + structDescriptor: SerialDescriptor, + ): TsDeclaration { + val resultId = context.elementId(structDescriptor) + + val properties = structDescriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> + val name = structDescriptor.getElementName(index) + val fieldTypeRef = context.typeRef(fieldDescriptor) + when { + structDescriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeRef) + else -> TsProperty.Required(name, fieldTypeRef) + } + }.toSet() + return TsDeclaration.TsInterface(resultId, properties, TsPolymorphicDiscriminator.Open) + } + + + private fun convertEnum( + context: KxsTsConvertorContext, + enumDescriptor: SerialDescriptor, + ): TsDeclaration.TsEnum { + val resultId = context.elementId(enumDescriptor) + return TsDeclaration.TsEnum(resultId, enumDescriptor.elementNames.toSet()) + } + + + private fun convertList( + context: KxsTsConvertorContext, + listDescriptor: SerialDescriptor, + ): TsLiteral.TsList { + val elementDescriptor = listDescriptor.elementDescriptors.first() + val elementTypeRef = context.typeRef(elementDescriptor) + return TsLiteral.TsList(elementTypeRef) + } + + + private fun convertMap( + context: KxsTsConvertorContext, + mapDescriptor: SerialDescriptor, + ): TsLiteral.TsMap { + + val (keyDescriptor, valueDescriptor) = mapDescriptor.elementDescriptors.toList() + + val keyTypeRef = context.typeRef(keyDescriptor) + val valueTypeRef = context.typeRef(valueDescriptor) + + val type = context.mapType(keyDescriptor) + + return TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) + } + } +} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt new file mode 100644 index 00000000..61bdfdbc --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt @@ -0,0 +1,42 @@ +package dev.adamko.kxstsgen + + +import kotlinx.serialization.descriptors.PolymorphicKind +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.SerialKind +import kotlinx.serialization.descriptors.StructureKind + +fun interface TsElementIdConverter { + + operator fun invoke(descriptor: SerialDescriptor): TsElementId + + object Default : TsElementIdConverter { + override fun invoke(descriptor: SerialDescriptor): TsElementId { + val targetId = TsElementId(descriptor.serialName.removeSuffix("?")) + + return when (descriptor.kind) { + PolymorphicKind.OPEN -> TsElementId( + targetId.namespace + "." + targetId.name.substringAfter("<").substringBeforeLast(">") + ) + PolymorphicKind.SEALED, + PrimitiveKind.BOOLEAN, + PrimitiveKind.BYTE, + PrimitiveKind.CHAR, + PrimitiveKind.DOUBLE, + PrimitiveKind.FLOAT, + PrimitiveKind.INT, + PrimitiveKind.LONG, + PrimitiveKind.SHORT, + PrimitiveKind.STRING, + SerialKind.CONTEXTUAL, + SerialKind.ENUM, + StructureKind.CLASS, + StructureKind.LIST, + StructureKind.MAP, + StructureKind.OBJECT -> targetId + } + } + + } +} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsMapTypeConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsMapTypeConverter.kt new file mode 100644 index 00000000..1ecf880e --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsMapTypeConverter.kt @@ -0,0 +1,41 @@ +package dev.adamko.kxstsgen + + +import kotlinx.serialization.descriptors.PolymorphicKind +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.SerialKind +import kotlinx.serialization.descriptors.StructureKind + + +fun interface TsMapTypeConverter { + + operator fun invoke(descriptor: SerialDescriptor): TsLiteral.TsMap.Type + + object Default : TsMapTypeConverter { + + override operator fun invoke(descriptor: SerialDescriptor): TsLiteral.TsMap.Type { + return when (descriptor.kind) { + SerialKind.ENUM -> TsLiteral.TsMap.Type.MAPPED_OBJECT + + PrimitiveKind.STRING -> TsLiteral.TsMap.Type.INDEX_SIGNATURE + + SerialKind.CONTEXTUAL, + PrimitiveKind.BOOLEAN, + PrimitiveKind.BYTE, + PrimitiveKind.CHAR, + PrimitiveKind.SHORT, + PrimitiveKind.INT, + PrimitiveKind.LONG, + PrimitiveKind.FLOAT, + PrimitiveKind.DOUBLE, + StructureKind.CLASS, + StructureKind.LIST, + StructureKind.MAP, + StructureKind.OBJECT, + PolymorphicKind.SEALED, + PolymorphicKind.OPEN -> TsLiteral.TsMap.Type.MAP + } + } + } +} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt new file mode 100644 index 00000000..842455eb --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt @@ -0,0 +1,66 @@ +package dev.adamko.kxstsgen + + +import kotlinx.serialization.descriptors.PolymorphicKind +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.SerialKind +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.descriptors.elementDescriptors + + +fun interface TsTypeRefConverter { + + operator fun invoke(context: KxsTsConvertorContext, descriptor: SerialDescriptor): TsTypeRef + + object Default : TsTypeRefConverter { + override fun invoke( + context: KxsTsConvertorContext, + descriptor: SerialDescriptor, + ): TsTypeRef { + return when (descriptor.kind) { + is PrimitiveKind -> { + val tsPrimitive = when (descriptor.kind as PrimitiveKind) { + PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean + + PrimitiveKind.BYTE, + PrimitiveKind.SHORT, + PrimitiveKind.INT, + PrimitiveKind.LONG, + PrimitiveKind.FLOAT, + PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber + + PrimitiveKind.CHAR, + PrimitiveKind.STRING -> TsLiteral.Primitive.TsString + } + TsTypeRef.Literal(tsPrimitive, descriptor.isNullable) + } + + StructureKind.LIST -> { + val elementDescriptor = descriptor.elementDescriptors.first() + val elementTypeRef = context.typeRef(elementDescriptor) + val listRef = TsLiteral.TsList(elementTypeRef) + TsTypeRef.Literal(listRef, descriptor.isNullable) + } + StructureKind.MAP -> { + val (keyDescriptor, valueDescriptor) = descriptor.elementDescriptors.toList() + val keyTypeRef = context.typeRef(keyDescriptor) + val valueTypeRef = context.typeRef(valueDescriptor) + val type = context.mapType(keyDescriptor) + val map = TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) + TsTypeRef.Literal(map, descriptor.isNullable) + } + + SerialKind.CONTEXTUAL, + PolymorphicKind.SEALED, + PolymorphicKind.OPEN, + SerialKind.ENUM, + StructureKind.CLASS, + StructureKind.OBJECT -> { + val id = context.elementId(descriptor) + TsTypeRef.Named(id, descriptor.isNullable) + } + } + } + } +} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt new file mode 100644 index 00000000..d22d2404 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt @@ -0,0 +1,164 @@ +@file:OptIn(InternalSerializationApi::class) + +package dev.adamko.kxstsgen + +import dev.adamko.kxstsgen.util.MutableMapWithDefaultPut +import kotlinx.serialization.InternalSerializationApi +import kotlinx.serialization.KSerializer +import kotlinx.serialization.PolymorphicSerializer +import kotlinx.serialization.SealedClassSerializer +import kotlinx.serialization.descriptors.PolymorphicKind +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.SerialKind +import kotlinx.serialization.descriptors.StructureKind +import kotlinx.serialization.descriptors.elementDescriptors + + +// private val polymorphicDescriptorData: MutableMap +// by MapWithPutDefault { descriptor -> +// polymorphicSerializerData.entries.firstOrNull { (serializer, _) -> +// serializer.descriptor == descriptor +// }?.value +// } + +/** + * Recursively extract all descriptors from a serializer and its elements. + */ +fun interface SerializerDescriptorsExtractor { + + operator fun invoke( + allDescriptorData: Iterable, + ): Set + + + object Default : SerializerDescriptorsExtractor { + + override operator fun invoke( + allDescriptorData: Iterable, + ): Set { + return allDescriptorData.flatMap { descriptorData -> + when (descriptorData) { + is DescriptorData.Polymorphic -> descriptorData.subclasses // only yield subclasses + is DescriptorData.Monomorphic -> listOf(descriptorData.descriptor) + } + }.flatMap { descriptor -> extractedDescriptors.getValue(descriptor) } + .distinct() + .toSet() + } + + private val extractedDescriptors by MutableMapWithDefaultPut> { descriptor -> + sequence { + val seen = mutableSetOf() + val deque = ArrayDeque() + deque.addLast(descriptor) + + while (deque.isNotEmpty()) { + val next = deque.removeFirst() + + val nextElementDescriptors = elementDescriptors.getValue(next) + + nextElementDescriptors + .filter { it !in seen } + .forEach { deque.addLast(it) } + + seen.add(next) + yield(next) + } + }.distinct() + } + + 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 -> descriptor + .elementDescriptors + .filter { it.kind is PolymorphicKind } + .flatMap { it.elementDescriptors } + } + } + + } +} + + +/** Create [DescriptorData] */ +fun interface DescriptorDataProcessor { + operator fun invoke( + context: KxsTsConvertorContext, + config: KxsTsConfig, + serializer: KSerializer<*>, + ): DescriptorData + + object Default : DescriptorDataProcessor { + override fun invoke( + context: KxsTsConvertorContext, + config: KxsTsConfig, + serializer: KSerializer<*>, + ): DescriptorData { + return when (serializer) { + is PolymorphicSerializer<*> -> + DescriptorData.Polymorphic.Open( + descriptor = serializer.descriptor, + subclasses = config.polymorphicDescriptors.getValue(serializer.baseClass) + ) + + is SealedClassSerializer<*> -> + DescriptorData.Polymorphic.Closed( + descriptor = serializer.descriptor, + subclasses = extractSealedSubclassSerializers(serializer).map { it.descriptor }.toSet(), + ) + + else -> + DescriptorData.Monomorphic(serializer.descriptor) + } + } + } +} + + +sealed class DescriptorData { + abstract val descriptor: SerialDescriptor + + data class Monomorphic( + override val descriptor: SerialDescriptor, + ) : DescriptorData() + + sealed class Polymorphic : DescriptorData() { + + abstract val subclasses: Set + + data class Closed( + override val descriptor: SerialDescriptor, + override val subclasses: Set, + ) : Polymorphic() + + data class Open( + override val descriptor: SerialDescriptor, + override val subclasses: Set, + ) : Polymorphic() + + } + + override fun hashCode(): Int = descriptor.hashCode() + override fun equals(other: Any?): Boolean = (descriptor == other) +} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/serializerExtractors.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/serializerExtractors.kt index 930b589e..d7945929 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/serializerExtractors.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/serializerExtractors.kt @@ -21,10 +21,10 @@ expect fun extractSealedSubclassSerializers( /** Hacky exploit to capture the [KSerializer] of a [ContextualSerializer]. */ fun extractContextualSerializer( serializer: ContextualSerializer<*>, - kxsTsModule: KxsTsModule, + kxsTsConfig: KxsTsConfig, ): KSerializer<*>? { return try { - val decoder = ContextualSerializerCaptorDecoder(kxsTsModule.serializersModule) + val decoder = ContextualSerializerCaptorDecoder(kxsTsConfig.serializersModule) serializer.deserialize(decoder) null // this should never be hit, decoder should always throw an exception } catch (e: SerializerCaptorException) { @@ -45,6 +45,7 @@ private class ContextualSerializerCaptorDecoder( throw SerializerCaptorException(deserializer as KSerializer) } + private class SerializerCaptorException(val serializer: KSerializer<*>) : Exception() diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt index 72a40215..eea6be94 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt @@ -36,6 +36,12 @@ sealed interface TsDeclaration : TsElement { val members: Set, ) : TsDeclaration + + data class TsNamespace( + override val id: TsElementId, + val members: Set, + ) : TsDeclaration + } @@ -90,6 +96,7 @@ sealed interface TsTypeRef { } + // source: kxs sealed interface TsProperty { val name: String @@ -120,65 +127,3 @@ sealed interface TsPolymorphicDiscriminator { // source: introspection object Open : TsPolymorphicDiscriminator } - - -/* -//fun TsTypeRef.toTsTypeParamConstant(): TsTypeParam.Constant = TsTypeParam.Constant(this) - -///** -// * A generic type parameter, either constant or generic. -// * -// * The output depends on whether it is used in a [TsStructure.TsInterface] or [TsProperty]. -// * -// * ```typescript -// * // T is 'TsTypeParam.Generic' -// * interface Box { value: T; } -// * interface Pet { pet: T; } -// * interface NamedBox { namedValue: T; } -// * interface NamedPetBox { pet: T; } -// * -// * // 'string' and 'Dog' are 'TsTypeParam.Constant' -// * interface Person { aliases: string[] } -// * interface DogBox extends PetBox { override pet: Dog } -// * ``` -// */ -//// source: introspection -//sealed interface TsTypeParam { -// -// data class Generic( -// val name: String, -// val constraints: Set? = null -// ) : TsTypeParam -// -// data class Constant( -// val typeRef: TsTypeRef -// ) : TsTypeParam -// -//} - - -///** Defines how many generic types can be accepted. */ -//// source: introspection -//sealed interface TsGenericSlot { -// -// /** Enums, primitives, properties can't have generic slots. */ -// object None : TsGenericSlot -// -// /** A single generic type, e.g. `List` */ -// data class Value( -// val valueTyping: TsTypeParam, -// ) : TsGenericSlot -// -// /** The generic types of a `Map` */ -// data class KeyValue( -// val keyTyping: TsTypeParam, -// val valueTyping: TsTypeParam, -// ) : TsGenericSlot -// -// /** Can have zero-to-many [TsTypeParam]s. */ -// data class Multiple( -// val typings: Set, -// ) : TsGenericSlot -// -//} -*/ diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/util/mapWithDefaultDelegate.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/util/mapWithDefaultDelegate.kt new file mode 100644 index 00000000..cec51cf3 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/util/mapWithDefaultDelegate.kt @@ -0,0 +1,38 @@ +package dev.adamko.kxstsgen.util + +import kotlin.properties.ReadWriteProperty +import kotlin.reflect.KProperty + + +class MutableMapWithDefaultPut( + initial: Map = emptyMap(), + private val defaultValue: (key: K) -> V, +) : ReadWriteProperty> { + + private var map: MutableMap = with(initial.toMutableMap()) { + withDefault { key -> getOrPut(key) { defaultValue(key) } } + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): MutableMap = map + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: MutableMap) { + this.map = value + } +} + + +class MapWithDefaultPut( + initial: Map = emptyMap(), + private val defaultValue: (key: K) -> V, +) : ReadWriteProperty> { + + private var map: Map = with(initial.toMutableMap()) { + withDefault { key -> getOrPut(key) { defaultValue(key) } } + } + + override fun getValue(thisRef: Any?, property: KProperty<*>): Map = map + + override fun setValue(thisRef: Any?, property: KProperty<*>, value: Map) { + this.map = value + } +} From 439c07526eec86807cc5bf05f5d19f0b340e0900 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 2 Apr 2022 12:40:05 +0200 Subject: [PATCH 17/46] sealed classes working! - 'export' all TS Declarations - ignore blank lines in knit tests - --- docs/abstract-classes.md | 4 +- docs/basic-classes.md | 8 +- docs/default-values.md | 4 +- docs/edgecases.md | 12 +- docs/knit.properties | 2 +- .../example-polymorphic-sealed-class-01.kt | 4 +- docs/knit/knit-test.ftl | 2 + docs/knit/test/AbstractClassesTest.kt | 16 +- docs/knit/test/BasicClassesTest.kt | 24 ++- docs/knit/test/DefaultValuesTest.kt | 12 +- docs/knit/test/EdgeCasesTest.kt | 24 +-- docs/knit/test/EnumClassTest.kt | 8 +- docs/knit/test/ListsTests.kt | 6 +- docs/knit/test/MapsTests.kt | 28 ++-- docs/knit/test/PolymorphismTest.kt | 54 ++++--- docs/knit/test/ValueClassesTest.kt | 32 ++-- docs/lists.md | 2 +- docs/maps.md | 12 +- docs/polymorphism.md | 38 +++-- docs/value-classes.md | 16 +- .../KxsTsConvertorContext.kt | 10 +- .../dev.adamko.kxstsgen/KxsTsProcessor.kt | 9 +- .../KxsTsSourceCodeGenerator.kt | 151 ++++++++++++++---- .../dev.adamko.kxstsgen/TsElementConverter.kt | 70 ++++++-- .../dev.adamko.kxstsgen/TsTypeRefConverter.kt | 2 +- .../dev.adamko.kxstsgen/processSerializers.kt | 17 +- .../kotlin/dev.adamko.kxstsgen/tsElements.kt | 52 ++++-- .../kxstsgen/serializerExtractorsJvm.kt | 13 +- 28 files changed, 430 insertions(+), 202 deletions(-) diff --git a/docs/abstract-classes.md b/docs/abstract-classes.md index ff76aa1b..e90bfe69 100644 --- a/docs/abstract-classes.md +++ b/docs/abstract-classes.md @@ -62,7 +62,7 @@ fun main() { > You can get the full code [here](./knit/example/example-abstract-class-primitive-fields-01.kt). ```typescript -interface SimpleTypes { +export interface SimpleTypes { aString: string; anInt: number; aDouble: number; @@ -93,7 +93,7 @@ fun main() { > You can get the full code [here](./knit/example/example-abstract-class-abstract-field-01.kt). ```typescript -interface Color { +export interface Color { rgb: number; } ``` diff --git a/docs/basic-classes.md b/docs/basic-classes.md index 211292f0..c84704fa 100644 --- a/docs/basic-classes.md +++ b/docs/basic-classes.md @@ -36,7 +36,7 @@ fun main() { > You can get the full code [here](./knit/example/example-plain-class-single-field-01.kt). ```typescript -interface Color { +export interface Color { rgb: number; } ``` @@ -64,7 +64,7 @@ fun main() { > You can get the full code [here](./knit/example/example-plain-class-primitive-fields-01.kt). ```typescript -interface SimpleTypes { +export interface SimpleTypes { aString: string; anInt: number; aDouble: number; @@ -96,7 +96,7 @@ fun main() { > You can get the full code [here](./knit/example/example-plain-data-class-01.kt). ```typescript -interface SomeDataClass { +export interface SomeDataClass { aString: string; anInt: number; aDouble: number; @@ -137,7 +137,7 @@ fun main() { > You can get the full code [here](./knit/example/example-plain-class-primitive-fields-02.kt). ```typescript -interface SimpleTypes { +export interface SimpleTypes { } ``` diff --git a/docs/default-values.md b/docs/default-values.md index 69c541a4..49677806 100644 --- a/docs/default-values.md +++ b/docs/default-values.md @@ -37,7 +37,7 @@ fun main() { > You can get the full code [here](./knit/example/example-default-values-single-field-01.kt). ```typescript -interface Color { +export interface Color { rgb?: number; } ``` @@ -67,7 +67,7 @@ Email has no default, so it is not marked as optional. Phone number and is nullable, and has a default, so i ```typescript -interface ContactDetails { +export interface ContactDetails { email: string | null; phoneNumber?: string | null; active?: boolean | null; diff --git a/docs/edgecases.md b/docs/edgecases.md index 809c7389..7d91135c 100644 --- a/docs/edgecases.md +++ b/docs/edgecases.md @@ -43,11 +43,11 @@ fun main() { > You can get the full code [here](./knit/example/example-edgecase-recursive-references-01.kt). ```typescript -interface A { +export interface A { b: B; } -interface B { +export interface B { a: A; } ``` @@ -76,11 +76,11 @@ fun main() { > You can get the full code [here](./knit/example/example-edgecase-recursive-references-02.kt). ```typescript -interface A { +export interface A { list: B[]; } -interface B { +export interface B { list: A[]; } ``` @@ -109,11 +109,11 @@ fun main() { > You can get the full code [here](./knit/example/example-edgecase-recursive-references-03.kt). ```typescript -interface A { +export interface A { map: { [key: string]: B }; } -interface B { +export interface B { map: { [key: string]: A }; } ``` diff --git a/docs/knit.properties b/docs/knit.properties index 928c4f6c..ab488d8c 100644 --- a/docs/knit.properties +++ b/docs/knit.properties @@ -6,4 +6,4 @@ test.package=dev.adamko.kxstsgen.example.test test.template=./knit/knit-test.ftl test.language=typescript knit.include=./knit/knit-include.ftl -test.mode.=joinToString(\"\\n\")\n .shouldBe +test.mode.=joinToString(\"\\n\") { it.ifBlank { "" } }\n .shouldBe diff --git a/docs/knit/example/example-polymorphic-sealed-class-01.kt b/docs/knit/example/example-polymorphic-sealed-class-01.kt index d19dbd76..43236c77 100644 --- a/docs/knit/example/example-polymorphic-sealed-class-01.kt +++ b/docs/knit/example/example-polymorphic-sealed-class-01.kt @@ -11,7 +11,7 @@ sealed class Project { } @Serializable -@SerialName("owned-project") +@SerialName("OProj") class OwnedProject(override val name: String, val owner: String) : Project() @Serializable @@ -19,5 +19,5 @@ class DeprecatedProject(override val name: String, val reason: String) : Project fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(OwnedProject.serializer())) + println(tsGenerator.generate(Project.serializer())) } diff --git a/docs/knit/knit-test.ftl b/docs/knit/knit-test.ftl index 9d910b67..84baec7e 100644 --- a/docs/knit/knit-test.ftl +++ b/docs/knit/knit-test.ftl @@ -21,6 +21,8 @@ class ${test.name} { |${line} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) <#else>.also { lines -> check(${case.param}) diff --git a/docs/knit/test/AbstractClassesTest.kt b/docs/knit/test/AbstractClassesTest.kt index 017868e9..05f93f95 100644 --- a/docs/knit/test/AbstractClassesTest.kt +++ b/docs/knit/test/AbstractClassesTest.kt @@ -11,7 +11,7 @@ class AbstractClassesTest { fun testExampleAbstractClassSingleField01() { captureOutput("ExampleAbstractClassSingleField01") { dev.adamko.kxstsgen.example.exampleAbstractClassSingleField01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ @@ -19,6 +19,8 @@ class AbstractClassesTest { | rgb: number; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -26,11 +28,11 @@ class AbstractClassesTest { fun testExampleAbstractClassPrimitiveFields01() { captureOutput("ExampleAbstractClassPrimitiveFields01") { dev.adamko.kxstsgen.example.exampleAbstractClassPrimitiveFields01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface SimpleTypes { + |export interface SimpleTypes { | aString: string; | anInt: number; | aDouble: number; @@ -38,6 +40,8 @@ class AbstractClassesTest { | privateMember: string; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -45,14 +49,16 @@ class AbstractClassesTest { fun testExampleAbstractClassAbstractField01() { captureOutput("ExampleAbstractClassAbstractField01") { dev.adamko.kxstsgen.example.exampleAbstractClassAbstractField01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface Color { + |export interface Color { | rgb: number; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } } diff --git a/docs/knit/test/BasicClassesTest.kt b/docs/knit/test/BasicClassesTest.kt index f2006d5a..60ec35c2 100644 --- a/docs/knit/test/BasicClassesTest.kt +++ b/docs/knit/test/BasicClassesTest.kt @@ -11,14 +11,16 @@ class BasicClassesTest { fun testExamplePlainClassSingleField01() { captureOutput("ExamplePlainClassSingleField01") { dev.adamko.kxstsgen.example.examplePlainClassSingleField01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface Color { + |export interface Color { | rgb: number; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -26,11 +28,11 @@ class BasicClassesTest { fun testExamplePlainClassPrimitiveFields01() { captureOutput("ExamplePlainClassPrimitiveFields01") { dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface SimpleTypes { + |export interface SimpleTypes { | aString: string; | anInt: number; | aDouble: number; @@ -38,6 +40,8 @@ class BasicClassesTest { | privateMember: string; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -45,11 +49,11 @@ class BasicClassesTest { fun testExamplePlainDataClass01() { captureOutput("ExamplePlainDataClass01") { dev.adamko.kxstsgen.example.examplePlainDataClass01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface SomeDataClass { + |export interface SomeDataClass { | aString: string; | anInt: number; | aDouble: number; @@ -57,6 +61,8 @@ class BasicClassesTest { | privateMember: string; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -64,13 +70,15 @@ class BasicClassesTest { fun testExamplePlainClassPrimitiveFields02() { captureOutput("ExamplePlainClassPrimitiveFields02") { dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields02.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface SimpleTypes { + |export interface SimpleTypes { |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } } diff --git a/docs/knit/test/DefaultValuesTest.kt b/docs/knit/test/DefaultValuesTest.kt index 27a48be8..ce974bbf 100644 --- a/docs/knit/test/DefaultValuesTest.kt +++ b/docs/knit/test/DefaultValuesTest.kt @@ -11,14 +11,16 @@ class DefaultValuesTest { fun testExampleDefaultValuesSingleField01() { captureOutput("ExampleDefaultValuesSingleField01") { dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface Color { + |export interface Color { | rgb?: number; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -26,16 +28,18 @@ class DefaultValuesTest { fun testExampleDefaultValuesPrimitiveFields01() { captureOutput("ExampleDefaultValuesPrimitiveFields01") { dev.adamko.kxstsgen.example.exampleDefaultValuesPrimitiveFields01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface ContactDetails { + |export interface ContactDetails { | email: string | null; | phoneNumber?: string | null; | active?: boolean | null; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } } diff --git a/docs/knit/test/EdgeCasesTest.kt b/docs/knit/test/EdgeCasesTest.kt index f34b293f..d83052f0 100644 --- a/docs/knit/test/EdgeCasesTest.kt +++ b/docs/knit/test/EdgeCasesTest.kt @@ -11,18 +11,20 @@ class EdgeCasesTest { fun testExampleEdgecaseRecursiveReferences01() { captureOutput("ExampleEdgecaseRecursiveReferences01") { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface A { + |export interface A { | b: B; |} | - |interface B { + |export interface B { | a: A; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -30,18 +32,20 @@ class EdgeCasesTest { fun testExampleEdgecaseRecursiveReferences02() { captureOutput("ExampleEdgecaseRecursiveReferences02") { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences02.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface A { + |export interface A { | list: B[]; |} | - |interface B { + |export interface B { | list: A[]; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -49,18 +53,20 @@ class EdgeCasesTest { fun testExampleEdgecaseRecursiveReferences03() { captureOutput("ExampleEdgecaseRecursiveReferences03") { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences03.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface A { + |export interface A { | map: { [key: string]: B }; |} | - |interface B { + |export interface B { | map: { [key: string]: A }; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } } diff --git a/docs/knit/test/EnumClassTest.kt b/docs/knit/test/EnumClassTest.kt index d1d94b1b..31de1465 100644 --- a/docs/knit/test/EnumClassTest.kt +++ b/docs/knit/test/EnumClassTest.kt @@ -11,7 +11,7 @@ class EnumClassTest { fun testExampleEnumClass01() { captureOutput("ExampleEnumClass01") { dev.adamko.kxstsgen.example.exampleEnumClass01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ @@ -21,6 +21,8 @@ class EnumClassTest { | Gamma = "Gamma", |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -28,7 +30,7 @@ class EnumClassTest { fun testExampleEnumClass02() { captureOutput("ExampleEnumClass02") { dev.adamko.kxstsgen.example.exampleEnumClass02.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ @@ -38,6 +40,8 @@ class EnumClassTest { | Gamma = "Gamma", |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } } diff --git a/docs/knit/test/ListsTests.kt b/docs/knit/test/ListsTests.kt index 208f7afb..b10e570f 100644 --- a/docs/knit/test/ListsTests.kt +++ b/docs/knit/test/ListsTests.kt @@ -11,16 +11,18 @@ class ListsTests { fun testExampleListPrimitive01() { captureOutput("ExampleListPrimitive01") { dev.adamko.kxstsgen.example.exampleListPrimitive01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface MyLists { + |export interface MyLists { | strings: string[]; | ints: number[]; | longs: number[]; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } } diff --git a/docs/knit/test/MapsTests.kt b/docs/knit/test/MapsTests.kt index adf6bda6..59bc30f7 100644 --- a/docs/knit/test/MapsTests.kt +++ b/docs/knit/test/MapsTests.kt @@ -11,14 +11,16 @@ class MapsTests { fun testExampleMapPrimitive01() { captureOutput("ExampleMapPrimitive01") { dev.adamko.kxstsgen.example.exampleMapPrimitive01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface Config { + |export interface Config { | properties: { [key: string]: string }; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -26,11 +28,11 @@ class MapsTests { fun testExampleMapPrimitive02() { captureOutput("ExampleMapPrimitive02") { dev.adamko.kxstsgen.example.exampleMapPrimitive02.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface Application { + |export interface Application { | settings: { [key in SettingKeys]: string }; |} | @@ -39,6 +41,8 @@ class MapsTests { | MAX_MEMORY = "MAX_MEMORY", |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -46,14 +50,16 @@ class MapsTests { fun testExampleMapPrimitive03() { captureOutput("ExampleMapPrimitive03") { dev.adamko.kxstsgen.example.exampleMapPrimitive03.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface Config { + |export interface Config { | properties: { [key: string | null]: string | null }; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -61,23 +67,25 @@ class MapsTests { fun testExampleMapComplex01() { captureOutput("ExampleMapComplex01") { dev.adamko.kxstsgen.example.exampleMapComplex01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface CanvasProperties { + |export interface CanvasProperties { | colourNames: Map; |} | - |interface Colour { + |export interface Colour { | r: UByte; | g: UByte; | b: UByte; | a: UByte; |} | - |type UByte = number; + |export type UByte = number; """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } } diff --git a/docs/knit/test/PolymorphismTest.kt b/docs/knit/test/PolymorphismTest.kt index 26ba68ce..2dba4bbc 100644 --- a/docs/knit/test/PolymorphismTest.kt +++ b/docs/knit/test/PolymorphismTest.kt @@ -11,11 +11,11 @@ class PolymorphismTest { fun testExamplePolymorphicAbstractClassPrimitiveFields01() { captureOutput("ExamplePolymorphicAbstractClassPrimitiveFields01") { dev.adamko.kxstsgen.example.examplePolymorphicAbstractClassPrimitiveFields01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface SimpleTypes { + |export interface SimpleTypes { | aString: string; | anInt: number; | aDouble: number; @@ -23,6 +23,8 @@ class PolymorphismTest { | privateMember: string; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -30,14 +32,16 @@ class PolymorphismTest { fun testExamplePolymorphicStaticTypes01() { captureOutput("ExamplePolymorphicStaticTypes01") { dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface Project { + |export interface Project { | name: string; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -45,19 +49,21 @@ class PolymorphismTest { fun testExamplePolymorphicStaticTypes02() { captureOutput("ExamplePolymorphicStaticTypes02") { dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes02.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |interface Project { + |export interface Project { | name: string; |} | - |interface OwnedProject extends Project { + |export interface OwnedProject extends Project { | name: string; | owner: string; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -65,33 +71,33 @@ class PolymorphismTest { fun testExamplePolymorphicSealedClass01() { captureOutput("ExamplePolymorphicSealedClass01") { dev.adamko.kxstsgen.example.examplePolymorphicSealedClass01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |export type Project = Project.Owned | Project.Deprecated + |export type Project = Project.DeprecatedProject | Project.OProj; | |export namespace Project { - | | export enum Type { - | Owned = "owned-project", - | Deprecated = "DeprecatedProject", + | OProj = "OProj", + | DeprecatedProject = "DeprecatedProject", | } | - | export interface Owned { - | type: Type.Owned; + | export interface OProj { + | type: Type.OProj; | name: string; | owner: string; | } | - | export interface Deprecated { - | type: Type.Deprecated; + | export interface DeprecatedProject { + | type: Type.DeprecatedProject; | name: string; | reason: string; | } - | |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -99,7 +105,7 @@ class PolymorphismTest { fun testExamplePolymorphicSealedClass02() { captureOutput("ExamplePolymorphicSealedClass02") { dev.adamko.kxstsgen.example.examplePolymorphicSealedClass02.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ @@ -141,6 +147,8 @@ class PolymorphismTest { | |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -148,7 +156,7 @@ class PolymorphismTest { fun testExamplePolymorphicObjects01() { captureOutput("ExamplePolymorphicObjects01") { dev.adamko.kxstsgen.example.examplePolymorphicObjects01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ @@ -157,17 +165,19 @@ class PolymorphismTest { | TextResponse = "TextResponse", |} | - |type Response = EmptyResponse | TextResponse + |export type Response = EmptyResponse | TextResponse | - |interface EmptyResponse { + |export interface EmptyResponse { | type: ResponseKind.EmptyResponse; |} | - |interface TextResponse { + |export interface TextResponse { | type: ResponseKind.TextResponse; | text: string; |} """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } } diff --git a/docs/knit/test/ValueClassesTest.kt b/docs/knit/test/ValueClassesTest.kt index bed0c591..7402095e 100644 --- a/docs/knit/test/ValueClassesTest.kt +++ b/docs/knit/test/ValueClassesTest.kt @@ -11,12 +11,14 @@ class ValueClassesTest { fun testExampleValueClasses01() { captureOutput("ExampleValueClasses01") { dev.adamko.kxstsgen.example.exampleValueClasses01.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |type AuthToken = string; + |export type AuthToken = string; """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -24,18 +26,20 @@ class ValueClassesTest { fun testExampleValueClasses02() { captureOutput("ExampleValueClasses02") { dev.adamko.kxstsgen.example.exampleValueClasses02.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |type UByte = number; + |export type UByte = number; | - |type UShort = number; + |export type UShort = number; | - |type UInt = number; + |export type UInt = number; | - |type ULong = number; + |export type ULong = number; """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -43,12 +47,14 @@ class ValueClassesTest { fun testExampleValueClasses03() { captureOutput("ExampleValueClasses03") { dev.adamko.kxstsgen.example.exampleValueClasses03.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |type ULong = number & { __ULong__: void }; + |export type ULong = number & { __ULong__: void }; """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } @@ -56,14 +62,16 @@ class ValueClassesTest { fun testExampleValueClasses04() { captureOutput("ExampleValueClasses04") { dev.adamko.kxstsgen.example.exampleValueClasses04.main() - }.joinToString("\n") + }.joinToString("\n") { it.ifBlank { "" } } .shouldBe( // language=TypeScript """ - |type UserCount = UInt; + |export type UserCount = UInt; | - |type UInt = number; + |export type UInt = number; """.trimMargin() + .lines() + .joinToString("\n") { it.ifBlank { "" } } ) } } diff --git a/docs/lists.md b/docs/lists.md index aff255c0..f89dfe4a 100644 --- a/docs/lists.md +++ b/docs/lists.md @@ -36,7 +36,7 @@ fun main() { > You can get the full code [here](./knit/example/example-list-primitive-01.kt). ```typescript -interface MyLists { +export interface MyLists { strings: string[]; ints: number[]; longs: number[]; diff --git a/docs/maps.md b/docs/maps.md index d90f27ec..ee85917c 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -37,7 +37,7 @@ fun main() { > You can get the full code [here](./knit/example/example-map-primitive-01.kt). ```typescript -interface Config { +export interface Config { properties: { [key: string]: string }; } ``` @@ -67,7 +67,7 @@ fun main() { > You can get the full code [here](./knit/example/example-map-primitive-02.kt). ```typescript -interface Application { +export interface Application { settings: { [key in SettingKeys]: string }; } @@ -96,7 +96,7 @@ fun main() { > You can get the full code [here](./knit/example/example-map-primitive-03.kt). ```typescript -interface Config { +export interface Config { properties: { [key: string | null]: string | null }; } ``` @@ -128,18 +128,18 @@ fun main() { > You can get the full code [here](./knit/example/example-map-complex-01.kt). ```typescript -interface CanvasProperties { +export interface CanvasProperties { colourNames: Map; } -interface Colour { +export interface Colour { r: UByte; g: UByte; b: UByte; a: UByte; } -type UByte = number; +export type UByte = number; ``` diff --git a/docs/polymorphism.md b/docs/polymorphism.md index b827e720..262cf2f8 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -45,7 +45,7 @@ fun main() { > You can get the full code [here](./knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt). ```typescript -interface SimpleTypes { +export interface SimpleTypes { aString: string; anInt: number; aDouble: number; @@ -79,7 +79,7 @@ fun main() { Only the Project class properties are generated. ```typescript -interface Project { +export interface Project { name: string; } ``` @@ -112,11 +112,11 @@ fun main() { > You can get the full code [here](./knit/example/example-polymorphic-static-types-02.kt). ```typescript -interface Project { +export interface Project { name: string; } -interface OwnedProject extends Project { +export interface OwnedProject extends Project { name: string; owner: string; } @@ -135,7 +135,7 @@ sealed class Project { } @Serializable -@SerialName("owned-project") +@SerialName("OProj") class OwnedProject(override val name: String, val owner: String) : Project() @Serializable @@ -143,34 +143,32 @@ class DeprecatedProject(override val name: String, val reason: String) : Project fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(OwnedProject.serializer())) + println(tsGenerator.generate(Project.serializer())) } ``` > You can get the full code [here](./knit/example/example-polymorphic-sealed-class-01.kt). ```typescript -export type Project = Project.Owned | Project.Deprecated +export type Project = Project.DeprecatedProject | Project.OProj; export namespace Project { - export enum Type { - Owned = "owned-project", - Deprecated = "DeprecatedProject", + OProj = "OProj", + DeprecatedProject = "DeprecatedProject", } - export interface Owned { - type: Type.Owned; + export interface OProj { + type: Type.OProj; name: string; owner: string; } - export interface Deprecated { - type: Type.Deprecated; + export interface DeprecatedProject { + type: Type.DeprecatedProject; name: string; reason: string; } - } ``` @@ -289,13 +287,13 @@ export enum ResponseKind { TextResponse = "TextResponse", } -type Response = EmptyResponse | TextResponse +export type Response = EmptyResponse | TextResponse -interface EmptyResponse { +export interface EmptyResponse { type: ResponseKind.EmptyResponse; } -interface TextResponse { +export interface TextResponse { type: ResponseKind.TextResponse; text: string; } @@ -331,9 +329,9 @@ fun main() { > You can get the full code [here](./knit/example/example-generics-01.kt). ```typescript -type Double = number & { __kotlin_Double__: void } +export type Double = number & { __kotlin_Double__: void } -interface Box { +export interface Box { value: Double } ``` diff --git a/docs/value-classes.md b/docs/value-classes.md index 91cf0205..c8dd255d 100644 --- a/docs/value-classes.md +++ b/docs/value-classes.md @@ -39,7 +39,7 @@ fun main() { > You can get the full code [here](./knit/example/example-value-classes-01.kt). ```typescript -type AuthToken = string; +export type AuthToken = string; ``` @@ -69,13 +69,13 @@ fun main() { > You can get the full code [here](./knit/example/example-value-classes-02.kt). ```typescript -type UByte = number; +export type UByte = number; -type UShort = number; +export type UShort = number; -type UInt = number; +export type UInt = number; -type ULong = number; +export type ULong = number; ``` @@ -113,7 +113,7 @@ fun main() { > You can get the full code [here](./knit/example/example-value-classes-03.kt). ```typescript -type ULong = number & { __ULong__: void }; +export type ULong = number & { __ULong__: void }; ``` @@ -138,9 +138,9 @@ fun main() { > You can get the full code [here](./knit/example/example-value-classes-04.kt). ```typescript -type UserCount = UInt; +export type UserCount = UInt; -type UInt = number; +export type UInt = number; ``` diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt index 5a67cf63..645c8ee3 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt @@ -12,7 +12,7 @@ interface KxsTsConvertorContext { fun mapType(descriptor: SerialDescriptor): TsLiteral.TsMap.Type - fun element(descriptor: SerialDescriptor): TsElement +// fun element(descriptor: SerialDescriptor): TsElement class Default( elementIds: Map = mapOf(), @@ -34,8 +34,8 @@ interface KxsTsConvertorContext { private val mapTypes: MutableMap by MutableMapWithDefaultPut(mapTypes) { mapTypeConverter(it) } - private val convertedElements: MutableMap - by MutableMapWithDefaultPut(convertedElements) { elementConverter(this, it) } +// private val convertedElements: MutableMap +// by MutableMapWithDefaultPut(convertedElements) { elementConverter(this, it) } override fun elementId(descriptor: SerialDescriptor): TsElementId = elementIds.getValue(descriptor) @@ -46,8 +46,8 @@ interface KxsTsConvertorContext { override fun mapType(descriptor: SerialDescriptor): TsLiteral.TsMap.Type = mapTypes.getValue(descriptor) - override fun element(descriptor: SerialDescriptor): TsElement = - convertedElements.getValue(descriptor) +// override fun element(descriptor: SerialDescriptor): TsElement = +// convertedElements.getValue(descriptor) } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt index 5a0ede01..82724e37 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt @@ -32,16 +32,17 @@ class KxsTsProcessor( } fun process(): String { - val allDescriptorData = serializers.associate { serializer -> - serializer.descriptor to descriptorData.getValue(serializer) - } + val allDescriptorData: Map = + serializers.associate { serializer -> + serializer.descriptor to descriptorData.getValue(serializer) + } val allSerialDescriptors: Set = serializerDescriptorsExtractor(allDescriptorData.values) val allTsElements: List = allSerialDescriptors.map { descriptor -> - TsElementConverter.Default(context, descriptor) + TsElementConverter.Default(context, descriptor, allDescriptorData[descriptor]) } return allTsElements diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index c6ef980d..a1258ce8 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -16,14 +16,14 @@ abstract class KxsTsSourceCodeGenerator( is TsDeclaration.TsEnum -> generateEnum(element) is TsDeclaration.TsInterface -> generateInterface(element) is TsDeclaration.TsNamespace -> generateNamespace(element) - is TsDeclaration.TsTypeAlias -> generateTypeAlias(element) + is TsDeclaration.TsType -> generateType(element) } } abstract fun generateEnum(enum: TsDeclaration.TsEnum): String abstract fun generateInterface(element: TsDeclaration.TsInterface): String abstract fun generateNamespace(namespace: TsDeclaration.TsNamespace): String - abstract fun generateTypeAlias(element: TsDeclaration.TsTypeAlias): String + abstract fun generateType(element: TsDeclaration.TsType): String abstract fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String abstract fun generatePrimitive(primitive: TsLiteral.Primitive): String @@ -83,42 +83,132 @@ abstract class KxsTsSourceCodeGenerator( override fun generateInterface(element: TsDeclaration.TsInterface): String { - val properties = element - .properties - .joinToString(separator = "\n") { property -> - val separator = when (property) { - is TsProperty.Optional -> "?: " - is TsProperty.Required -> ": " + 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 propertyType = generateTypeReference(property.typeRef) - // generate ` name: Type;` - // or ` name:? Type;` - "${property.name}${separator}${propertyType};" } + } + } - return buildString { - appendLine("interface ${element.id.name} {") - if (properties.isNotBlank()) { - appendLine(properties.prependIndent(config.indent)) + private fun generatePolyOpen( + element: TsDeclaration.TsInterface, + polymorphism: TsPolymorphism.Open, + ): String { + + val subInterfaceRefs = polymorphism.subclasses.map { + TsTypeRef.Declaration(it.id, 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.TsType( + element.id, + subInterfaceRefs + ) + + return listOf(subInterfaceTypeUnion, namespace).joinToString("\n\n") { + generateDeclaration(it) + } + } + + private fun generatePolyClosed( + element: TsDeclaration.TsInterface, + polymorphism: TsPolymorphism.Sealed, + ): String { + val namespaceId = element.id + val namespaceRef = TsTypeRef.Declaration(namespaceId, false) + + val subInterfaceRefs: Map = + polymorphism.subclasses.associateBy { subclass -> + TsTypeRef.Property(subclass.id, namespaceRef, false) } - append("}") + + 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, false) + + val subInterfacesWithTypeProp = subInterfaceRefs.map { (subInterfaceRef, subclass) -> + val id = TsElementId( + """ + |${discriminatorEnum.id.name}.${subInterfaceRef.id.name} + """.trimMargin() + ) + + val typeProp = TsProperty.Required( + polymorphism.discriminatorName, + TsTypeRef.Property(id, discriminatorEnumRef, false), + ) + + subclass.copy(properties = setOf(typeProp) + subclass.properties) + } + + val subInterfaceTypeUnion = TsDeclaration.TsType( + element.id, + subInterfaceRefs.keys + ) + + val namespace = TsDeclaration.TsNamespace( + namespaceId, + buildSet { + add(discriminatorEnum) + addAll(subInterfacesWithTypeProp) + } + ) + + return listOf(subInterfaceTypeUnion, namespace).joinToString("\n\n") { + generateDeclaration(it) } } -// private fun generateSealedSubInterfaces(sealed: TsPolymorphicDiscriminator.Sealed): String { -// -// val enumDiscriminator = generateEnum(sealed.discriminator) -// -// val sealedType = sealed.children.map { it } -// } - override fun generateTypeAlias(element: TsDeclaration.TsTypeAlias): String { - val aliases = generateTypeReference(element.typeRef) + override fun generateType(element: TsDeclaration.TsType): String { + val aliases = + element.typeRefs + .map { generateTypeReference(it) } + .sorted() + .joinToString(" | ") return when (config.typeAliasTyping) { KxsTsConfig.TypeAliasTypingConfig.None -> """ - |type ${element.id.name} = ${aliases}; + |export type ${element.id.name} = ${aliases}; """.trimMargin() KxsTsConfig.TypeAliasTypingConfig.BrandTyping -> { @@ -132,7 +222,7 @@ abstract class KxsTsSourceCodeGenerator( }.joinToString("") """ - |type ${element.id.name} = $aliases & { __${brandType}__: void }; + |export type ${element.id.name} = $aliases & { __${brandType}__: void }; """.trimMargin() } } @@ -145,7 +235,7 @@ abstract class KxsTsSourceCodeGenerator( */ override fun generateTypeReference(typeRef: TsTypeRef): String { val plainType: String = when (typeRef) { - is TsTypeRef.Literal -> when (typeRef.element) { + is TsTypeRef.Literal -> when (typeRef.element) { is TsLiteral.Primitive -> generatePrimitive(typeRef.element) is TsLiteral.TsList -> { val valueTypeRef = generateTypeReference(typeRef.element.valueTypeRef) @@ -153,8 +243,9 @@ abstract class KxsTsSourceCodeGenerator( } is TsLiteral.TsMap -> generateMapTypeReference(typeRef.element) } - is TsTypeRef.Named -> typeRef.id.name - is TsTypeRef.Unknown -> generateTypeReference(typeRef.ref) + is TsTypeRef.Declaration -> typeRef.id.name + is TsTypeRef.Property -> generateTypeReference(typeRef.declaration) + "." + typeRef.id.name + is TsTypeRef.Unknown -> generateTypeReference(typeRef.ref) } return buildString { diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt index 109c22e4..b9d58f28 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt @@ -14,6 +14,7 @@ fun interface TsElementConverter { operator fun invoke( context: KxsTsConvertorContext, descriptor: SerialDescriptor, + descriptorData: DescriptorData?, ): TsElement object Default : TsElementConverter { @@ -21,13 +22,28 @@ fun interface TsElementConverter { override operator fun invoke( context: KxsTsConvertorContext, descriptor: SerialDescriptor, + descriptorData: DescriptorData?, + ): TsElement { + return convertMonomorphicDescriptor(context, descriptor) + +// return when (descriptorData) { +// is DescriptorData.Polymorphic -> convertPolymorphicDescriptor( +// context, +// descriptorData, +// ) +// null, +// is DescriptorData.Monomorphic -> setOf(convertMonomorphicDescriptor(context, descriptor)) +// } + + } + + + private fun convertMonomorphicDescriptor( + context: KxsTsConvertorContext, + descriptor: SerialDescriptor, ): TsElement { return when (descriptor.kind) { SerialKind.ENUM -> convertEnum(context, descriptor) - SerialKind.CONTEXTUAL -> { - // TODO contextual - TsLiteral.Primitive.TsAny - } PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean @@ -45,15 +61,47 @@ fun interface TsElementConverter { StructureKind.MAP -> convertMap(context, descriptor) StructureKind.CLASS, - StructureKind.OBJECT, - PolymorphicKind.SEALED, - PolymorphicKind.OPEN -> when { + StructureKind.OBJECT -> when { descriptor.isInline -> convertTypeAlias(context, descriptor) - else -> convertInterface(context, descriptor) + else -> convertInterface(context, descriptor, null) } + + // TODO handle contextual + SerialKind.CONTEXTUAL -> TsLiteral.Primitive.TsAny + + PolymorphicKind.SEALED, + PolymorphicKind.OPEN -> convertPolymorphic(context, descriptor) } } + private fun convertPolymorphic( + context: KxsTsConvertorContext, + descriptor: SerialDescriptor, + ): TsDeclaration { + + val discriminatorIndex = descriptor.elementDescriptors + .indexOfFirst { it.kind == PrimitiveKind.STRING } + val discriminatorName = descriptor.getElementName(discriminatorIndex) + + val subclasses = descriptor + .elementDescriptors + .first { it.kind == SerialKind.CONTEXTUAL } + .elementDescriptors + + val subclassInterfaces = subclasses + .map { convertMonomorphicDescriptor(context, it) } + .filterIsInstance() + .toSet() + + val polymorphism = when (descriptor.kind) { + PolymorphicKind.SEALED -> TsPolymorphism.Sealed(discriminatorName, subclassInterfaces) + PolymorphicKind.OPEN -> TsPolymorphism.Open(discriminatorName, subclassInterfaces) + else -> error("unexpected SerialKind ${descriptor.kind}") // TODO 'else' branch shouldn't be needed + } + + return convertInterface(context, descriptor, polymorphism) + } + private fun convertTypeAlias( context: KxsTsConvertorContext, @@ -62,14 +110,14 @@ fun interface TsElementConverter { val resultId = context.elementId(structDescriptor) val fieldDescriptor = structDescriptor.elementDescriptors.first() val fieldTypeRef = context.typeRef(fieldDescriptor) - return TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) - + return TsDeclaration.TsType(resultId, fieldTypeRef) } private fun convertInterface( context: KxsTsConvertorContext, structDescriptor: SerialDescriptor, + polymorphism: TsPolymorphism?, ): TsDeclaration { val resultId = context.elementId(structDescriptor) @@ -81,7 +129,7 @@ fun interface TsElementConverter { else -> TsProperty.Required(name, fieldTypeRef) } }.toSet() - return TsDeclaration.TsInterface(resultId, properties, TsPolymorphicDiscriminator.Open) + return TsDeclaration.TsInterface(resultId, properties, polymorphism) } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt index 842455eb..ee163ab5 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt @@ -58,7 +58,7 @@ fun interface TsTypeRefConverter { StructureKind.CLASS, StructureKind.OBJECT -> { val id = context.elementId(descriptor) - TsTypeRef.Named(id, descriptor.isNullable) + TsTypeRef.Declaration(id, descriptor.isNullable) } } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt index d22d2404..7bee6a12 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt @@ -39,11 +39,10 @@ fun interface SerializerDescriptorsExtractor { ): Set { return allDescriptorData.flatMap { descriptorData -> when (descriptorData) { - is DescriptorData.Polymorphic -> descriptorData.subclasses // only yield subclasses + is DescriptorData.Polymorphic -> listOf(descriptorData.descriptor) is DescriptorData.Monomorphic -> listOf(descriptorData.descriptor) } }.flatMap { descriptor -> extractedDescriptors.getValue(descriptor) } - .distinct() .toSet() } @@ -122,11 +121,21 @@ fun interface DescriptorDataProcessor { subclasses = config.polymorphicDescriptors.getValue(serializer.baseClass) ) - is SealedClassSerializer<*> -> + is SealedClassSerializer<*> -> { + + val subclasses = serializer.descriptor + .elementDescriptors + .toList() + .first { it.kind == SerialKind.CONTEXTUAL } + .elementDescriptors + .filter { it.kind == StructureKind.CLASS } + .toSet() + DescriptorData.Polymorphic.Closed( descriptor = serializer.descriptor, - subclasses = extractSealedSubclassSerializers(serializer).map { it.descriptor }.toSet(), + subclasses = subclasses, ) + } else -> DescriptorData.Monomorphic(serializer.descriptor) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt index eea6be94..da5f8203 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt @@ -18,16 +18,19 @@ sealed interface TsElement sealed interface TsDeclaration : TsElement { val id: TsElementId - data class TsTypeAlias( + data class TsType( override val id: TsElementId, - val typeRef: TsTypeRef, - ) : TsDeclaration + val typeRefs: Set, + ) : TsDeclaration { + constructor(id: TsElementId, typeRef: TsTypeRef, vararg typeRefs: TsTypeRef) : + this(id, typeRefs.toSet() + typeRef) + } data class TsInterface( override val id: TsElementId, val properties: Set, - val polymorphism: TsPolymorphicDiscriminator, + val polymorphism: TsPolymorphism?, ) : TsDeclaration @@ -87,7 +90,17 @@ sealed interface TsTypeRef { data class Literal(val element: TsLiteral, override val nullable: Boolean) : TsTypeRef - data class Named(val id: TsElementId, override val nullable: Boolean) : TsTypeRef + data class Declaration( + val id: TsElementId, + override val nullable: Boolean, + ) : TsTypeRef + + /** A property within another declaration (e.g. an enum value, or type within a namespace) */ + data class Property( + val id: TsElementId, + val declaration: Declaration, + override val nullable: Boolean, + ) : TsTypeRef object Unknown : TsTypeRef { val ref: Literal = Literal(TsLiteral.Primitive.TsUnknown, false) @@ -114,16 +127,23 @@ sealed interface TsProperty { } -// source: kxs & introspection -sealed interface TsPolymorphicDiscriminator { - // source: introspection +sealed interface TsPolymorphism { + + val discriminatorName: String + val subclasses: Set + data class Sealed( - // source: kxs - val discriminator: TsDeclaration.TsEnum, - // source: introspection - val children: Set = setOf(), - ) : TsPolymorphicDiscriminator - - // source: introspection - object Open : TsPolymorphicDiscriminator + override val discriminatorName: String, + override val subclasses: Set, + ) : TsPolymorphism + + data class Open( + override val discriminatorName: String, + override val subclasses: Set, + ) : TsPolymorphism + +// object None : TsPolymorphism { +// override val discriminatorName: Nothing = error("not implemented") +// override val subclasses: Nothing = error("not implemented") +// } } diff --git a/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/serializerExtractorsJvm.kt b/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/serializerExtractorsJvm.kt index fe39f2f4..54c925d5 100644 --- a/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/serializerExtractorsJvm.kt +++ b/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/serializerExtractorsJvm.kt @@ -2,7 +2,8 @@ package dev.adamko.kxstsgen import kotlin.reflect.KClass -import kotlin.reflect.KProperty1 +import kotlin.reflect.* +import kotlin.reflect.full.declaredMemberFunctions import kotlin.reflect.full.declaredMemberProperties import kotlin.reflect.full.functions import kotlin.reflect.jvm.isAccessible @@ -43,10 +44,12 @@ actual fun extractContextualDescriptor( /** Access the private `.serializer()` function in [ContextualSerializer] */ @Suppress("UNCHECKED_CAST") -private val contextualSerializerAccessor: (SerializersModule) -> KSerializer<*> = - ContextualSerializer::class - .functions +private val contextualSerializerAccessor: KFunction1> = + (ContextualSerializer::class + .declaredMemberFunctions .firstOrNull { it.name == "serializer" } ?.apply { isAccessible = true } - as? (SerializersModule) -> KSerializer<*> + as KFunction1> + ) ?: error("Can't access ContextualSerializer.serializer()") +// as (SerializersModule) -> KSerializer<*> From 9faff4af9fc730228717d14b82f4b25c158134e0 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sat, 2 Apr 2022 19:26:13 +0200 Subject: [PATCH 18/46] some Sealed interfaces and type ref improvements --- .../KxsTsSourceCodeGenerator.kt | 53 ++++++++++++++----- .../dev.adamko.kxstsgen/TsElementConverter.kt | 13 ++--- .../dev.adamko.kxstsgen/TsTypeRefConverter.kt | 2 +- .../kotlin/dev.adamko.kxstsgen/tsElements.kt | 15 +++--- 4 files changed, 57 insertions(+), 26 deletions(-) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index a1258ce8..f2861c84 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -25,9 +25,16 @@ abstract class KxsTsSourceCodeGenerator( abstract fun generateNamespace(namespace: TsDeclaration.TsNamespace): String abstract fun generateType(element: TsDeclaration.TsType): String - abstract fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String + abstract fun generateMapTypeReference( +// requestorId: TsElementId?, + tsMap: TsLiteral.TsMap, + ): String + abstract fun generatePrimitive(primitive: TsLiteral.Primitive): String - abstract fun generateTypeReference(typeRef: TsTypeRef): String + abstract fun generateTypeReference( +// rootId: TsElementId?, + typeRef: TsTypeRef, + ): String open class Default( config: KxsTsConfig, @@ -115,9 +122,11 @@ abstract class KxsTsSourceCodeGenerator( 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, false) + TsTypeRef.Declaration(it.id, namespaceRef, false) }.toSet() val discriminatorProperty = TsProperty.Required( @@ -150,11 +159,12 @@ abstract class KxsTsSourceCodeGenerator( polymorphism: TsPolymorphism.Sealed, ): String { val namespaceId = element.id - val namespaceRef = TsTypeRef.Declaration(namespaceId, false) + val namespaceRef = TsTypeRef.Declaration(namespaceId, null, false) - val subInterfaceRefs: Map = + val subInterfaceRefs: Map = polymorphism.subclasses.associateBy { subclass -> - TsTypeRef.Property(subclass.id, namespaceRef, false) + val subclassId = TsElementId(namespaceId.toString() + "." + subclass.id.name) + TsTypeRef.Declaration(subclassId, namespaceRef, false) } val discriminatorEnum = TsDeclaration.TsEnum( @@ -162,10 +172,10 @@ abstract class KxsTsSourceCodeGenerator( subInterfaceRefs.keys.map { it.id.name }.toSet(), ) - val discriminatorEnumRef = TsTypeRef.Declaration(discriminatorEnum.id, false) + val discriminatorEnumRef = TsTypeRef.Declaration(discriminatorEnum.id, namespaceRef, false) val subInterfacesWithTypeProp = subInterfaceRefs.map { (subInterfaceRef, subclass) -> - val id = TsElementId( + val typePropId = TsElementId( """ |${discriminatorEnum.id.name}.${subInterfaceRef.id.name} """.trimMargin() @@ -173,7 +183,7 @@ abstract class KxsTsSourceCodeGenerator( val typeProp = TsProperty.Required( polymorphism.discriminatorName, - TsTypeRef.Property(id, discriminatorEnumRef, false), + TsTypeRef.Declaration(typePropId, discriminatorEnumRef, false), ) subclass.copy(properties = setOf(typeProp) + subclass.properties) @@ -233,18 +243,32 @@ abstract class KxsTsSourceCodeGenerator( * A type-reference, be it for the field of an interface, a type alias, or a generic type * constraint. */ - override fun generateTypeReference(typeRef: TsTypeRef): String { + override fun generateTypeReference( +// rootId: TsElementId?, + typeRef: TsTypeRef, + ): String { val plainType: String = when (typeRef) { is TsTypeRef.Literal -> when (typeRef.element) { is TsLiteral.Primitive -> generatePrimitive(typeRef.element) + is TsLiteral.TsList -> { val valueTypeRef = generateTypeReference(typeRef.element.valueTypeRef) "$valueTypeRef[]" } + is TsLiteral.TsMap -> generateMapTypeReference(typeRef.element) } - is TsTypeRef.Declaration -> typeRef.id.name - is TsTypeRef.Property -> generateTypeReference(typeRef.declaration) + "." + typeRef.id.name + + is TsTypeRef.Declaration -> { + + if (typeRef.parent != null) { + val parentRef = generateTypeReference(typeRef.parent) + "${parentRef}.${typeRef.id.name}" + } else { + typeRef.id.name + } + } + is TsTypeRef.Unknown -> generateTypeReference(typeRef.ref) } @@ -274,7 +298,10 @@ abstract class KxsTsSourceCodeGenerator( } - override fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String { + override fun generateMapTypeReference( +// rootId: TsElementId?, + tsMap: TsLiteral.TsMap + ): String { val keyTypeRef = generateTypeReference(tsMap.keyTypeRef) val valueTypeRef = generateTypeReference(tsMap.valueTypeRef) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt index b9d58f28..f654496e 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt @@ -91,6 +91,7 @@ fun interface TsElementConverter { val subclassInterfaces = subclasses .map { convertMonomorphicDescriptor(context, it) } .filterIsInstance() + .map { it.copy(id = TsElementId("${descriptor.serialName}.${it.id.name}")) } .toSet() val polymorphism = when (descriptor.kind) { @@ -116,17 +117,17 @@ fun interface TsElementConverter { private fun convertInterface( context: KxsTsConvertorContext, - structDescriptor: SerialDescriptor, + descriptor: SerialDescriptor, polymorphism: TsPolymorphism?, ): TsDeclaration { - val resultId = context.elementId(structDescriptor) + val resultId = context.elementId(descriptor) - val properties = structDescriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> - val name = structDescriptor.getElementName(index) + val properties = descriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> + val name = descriptor.getElementName(index) val fieldTypeRef = context.typeRef(fieldDescriptor) when { - structDescriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeRef) - else -> TsProperty.Required(name, fieldTypeRef) + descriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeRef) + else -> TsProperty.Required(name, fieldTypeRef) } }.toSet() return TsDeclaration.TsInterface(resultId, properties, polymorphism) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt index ee163ab5..4f854a76 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt @@ -58,7 +58,7 @@ fun interface TsTypeRefConverter { StructureKind.CLASS, StructureKind.OBJECT -> { val id = context.elementId(descriptor) - TsTypeRef.Declaration(id, descriptor.isNullable) + TsTypeRef.Declaration(id, null, descriptor.isNullable) } } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt index da5f8203..f60b1504 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt @@ -9,6 +9,8 @@ value class TsElementId(private val id: String) { get() = id.substringBeforeLast(".") val name: String get() = id.substringAfterLast(".") + + override fun toString(): String = id } @@ -92,15 +94,16 @@ sealed interface TsTypeRef { data class Declaration( val id: TsElementId, + val parent: Declaration?, override val nullable: Boolean, ) : TsTypeRef - /** A property within another declaration (e.g. an enum value, or type within a namespace) */ - data class Property( - val id: TsElementId, - val declaration: Declaration, - override val nullable: Boolean, - ) : TsTypeRef +// /** A property within another declaration (e.g. an enum value, or type within a namespace) */ +// data class Property( +// val id: TsElementId, +// val declaration: Declaration, +// override val nullable: Boolean, +// ) : TsTypeRef object Unknown : TsTypeRef { val ref: Literal = Literal(TsLiteral.Primitive.TsUnknown, false) From 52e0e25e786ddbf1d5290b304293933486c89fac Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 10:39:20 +0200 Subject: [PATCH 19/46] temp fix for open poly classes --- docs/polymorphism.md | 32 ++++++++++--------- .../dev.adamko.kxstsgen/TsElementConverter.kt | 30 ++++++++++------- 2 files changed, 35 insertions(+), 27 deletions(-) diff --git a/docs/polymorphism.md b/docs/polymorphism.md index 262cf2f8..021bb458 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -45,13 +45,14 @@ fun main() { > You can get the full code [here](./knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt). ```typescript -export interface SimpleTypes { - aString: string; - anInt: number; - aDouble: number; - bool: boolean; - privateMember: string; -} +export type SimpleTypes = any; +// export interface SimpleTypes { +// aString: string; +// anInt: number; +// aDouble: number; +// bool: boolean; +// privateMember: string; +// } ``` @@ -112,14 +113,15 @@ fun main() { > You can get the full code [here](./knit/example/example-polymorphic-static-types-02.kt). ```typescript -export interface Project { - name: string; -} - -export interface OwnedProject extends Project { - name: string; - owner: string; -} +export type Project = any; +// export interface Project { +// name: string; +// } +// +// export interface OwnedProject extends Project { +// name: string; +// owner: string; +// } ``` diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt index f654496e..56add068 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt @@ -43,34 +43,40 @@ fun interface TsElementConverter { descriptor: SerialDescriptor, ): TsElement { return when (descriptor.kind) { - SerialKind.ENUM -> convertEnum(context, descriptor) + SerialKind.ENUM -> convertEnum(context, descriptor) - PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean + PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean PrimitiveKind.CHAR, - PrimitiveKind.STRING -> TsLiteral.Primitive.TsString + PrimitiveKind.STRING -> TsLiteral.Primitive.TsString PrimitiveKind.BYTE, PrimitiveKind.SHORT, PrimitiveKind.INT, PrimitiveKind.LONG, PrimitiveKind.FLOAT, - PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber + PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber - StructureKind.LIST -> convertList(context, descriptor) - StructureKind.MAP -> convertMap(context, descriptor) + StructureKind.LIST -> convertList(context, descriptor) + StructureKind.MAP -> convertMap(context, descriptor) StructureKind.CLASS, - StructureKind.OBJECT -> when { + StructureKind.OBJECT -> when { descriptor.isInline -> convertTypeAlias(context, descriptor) else -> convertInterface(context, descriptor, null) } - // TODO handle contextual - SerialKind.CONTEXTUAL -> TsLiteral.Primitive.TsAny - PolymorphicKind.SEALED, - PolymorphicKind.OPEN -> convertPolymorphic(context, descriptor) + PolymorphicKind.SEALED -> convertPolymorphic(context, descriptor) + + // TODO handle contextual + // TODO handle polymorphic open + SerialKind.CONTEXTUAL, + PolymorphicKind.OPEN -> { + val resultId = context.elementId(descriptor) + val fieldTypeRef = TsTypeRef.Literal(TsLiteral.Primitive.TsAny, false) + TsDeclaration.TsType(resultId, fieldTypeRef) + } } } @@ -97,7 +103,7 @@ fun interface TsElementConverter { val polymorphism = when (descriptor.kind) { PolymorphicKind.SEALED -> TsPolymorphism.Sealed(discriminatorName, subclassInterfaces) PolymorphicKind.OPEN -> TsPolymorphism.Open(discriminatorName, subclassInterfaces) - else -> error("unexpected SerialKind ${descriptor.kind}") // TODO 'else' branch shouldn't be needed + else -> error("Can't convert non-polymorphic SerialKind ${descriptor.kind} to polymorphic interface") } return convertInterface(context, descriptor, polymorphism) From ed28d6dbfab6bdfc447105e89ba213374c9b2ee2 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 10:41:19 +0200 Subject: [PATCH 20/46] normalize knit code comparisons --- docs/knit.properties | 2 +- docs/knit/build.gradle.kts | 6 +- docs/knit/knit-test.ftl | 4 +- docs/knit/test/AbstractClassesTest.kt | 45 +++---- docs/knit/test/BasicClassesTest.kt | 21 ++-- docs/knit/test/DefaultValuesTest.kt | 11 +- docs/knit/test/EdgeCasesTest.kt | 16 ++- docs/knit/test/EnumClassTest.kt | 11 +- docs/knit/test/ListsTests.kt | 6 +- docs/knit/test/MapsTests.kt | 21 ++-- docs/knit/test/PolymorphismTest.kt | 164 +++++++++++++++----------- docs/knit/test/ValueClassesTest.kt | 21 ++-- docs/knit/util/strings.kt | 20 ++++ 13 files changed, 193 insertions(+), 155 deletions(-) create mode 100644 docs/knit/util/strings.kt diff --git a/docs/knit.properties b/docs/knit.properties index ab488d8c..c5287279 100644 --- a/docs/knit.properties +++ b/docs/knit.properties @@ -6,4 +6,4 @@ test.package=dev.adamko.kxstsgen.example.test test.template=./knit/knit-test.ftl test.language=typescript knit.include=./knit/knit-include.ftl -test.mode.=joinToString(\"\\n\") { it.ifBlank { "" } }\n .shouldBe +test.mode.=normalizeJoin()\n .shouldBe diff --git a/docs/knit/build.gradle.kts b/docs/knit/build.gradle.kts index dd685be5..751b5b6a 100644 --- a/docs/knit/build.gradle.kts +++ b/docs/knit/build.gradle.kts @@ -31,7 +31,11 @@ tasks.withType { } sourceSets.test { - java.srcDirs("example", "test") + java.srcDirs( + "example", + "test", + "util", + ) } knit { diff --git a/docs/knit/knit-test.ftl b/docs/knit/knit-test.ftl index 84baec7e..78bac9ff 100644 --- a/docs/knit/knit-test.ftl +++ b/docs/knit/knit-test.ftl @@ -7,6 +7,7 @@ package ${test.package} import io.kotest.matchers.* import kotlinx.knit.test.* import org.junit.jupiter.api.Test +import dev.adamko.kxstsgen.util.* class ${test.name} { <#list cases as case><#assign method = test["mode.${case.param}"]!"custom"> @@ -21,8 +22,7 @@ class ${test.name} { |${line} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) <#else>.also { lines -> check(${case.param}) diff --git a/docs/knit/test/AbstractClassesTest.kt b/docs/knit/test/AbstractClassesTest.kt index 05f93f95..3c71a4aa 100644 --- a/docs/knit/test/AbstractClassesTest.kt +++ b/docs/knit/test/AbstractClassesTest.kt @@ -5,22 +5,23 @@ 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 AbstractClassesTest { @Test fun testExampleAbstractClassSingleField01() { captureOutput("ExampleAbstractClassSingleField01") { dev.adamko.kxstsgen.example.exampleAbstractClassSingleField01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ - |interface Color { - | rgb: number; - |} + |export type Color = any; + |// interface Color { + |// rgb: number; + |// } """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -28,20 +29,20 @@ class AbstractClassesTest { fun testExampleAbstractClassPrimitiveFields01() { captureOutput("ExampleAbstractClassPrimitiveFields01") { dev.adamko.kxstsgen.example.exampleAbstractClassPrimitiveFields01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ - |export interface SimpleTypes { - | aString: string; - | anInt: number; - | aDouble: number; - | bool: boolean; - | privateMember: string; - |} + |export type SimpleTypes = any; + |// export interface SimpleTypes { + |// aString: string; + |// anInt: number; + |// aDouble: number; + |// bool: boolean; + |// privateMember: string; + |// } """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -49,16 +50,16 @@ class AbstractClassesTest { fun testExampleAbstractClassAbstractField01() { captureOutput("ExampleAbstractClassAbstractField01") { dev.adamko.kxstsgen.example.exampleAbstractClassAbstractField01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ - |export interface Color { - | rgb: number; - |} + |export type AbstractSimpleTypes = any; + |// export interface AbstractSimpleTypes { + |// rgb: number; + |// } """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } } diff --git a/docs/knit/test/BasicClassesTest.kt b/docs/knit/test/BasicClassesTest.kt index 60ec35c2..9daaa133 100644 --- a/docs/knit/test/BasicClassesTest.kt +++ b/docs/knit/test/BasicClassesTest.kt @@ -5,13 +5,14 @@ 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 BasicClassesTest { @Test fun testExamplePlainClassSingleField01() { captureOutput("ExamplePlainClassSingleField01") { dev.adamko.kxstsgen.example.examplePlainClassSingleField01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -19,8 +20,7 @@ class BasicClassesTest { | rgb: number; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -28,7 +28,7 @@ class BasicClassesTest { fun testExamplePlainClassPrimitiveFields01() { captureOutput("ExamplePlainClassPrimitiveFields01") { dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -40,8 +40,7 @@ class BasicClassesTest { | privateMember: string; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -49,7 +48,7 @@ class BasicClassesTest { fun testExamplePlainDataClass01() { captureOutput("ExamplePlainDataClass01") { dev.adamko.kxstsgen.example.examplePlainDataClass01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -61,8 +60,7 @@ class BasicClassesTest { | privateMember: string; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -70,15 +68,14 @@ class BasicClassesTest { fun testExamplePlainClassPrimitiveFields02() { captureOutput("ExamplePlainClassPrimitiveFields02") { dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields02.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ |export interface SimpleTypes { |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } } diff --git a/docs/knit/test/DefaultValuesTest.kt b/docs/knit/test/DefaultValuesTest.kt index ce974bbf..7880ead5 100644 --- a/docs/knit/test/DefaultValuesTest.kt +++ b/docs/knit/test/DefaultValuesTest.kt @@ -5,13 +5,14 @@ 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 DefaultValuesTest { @Test fun testExampleDefaultValuesSingleField01() { captureOutput("ExampleDefaultValuesSingleField01") { dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -19,8 +20,7 @@ class DefaultValuesTest { | rgb?: number; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -28,7 +28,7 @@ class DefaultValuesTest { fun testExampleDefaultValuesPrimitiveFields01() { captureOutput("ExampleDefaultValuesPrimitiveFields01") { dev.adamko.kxstsgen.example.exampleDefaultValuesPrimitiveFields01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -38,8 +38,7 @@ class DefaultValuesTest { | active?: boolean | null; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } } diff --git a/docs/knit/test/EdgeCasesTest.kt b/docs/knit/test/EdgeCasesTest.kt index d83052f0..09d30de0 100644 --- a/docs/knit/test/EdgeCasesTest.kt +++ b/docs/knit/test/EdgeCasesTest.kt @@ -5,13 +5,14 @@ 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 EdgeCasesTest { @Test fun testExampleEdgecaseRecursiveReferences01() { captureOutput("ExampleEdgecaseRecursiveReferences01") { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -23,8 +24,7 @@ class EdgeCasesTest { | a: A; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -32,7 +32,7 @@ class EdgeCasesTest { fun testExampleEdgecaseRecursiveReferences02() { captureOutput("ExampleEdgecaseRecursiveReferences02") { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences02.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -44,8 +44,7 @@ class EdgeCasesTest { | list: A[]; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -53,7 +52,7 @@ class EdgeCasesTest { fun testExampleEdgecaseRecursiveReferences03() { captureOutput("ExampleEdgecaseRecursiveReferences03") { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences03.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -65,8 +64,7 @@ class EdgeCasesTest { | map: { [key: string]: A }; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } } diff --git a/docs/knit/test/EnumClassTest.kt b/docs/knit/test/EnumClassTest.kt index 31de1465..0d1f82ba 100644 --- a/docs/knit/test/EnumClassTest.kt +++ b/docs/knit/test/EnumClassTest.kt @@ -5,13 +5,14 @@ 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 EnumClassTest { @Test fun testExampleEnumClass01() { captureOutput("ExampleEnumClass01") { dev.adamko.kxstsgen.example.exampleEnumClass01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -21,8 +22,7 @@ class EnumClassTest { | Gamma = "Gamma", |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -30,7 +30,7 @@ class EnumClassTest { fun testExampleEnumClass02() { captureOutput("ExampleEnumClass02") { dev.adamko.kxstsgen.example.exampleEnumClass02.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -40,8 +40,7 @@ class EnumClassTest { | Gamma = "Gamma", |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } } diff --git a/docs/knit/test/ListsTests.kt b/docs/knit/test/ListsTests.kt index b10e570f..8ab6deb0 100644 --- a/docs/knit/test/ListsTests.kt +++ b/docs/knit/test/ListsTests.kt @@ -5,13 +5,14 @@ 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 ListsTests { @Test fun testExampleListPrimitive01() { captureOutput("ExampleListPrimitive01") { dev.adamko.kxstsgen.example.exampleListPrimitive01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -21,8 +22,7 @@ class ListsTests { | longs: number[]; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } } diff --git a/docs/knit/test/MapsTests.kt b/docs/knit/test/MapsTests.kt index 59bc30f7..643c8c2f 100644 --- a/docs/knit/test/MapsTests.kt +++ b/docs/knit/test/MapsTests.kt @@ -5,13 +5,14 @@ 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 MapsTests { @Test fun testExampleMapPrimitive01() { captureOutput("ExampleMapPrimitive01") { dev.adamko.kxstsgen.example.exampleMapPrimitive01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -19,8 +20,7 @@ class MapsTests { | properties: { [key: string]: string }; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -28,7 +28,7 @@ class MapsTests { fun testExampleMapPrimitive02() { captureOutput("ExampleMapPrimitive02") { dev.adamko.kxstsgen.example.exampleMapPrimitive02.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -41,8 +41,7 @@ class MapsTests { | MAX_MEMORY = "MAX_MEMORY", |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -50,7 +49,7 @@ class MapsTests { fun testExampleMapPrimitive03() { captureOutput("ExampleMapPrimitive03") { dev.adamko.kxstsgen.example.exampleMapPrimitive03.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -58,8 +57,7 @@ class MapsTests { | properties: { [key: string | null]: string | null }; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -67,7 +65,7 @@ class MapsTests { fun testExampleMapComplex01() { captureOutput("ExampleMapComplex01") { dev.adamko.kxstsgen.example.exampleMapComplex01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -84,8 +82,7 @@ class MapsTests { | |export type UByte = number; """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } } diff --git a/docs/knit/test/PolymorphismTest.kt b/docs/knit/test/PolymorphismTest.kt index 2dba4bbc..271f41ec 100644 --- a/docs/knit/test/PolymorphismTest.kt +++ b/docs/knit/test/PolymorphismTest.kt @@ -5,26 +5,27 @@ 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 PolymorphismTest { @Test fun testExamplePolymorphicAbstractClassPrimitiveFields01() { captureOutput("ExamplePolymorphicAbstractClassPrimitiveFields01") { dev.adamko.kxstsgen.example.examplePolymorphicAbstractClassPrimitiveFields01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ - |export interface SimpleTypes { - | aString: string; - | anInt: number; - | aDouble: number; - | bool: boolean; - | privateMember: string; - |} + |export type SimpleTypes = any; + |// export interface SimpleTypes { + |// aString: string; + |// anInt: number; + |// aDouble: number; + |// bool: boolean; + |// privateMember: string; + |// } """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -32,7 +33,7 @@ class PolymorphismTest { fun testExamplePolymorphicStaticTypes01() { captureOutput("ExamplePolymorphicStaticTypes01") { dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -40,8 +41,7 @@ class PolymorphismTest { | name: string; |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -49,21 +49,21 @@ class PolymorphismTest { fun testExamplePolymorphicStaticTypes02() { captureOutput("ExamplePolymorphicStaticTypes02") { dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes02.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ - |export interface Project { - | name: string; - |} - | - |export interface OwnedProject extends Project { - | name: string; - | owner: string; - |} + |export type Project = any; + |// export interface Project { + |// name: string; + |// } + |// + |// export interface OwnedProject extends Project { + |// name: string; + |// owner: string; + |// } """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -71,7 +71,7 @@ class PolymorphismTest { fun testExamplePolymorphicSealedClass01() { captureOutput("ExamplePolymorphicSealedClass01") { dev.adamko.kxstsgen.example.examplePolymorphicSealedClass01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -84,20 +84,19 @@ class PolymorphismTest { | } | | export interface OProj { - | type: Type.OProj; + | type: Project.Type.OProj; | name: string; | owner: string; | } | | export interface DeprecatedProject { - | type: Type.DeprecatedProject; + | type: Project.Type.DeprecatedProject; | name: string; | reason: string; | } |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -105,50 +104,76 @@ class PolymorphismTest { fun testExamplePolymorphicSealedClass02() { captureOutput("ExamplePolymorphicSealedClass02") { dev.adamko.kxstsgen.example.examplePolymorphicSealedClass02.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ - |export type Dog = Dog.Mutt | Dog.Retriever + |export type Dog = Dog.Golden | Dog.Mutt | Dog.NovaScotia; | |export namespace Dog { - | | export enum Type { | Mutt = "Mutt", + | Golden = "Golden", + | NovaScotia = "NovaScotia", | } | | export interface Mutt { - | type: Type.Mutt; + | type: Dog.Type.Mutt; | name: string; | loveable?: boolean; | } | - | export type Retriever = Retriever.Golden | Retriever.NovaScotia - | - | export namespace Retriever { - | - | export enum Type { - | Golden = "Golden", - | NovaScotia = "NovaScotia", - | } - | - | export interface Golden { - | type: Type.Golden; - | name: string; - | cute?: boolean; - | } - | - | export interface NovaScotia { - | type: Type.NovaScotia; - | name: string; - | adorable?: boolean; - | } + | export interface Golden { + | type: Dog.Type.Golden; + | name: string; + | colour: string; + | cute?: boolean; | } | + | export interface NovaScotia { + | type: Dog.Type.NovaScotia; + | name: string; + | colour: string; + | adorable?: boolean; + | } |} + |// Nested sealed classes don't work at the moment :( + |// export type Dog = Dog.Mutt | Dog.Retriever + |// + |// export namespace Dog { + |// export enum Type { + |// Mutt = "Mutt", + |// } + |// + |// export interface Mutt { + |// type: Type.Mutt; + |// name: string; + |// loveable?: boolean; + |// } + |// + |// export type Retriever = Retriever.Golden | Retriever.NovaScotia + |// + |// export namespace Retriever { + |// export enum Type { + |// Golden = "Golden", + |// NovaScotia = "NovaScotia", + |// } + |// + |// export interface Golden { + |// type: Type.Golden; + |// name: string; + |// cute?: boolean; + |// } + |// + |// export interface NovaScotia { + |// type: Type.NovaScotia; + |// name: string; + |// adorable?: boolean; + |// } + |// } + |// } """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -156,28 +181,29 @@ class PolymorphismTest { fun testExamplePolymorphicObjects01() { captureOutput("ExamplePolymorphicObjects01") { dev.adamko.kxstsgen.example.examplePolymorphicObjects01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ - |export enum ResponseKind { - | EmptyResponse = "EmptyResponse", - | TextResponse = "TextResponse", - |} + |export type Response = Response.EmptyResponse | Response.TextResponse; | - |export type Response = EmptyResponse | TextResponse + |export namespace Response { + | export enum Type { + | EmptyResponse = "EmptyResponse", + | TextResponse = "TextResponse", + | } | - |export interface EmptyResponse { - | type: ResponseKind.EmptyResponse; - |} + | export interface EmptyResponse { + | type: Response.Type.EmptyResponse; + | } | - |export interface TextResponse { - | type: ResponseKind.TextResponse; - | text: string; + | export interface TextResponse { + | type: Response.Type.TextResponse; + | text: string; + | } |} """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } } diff --git a/docs/knit/test/ValueClassesTest.kt b/docs/knit/test/ValueClassesTest.kt index 7402095e..845c6b6f 100644 --- a/docs/knit/test/ValueClassesTest.kt +++ b/docs/knit/test/ValueClassesTest.kt @@ -5,20 +5,20 @@ 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 ValueClassesTest { @Test fun testExampleValueClasses01() { captureOutput("ExampleValueClasses01") { dev.adamko.kxstsgen.example.exampleValueClasses01.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ |export type AuthToken = string; """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -26,7 +26,7 @@ class ValueClassesTest { fun testExampleValueClasses02() { captureOutput("ExampleValueClasses02") { dev.adamko.kxstsgen.example.exampleValueClasses02.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -38,8 +38,7 @@ class ValueClassesTest { | |export type ULong = number; """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -47,14 +46,13 @@ class ValueClassesTest { fun testExampleValueClasses03() { captureOutput("ExampleValueClasses03") { dev.adamko.kxstsgen.example.exampleValueClasses03.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ |export type ULong = number & { __ULong__: void }; """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } @@ -62,7 +60,7 @@ class ValueClassesTest { fun testExampleValueClasses04() { captureOutput("ExampleValueClasses04") { dev.adamko.kxstsgen.example.exampleValueClasses04.main() - }.joinToString("\n") { it.ifBlank { "" } } + }.normalizeJoin() .shouldBe( // language=TypeScript """ @@ -70,8 +68,7 @@ class ValueClassesTest { | |export type UInt = number; """.trimMargin() - .lines() - .joinToString("\n") { it.ifBlank { "" } } + .normalize() ) } } diff --git a/docs/knit/util/strings.kt b/docs/knit/util/strings.kt new file mode 100644 index 00000000..08a8fa76 --- /dev/null +++ b/docs/knit/util/strings.kt @@ -0,0 +1,20 @@ +package dev.adamko.kxstsgen.util + +/** + * * filter out lines that are `//` comments + * * convert whitespace-only lines to an empty string + * * remove `//` comments + * * remove trailing whitespace + */ +fun String.normalize(): String = + lines() + .filterNot { it.trimStart().startsWith("//") } + .joinToString("\n") { + it.substringBeforeLast("//") + .trimEnd() + .ifBlank { "" } + } + +/** [normalize] each String, then [join][joinToString] them */ +fun Iterable.normalizeJoin(): String = + joinToString("\n") { it.normalize() } From 44d01c122f41da3550aac08e8cbbde53f5a20d4a Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 10:41:46 +0200 Subject: [PATCH 21/46] update tests after temp fix for open poly classes --- docs/abstract-classes.md | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/docs/abstract-classes.md b/docs/abstract-classes.md index e90bfe69..10fb0678 100644 --- a/docs/abstract-classes.md +++ b/docs/abstract-classes.md @@ -34,9 +34,10 @@ fun main() { > You can get the full code [here](./knit/example/example-abstract-class-single-field-01.kt). ```typescript -interface Color { - rgb: number; -} +export type Color = any; +// interface Color { +// rgb: number; +// } ``` @@ -62,13 +63,14 @@ fun main() { > You can get the full code [here](./knit/example/example-abstract-class-primitive-fields-01.kt). ```typescript -export interface SimpleTypes { - aString: string; - anInt: number; - aDouble: number; - bool: boolean; - privateMember: string; -} +export type SimpleTypes = any; +// export interface SimpleTypes { +// aString: string; +// anInt: number; +// aDouble: number; +// bool: boolean; +// privateMember: string; +// } ``` @@ -93,9 +95,10 @@ fun main() { > You can get the full code [here](./knit/example/example-abstract-class-abstract-field-01.kt). ```typescript -export interface Color { - rgb: number; -} +export type AbstractSimpleTypes = any; +// export interface AbstractSimpleTypes { +// rgb: number; +// } ``` From 053ff3e55c085e075fddaadc5effa5d9276bf099 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 10:42:05 +0200 Subject: [PATCH 22/46] update sealed poly tests --- .../example/example-polymorphic-objects-01.kt | 5 +- docs/polymorphism.md | 120 ++++++++++++------ 2 files changed, 79 insertions(+), 46 deletions(-) diff --git a/docs/knit/example/example-polymorphic-objects-01.kt b/docs/knit/example/example-polymorphic-objects-01.kt index 6cc50be3..30fce999 100644 --- a/docs/knit/example/example-polymorphic-objects-01.kt +++ b/docs/knit/example/example-polymorphic-objects-01.kt @@ -17,9 +17,6 @@ class TextResponse(val text: String) : Response() fun main() { val tsGenerator = KxsTsGenerator() println( - tsGenerator.generate( - EmptyResponse.serializer(), - TextResponse.serializer(), - ) + tsGenerator.generate(Response.serializer()) ) } diff --git a/docs/polymorphism.md b/docs/polymorphism.md index 021bb458..5db64c38 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -128,7 +128,14 @@ export type Project = any; ### Sealed classes -https://www.typescriptlang.org/docs/handbook/enums.html#union-enums-and-enum-member-types +Sealed classes are the best way to generate TypeScript interface so far, because all subclasses are +known at compile-time. + +A sealed class will be converted as a +[union enum, with enum member types](https://www.typescriptlang.org/docs/handbook/enums.html#union-enums-and-enum-member-types) +. + +This has many benefits that closely match how sealed classes work in Kotlin. ```kotlin @Serializable @@ -161,13 +168,13 @@ export namespace Project { } export interface OProj { - type: Type.OProj; + type: Project.Type.OProj; name: string; owner: string; } export interface DeprecatedProject { - type: Type.DeprecatedProject; + type: Project.Type.DeprecatedProject; name: string; reason: string; } @@ -178,7 +185,10 @@ export namespace Project { ### Nested sealed classes -[Union enums and enum member types](https://www.typescriptlang.org/docs/handbook/enums.html#union-enums-and-enum-member-types) +Nested sealed classes are 'invisible' to Kotlinx Serialization. In this +example, `sealed class Retriever` is ignored. + +For now, it's recommended to avoid nested sealed classes. ```kotlin @Serializable @@ -217,43 +227,70 @@ fun main() { > You can get the full code [here](./knit/example/example-polymorphic-sealed-class-02.kt). ```typescript -export type Dog = Dog.Mutt | Dog.Retriever +export type Dog = Dog.Golden | Dog.Mutt | Dog.NovaScotia; export namespace Dog { - export enum Type { Mutt = "Mutt", + Golden = "Golden", + NovaScotia = "NovaScotia", } export interface Mutt { - type: Type.Mutt; + type: Dog.Type.Mutt; name: string; loveable?: boolean; } - export type Retriever = Retriever.Golden | Retriever.NovaScotia - - export namespace Retriever { - - export enum Type { - Golden = "Golden", - NovaScotia = "NovaScotia", - } - - export interface Golden { - type: Type.Golden; - name: string; - cute?: boolean; - } - - export interface NovaScotia { - type: Type.NovaScotia; - name: string; - adorable?: boolean; - } + export interface Golden { + type: Dog.Type.Golden; + name: string; + colour: string; + cute?: boolean; } + export interface NovaScotia { + type: Dog.Type.NovaScotia; + name: string; + colour: string; + adorable?: boolean; + } } +// Nested sealed classes don't work at the moment :( +// export type Dog = Dog.Mutt | Dog.Retriever +// +// export namespace Dog { +// export enum Type { +// Mutt = "Mutt", +// } +// +// export interface Mutt { +// type: Type.Mutt; +// name: string; +// loveable?: boolean; +// } +// +// export type Retriever = Retriever.Golden | Retriever.NovaScotia +// +// export namespace Retriever { +// export enum Type { +// Golden = "Golden", +// NovaScotia = "NovaScotia", +// } +// +// export interface Golden { +// type: Type.Golden; +// name: string; +// cute?: boolean; +// } +// +// export interface NovaScotia { +// type: Type.NovaScotia; +// name: string; +// adorable?: boolean; +// } +// } +// } ``` @@ -273,10 +310,7 @@ class TextResponse(val text: String) : Response() fun main() { val tsGenerator = KxsTsGenerator() println( - tsGenerator.generate( - EmptyResponse.serializer(), - TextResponse.serializer(), - ) + tsGenerator.generate(Response.serializer()) ) } ``` @@ -284,20 +318,22 @@ fun main() { > You can get the full code [here](./knit/example/example-polymorphic-objects-01.kt). ```typescript -export enum ResponseKind { - EmptyResponse = "EmptyResponse", - TextResponse = "TextResponse", -} +export type Response = Response.EmptyResponse | Response.TextResponse; -export type Response = EmptyResponse | TextResponse +export namespace Response { + export enum Type { + EmptyResponse = "EmptyResponse", + TextResponse = "TextResponse", + } -export interface EmptyResponse { - type: ResponseKind.EmptyResponse; -} + export interface EmptyResponse { + type: Response.Type.EmptyResponse; + } -export interface TextResponse { - type: ResponseKind.TextResponse; - text: string; + export interface TextResponse { + type: Response.Type.TextResponse; + text: string; + } } ``` From ac2cafad0c52c15555eb28a3bc9bc02aea2394b3 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 10:42:14 +0200 Subject: [PATCH 23/46] rm migrated code --- .../dev.adamko.kxstsgen/KxsTsGenerator.kt | 306 ------------------ 1 file changed, 306 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 9879e890..04914458 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 @@ -1,8 +1,5 @@ -@file:OptIn(InternalSerializationApi::class) - package dev.adamko.kxstsgen -import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.modules.SerializersModule @@ -25,306 +22,3 @@ class KxsTsGenerator( return processor.process() } } - - -//class TsConverter( -// private val kxsTsConfig: KxsTsConfig, -// elementsState: Map = emptyMap(), -//) { -// private val elementsState: MutableMap = elementsState.toMutableMap() -// -// private val descriptorExtractor = DescriptorExtractor(kxsTsConfig) -// -// val convertedElements: Set -// get() = elementsState.values.toSet() -// -// operator fun invoke(serializer: KSerializer<*>) { -// val descriptors: Map = descriptorExtractor(serializer) -// descriptors.keys.forEach { descriptor -> convertToTsElement(descriptor, descriptors) } -// } -// -// private fun convertToTsElement( -// descriptor: SerialDescriptor, -// typeRefs: Map, -// ): TsElement { -// return elementsState.getOrPut(descriptor) { -// -// when (descriptor.kind) { -// SerialKind.ENUM -> convertEnum(descriptor) -// SerialKind.CONTEXTUAL -> { -// // TODO contextual -// TsLiteral.Primitive.TsAny -// } -// -// PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean -// -// PrimitiveKind.CHAR, -// PrimitiveKind.STRING -> TsLiteral.Primitive.TsString -// -// PrimitiveKind.BYTE, -// PrimitiveKind.SHORT, -// PrimitiveKind.INT, -// PrimitiveKind.LONG, -// PrimitiveKind.FLOAT, -// PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber -// -// StructureKind.LIST -> convertList(descriptor, typeRefs) -// StructureKind.MAP -> convertMap(descriptor, typeRefs) -// -// StructureKind.CLASS, -// StructureKind.OBJECT, -// PolymorphicKind.SEALED, -// PolymorphicKind.OPEN -> when { -// descriptor.isInline -> convertTypeAlias(descriptor, typeRefs) -// else -> convertInterface(descriptor, typeRefs) -// } -// } -// } -// } -// -// -// private fun convertTypeAlias( -// structDescriptor: SerialDescriptor, -// typeRefs: Map, -// ): TsDeclaration { -// val resultId = createElementId(structDescriptor) -// val fieldDescriptor = structDescriptor.elementDescriptors.first() -// val fieldTypeRef = typeRefs[fieldDescriptor] ?: TsTypeRef.Unknown -// return TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) -// -// } -// -// -// private fun convertInterface( -// structDescriptor: SerialDescriptor, -// typeRefs: Map, -// ): TsDeclaration { -// val resultId = createElementId(structDescriptor) -// -// val properties = structDescriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> -// val name = structDescriptor.getElementName(index) -// val fieldTypeRef = typeRefs[fieldDescriptor] ?: TsTypeRef.Unknown -// when { -// structDescriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeRef) -// else -> TsProperty.Required(name, fieldTypeRef) -// } -// }.toSet() -// return TsDeclaration.TsInterface(resultId, properties, TsPolymorphicDiscriminator.Open) -// } -// -// -// private fun convertEnum( -// enumDescriptor: SerialDescriptor, -// ): TsDeclaration.TsEnum { -// val resultId = createElementId(enumDescriptor) -// return TsDeclaration.TsEnum(resultId, enumDescriptor.elementNames.toSet()) -// } -// -// -// private fun convertList( -// listDescriptor: SerialDescriptor, -// typeRefs: Map, -// ): TsLiteral.TsList { -// val elementDescriptor = listDescriptor.elementDescriptors.first() -// val elementTypeRef = typeRefs[elementDescriptor] ?: TsTypeRef.Unknown -// return TsLiteral.TsList(elementTypeRef) -// } -// -// -// private fun convertMap( -// mapDescriptor: SerialDescriptor, -// typeRefs: Map, -// ): TsLiteral.TsMap { -// -// val (keyDescriptor, valueDescriptor) = mapDescriptor.elementDescriptors.toList() -// -// val keyTypeRef = typeRefs[keyDescriptor] ?: TsTypeRef.Unknown -// val valueTypeRef = typeRefs[valueDescriptor] ?: TsTypeRef.Unknown -// -// val type = convertMapType(keyDescriptor) -// -// return TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) -// } -// -// -//} -// -// -//class DescriptorExtractor( -// private val kxsTsConfig: KxsTsConfig, -//) { -// -// operator fun invoke(serializer: KSerializer<*>): Map { -// return sequence { -// when (serializer) { -// is PolymorphicSerializer<*> -> { -// val x = kxsTsConfig.serializersModule.getPolymorphicDescriptors(serializer.descriptor) -// yieldAll(x) -// } -// is SealedClassSerializer<*> -> -// yield(serializer.descriptor) -// is ContextualSerializer<*> -> -// yield(extractContextualSerializer(serializer, kxsTsConfig)?.descriptor) -// else -> -// yield(serializer.descriptor) -// } -// }.filterNotNull() -// .flatMap { descriptor -> extractAll(descriptor) } -// .distinct() -// .associateWith { descriptor -> -// createTypeRef(descriptor) -// } -// } -// -// private fun extractAll(descriptor: SerialDescriptor): Sequence { -// return sequence { -// val seen = mutableSetOf() -// val deque = ArrayDeque() -// deque.addLast(descriptor) -// while (deque.isNotEmpty()) { -// val next = deque.removeFirst() -// -// val nextElementDescriptors = extractElementDescriptors(next) -// -// nextElementDescriptors -// .filter { it !in seen } -// .forEach { deque.addLast(it) } -// -// seen.add(next) -// yield(next) -// } -// }.distinct() -// } -// -// -// private fun extractElementDescriptors(serialDescriptor: SerialDescriptor): Iterable { -// return when (serialDescriptor.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 -> serialDescriptor.elementDescriptors -// -// PolymorphicKind.SEALED, -// PolymorphicKind.OPEN -> serialDescriptor -// .elementDescriptors -// .filter { it.kind is PolymorphicKind } -// .flatMap { it.elementDescriptors } -// } -// } -// -// -// private fun createTypeRef(descriptor: SerialDescriptor): TsTypeRef { -// return when (descriptor.kind) { -// is PrimitiveKind -> { -// val tsPrimitive = when (descriptor.kind as PrimitiveKind) { -// PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean -// -// PrimitiveKind.BYTE, -// PrimitiveKind.SHORT, -// PrimitiveKind.INT, -// PrimitiveKind.LONG, -// PrimitiveKind.FLOAT, -// PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber -// -// PrimitiveKind.CHAR, -// PrimitiveKind.STRING -> TsLiteral.Primitive.TsString -// } -// TsTypeRef.Literal(tsPrimitive, descriptor.isNullable) -// } -// -// StructureKind.LIST -> { -// val elementDescriptor = descriptor.elementDescriptors.first() -// val elementTypeRef = createTypeRef(elementDescriptor) -// val listRef = TsLiteral.TsList(elementTypeRef) -// TsTypeRef.Literal(listRef, descriptor.isNullable) -// } -// StructureKind.MAP -> { -// val (keyDescriptor, valueDescriptor) = descriptor.elementDescriptors.toList() -// val keyTypeRef = createTypeRef(keyDescriptor) -// val valueTypeRef = createTypeRef(valueDescriptor) -// val type = convertMapType(keyDescriptor) -// val map = TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) -// TsTypeRef.Literal(map, descriptor.isNullable) -// } -// -// SerialKind.CONTEXTUAL, -// PolymorphicKind.SEALED, -// PolymorphicKind.OPEN, -// SerialKind.ENUM, -// StructureKind.CLASS, -// StructureKind.OBJECT -> { -// val id = createElementId(descriptor) -// TsTypeRef.Named(id, descriptor.isNullable) -// } -// } -// } -// -//} -// -// -// -// -//private fun createElementId(descriptor: SerialDescriptor): TsElementId { -// -// val targetId = TsElementId(descriptor.serialName.removeSuffix("?")) -// -// return when (descriptor.kind) { -// PolymorphicKind.OPEN -> TsElementId( -// targetId.namespace + "." + targetId.name.substringAfter("<").substringBeforeLast(">") -// ) -// PolymorphicKind.SEALED, -// PrimitiveKind.BOOLEAN, -// PrimitiveKind.BYTE, -// PrimitiveKind.CHAR, -// PrimitiveKind.DOUBLE, -// PrimitiveKind.FLOAT, -// PrimitiveKind.INT, -// PrimitiveKind.LONG, -// PrimitiveKind.SHORT, -// PrimitiveKind.STRING, -// SerialKind.CONTEXTUAL, -// SerialKind.ENUM, -// StructureKind.CLASS, -// StructureKind.LIST, -// StructureKind.MAP, -// StructureKind.OBJECT -> targetId -// } -//} -// -//private fun convertMapType(keyDescriptor: SerialDescriptor): TsLiteral.TsMap.Type { -// return when (keyDescriptor.kind) { -// SerialKind.ENUM -> TsLiteral.TsMap.Type.MAPPED_OBJECT -// -// PrimitiveKind.STRING -> TsLiteral.TsMap.Type.INDEX_SIGNATURE -// -// SerialKind.CONTEXTUAL, -// PrimitiveKind.BOOLEAN, -// PrimitiveKind.BYTE, -// PrimitiveKind.CHAR, -// PrimitiveKind.SHORT, -// PrimitiveKind.INT, -// PrimitiveKind.LONG, -// PrimitiveKind.FLOAT, -// PrimitiveKind.DOUBLE, -// StructureKind.CLASS, -// StructureKind.LIST, -// StructureKind.MAP, -// StructureKind.OBJECT, -// PolymorphicKind.SEALED, -// PolymorphicKind.OPEN -> TsLiteral.TsMap.Type.MAP -// } -//} From ee2f3d15a096c5427d2a129bbedc3b96479a9d44 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 10:49:45 +0200 Subject: [PATCH 24/46] rm unneeded TsTypeRef.Unknown --- .../dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt | 2 -- .../kotlin/dev.adamko.kxstsgen/tsElements.kt | 12 ------------ 2 files changed, 14 deletions(-) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index f2861c84..1773547d 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -268,8 +268,6 @@ abstract class KxsTsSourceCodeGenerator( typeRef.id.name } } - - is TsTypeRef.Unknown -> generateTypeReference(typeRef.ref) } return buildString { diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt index f60b1504..14e02d24 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt @@ -98,18 +98,6 @@ sealed interface TsTypeRef { override val nullable: Boolean, ) : TsTypeRef -// /** A property within another declaration (e.g. an enum value, or type within a namespace) */ -// data class Property( -// val id: TsElementId, -// val declaration: Declaration, -// override val nullable: Boolean, -// ) : TsTypeRef - - object Unknown : TsTypeRef { - val ref: Literal = Literal(TsLiteral.Primitive.TsUnknown, false) - override val nullable: Boolean by ref::nullable - } - } From 5331cd47c3f551822602b9a55fe2413e9012f187 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 11:20:34 +0200 Subject: [PATCH 25/46] rename TsType to TsTypeAlias, and update docs in tsElements.kt --- .../KxsTsSourceCodeGenerator.kt | 30 +++++---- .../dev.adamko.kxstsgen/TsElementConverter.kt | 4 +- .../kotlin/dev.adamko.kxstsgen/tsElements.kt | 61 ++++++++++++++++--- 3 files changed, 72 insertions(+), 23 deletions(-) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index 1773547d..ae9f9d97 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -16,25 +16,19 @@ abstract class KxsTsSourceCodeGenerator( is TsDeclaration.TsEnum -> generateEnum(element) is TsDeclaration.TsInterface -> generateInterface(element) is TsDeclaration.TsNamespace -> generateNamespace(element) - is TsDeclaration.TsType -> generateType(element) + is TsDeclaration.TsTypeAlias -> generateType(element) } } abstract fun generateEnum(enum: TsDeclaration.TsEnum): String abstract fun generateInterface(element: TsDeclaration.TsInterface): String abstract fun generateNamespace(namespace: TsDeclaration.TsNamespace): String - abstract fun generateType(element: TsDeclaration.TsType): String + abstract fun generateType(element: TsDeclaration.TsTypeAlias): String - abstract fun generateMapTypeReference( -// requestorId: TsElementId?, - tsMap: TsLiteral.TsMap, - ): String + abstract fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String abstract fun generatePrimitive(primitive: TsLiteral.Primitive): String - abstract fun generateTypeReference( -// rootId: TsElementId?, - typeRef: TsTypeRef, - ): String + abstract fun generateTypeReference(typeRef: TsTypeRef): String open class Default( config: KxsTsConfig, @@ -118,6 +112,8 @@ abstract class KxsTsSourceCodeGenerator( } } + + // note: this isn't used because at present poly-open descriptors are converted to 'any' private fun generatePolyOpen( element: TsDeclaration.TsInterface, polymorphism: TsPolymorphism.Open, @@ -144,7 +140,7 @@ abstract class KxsTsSourceCodeGenerator( subInterfaces, ) - val subInterfaceTypeUnion = TsDeclaration.TsType( + val subInterfaceTypeUnion = TsDeclaration.TsTypeAlias( element.id, subInterfaceRefs ) @@ -154,6 +150,14 @@ abstract class KxsTsSourceCodeGenerator( } } + /** + * 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 + */ private fun generatePolyClosed( element: TsDeclaration.TsInterface, polymorphism: TsPolymorphism.Sealed, @@ -189,7 +193,7 @@ abstract class KxsTsSourceCodeGenerator( subclass.copy(properties = setOf(typeProp) + subclass.properties) } - val subInterfaceTypeUnion = TsDeclaration.TsType( + val subInterfaceTypeUnion = TsDeclaration.TsTypeAlias( element.id, subInterfaceRefs.keys ) @@ -208,7 +212,7 @@ abstract class KxsTsSourceCodeGenerator( } - override fun generateType(element: TsDeclaration.TsType): String { + override fun generateType(element: TsDeclaration.TsTypeAlias): String { val aliases = element.typeRefs .map { generateTypeReference(it) } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt index 56add068..a806b2e3 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt @@ -75,7 +75,7 @@ fun interface TsElementConverter { PolymorphicKind.OPEN -> { val resultId = context.elementId(descriptor) val fieldTypeRef = TsTypeRef.Literal(TsLiteral.Primitive.TsAny, false) - TsDeclaration.TsType(resultId, fieldTypeRef) + TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) } } } @@ -117,7 +117,7 @@ fun interface TsElementConverter { val resultId = context.elementId(structDescriptor) val fieldDescriptor = structDescriptor.elementDescriptors.first() val fieldTypeRef = context.typeRef(fieldDescriptor) - return TsDeclaration.TsType(resultId, fieldTypeRef) + return TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt index 14e02d24..f32e6d15 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt @@ -1,8 +1,18 @@ package dev.adamko.kxstsgen +import dev.adamko.kxstsgen.TsProperty.Optional +import dev.adamko.kxstsgen.TsProperty.Required import kotlin.jvm.JvmInline +import kotlinx.serialization.descriptors.SerialDescriptor +/** + * A unique identifier for a [TsElement]. + * + * This is usually generated from [SerialDescriptor.serialName]. + */ +// Note: A value class probably isn't the best choice here. The manual String manipulation is +// restrictive and clunky, and makes nested references very difficult to decipher. @JvmInline value class TsElementId(private val id: String) { val namespace: String @@ -13,14 +23,25 @@ value class TsElementId(private val id: String) { override fun toString(): String = id } - +/** + * Some TypeScript source code element. Either a [TsLiteral] or a [TsDeclaration]. + */ sealed interface TsElement +/** + * Declarations are named elements that developers create in TypeScript source code. + * + * For example, an [interface][TsDeclaration.TsInterface] is declared. In contrast, the interface + * may have a [string][TsLiteral.Primitive.TsString] property, which is represented as a + * [TsLiteral]. + */ sealed interface TsDeclaration : TsElement { val id: TsElementId - data class TsType( + + /** A named reference to one or more other types. */ + data class TsTypeAlias( override val id: TsElementId, val typeRefs: Set, ) : TsDeclaration { @@ -50,6 +71,7 @@ sealed interface TsDeclaration : TsElement { } +/** Literal built-in TypeScript elements. */ sealed interface TsLiteral : TsElement { sealed interface Primitive : TsLiteral { @@ -61,6 +83,8 @@ sealed interface TsLiteral : TsElement { object TsObject : Primitive object TsAny : Primitive + + // the remaining primitives are defined, but unused object TsNever : Primitive object TsNull : Primitive object TsUndefined : Primitive @@ -68,10 +92,14 @@ sealed interface TsLiteral : TsElement { object TsVoid : Primitive } + + /** A list with elements of type [valueTypeRef]. */ data class TsList( val valueTypeRef: TsTypeRef, ) : TsLiteral + + /** A key-value map. */ data class TsMap( val keyTypeRef: TsTypeRef, val valueTypeRef: TsTypeRef, @@ -87,11 +115,19 @@ sealed interface TsLiteral : TsElement { } +/** + * A reference to some [TsElement]. The reference may be [nullable]. + * + * A reference does not require the target to be generated. + * This helps prevent circular dependencies causing a lock. + */ sealed interface TsTypeRef { val nullable: Boolean + data class Literal(val element: TsLiteral, override val nullable: Boolean) : TsTypeRef + data class Declaration( val id: TsElementId, val parent: Declaration?, @@ -101,16 +137,23 @@ sealed interface TsTypeRef { } -// source: kxs +/** + * A property within an [interface][TsDeclaration.TsInterface] + * + * In property may be [Required] or [Optional]. See the TypeScript docs: + * ['Optional Properties'](https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties) + */ sealed interface TsProperty { val name: String val typeRef: TsTypeRef + data class Required( override val name: String, override val typeRef: TsTypeRef, ) : TsProperty + data class Optional( override val name: String, override val typeRef: TsTypeRef, @@ -118,23 +161,25 @@ sealed interface 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 */ data class Open( override val discriminatorName: String, override val subclasses: Set, ) : TsPolymorphism - -// object None : TsPolymorphism { -// override val discriminatorName: Nothing = error("not implemented") -// override val subclasses: Nothing = error("not implemented") -// } } From dc1fd8132ee372566b260995f07d42a6a44bc679 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 11:41:11 +0200 Subject: [PATCH 26/46] code cleanup, documentation --- .../kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt | 63 +++++++------------ .../dev.adamko.kxstsgen/KxsTsProcessor.kt | 4 +- .../KxsTsSourceCodeGenerator.kt | 2 +- .../dev.adamko.kxstsgen/processSerializers.kt | 7 --- .../kotlin/dev.adamko.kxstsgen/tsElements.kt | 1 + 5 files changed, 25 insertions(+), 52 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 599dd5ce..87d321bc 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 @@ -12,10 +12,20 @@ import kotlinx.serialization.modules.EmptySerializersModule import kotlinx.serialization.modules.SerializersModule import kotlinx.serialization.modules.SerializersModuleCollector + +/** + * @param[indent] Define the indentation that is used when generating source code + * @param[declarationSeparator] The string that is used when joining [TsDeclaration]s + * @param[namespaceConfig] (UNIMPLEMENTED) How elements are grouped into [TsDeclaration.TsNamespace]s. + * @param[typeAliasTyping] (UNIMPLEMENTED) Control if type aliases are simple, or 'branded'. + * @param[serializersModule] Used to obtain contextual and polymorphic information. + */ data class KxsTsConfig( val indent: String = " ", - val structureSeparator: String = "\n\n", + val declarationSeparator: String = "\n\n", + @UnimplementedKxTsGenApi val namespaceConfig: NamespaceConfig = NamespaceConfig.Disabled, + @UnimplementedKxTsGenApi val typeAliasTyping: TypeAliasTypingConfig = TypeAliasTypingConfig.None, val serializersModule: SerializersModule = EmptySerializersModule, ) { @@ -82,44 +92,13 @@ data class KxsTsConfig( } } -// -// -//class SerializerClassMap { -// private val _map: MutableMap, KClass<*>> = mutableMapOf() -// -// val size: Int by _map::size -// -// fun containsKey(key: KSerializer<*>): Boolean = _map.containsKey(key) -// fun containsValue(value: KClass<*>): Boolean = _map.containsValue(value) -// -// -// fun isEmpty(): Boolean = _map.isEmpty() -// -// val keys: MutableSet> by _map::keys -// val values: MutableCollection> by _map::values -// -// fun clear(): Unit = _map.clear() -// -// @Suppress("UNCHECKED_CAST") -// fun get(key: KSerializer): KClass? = -// _map[key] as KClass? -// -// @Suppress("UNCHECKED_CAST") -// fun put(key: KSerializer, value: KClass): KClass? = -// _map.put(key, value) as KClass? -// -// @Suppress("UNCHECKED_CAST") -// fun remove(key: KSerializer): KClass? = _map.remove(key) as KClass? -// -// fun entries(): Set> = _map.map { Entry(it) }.toSet() -// -// data class Entry( -// val serializer: KSerializer, -// val kClass: KClass, -// ) { -// @Suppress("UNCHECKED_CAST") -// constructor(entry: Map.Entry<*, *>) -// : this(entry.key as KSerializer, entry.value as KClass) -// } -// -//} + +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.PROPERTY, + AnnotationTarget.FUNCTION, + AnnotationTarget.TYPEALIAS +) +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) +@MustBeDocumented +annotation class UnimplementedKxTsGenApi diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt index 82724e37..2b05d38a 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt @@ -52,10 +52,10 @@ class KxsTsProcessor( .filterIsInstance() .map { element -> sourceCodeGenerator.generateDeclaration(element) } .filter { it.isNotBlank() } - .joinToString(config.structureSeparator) + .joinToString(config.declarationSeparator) } .values// TODO create namespaces - .joinToString(config.structureSeparator) + .joinToString(config.declarationSeparator) } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index ae9f9d97..77fa0bae 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -53,7 +53,7 @@ abstract class KxsTsSourceCodeGenerator( val namespaceContent = namespace .members - .joinToString(config.structureSeparator) { declaration -> + .joinToString(config.declarationSeparator) { declaration -> generateDeclaration(declaration) } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt index 7bee6a12..715dd449 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt @@ -15,13 +15,6 @@ import kotlinx.serialization.descriptors.StructureKind import kotlinx.serialization.descriptors.elementDescriptors -// private val polymorphicDescriptorData: MutableMap -// by MapWithPutDefault { descriptor -> -// polymorphicSerializerData.entries.firstOrNull { (serializer, _) -> -// serializer.descriptor == descriptor -// }?.value -// } - /** * Recursively extract all descriptors from a serializer and its elements. */ diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt index f32e6d15..7f3b889d 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt @@ -178,6 +178,7 @@ sealed interface TsPolymorphism { /** Note: [Open] is not implemented correctly */ + @UnimplementedKxTsGenApi data class Open( override val discriminatorName: String, override val subclasses: Set, From 48f5d248655e9edbfa7de11a16e9da37eabfa46a Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 13:10:11 +0200 Subject: [PATCH 27/46] docs, more Maps tests/examples/workarounds --- README.md | 21 ++- docs/default-values.md | 49 +++++- ...mple-default-values-primitive-fields-01.kt | 7 +- .../example-default-values-single-field-01.kt | 4 +- .../example-default-values-single-field-02.kt | 14 ++ docs/knit/example/example-map-complex-02.kt | 49 ++++++ docs/knit/example/example-map-complex-03.kt | 56 ++++++ docs/knit/test/DefaultValuesTest.kt | 21 ++- docs/knit/test/MapsTests.kt | 34 ++++ docs/maps.md | 159 ++++++++++++++++++ docs/polymorphism.md | 2 +- 11 files changed, 400 insertions(+), 16 deletions(-) create mode 100644 docs/knit/example/example-default-values-single-field-02.kt create mode 100644 docs/knit/example/example-map-complex-02.kt create mode 100644 docs/knit/example/example-map-complex-03.kt diff --git a/README.md b/README.md index a26a1d85..4a6b1612 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Kotlinx Serialization TypeScript Generator -Create TypeScript interfaces from Kotlin classes +Create TypeScript interfaces from Kotlinx Serialization classes. ```kotlin @Serializable @@ -21,4 +21,23 @@ interface PlayerDetails { } ``` +The aim is to create TypeScript interfaces that can accurately produce JSON that Kotlinx +Serialization can parse. + See [the docs](./docs) for working examples. + +## Status + +This is a proof-of-concept. + +| | Status | Notes | +|---------------------------------------|----------------------------------------------------------|:--------------------------------------------------------------------------------------------------| +| Basic classes | ✅ [example](./docs/basic-classes.md) | | +| Nullable and default-value properties | ✅ [example](./docs/default-values.md) | | +| Value classes | ✅ [example](./docs/value-classes.md) | | +| Enums | ✅ [example](./docs/enums.md) | | +| Lists | ✅ [example](./docs/lists.md) | | +| Maps | ✅/⚠ [example](./docs/maps.md) | Maps with complex keys are converted to an ES6 Map. [See](./docs/maps.md#maps-with-complex-keys) | +| Polymorphism - Sealed classes | ✅/⚠ [example](./docs/polymorphism.md#sealed-classes) | Nested sealed classes are ignored | +| Polymorphism - Open classes | ❌ [example](./docs/abstract-classes.md) | Not implemented. Converted to `type MyClass = any` | +| Edge cases - circular dependencies | ✅ [example](./docs/edgecases.md) | | diff --git a/docs/default-values.md b/docs/default-values.md index 49677806..697d06db 100644 --- a/docs/default-values.md +++ b/docs/default-values.md @@ -6,6 +6,7 @@ * [Introduction](#introduction) * [Default values](#default-values) + * [Nullable values](#nullable-values) * [Default and nullable](#default-and-nullable) @@ -18,6 +19,7 @@ import dev.adamko.kxstsgen.* ## Introduction +Some properties of a class are optional, or nullable, or both. ### Default values @@ -26,32 +28,64 @@ it will be marked as optional using the `?:` notation. ```kotlin @Serializable -class Color(val rgb: Int = 12345) +class Colour(val rgb: Int = 12345) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Color.serializer())) + println(tsGenerator.generate(Colour.serializer())) } ``` > You can get the full code [here](./knit/example/example-default-values-single-field-01.kt). ```typescript -export interface Color { +export interface Colour { rgb?: number; } ``` +### Nullable values + +Properties might be required, but the value can be nullable. In TypeScript that is represented with +a type union that includes `null`. + +```kotlin +@Serializable +class Colour(val rgb: Int?) // 'rgb' is required, but the value can be null + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Colour.serializer())) +} +``` + +> You can get the full code [here](./knit/example/example-default-values-single-field-02.kt). + +```typescript +export interface Colour { + rgb: number | null; +} +``` + + + ### Default and nullable +A property can be both nullable and optional, which gives four possible options. + ```kotlin @Serializable data class ContactDetails( + // nullable: ❌, optional: ❌ + val name: String, + // nullable: ✅, optional: ❌ val email: String?, + // nullable: ❌, optional: ✅ + val active: Boolean = true, + // nullable: ✅, optional: ✅ val phoneNumber: String? = null, - val active: Boolean? = true, ) fun main() { @@ -62,15 +96,12 @@ fun main() { > You can get the full code [here](./knit/example/example-default-values-primitive-fields-01.kt). -Email has no default, so it is not marked as optional. - -Phone number and is nullable, and has a default, so i - ```typescript export interface ContactDetails { + name: string; email: string | null; + active?: boolean; phoneNumber?: string | null; - active?: boolean | null; } ``` diff --git a/docs/knit/example/example-default-values-primitive-fields-01.kt b/docs/knit/example/example-default-values-primitive-fields-01.kt index b11a011f..420b877c 100644 --- a/docs/knit/example/example-default-values-primitive-fields-01.kt +++ b/docs/knit/example/example-default-values-primitive-fields-01.kt @@ -7,9 +7,14 @@ import dev.adamko.kxstsgen.* @Serializable data class ContactDetails( + // nullable: ❌, optional: ❌ + val name: String, + // nullable: ✅, optional: ❌ val email: String?, + // nullable: ❌, optional: ✅ + val active: Boolean = true, + // nullable: ✅, optional: ✅ val phoneNumber: String? = null, - val active: Boolean? = true, ) fun main() { diff --git a/docs/knit/example/example-default-values-single-field-01.kt b/docs/knit/example/example-default-values-single-field-01.kt index bd226807..a269e75f 100644 --- a/docs/knit/example/example-default-values-single-field-01.kt +++ b/docs/knit/example/example-default-values-single-field-01.kt @@ -6,9 +6,9 @@ import kotlinx.serialization.* import dev.adamko.kxstsgen.* @Serializable -class Color(val rgb: Int = 12345) +class Colour(val rgb: Int = 12345) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Color.serializer())) + println(tsGenerator.generate(Colour.serializer())) } diff --git a/docs/knit/example/example-default-values-single-field-02.kt b/docs/knit/example/example-default-values-single-field-02.kt new file mode 100644 index 00000000..e530f3cd --- /dev/null +++ b/docs/knit/example/example-default-values-single-field-02.kt @@ -0,0 +1,14 @@ +// This file was automatically generated from default-values.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField02 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +class Colour(val rgb: Int?) // 'rgb' is required, but the value can be null + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Colour.serializer())) +} diff --git a/docs/knit/example/example-map-complex-02.kt b/docs/knit/example/example-map-complex-02.kt new file mode 100644 index 00000000..089257a6 --- /dev/null +++ b/docs/knit/example/example-map-complex-02.kt @@ -0,0 +1,49 @@ +// This file was automatically generated from maps.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleMapComplex02 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +data class Colour( + val r: UByte, + val g: UByte, + val b: UByte, + val a: UByte, +) + +/** + * Encode a [Colour] as an 8-character string + * + * Red, green, blue, and alpha are encoded as base-16 strings. + */ +@Serializable +@JvmInline +value class ColourMapKey(private val rgba: String) { + constructor(colour: Colour) : this( + listOf( + colour.r, + colour.g, + colour.b, + colour.a, + ).joinToString("") { + it.toString(16).padStart(2, '0') + } + ) + + fun toColour(): Colour { + val (r, g, b, a) = rgba.chunked(2).map { it.toUByte(16) } + return Colour(r, g, b, a) + } +} + +@Serializable +data class CanvasProperties( + val colourNames: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(CanvasProperties.serializer())) +} diff --git a/docs/knit/example/example-map-complex-03.kt b/docs/knit/example/example-map-complex-03.kt new file mode 100644 index 00000000..bbbe26ae --- /dev/null +++ b/docs/knit/example/example-map-complex-03.kt @@ -0,0 +1,56 @@ +// This file was automatically generated from maps.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleMapComplex03 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +@Serializable(with = ColourAsStringSerializer::class) +data class Colour( + val r: UByte, + val g: UByte, + val b: UByte, + val a: UByte, +) + +/** + * Encode a [Colour] as an 8-character string + * + * Red, green, blue, and alpha are encoded as base-16 strings. + */ +object ColourAsStringSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("Colour", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Colour) { + encoder.encodeString( + listOf( + value.r, + value.g, + value.b, + value.a, + ).joinToString("") { + it.toString(16).padStart(2, '0') + } + ) + } + + override fun deserialize(decoder: Decoder): Colour { + val string = decoder.decodeString() + val (r, g, b, a) = string.chunked(2).map { it.toUByte(16) } + return Colour(r, g, b, a) + } +} + +@Serializable +data class CanvasProperties( + val colourNames: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(CanvasProperties.serializer())) +} diff --git a/docs/knit/test/DefaultValuesTest.kt b/docs/knit/test/DefaultValuesTest.kt index 7880ead5..9ccdcf08 100644 --- a/docs/knit/test/DefaultValuesTest.kt +++ b/docs/knit/test/DefaultValuesTest.kt @@ -16,7 +16,7 @@ class DefaultValuesTest { .shouldBe( // language=TypeScript """ - |export interface Color { + |export interface Colour { | rgb?: number; |} """.trimMargin() @@ -24,6 +24,22 @@ class DefaultValuesTest { ) } + @Test + fun testExampleDefaultValuesSingleField02() { + captureOutput("ExampleDefaultValuesSingleField02") { + dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField02.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export interface Colour { + | rgb: number | null; + |} + """.trimMargin() + .normalize() + ) + } + @Test fun testExampleDefaultValuesPrimitiveFields01() { captureOutput("ExampleDefaultValuesPrimitiveFields01") { @@ -33,9 +49,10 @@ class DefaultValuesTest { // language=TypeScript """ |export interface ContactDetails { + | name: string; | email: string | null; + | active?: boolean; | phoneNumber?: string | null; - | active?: boolean | null; |} """.trimMargin() .normalize() diff --git a/docs/knit/test/MapsTests.kt b/docs/knit/test/MapsTests.kt index 643c8c2f..28dfb15f 100644 --- a/docs/knit/test/MapsTests.kt +++ b/docs/knit/test/MapsTests.kt @@ -85,4 +85,38 @@ class MapsTests { .normalize() ) } + + @Test + fun testExampleMapComplex02() { + captureOutput("ExampleMapComplex02") { + dev.adamko.kxstsgen.example.exampleMapComplex02.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export interface CanvasProperties { + | colourNames: Map; + |} + | + |export type ColourMapKey = string; + """.trimMargin() + .normalize() + ) + } + + @Test + fun testExampleMapComplex03() { + captureOutput("ExampleMapComplex03") { + dev.adamko.kxstsgen.example.exampleMapComplex03.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export interface CanvasProperties { + | colourNames: { [key: string]: string }; + |} + """.trimMargin() + .normalize() + ) + } } diff --git a/docs/maps.md b/docs/maps.md index ee85917c..f330403a 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -9,6 +9,9 @@ * [Enum keys](#enum-keys) * [Nullable keys and values](#nullable-keys-and-values) * [Maps with complex keys](#maps-with-complex-keys) + * [ES6 Map](#es6-map) + * [Maps with complex keys - Map Key class](#maps-with-complex-keys---map-key-class) + * [Maps with complex keys - custom serializer workaround](#maps-with-complex-keys---custom-serializer-workaround) @@ -105,6 +108,23 @@ export interface Config { ### Maps with complex keys +JSON maps **must** have keys that are either strings, positive integers, or enums. + +See [the Kotlinx Serialization docs](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/json.md#allowing-structured-map-keys) +. + +As a workaround, maps with structured keys are generated as [ES6 maps](#es6-map). + +To produce correct JSON, +either [write a custom serializer](#maps-with-complex-keys---custom-serializer-workaround) +or [use an explicit map-key class](#maps-with-complex-keys---map-key-class). + +#### ES6 Map + +This is the default behaviour of KxsTsGen when it encounters complex map keys. + +KxsTsGen produces valid TypeScript, but the TypeScript might not produce correct JSON. + ```kotlin @Serializable data class Colour( @@ -143,3 +163,142 @@ export type UByte = number; ``` + +#### Maps with complex keys - Map Key class + +This approach is less optimised, but more declarative and easier to understand than writing custom +serializers. + +Because the value class `ColourMapKey` has a single string value, the descriptor is a +`PrimitiveKind.STRING`. + +KxsTsGen will generate a JSON-safe mapped-type property. + +```kotlin +@Serializable +data class Colour( + val r: UByte, + val g: UByte, + val b: UByte, + val a: UByte, +) + +/** + * Encode a [Colour] as an 8-character string + * + * Red, green, blue, and alpha are encoded as base-16 strings. + */ +@Serializable +@JvmInline +value class ColourMapKey(private val rgba: String) { + constructor(colour: Colour) : this( + listOf( + colour.r, + colour.g, + colour.b, + colour.a, + ).joinToString("") { + it.toString(16).padStart(2, '0') + } + ) + + fun toColour(): Colour { + val (r, g, b, a) = rgba.chunked(2).map { it.toUByte(16) } + return Colour(r, g, b, a) + } +} + +@Serializable +data class CanvasProperties( + val colourNames: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(CanvasProperties.serializer())) +} +``` + +> You can get the full code [here](./knit/example/example-map-complex-02.kt). + +```typescript +export interface CanvasProperties { + colourNames: Map; +} + +export type ColourMapKey = string; +``` + + + +#### Maps with complex keys - custom serializer workaround + +Define a custom serializer for `Colour` that will encode and decode to/from a string. + +When encoding or decoding values with Kotlinx Serialization, under the hood it will create suitable +map keys. + +Because the custom serializer is a `PrimitiveKind.STRING`, KxsTsGen will generate a JSON-safe +mapped-type property. + +```kotlin +import kotlinx.serialization.descriptors.* +import kotlinx.serialization.encoding.* + +@Serializable(with = ColourAsStringSerializer::class) +data class Colour( + val r: UByte, + val g: UByte, + val b: UByte, + val a: UByte, +) + +/** + * Encode a [Colour] as an 8-character string + * + * Red, green, blue, and alpha are encoded as base-16 strings. + */ +object ColourAsStringSerializer : KSerializer { + override val descriptor: SerialDescriptor = + PrimitiveSerialDescriptor("Colour", PrimitiveKind.STRING) + + override fun serialize(encoder: Encoder, value: Colour) { + encoder.encodeString( + listOf( + value.r, + value.g, + value.b, + value.a, + ).joinToString("") { + it.toString(16).padStart(2, '0') + } + ) + } + + override fun deserialize(decoder: Decoder): Colour { + val string = decoder.decodeString() + val (r, g, b, a) = string.chunked(2).map { it.toUByte(16) } + return Colour(r, g, b, a) + } +} + +@Serializable +data class CanvasProperties( + val colourNames: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(CanvasProperties.serializer())) +} +``` + +> You can get the full code [here](./knit/example/example-map-complex-03.kt). + +```typescript +export interface CanvasProperties { + colourNames: { [key: string]: string }; +} +``` + + diff --git a/docs/polymorphism.md b/docs/polymorphism.md index 5db64c38..0fd2e824 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -129,7 +129,7 @@ export type Project = any; ### Sealed classes Sealed classes are the best way to generate TypeScript interface so far, because all subclasses are -known at compile-time. +defined in the `SerialDescriptor`. A sealed class will be converted as a [union enum, with enum member types](https://www.typescriptlang.org/docs/handbook/enums.html#union-enums-and-enum-member-types) From fa883b1b0a363a56ca16980f02caf7975940eca8 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 13:11:13 +0200 Subject: [PATCH 28/46] update gitattributes --- .gitattributes | 62 ++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 5 deletions(-) diff --git a/.gitattributes b/.gitattributes index 77711366..c0b49b27 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,8 +1,60 @@ -* text eol=lf +# Common settings that generally should always be used with your language specific settings -*.java text diff=java -*.kt text diff=java -*.kts text diff=java +# Auto detect text files and perform LF normalization +* text eol=lf +# The above will handle all files NOT found below + +*.md text +*.tex text diff=tex +*.adoc text +*.textile text +*.mustache text +*.csv text +*.tab text +*.tsv text +*.txt text +*.sql text + +# Graphics +*.png binary +*.jpg binary +*.jpeg binary +*.gif binary +*.tif binary +*.tiff binary +*.ico binary +*.eps binary +# SVG treated as an asset (binary) by default. +*.svg text + +# Properties +*.json text +*.toml text +*.xml text +*.yaml text +*.yml text + +# Scripts +*.bash text eol=lf +*.fish text eol=lf +*.sh text eol=lf # These are explicitly windows files and should use crlf -*.bat text eol=crlf +*.bat text eol=crlf +*.cmd text eol=crlf +*.ps1 text eol=crlf + + +# JVM sources +*.java text diff=java +*.gradle text diff=java +*.kt text diff=java +*.kts text diff=java + +# Text files where line endings should be preserved +*.patch -text + +# +# Exclude files from exporting +.gitattributes export-ignore +.gitignore export-ignore From 6f3ad4e0fcfb9cb3c4ed255ead8848651ba2bbfb Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 13:17:15 +0200 Subject: [PATCH 29/46] rename 'knit' subproject to 'code' --- docs/abstract-classes.md | 6 +++--- docs/basic-classes.md | 8 ++++---- docs/{knit => code}/build.gradle.kts | 0 .../example-abstract-class-abstract-field-01.kt | 0 .../example-abstract-class-primitive-fields-01.kt | 0 .../example-abstract-class-single-field-01.kt | 0 .../example-default-values-primitive-fields-01.kt | 0 .../example-default-values-single-field-01.kt | 0 .../example-default-values-single-field-02.kt | 0 .../example-edgecase-recursive-references-01.kt | 0 .../example-edgecase-recursive-references-02.kt | 0 .../example-edgecase-recursive-references-03.kt | 0 .../example/example-enum-class-01.kt | 0 .../example/example-enum-class-02.kt | 0 docs/{knit => code}/example/example-generics-01.kt | 0 .../example/example-list-primitive-01.kt | 0 .../example/example-map-complex-01.kt | 0 .../example/example-map-complex-02.kt | 0 .../example/example-map-complex-03.kt | 0 .../example/example-map-primitive-01.kt | 0 .../example/example-map-primitive-02.kt | 0 .../example/example-map-primitive-03.kt | 0 .../example-plain-class-primitive-fields-01.kt | 0 .../example-plain-class-primitive-fields-02.kt | 0 .../example/example-plain-class-single-field-01.kt | 0 .../example/example-plain-data-class-01.kt | 0 ...lymorphic-abstract-class-primitive-fields-01.kt | 0 .../example/example-polymorphic-objects-01.kt | 0 .../example/example-polymorphic-sealed-class-01.kt | 0 .../example/example-polymorphic-sealed-class-02.kt | 0 .../example/example-polymorphic-static-types-01.kt | 0 .../example/example-polymorphic-static-types-02.kt | 0 .../example/example-value-classes-01.kt | 0 .../example/example-value-classes-02.kt | 0 .../example/example-value-classes-03.kt | 0 .../example/example-value-classes-04.kt | 0 docs/{knit => code}/knit-include.ftl | 0 docs/{knit => code}/knit-test.ftl | 0 docs/{knit => code}/test/AbstractClassesTest.kt | 0 docs/{knit => code}/test/BasicClassesTest.kt | 0 docs/{knit => code}/test/DefaultValuesTest.kt | 0 docs/{knit => code}/test/EdgeCasesTest.kt | 0 docs/{knit => code}/test/EnumClassTest.kt | 0 docs/{knit => code}/test/ListsTests.kt | 0 docs/{knit => code}/test/MapsTests.kt | 0 docs/{knit => code}/test/PolymorphismTest.kt | 0 docs/{knit => code}/test/ValueClassesTest.kt | 0 docs/{knit => code}/util/strings.kt | 0 docs/default-values.md | 6 +++--- docs/edgecases.md | 6 +++--- docs/enums.md | 4 ++-- docs/knit.properties | 8 ++++---- docs/lists.md | 2 +- docs/maps.md | 12 ++++++------ docs/polymorphism.md | 14 +++++++------- docs/value-classes.md | 8 ++++---- modules/kxs-ts-gen-core/build.gradle.kts | 1 + settings.gradle.kts | 2 +- 58 files changed, 39 insertions(+), 38 deletions(-) rename docs/{knit => code}/build.gradle.kts (100%) rename docs/{knit => code}/example/example-abstract-class-abstract-field-01.kt (100%) rename docs/{knit => code}/example/example-abstract-class-primitive-fields-01.kt (100%) rename docs/{knit => code}/example/example-abstract-class-single-field-01.kt (100%) rename docs/{knit => code}/example/example-default-values-primitive-fields-01.kt (100%) rename docs/{knit => code}/example/example-default-values-single-field-01.kt (100%) rename docs/{knit => code}/example/example-default-values-single-field-02.kt (100%) rename docs/{knit => code}/example/example-edgecase-recursive-references-01.kt (100%) rename docs/{knit => code}/example/example-edgecase-recursive-references-02.kt (100%) rename docs/{knit => code}/example/example-edgecase-recursive-references-03.kt (100%) rename docs/{knit => code}/example/example-enum-class-01.kt (100%) rename docs/{knit => code}/example/example-enum-class-02.kt (100%) rename docs/{knit => code}/example/example-generics-01.kt (100%) rename docs/{knit => code}/example/example-list-primitive-01.kt (100%) rename docs/{knit => code}/example/example-map-complex-01.kt (100%) rename docs/{knit => code}/example/example-map-complex-02.kt (100%) rename docs/{knit => code}/example/example-map-complex-03.kt (100%) rename docs/{knit => code}/example/example-map-primitive-01.kt (100%) rename docs/{knit => code}/example/example-map-primitive-02.kt (100%) rename docs/{knit => code}/example/example-map-primitive-03.kt (100%) rename docs/{knit => code}/example/example-plain-class-primitive-fields-01.kt (100%) rename docs/{knit => code}/example/example-plain-class-primitive-fields-02.kt (100%) rename docs/{knit => code}/example/example-plain-class-single-field-01.kt (100%) rename docs/{knit => code}/example/example-plain-data-class-01.kt (100%) rename docs/{knit => code}/example/example-polymorphic-abstract-class-primitive-fields-01.kt (100%) rename docs/{knit => code}/example/example-polymorphic-objects-01.kt (100%) rename docs/{knit => code}/example/example-polymorphic-sealed-class-01.kt (100%) rename docs/{knit => code}/example/example-polymorphic-sealed-class-02.kt (100%) rename docs/{knit => code}/example/example-polymorphic-static-types-01.kt (100%) rename docs/{knit => code}/example/example-polymorphic-static-types-02.kt (100%) rename docs/{knit => code}/example/example-value-classes-01.kt (100%) rename docs/{knit => code}/example/example-value-classes-02.kt (100%) rename docs/{knit => code}/example/example-value-classes-03.kt (100%) rename docs/{knit => code}/example/example-value-classes-04.kt (100%) rename docs/{knit => code}/knit-include.ftl (100%) rename docs/{knit => code}/knit-test.ftl (100%) rename docs/{knit => code}/test/AbstractClassesTest.kt (100%) rename docs/{knit => code}/test/BasicClassesTest.kt (100%) rename docs/{knit => code}/test/DefaultValuesTest.kt (100%) rename docs/{knit => code}/test/EdgeCasesTest.kt (100%) rename docs/{knit => code}/test/EnumClassTest.kt (100%) rename docs/{knit => code}/test/ListsTests.kt (100%) rename docs/{knit => code}/test/MapsTests.kt (100%) rename docs/{knit => code}/test/PolymorphismTest.kt (100%) rename docs/{knit => code}/test/ValueClassesTest.kt (100%) rename docs/{knit => code}/util/strings.kt (100%) diff --git a/docs/abstract-classes.md b/docs/abstract-classes.md index 10fb0678..fa41dc49 100644 --- a/docs/abstract-classes.md +++ b/docs/abstract-classes.md @@ -31,7 +31,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-abstract-class-single-field-01.kt). +> You can get the full code [here](./code/example/example-abstract-class-single-field-01.kt). ```typescript export type Color = any; @@ -60,7 +60,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-abstract-class-primitive-fields-01.kt). +> You can get the full code [here](./code/example/example-abstract-class-primitive-fields-01.kt). ```typescript export type SimpleTypes = any; @@ -92,7 +92,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-abstract-class-abstract-field-01.kt). +> You can get the full code [here](./code/example/example-abstract-class-abstract-field-01.kt). ```typescript export type AbstractSimpleTypes = any; diff --git a/docs/basic-classes.md b/docs/basic-classes.md index c84704fa..4153ba42 100644 --- a/docs/basic-classes.md +++ b/docs/basic-classes.md @@ -33,7 +33,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-plain-class-single-field-01.kt). +> You can get the full code [here](./code/example/example-plain-class-single-field-01.kt). ```typescript export interface Color { @@ -61,7 +61,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-plain-class-primitive-fields-01.kt). +> You can get the full code [here](./code/example/example-plain-class-primitive-fields-01.kt). ```typescript export interface SimpleTypes { @@ -93,7 +93,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-plain-data-class-01.kt). +> You can get the full code [here](./code/example/example-plain-data-class-01.kt). ```typescript export interface SomeDataClass { @@ -134,7 +134,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-plain-class-primitive-fields-02.kt). +> You can get the full code [here](./code/example/example-plain-class-primitive-fields-02.kt). ```typescript export interface SimpleTypes { diff --git a/docs/knit/build.gradle.kts b/docs/code/build.gradle.kts similarity index 100% rename from docs/knit/build.gradle.kts rename to docs/code/build.gradle.kts diff --git a/docs/knit/example/example-abstract-class-abstract-field-01.kt b/docs/code/example/example-abstract-class-abstract-field-01.kt similarity index 100% rename from docs/knit/example/example-abstract-class-abstract-field-01.kt rename to docs/code/example/example-abstract-class-abstract-field-01.kt diff --git a/docs/knit/example/example-abstract-class-primitive-fields-01.kt b/docs/code/example/example-abstract-class-primitive-fields-01.kt similarity index 100% rename from docs/knit/example/example-abstract-class-primitive-fields-01.kt rename to docs/code/example/example-abstract-class-primitive-fields-01.kt diff --git a/docs/knit/example/example-abstract-class-single-field-01.kt b/docs/code/example/example-abstract-class-single-field-01.kt similarity index 100% rename from docs/knit/example/example-abstract-class-single-field-01.kt rename to docs/code/example/example-abstract-class-single-field-01.kt diff --git a/docs/knit/example/example-default-values-primitive-fields-01.kt b/docs/code/example/example-default-values-primitive-fields-01.kt similarity index 100% rename from docs/knit/example/example-default-values-primitive-fields-01.kt rename to docs/code/example/example-default-values-primitive-fields-01.kt diff --git a/docs/knit/example/example-default-values-single-field-01.kt b/docs/code/example/example-default-values-single-field-01.kt similarity index 100% rename from docs/knit/example/example-default-values-single-field-01.kt rename to docs/code/example/example-default-values-single-field-01.kt diff --git a/docs/knit/example/example-default-values-single-field-02.kt b/docs/code/example/example-default-values-single-field-02.kt similarity index 100% rename from docs/knit/example/example-default-values-single-field-02.kt rename to docs/code/example/example-default-values-single-field-02.kt diff --git a/docs/knit/example/example-edgecase-recursive-references-01.kt b/docs/code/example/example-edgecase-recursive-references-01.kt similarity index 100% rename from docs/knit/example/example-edgecase-recursive-references-01.kt rename to docs/code/example/example-edgecase-recursive-references-01.kt diff --git a/docs/knit/example/example-edgecase-recursive-references-02.kt b/docs/code/example/example-edgecase-recursive-references-02.kt similarity index 100% rename from docs/knit/example/example-edgecase-recursive-references-02.kt rename to docs/code/example/example-edgecase-recursive-references-02.kt diff --git a/docs/knit/example/example-edgecase-recursive-references-03.kt b/docs/code/example/example-edgecase-recursive-references-03.kt similarity index 100% rename from docs/knit/example/example-edgecase-recursive-references-03.kt rename to docs/code/example/example-edgecase-recursive-references-03.kt diff --git a/docs/knit/example/example-enum-class-01.kt b/docs/code/example/example-enum-class-01.kt similarity index 100% rename from docs/knit/example/example-enum-class-01.kt rename to docs/code/example/example-enum-class-01.kt diff --git a/docs/knit/example/example-enum-class-02.kt b/docs/code/example/example-enum-class-02.kt similarity index 100% rename from docs/knit/example/example-enum-class-02.kt rename to docs/code/example/example-enum-class-02.kt diff --git a/docs/knit/example/example-generics-01.kt b/docs/code/example/example-generics-01.kt similarity index 100% rename from docs/knit/example/example-generics-01.kt rename to docs/code/example/example-generics-01.kt diff --git a/docs/knit/example/example-list-primitive-01.kt b/docs/code/example/example-list-primitive-01.kt similarity index 100% rename from docs/knit/example/example-list-primitive-01.kt rename to docs/code/example/example-list-primitive-01.kt diff --git a/docs/knit/example/example-map-complex-01.kt b/docs/code/example/example-map-complex-01.kt similarity index 100% rename from docs/knit/example/example-map-complex-01.kt rename to docs/code/example/example-map-complex-01.kt diff --git a/docs/knit/example/example-map-complex-02.kt b/docs/code/example/example-map-complex-02.kt similarity index 100% rename from docs/knit/example/example-map-complex-02.kt rename to docs/code/example/example-map-complex-02.kt diff --git a/docs/knit/example/example-map-complex-03.kt b/docs/code/example/example-map-complex-03.kt similarity index 100% rename from docs/knit/example/example-map-complex-03.kt rename to docs/code/example/example-map-complex-03.kt diff --git a/docs/knit/example/example-map-primitive-01.kt b/docs/code/example/example-map-primitive-01.kt similarity index 100% rename from docs/knit/example/example-map-primitive-01.kt rename to docs/code/example/example-map-primitive-01.kt diff --git a/docs/knit/example/example-map-primitive-02.kt b/docs/code/example/example-map-primitive-02.kt similarity index 100% rename from docs/knit/example/example-map-primitive-02.kt rename to docs/code/example/example-map-primitive-02.kt diff --git a/docs/knit/example/example-map-primitive-03.kt b/docs/code/example/example-map-primitive-03.kt similarity index 100% rename from docs/knit/example/example-map-primitive-03.kt rename to docs/code/example/example-map-primitive-03.kt diff --git a/docs/knit/example/example-plain-class-primitive-fields-01.kt b/docs/code/example/example-plain-class-primitive-fields-01.kt similarity index 100% rename from docs/knit/example/example-plain-class-primitive-fields-01.kt rename to docs/code/example/example-plain-class-primitive-fields-01.kt diff --git a/docs/knit/example/example-plain-class-primitive-fields-02.kt b/docs/code/example/example-plain-class-primitive-fields-02.kt similarity index 100% rename from docs/knit/example/example-plain-class-primitive-fields-02.kt rename to docs/code/example/example-plain-class-primitive-fields-02.kt diff --git a/docs/knit/example/example-plain-class-single-field-01.kt b/docs/code/example/example-plain-class-single-field-01.kt similarity index 100% rename from docs/knit/example/example-plain-class-single-field-01.kt rename to docs/code/example/example-plain-class-single-field-01.kt diff --git a/docs/knit/example/example-plain-data-class-01.kt b/docs/code/example/example-plain-data-class-01.kt similarity index 100% rename from docs/knit/example/example-plain-data-class-01.kt rename to docs/code/example/example-plain-data-class-01.kt diff --git a/docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt b/docs/code/example/example-polymorphic-abstract-class-primitive-fields-01.kt similarity index 100% rename from docs/knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt rename to docs/code/example/example-polymorphic-abstract-class-primitive-fields-01.kt diff --git a/docs/knit/example/example-polymorphic-objects-01.kt b/docs/code/example/example-polymorphic-objects-01.kt similarity index 100% rename from docs/knit/example/example-polymorphic-objects-01.kt rename to docs/code/example/example-polymorphic-objects-01.kt diff --git a/docs/knit/example/example-polymorphic-sealed-class-01.kt b/docs/code/example/example-polymorphic-sealed-class-01.kt similarity index 100% rename from docs/knit/example/example-polymorphic-sealed-class-01.kt rename to docs/code/example/example-polymorphic-sealed-class-01.kt diff --git a/docs/knit/example/example-polymorphic-sealed-class-02.kt b/docs/code/example/example-polymorphic-sealed-class-02.kt similarity index 100% rename from docs/knit/example/example-polymorphic-sealed-class-02.kt rename to docs/code/example/example-polymorphic-sealed-class-02.kt diff --git a/docs/knit/example/example-polymorphic-static-types-01.kt b/docs/code/example/example-polymorphic-static-types-01.kt similarity index 100% rename from docs/knit/example/example-polymorphic-static-types-01.kt rename to docs/code/example/example-polymorphic-static-types-01.kt diff --git a/docs/knit/example/example-polymorphic-static-types-02.kt b/docs/code/example/example-polymorphic-static-types-02.kt similarity index 100% rename from docs/knit/example/example-polymorphic-static-types-02.kt rename to docs/code/example/example-polymorphic-static-types-02.kt diff --git a/docs/knit/example/example-value-classes-01.kt b/docs/code/example/example-value-classes-01.kt similarity index 100% rename from docs/knit/example/example-value-classes-01.kt rename to docs/code/example/example-value-classes-01.kt diff --git a/docs/knit/example/example-value-classes-02.kt b/docs/code/example/example-value-classes-02.kt similarity index 100% rename from docs/knit/example/example-value-classes-02.kt rename to docs/code/example/example-value-classes-02.kt diff --git a/docs/knit/example/example-value-classes-03.kt b/docs/code/example/example-value-classes-03.kt similarity index 100% rename from docs/knit/example/example-value-classes-03.kt rename to docs/code/example/example-value-classes-03.kt diff --git a/docs/knit/example/example-value-classes-04.kt b/docs/code/example/example-value-classes-04.kt similarity index 100% rename from docs/knit/example/example-value-classes-04.kt rename to docs/code/example/example-value-classes-04.kt diff --git a/docs/knit/knit-include.ftl b/docs/code/knit-include.ftl similarity index 100% rename from docs/knit/knit-include.ftl rename to docs/code/knit-include.ftl diff --git a/docs/knit/knit-test.ftl b/docs/code/knit-test.ftl similarity index 100% rename from docs/knit/knit-test.ftl rename to docs/code/knit-test.ftl diff --git a/docs/knit/test/AbstractClassesTest.kt b/docs/code/test/AbstractClassesTest.kt similarity index 100% rename from docs/knit/test/AbstractClassesTest.kt rename to docs/code/test/AbstractClassesTest.kt diff --git a/docs/knit/test/BasicClassesTest.kt b/docs/code/test/BasicClassesTest.kt similarity index 100% rename from docs/knit/test/BasicClassesTest.kt rename to docs/code/test/BasicClassesTest.kt diff --git a/docs/knit/test/DefaultValuesTest.kt b/docs/code/test/DefaultValuesTest.kt similarity index 100% rename from docs/knit/test/DefaultValuesTest.kt rename to docs/code/test/DefaultValuesTest.kt diff --git a/docs/knit/test/EdgeCasesTest.kt b/docs/code/test/EdgeCasesTest.kt similarity index 100% rename from docs/knit/test/EdgeCasesTest.kt rename to docs/code/test/EdgeCasesTest.kt diff --git a/docs/knit/test/EnumClassTest.kt b/docs/code/test/EnumClassTest.kt similarity index 100% rename from docs/knit/test/EnumClassTest.kt rename to docs/code/test/EnumClassTest.kt diff --git a/docs/knit/test/ListsTests.kt b/docs/code/test/ListsTests.kt similarity index 100% rename from docs/knit/test/ListsTests.kt rename to docs/code/test/ListsTests.kt diff --git a/docs/knit/test/MapsTests.kt b/docs/code/test/MapsTests.kt similarity index 100% rename from docs/knit/test/MapsTests.kt rename to docs/code/test/MapsTests.kt diff --git a/docs/knit/test/PolymorphismTest.kt b/docs/code/test/PolymorphismTest.kt similarity index 100% rename from docs/knit/test/PolymorphismTest.kt rename to docs/code/test/PolymorphismTest.kt diff --git a/docs/knit/test/ValueClassesTest.kt b/docs/code/test/ValueClassesTest.kt similarity index 100% rename from docs/knit/test/ValueClassesTest.kt rename to docs/code/test/ValueClassesTest.kt diff --git a/docs/knit/util/strings.kt b/docs/code/util/strings.kt similarity index 100% rename from docs/knit/util/strings.kt rename to docs/code/util/strings.kt diff --git a/docs/default-values.md b/docs/default-values.md index 697d06db..8cc78c84 100644 --- a/docs/default-values.md +++ b/docs/default-values.md @@ -36,7 +36,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-default-values-single-field-01.kt). +> You can get the full code [here](./code/example/example-default-values-single-field-01.kt). ```typescript export interface Colour { @@ -61,7 +61,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-default-values-single-field-02.kt). +> You can get the full code [here](./code/example/example-default-values-single-field-02.kt). ```typescript export interface Colour { @@ -94,7 +94,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-default-values-primitive-fields-01.kt). +> You can get the full code [here](./code/example/example-default-values-primitive-fields-01.kt). ```typescript export interface ContactDetails { diff --git a/docs/edgecases.md b/docs/edgecases.md index 7d91135c..5f59b960 100644 --- a/docs/edgecases.md +++ b/docs/edgecases.md @@ -40,7 +40,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-edgecase-recursive-references-01.kt). +> You can get the full code [here](./code/example/example-edgecase-recursive-references-01.kt). ```typescript export interface A { @@ -73,7 +73,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-edgecase-recursive-references-02.kt). +> You can get the full code [here](./code/example/example-edgecase-recursive-references-02.kt). ```typescript export interface A { @@ -106,7 +106,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-edgecase-recursive-references-03.kt). +> You can get the full code [here](./code/example/example-edgecase-recursive-references-03.kt). ```typescript export interface A { diff --git a/docs/enums.md b/docs/enums.md index 2bd3e656..68d9a152 100644 --- a/docs/enums.md +++ b/docs/enums.md @@ -40,7 +40,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-enum-class-01.kt). +> You can get the full code [here](./code/example/example-enum-class-01.kt). ```typescript export enum SomeType { @@ -72,7 +72,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-enum-class-02.kt). +> You can get the full code [here](./code/example/example-enum-class-02.kt). ```typescript export enum SomeType2 { diff --git a/docs/knit.properties b/docs/knit.properties index c5287279..5b8bc032 100644 --- a/docs/knit.properties +++ b/docs/knit.properties @@ -1,9 +1,9 @@ -knit.dir=./knit/example/ -test.dir=./knit/test/ +knit.dir=./code/example/ +test.dir=./code/test/ knit.package=dev.adamko.kxstsgen.example test.package=dev.adamko.kxstsgen.example.test # -test.template=./knit/knit-test.ftl +test.template=./code/knit-test.ftl test.language=typescript -knit.include=./knit/knit-include.ftl +knit.include=./code/knit-include.ftl test.mode.=normalizeJoin()\n .shouldBe diff --git a/docs/lists.md b/docs/lists.md index f89dfe4a..b7992737 100644 --- a/docs/lists.md +++ b/docs/lists.md @@ -33,7 +33,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-list-primitive-01.kt). +> You can get the full code [here](./code/example/example-list-primitive-01.kt). ```typescript export interface MyLists { diff --git a/docs/maps.md b/docs/maps.md index f330403a..d77a3534 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -37,7 +37,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-map-primitive-01.kt). +> You can get the full code [here](./code/example/example-map-primitive-01.kt). ```typescript export interface Config { @@ -67,7 +67,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-map-primitive-02.kt). +> You can get the full code [here](./code/example/example-map-primitive-02.kt). ```typescript export interface Application { @@ -96,7 +96,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-map-primitive-03.kt). +> You can get the full code [here](./code/example/example-map-primitive-03.kt). ```typescript export interface Config { @@ -145,7 +145,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-map-complex-01.kt). +> You can get the full code [here](./code/example/example-map-complex-01.kt). ```typescript export interface CanvasProperties { @@ -219,7 +219,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-map-complex-02.kt). +> You can get the full code [here](./code/example/example-map-complex-02.kt). ```typescript export interface CanvasProperties { @@ -293,7 +293,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-map-complex-03.kt). +> You can get the full code [here](./code/example/example-map-complex-03.kt). ```typescript export interface CanvasProperties { diff --git a/docs/polymorphism.md b/docs/polymorphism.md index 0fd2e824..c8973278 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -42,7 +42,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-polymorphic-abstract-class-primitive-fields-01.kt). +> You can get the full code [here](./code/example/example-polymorphic-abstract-class-primitive-fields-01.kt). ```typescript export type SimpleTypes = any; @@ -75,7 +75,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-polymorphic-static-types-01.kt). +> You can get the full code [here](./code/example/example-polymorphic-static-types-01.kt). Only the Project class properties are generated. @@ -110,7 +110,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-polymorphic-static-types-02.kt). +> You can get the full code [here](./code/example/example-polymorphic-static-types-02.kt). ```typescript export type Project = any; @@ -156,7 +156,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-polymorphic-sealed-class-01.kt). +> You can get the full code [here](./code/example/example-polymorphic-sealed-class-01.kt). ```typescript export type Project = Project.DeprecatedProject | Project.OProj; @@ -224,7 +224,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-polymorphic-sealed-class-02.kt). +> You can get the full code [here](./code/example/example-polymorphic-sealed-class-02.kt). ```typescript export type Dog = Dog.Golden | Dog.Mutt | Dog.NovaScotia; @@ -315,7 +315,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-polymorphic-objects-01.kt). +> You can get the full code [here](./code/example/example-polymorphic-objects-01.kt). ```typescript export type Response = Response.EmptyResponse | Response.TextResponse; @@ -364,7 +364,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-generics-01.kt). +> You can get the full code [here](./code/example/example-generics-01.kt). ```typescript export type Double = number & { __kotlin_Double__: void } diff --git a/docs/value-classes.md b/docs/value-classes.md index c8dd255d..37ab8898 100644 --- a/docs/value-classes.md +++ b/docs/value-classes.md @@ -36,7 +36,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-value-classes-01.kt). +> You can get the full code [here](./code/example/example-value-classes-01.kt). ```typescript export type AuthToken = string; @@ -66,7 +66,7 @@ fun main() { -> You can get the full code [here](./knit/example/example-value-classes-02.kt). +> You can get the full code [here](./code/example/example-value-classes-02.kt). ```typescript export type UByte = number; @@ -110,7 +110,7 @@ fun main() { -> You can get the full code [here](./knit/example/example-value-classes-03.kt). +> You can get the full code [here](./code/example/example-value-classes-03.kt). ```typescript export type ULong = number & { __ULong__: void }; @@ -135,7 +135,7 @@ fun main() { } ``` -> You can get the full code [here](./knit/example/example-value-classes-04.kt). +> You can get the full code [here](./code/example/example-value-classes-04.kt). ```typescript export type UserCount = UInt; diff --git a/modules/kxs-ts-gen-core/build.gradle.kts b/modules/kxs-ts-gen-core/build.gradle.kts index f1f98ea2..7b0408ad 100644 --- a/modules/kxs-ts-gen-core/build.gradle.kts +++ b/modules/kxs-ts-gen-core/build.gradle.kts @@ -1,5 +1,6 @@ plugins { buildsrc.convention.`kotlin-multiplatform` + buildsrc.convention.`maven-publish` kotlin("plugin.serialization") // id("org.jetbrains.reflekt") } diff --git a/settings.gradle.kts b/settings.gradle.kts index d75a6c4e..577556b0 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -5,7 +5,7 @@ apply(from = "./buildSrc/repositories.settings.gradle.kts") include( ":modules:kxs-ts-gen-core", ":modules:kxs-ts-gen-processor", - ":docs:knit", + ":docs:code", ) enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") From aa13efa85024f9f24484301b07a37ebb67227b3a Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 13:38:31 +0200 Subject: [PATCH 30/46] prep for jitpack --- .../convention/maven-publish.gradle.kts | 23 +++++++++++++++++++ jitpack.yml | 5 ++++ 2 files changed, 28 insertions(+) create mode 100644 buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts create mode 100644 jitpack.yml diff --git a/buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts new file mode 100644 index 00000000..34e1326a --- /dev/null +++ b/buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts @@ -0,0 +1,23 @@ +package buildsrc.convention + +plugins { + `maven-publish` +} + +plugins.withType(JavaPlugin::class.java) { + publishing { + publications { + create("mavenJava") { + from(components["java"]) + } + } + } +} + +tasks + .matching { it.name in listOf("publish", "publishToMavenLocal") } + .configureEach { + doLast { + logger.lifecycle("[${this.name}] ${project.group}:${project.name}:${project.version}") + } + } diff --git a/jitpack.yml b/jitpack.yml new file mode 100644 index 00000000..a5396c12 --- /dev/null +++ b/jitpack.yml @@ -0,0 +1,5 @@ +# https://jitpack.io/docs/BUILDING/ + +# https://jitpack.io/docs/BUILDING/#java-version +jdk: + - openjdk11 From e0583a3e64cb8042e0f36efed4da17b7916eaa6a Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 13:38:41 +0200 Subject: [PATCH 31/46] docs update --- README.md | 33 +++++++++++-------- modules/kxs-ts-gen-processor/build.gradle.kts | 2 ++ 2 files changed, 22 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4a6b1612..42acef0e 100644 --- a/README.md +++ b/README.md @@ -21,8 +21,12 @@ interface PlayerDetails { } ``` -The aim is to create TypeScript interfaces that can accurately produce JSON that Kotlinx -Serialization can parse. +The aim is to create TypeScript interfaces that can accurately produce Kotlinx Serialization +compatible JSON. + +The Kotlinx Serialization API should be used to generate TypeScript. The +[`SerialDescriptor`s](https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-core/kotlinx.serialization.descriptors/-serial-descriptor/index.html) +are flexible and comprehensive enough to allow for accurate TypeScript code, without any deviation. See [the docs](./docs) for working examples. @@ -30,14 +34,17 @@ See [the docs](./docs) for working examples. This is a proof-of-concept. -| | Status | Notes | -|---------------------------------------|----------------------------------------------------------|:--------------------------------------------------------------------------------------------------| -| Basic classes | ✅ [example](./docs/basic-classes.md) | | -| Nullable and default-value properties | ✅ [example](./docs/default-values.md) | | -| Value classes | ✅ [example](./docs/value-classes.md) | | -| Enums | ✅ [example](./docs/enums.md) | | -| Lists | ✅ [example](./docs/lists.md) | | -| Maps | ✅/⚠ [example](./docs/maps.md) | Maps with complex keys are converted to an ES6 Map. [See](./docs/maps.md#maps-with-complex-keys) | -| Polymorphism - Sealed classes | ✅/⚠ [example](./docs/polymorphism.md#sealed-classes) | Nested sealed classes are ignored | -| Polymorphism - Open classes | ❌ [example](./docs/abstract-classes.md) | Not implemented. Converted to `type MyClass = any` | -| Edge cases - circular dependencies | ✅ [example](./docs/edgecases.md) | | +| | Status | Notes | +|---------------------------------------|----------------------------------------------------------|:-------------------------------------------------------------------------------------------------| +| Kotlin multiplatform | ❓ | The codebase is multiplatform, but only JVM has been tested | +| `@SerialName` | ✅/⚠ | The serial name is directly converted and might produce invalid TypeScript | +| Basic classes | ✅ [example](./docs/basic-classes.md) | | +| Nullable and default-value properties | ✅ [example](./docs/default-values.md) | | +| Value classes | ✅ [example](./docs/value-classes.md) | | +| Enums | ✅ [example](./docs/enums.md) | | +| Lists | ✅ [example](./docs/lists.md) | | +| Maps | ✅/⚠ [example](./docs/maps.md) | Maps with complex keys are converted to an ES6 Map, [see](./docs/maps.md#maps-with-complex-keys) | +| Polymorphism - Sealed classes | ✅/⚠ [example](./docs/polymorphism.md#sealed-classes) | Nested sealed classes are ignored, [see](./docs/polymorphism.md#nested-sealed-classes) | +| Polymorphism - Open classes | ❌ [example](./docs/abstract-classes.md) | Not implemented. Converted to `type MyClass = any` | +| `@JsonClassDiscriminator` | ❌ | Not implemented | +| Edge cases - circular dependencies | ✅ [example](./docs/edgecases.md) | | diff --git a/modules/kxs-ts-gen-processor/build.gradle.kts b/modules/kxs-ts-gen-processor/build.gradle.kts index d09bfefa..81c344f3 100644 --- a/modules/kxs-ts-gen-processor/build.gradle.kts +++ b/modules/kxs-ts-gen-processor/build.gradle.kts @@ -7,6 +7,8 @@ plugins { // id("org.jetbrains.reflekt") } +description = "Experimental alternative to Kotlinx Serialization. Currently unused." + val kspVersion = "1.6.10-1.0.4" val kotlinCompileTestingVersion = "1.4.7" val kotlinxSerializationVersion = "1.3.2" // TODO put dependencies in libs.version.toml From ffd1a72939e9ea71dcacc60891808945798bdd10 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 14:02:02 +0200 Subject: [PATCH 32/46] improve recursive descriptor extraction --- .../dev.adamko.kxstsgen/processSerializers.kt | 31 +++++++++---------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt index 715dd449..9b7bbea4 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt @@ -40,24 +40,21 @@ fun interface SerializerDescriptorsExtractor { } private val extractedDescriptors by MutableMapWithDefaultPut> { descriptor -> - sequence { - val seen = mutableSetOf() - val deque = ArrayDeque() - deque.addLast(descriptor) - - while (deque.isNotEmpty()) { - val next = deque.removeFirst() - - val nextElementDescriptors = elementDescriptors.getValue(next) - - nextElementDescriptors - .filter { it !in seen } - .forEach { deque.addLast(it) } + extractDescriptors(descriptor).asSequence() + } - seen.add(next) - yield(next) - } - }.distinct() + private tailrec fun extractDescriptors( + current: SerialDescriptor? = null, + queue: ArrayDeque = ArrayDeque(), + extracted: Set = emptySet(), + ): Set { + return if (current == null) { + extracted + } else { + val currentDescriptors = elementDescriptors.getValue(current) + queue.addAll(currentDescriptors - extracted) + extractDescriptors(queue.removeFirstOrNull(), queue, extracted + current) + } } private val elementDescriptors by MutableMapWithDefaultPut> { descriptor -> From 83caceaac575282d994a7b8934412632b462fe89 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 14:38:38 +0200 Subject: [PATCH 33/46] version bum git-versioning plugin --- build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle.kts b/build.gradle.kts index c5b56ab9..f32ce3a0 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -3,7 +3,7 @@ import buildsrc.config.excludeGeneratedGradleDsl plugins { base idea - id("me.qoomon.git-versioning") version "5.1.2" + id("me.qoomon.git-versioning") version "5.1.5" id("org.jetbrains.kotlinx.kover") } From b093dac7cad33cc17d587000130b746a06539248 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 21:47:18 +0200 Subject: [PATCH 34/46] better helper function for mutable maps with default puts --- .../util/mapWithDefaultDelegate.kt | 23 +++++++++++++++---- 1 file changed, 18 insertions(+), 5 deletions(-) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/util/mapWithDefaultDelegate.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/util/mapWithDefaultDelegate.kt index cec51cf3..1abe6a83 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/util/mapWithDefaultDelegate.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/util/mapWithDefaultDelegate.kt @@ -1,5 +1,6 @@ package dev.adamko.kxstsgen.util +import kotlin.jvm.JvmName import kotlin.properties.ReadWriteProperty import kotlin.reflect.KProperty @@ -9,14 +10,12 @@ class MutableMapWithDefaultPut( private val defaultValue: (key: K) -> V, ) : ReadWriteProperty> { - private var map: MutableMap = with(initial.toMutableMap()) { - withDefault { key -> getOrPut(key) { defaultValue(key) } } - } + private var map: MutableMap = initial.toMutableMap().withDefaultPut(defaultValue) override fun getValue(thisRef: Any?, property: KProperty<*>): MutableMap = map override fun setValue(thisRef: Any?, property: KProperty<*>, value: MutableMap) { - this.map = value + this.map = value.withDefaultPut(defaultValue) } } @@ -33,6 +32,20 @@ class MapWithDefaultPut( override fun getValue(thisRef: Any?, property: KProperty<*>): Map = map override fun setValue(thisRef: Any?, property: KProperty<*>, value: Map) { - this.map = value + this.map = value.toMutableMap().withDefaultPut(defaultValue) } } + + +@JvmName("mapWithDefaultPut") +fun Map.withDefaultPut(defaultValue: (key: K) -> V): Map = + with(this.toMutableMap()) { + withDefault { key -> getOrPut(key) { defaultValue(key) } } + } + + +@JvmName("mutableMapWithDefaultPut") +fun MutableMap.withDefaultPut(defaultValue: (key: K) -> V): MutableMap = + with(this) { + withDefault { key -> getOrPut(key) { defaultValue(key) } } + } From 674b6df989b789969787c8dfb706717204d6d21c Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 21:49:53 +0200 Subject: [PATCH 35/46] simplify the API --- .../example-polymorphic-static-types-02.kt | 5 +- docs/polymorphism.md | 5 +- .../KxsTsConvertorContext.kt | 54 ---------- .../dev.adamko.kxstsgen/KxsTsGenerator.kt | 45 ++++++-- .../dev.adamko.kxstsgen/KxsTsProcessor.kt | 62 ----------- .../KxsTsSourceCodeGenerator.kt | 6 +- .../dev.adamko.kxstsgen/TsElementConverter.kt | 82 +++++--------- .../TsElementIdConverter.kt | 2 +- .../dev.adamko.kxstsgen/TsTypeRefConverter.kt | 101 +++++++++++------- .../dev.adamko.kxstsgen/processSerializers.kt | 95 +--------------- .../kotlin/dev.adamko.kxstsgen/tsElements.kt | 5 +- 11 files changed, 144 insertions(+), 318 deletions(-) delete mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt delete mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt diff --git a/docs/code/example/example-polymorphic-static-types-02.kt b/docs/code/example/example-polymorphic-static-types-02.kt index 06066533..88858347 100644 --- a/docs/code/example/example-polymorphic-static-types-02.kt +++ b/docs/code/example/example-polymorphic-static-types-02.kt @@ -22,6 +22,9 @@ val module = SerializersModule { } fun main() { - val tsGenerator = KxsTsGenerator(serializersModule = module) + val config = KxsTsConfig(serializersModule = module) + + val tsGenerator = KxsTsGenerator(config) + println(tsGenerator.generate(Project.serializer())) } diff --git a/docs/polymorphism.md b/docs/polymorphism.md index c8973278..d39828db 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -105,7 +105,10 @@ val module = SerializersModule { } fun main() { - val tsGenerator = KxsTsGenerator(serializersModule = module) + val config = KxsTsConfig(serializersModule = module) + + val tsGenerator = KxsTsGenerator(config) + println(tsGenerator.generate(Project.serializer())) } ``` diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt deleted file mode 100644 index 645c8ee3..00000000 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsConvertorContext.kt +++ /dev/null @@ -1,54 +0,0 @@ -package dev.adamko.kxstsgen - -import dev.adamko.kxstsgen.util.MutableMapWithDefaultPut -import kotlinx.serialization.descriptors.SerialDescriptor - -interface KxsTsConvertorContext { -// val config: KxsTsConfig - - fun elementId(descriptor: SerialDescriptor): TsElementId - - fun typeRef(descriptor: SerialDescriptor): TsTypeRef - - fun mapType(descriptor: SerialDescriptor): TsLiteral.TsMap.Type - -// fun element(descriptor: SerialDescriptor): TsElement - - class Default( - elementIds: Map = mapOf(), - typeRefs: Map = mapOf(), - mapTypes: Map = mapOf(), - convertedElements: Map = mapOf(), - private val elementIdConverter: TsElementIdConverter = TsElementIdConverter.Default, - private val elementConverter: TsElementConverter = TsElementConverter.Default, - private val typeRefConverter: TsTypeRefConverter = TsTypeRefConverter.Default, - private val mapTypeConverter: TsMapTypeConverter = TsMapTypeConverter.Default, - ) : KxsTsConvertorContext { - - private val elementIds: MutableMap - by MutableMapWithDefaultPut(elementIds) { elementIdConverter(it) } - - private val typeRefs: MutableMap - by MutableMapWithDefaultPut(typeRefs) { typeRefConverter(this, it) } - - private val mapTypes: MutableMap - by MutableMapWithDefaultPut(mapTypes) { mapTypeConverter(it) } - -// private val convertedElements: MutableMap -// by MutableMapWithDefaultPut(convertedElements) { elementConverter(this, it) } - - override fun elementId(descriptor: SerialDescriptor): TsElementId = - elementIds.getValue(descriptor) - - override fun typeRef(descriptor: SerialDescriptor): TsTypeRef = - typeRefs.getValue(descriptor) - - override fun mapType(descriptor: SerialDescriptor): TsLiteral.TsMap.Type = - mapTypes.getValue(descriptor) - -// override fun element(descriptor: SerialDescriptor): TsElement = -// convertedElements.getValue(descriptor) - - } - -} 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 04914458..8b332c29 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 @@ -1,24 +1,47 @@ package dev.adamko.kxstsgen import kotlinx.serialization.KSerializer -import kotlinx.serialization.modules.SerializersModule -class KxsTsGenerator( - private val config: KxsTsConfig = KxsTsConfig(), -) { +open class KxsTsGenerator( + val config: KxsTsConfig = KxsTsConfig(), - constructor( - serializersModule: SerializersModule, - ) : this(KxsTsConfig(serializersModule = serializersModule)) + val descriptorsExtractor: SerializerDescriptorsExtractor = SerializerDescriptorsExtractor.Default, + val elementIdConverter: TsElementIdConverter = TsElementIdConverter.Default, - fun generate(vararg serializers: KSerializer<*>): String { + val mapTypeConverter: TsMapTypeConverter = TsMapTypeConverter.Default, - val processor = KxsTsProcessor(config) + val typeRefConverter: TsTypeRefConverter = + TsTypeRefConverter.Default(elementIdConverter, mapTypeConverter), - serializers.forEach { processor.addSerializer(it) } + val elementConverter: TsElementConverter = + TsElementConverter.Default( + elementIdConverter, + mapTypeConverter, + typeRefConverter, + ), + + val sourceCodeGenerator: KxsTsSourceCodeGenerator = KxsTsSourceCodeGenerator.Default(config) +) { - return processor.process() + fun generate(vararg serializers: KSerializer<*>): String { + + val descriptors = serializers.flatMap { descriptorsExtractor(it) }.toSet() + + val elements = descriptors.map { elementConverter(it) } + + return elements + .groupBy { element -> sourceCodeGenerator.groupElementsBy(element) } + .mapValues { (_, elements) -> + elements + .filterIsInstance() + .map { element -> sourceCodeGenerator.generateDeclaration(element) } + .filter { it.isNotBlank() } + .joinToString(config.declarationSeparator) + } + .values// TODO create namespaces + .joinToString(config.declarationSeparator) } + } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt deleted file mode 100644 index 2b05d38a..00000000 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsProcessor.kt +++ /dev/null @@ -1,62 +0,0 @@ -package dev.adamko.kxstsgen - - -import dev.adamko.kxstsgen.util.MutableMapWithDefaultPut -import kotlinx.serialization.KSerializer -import kotlinx.serialization.descriptors.SerialDescriptor - - -class KxsTsProcessor( - val config: KxsTsConfig, - val context: KxsTsConvertorContext = KxsTsConvertorContext.Default(), - - val descriptorDataProcessor: DescriptorDataProcessor = - DescriptorDataProcessor.Default, - - val serializerDescriptorsExtractor: SerializerDescriptorsExtractor = - SerializerDescriptorsExtractor.Default, - - val sourceCodeGenerator: KxsTsSourceCodeGenerator = - KxsTsSourceCodeGenerator.Default(config, context), - - ) { - - private val descriptorData by MutableMapWithDefaultPut, DescriptorData> { serializer -> - descriptorDataProcessor(context, config, serializer) - } - - private val serializers = mutableSetOf>() - - fun addSerializer(serializer: KSerializer<*>) { - serializers += serializer - } - - fun process(): String { - val allDescriptorData: Map = - serializers.associate { serializer -> - serializer.descriptor to descriptorData.getValue(serializer) - } - - val allSerialDescriptors: Set = - serializerDescriptorsExtractor(allDescriptorData.values) - - val allTsElements: List = - allSerialDescriptors.map { descriptor -> - TsElementConverter.Default(context, descriptor, allDescriptorData[descriptor]) - } - - return allTsElements - .groupBy { element -> sourceCodeGenerator.groupElementsBy(element) } - .mapValues { (_, elements) -> - elements - .filterIsInstance() - .map { element -> sourceCodeGenerator.generateDeclaration(element) } - .filter { it.isNotBlank() } - .joinToString(config.declarationSeparator) - } - .values// TODO create namespaces - .joinToString(config.declarationSeparator) - } - - -} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt index 77fa0bae..81f03478 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/KxsTsSourceCodeGenerator.kt @@ -5,8 +5,7 @@ package dev.adamko.kxstsgen * Writes [TsElement]s as TypeScript source code. */ abstract class KxsTsSourceCodeGenerator( - val config: KxsTsConfig, - val context: KxsTsConvertorContext, + val config: KxsTsConfig = KxsTsConfig(), ) { abstract fun groupElementsBy(element: TsElement): String? @@ -32,8 +31,7 @@ abstract class KxsTsSourceCodeGenerator( open class Default( config: KxsTsConfig, - context: KxsTsConvertorContext, - ) : KxsTsSourceCodeGenerator(config, context) { + ) : KxsTsSourceCodeGenerator(config) { override fun groupElementsBy(element: TsElement): String { diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt index a806b2e3..00899761 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt @@ -12,38 +12,20 @@ import kotlinx.serialization.descriptors.elementNames fun interface TsElementConverter { operator fun invoke( - context: KxsTsConvertorContext, descriptor: SerialDescriptor, - descriptorData: DescriptorData?, ): TsElement - object Default : TsElementConverter { + open class Default( + val elementIdConverter: TsElementIdConverter, + val mapTypeConverter: TsMapTypeConverter, + val typeRefConverter: TsTypeRefConverter, + ) : TsElementConverter { override operator fun invoke( - context: KxsTsConvertorContext, - descriptor: SerialDescriptor, - descriptorData: DescriptorData?, - ): TsElement { - return convertMonomorphicDescriptor(context, descriptor) - -// return when (descriptorData) { -// is DescriptorData.Polymorphic -> convertPolymorphicDescriptor( -// context, -// descriptorData, -// ) -// null, -// is DescriptorData.Monomorphic -> setOf(convertMonomorphicDescriptor(context, descriptor)) -// } - - } - - - private fun convertMonomorphicDescriptor( - context: KxsTsConvertorContext, descriptor: SerialDescriptor, ): TsElement { return when (descriptor.kind) { - SerialKind.ENUM -> convertEnum(context, descriptor) + SerialKind.ENUM -> convertEnum(descriptor) PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean @@ -57,31 +39,30 @@ fun interface TsElementConverter { PrimitiveKind.FLOAT, PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber - StructureKind.LIST -> convertList(context, descriptor) - StructureKind.MAP -> convertMap(context, descriptor) + StructureKind.LIST -> convertList(descriptor) + StructureKind.MAP -> convertMap(descriptor) StructureKind.CLASS, StructureKind.OBJECT -> when { - descriptor.isInline -> convertTypeAlias(context, descriptor) - else -> convertInterface(context, descriptor, null) + descriptor.isInline -> convertTypeAlias(descriptor) + else -> convertInterface(descriptor, null) } - PolymorphicKind.SEALED -> convertPolymorphic(context, descriptor) + PolymorphicKind.SEALED -> convertPolymorphic(descriptor) // TODO handle contextual // TODO handle polymorphic open SerialKind.CONTEXTUAL, PolymorphicKind.OPEN -> { - val resultId = context.elementId(descriptor) + val resultId = elementIdConverter(descriptor) val fieldTypeRef = TsTypeRef.Literal(TsLiteral.Primitive.TsAny, false) TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) } } } - private fun convertPolymorphic( - context: KxsTsConvertorContext, + fun convertPolymorphic( descriptor: SerialDescriptor, ): TsDeclaration { @@ -95,7 +76,7 @@ fun interface TsElementConverter { .elementDescriptors val subclassInterfaces = subclasses - .map { convertMonomorphicDescriptor(context, it) } + .map { this(it) } .filterIsInstance() .map { it.copy(id = TsElementId("${descriptor.serialName}.${it.id.name}")) } .toSet() @@ -106,31 +87,29 @@ fun interface TsElementConverter { else -> error("Can't convert non-polymorphic SerialKind ${descriptor.kind} to polymorphic interface") } - return convertInterface(context, descriptor, polymorphism) + return convertInterface(descriptor, polymorphism) } - private fun convertTypeAlias( - context: KxsTsConvertorContext, + fun convertTypeAlias( structDescriptor: SerialDescriptor, ): TsDeclaration { - val resultId = context.elementId(structDescriptor) + val resultId = elementIdConverter(structDescriptor) val fieldDescriptor = structDescriptor.elementDescriptors.first() - val fieldTypeRef = context.typeRef(fieldDescriptor) + val fieldTypeRef = typeRefConverter(fieldDescriptor) return TsDeclaration.TsTypeAlias(resultId, fieldTypeRef) } - private fun convertInterface( - context: KxsTsConvertorContext, + fun convertInterface( descriptor: SerialDescriptor, polymorphism: TsPolymorphism?, ): TsDeclaration { - val resultId = context.elementId(descriptor) + val resultId = elementIdConverter(descriptor) val properties = descriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> val name = descriptor.getElementName(index) - val fieldTypeRef = context.typeRef(fieldDescriptor) + val fieldTypeRef = typeRefConverter(fieldDescriptor) when { descriptor.isElementOptional(index) -> TsProperty.Optional(name, fieldTypeRef) else -> TsProperty.Required(name, fieldTypeRef) @@ -140,36 +119,33 @@ fun interface TsElementConverter { } - private fun convertEnum( - context: KxsTsConvertorContext, + fun convertEnum( enumDescriptor: SerialDescriptor, ): TsDeclaration.TsEnum { - val resultId = context.elementId(enumDescriptor) + val resultId = elementIdConverter(enumDescriptor) return TsDeclaration.TsEnum(resultId, enumDescriptor.elementNames.toSet()) } - private fun convertList( - context: KxsTsConvertorContext, + fun convertList( listDescriptor: SerialDescriptor, ): TsLiteral.TsList { val elementDescriptor = listDescriptor.elementDescriptors.first() - val elementTypeRef = context.typeRef(elementDescriptor) + val elementTypeRef = typeRefConverter(elementDescriptor) return TsLiteral.TsList(elementTypeRef) } - private fun convertMap( - context: KxsTsConvertorContext, + fun convertMap( mapDescriptor: SerialDescriptor, ): TsLiteral.TsMap { val (keyDescriptor, valueDescriptor) = mapDescriptor.elementDescriptors.toList() - val keyTypeRef = context.typeRef(keyDescriptor) - val valueTypeRef = context.typeRef(valueDescriptor) + val keyTypeRef = typeRefConverter(keyDescriptor) + val valueTypeRef = typeRefConverter(valueDescriptor) - val type = context.mapType(keyDescriptor) + val type = mapTypeConverter(keyDescriptor) return TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt index 61bdfdbc..24258779 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt @@ -12,7 +12,7 @@ fun interface TsElementIdConverter { operator fun invoke(descriptor: SerialDescriptor): TsElementId object Default : TsElementIdConverter { - override fun invoke(descriptor: SerialDescriptor): TsElementId { + override operator fun invoke(descriptor: SerialDescriptor): TsElementId { val targetId = TsElementId(descriptor.serialName.removeSuffix("?")) return when (descriptor.kind) { diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt index 4f854a76..73f9f3b5 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt @@ -11,56 +11,77 @@ import kotlinx.serialization.descriptors.elementDescriptors fun interface TsTypeRefConverter { - operator fun invoke(context: KxsTsConvertorContext, descriptor: SerialDescriptor): TsTypeRef + operator fun invoke(descriptor: SerialDescriptor): TsTypeRef - object Default : TsTypeRefConverter { - override fun invoke( - context: KxsTsConvertorContext, + + open class Default( + val elementIdConverter: TsElementIdConverter = TsElementIdConverter.Default, + val mapTypeConverter: TsMapTypeConverter = TsMapTypeConverter.Default, + ) : TsTypeRefConverter { + + override operator fun invoke( descriptor: SerialDescriptor, ): TsTypeRef { - return when (descriptor.kind) { - is PrimitiveKind -> { - val tsPrimitive = when (descriptor.kind as PrimitiveKind) { - PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean - - PrimitiveKind.BYTE, - PrimitiveKind.SHORT, - PrimitiveKind.INT, - PrimitiveKind.LONG, - PrimitiveKind.FLOAT, - PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber - - PrimitiveKind.CHAR, - PrimitiveKind.STRING -> TsLiteral.Primitive.TsString - } - TsTypeRef.Literal(tsPrimitive, descriptor.isNullable) - } - - StructureKind.LIST -> { - val elementDescriptor = descriptor.elementDescriptors.first() - val elementTypeRef = context.typeRef(elementDescriptor) - val listRef = TsLiteral.TsList(elementTypeRef) - TsTypeRef.Literal(listRef, descriptor.isNullable) - } - StructureKind.MAP -> { - val (keyDescriptor, valueDescriptor) = descriptor.elementDescriptors.toList() - val keyTypeRef = context.typeRef(keyDescriptor) - val valueTypeRef = context.typeRef(valueDescriptor) - val type = context.mapType(keyDescriptor) - val map = TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) - TsTypeRef.Literal(map, descriptor.isNullable) - } + return when (val descriptorKind = descriptor.kind) { + is PrimitiveKind -> primitiveTypeRef(descriptor, descriptorKind) + + StructureKind.LIST -> listTypeRef(descriptor) + StructureKind.MAP -> mapTypeRef(descriptor) SerialKind.CONTEXTUAL, PolymorphicKind.SEALED, PolymorphicKind.OPEN, SerialKind.ENUM, StructureKind.CLASS, - StructureKind.OBJECT -> { - val id = context.elementId(descriptor) - TsTypeRef.Declaration(id, null, descriptor.isNullable) - } + StructureKind.OBJECT -> declarationTypeRef(descriptor) + } + } + + fun primitiveTypeRef( + descriptor: SerialDescriptor, + kind: PrimitiveKind, + ): TsTypeRef.Literal { + val tsPrimitive = when (kind) { + PrimitiveKind.BOOLEAN -> TsLiteral.Primitive.TsBoolean + + PrimitiveKind.BYTE, + PrimitiveKind.SHORT, + PrimitiveKind.INT, + PrimitiveKind.LONG, + PrimitiveKind.FLOAT, + PrimitiveKind.DOUBLE -> TsLiteral.Primitive.TsNumber + + PrimitiveKind.CHAR, + PrimitiveKind.STRING -> TsLiteral.Primitive.TsString } + return TsTypeRef.Literal(tsPrimitive, descriptor.isNullable) + } + + + fun mapTypeRef(descriptor: SerialDescriptor): TsTypeRef.Literal { + val (keyDescriptor, valueDescriptor) = descriptor.elementDescriptors.toList() + val keyTypeRef = this(keyDescriptor) + val valueTypeRef = this(valueDescriptor) + val type = mapTypeConverter(keyDescriptor) + val map = TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) + return TsTypeRef.Literal(map, descriptor.isNullable) + } + + + fun listTypeRef(descriptor: SerialDescriptor): TsTypeRef.Literal { + val elementDescriptor = descriptor.elementDescriptors.first() + val elementTypeRef = this(elementDescriptor) + val listRef = TsLiteral.TsList(elementTypeRef) + return TsTypeRef.Literal(listRef, descriptor.isNullable) + } + + + fun declarationTypeRef( + descriptor: SerialDescriptor + ): TsTypeRef.Declaration { + val id = elementIdConverter(descriptor) + return TsTypeRef.Declaration(id, null, descriptor.isNullable) } } + } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt index 9b7bbea4..f0e81044 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt @@ -1,12 +1,8 @@ -@file:OptIn(InternalSerializationApi::class) - package dev.adamko.kxstsgen import dev.adamko.kxstsgen.util.MutableMapWithDefaultPut import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer -import kotlinx.serialization.PolymorphicSerializer -import kotlinx.serialization.SealedClassSerializer import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor @@ -21,27 +17,18 @@ import kotlinx.serialization.descriptors.elementDescriptors fun interface SerializerDescriptorsExtractor { operator fun invoke( - allDescriptorData: Iterable, + serializer: KSerializer<*> ): Set - object Default : SerializerDescriptorsExtractor { + object Default : SerializerDescriptorsExtractor { override operator fun invoke( - allDescriptorData: Iterable, + serializer: KSerializer<*> ): Set { - return allDescriptorData.flatMap { descriptorData -> - when (descriptorData) { - is DescriptorData.Polymorphic -> listOf(descriptorData.descriptor) - is DescriptorData.Monomorphic -> listOf(descriptorData.descriptor) - } - }.flatMap { descriptor -> extractedDescriptors.getValue(descriptor) } - .toSet() + return extractDescriptors(serializer.descriptor) } - private val extractedDescriptors by MutableMapWithDefaultPut> { descriptor -> - extractDescriptors(descriptor).asSequence() - } private tailrec fun extractDescriptors( current: SerialDescriptor? = null, @@ -57,6 +44,7 @@ fun interface SerializerDescriptorsExtractor { } } + private val elementDescriptors by MutableMapWithDefaultPut> { descriptor -> when (descriptor.kind) { SerialKind.ENUM -> emptyList() @@ -88,76 +76,3 @@ fun interface SerializerDescriptorsExtractor { } } - - -/** Create [DescriptorData] */ -fun interface DescriptorDataProcessor { - operator fun invoke( - context: KxsTsConvertorContext, - config: KxsTsConfig, - serializer: KSerializer<*>, - ): DescriptorData - - object Default : DescriptorDataProcessor { - override fun invoke( - context: KxsTsConvertorContext, - config: KxsTsConfig, - serializer: KSerializer<*>, - ): DescriptorData { - return when (serializer) { - is PolymorphicSerializer<*> -> - DescriptorData.Polymorphic.Open( - descriptor = serializer.descriptor, - subclasses = config.polymorphicDescriptors.getValue(serializer.baseClass) - ) - - is SealedClassSerializer<*> -> { - - val subclasses = serializer.descriptor - .elementDescriptors - .toList() - .first { it.kind == SerialKind.CONTEXTUAL } - .elementDescriptors - .filter { it.kind == StructureKind.CLASS } - .toSet() - - DescriptorData.Polymorphic.Closed( - descriptor = serializer.descriptor, - subclasses = subclasses, - ) - } - - else -> - DescriptorData.Monomorphic(serializer.descriptor) - } - } - } -} - - -sealed class DescriptorData { - abstract val descriptor: SerialDescriptor - - data class Monomorphic( - override val descriptor: SerialDescriptor, - ) : DescriptorData() - - sealed class Polymorphic : DescriptorData() { - - abstract val subclasses: Set - - data class Closed( - override val descriptor: SerialDescriptor, - override val subclasses: Set, - ) : Polymorphic() - - data class Open( - override val descriptor: SerialDescriptor, - override val subclasses: Set, - ) : Polymorphic() - - } - - override fun hashCode(): Int = descriptor.hashCode() - override fun equals(other: Any?): Boolean = (descriptor == other) -} diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt index 7f3b889d..b85cc40c 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt @@ -125,7 +125,10 @@ sealed interface TsTypeRef { val nullable: Boolean - data class Literal(val element: TsLiteral, override val nullable: Boolean) : TsTypeRef + data class Literal( + val element: TsLiteral, + override val nullable: Boolean, + ) : TsTypeRef data class Declaration( From cac1dcb7a5319340c0f6f0ce71fdff8e04f0bdde Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 21:55:37 +0200 Subject: [PATCH 36/46] code tidup --- .../kotlin/dev.adamko.kxstsgen/KxsTsConfig.kt | 11 ----------- .../kotlin/dev.adamko.kxstsgen/TsElementConverter.kt | 3 ++- .../kotlin/dev.adamko.kxstsgen/_annotations.kt | 11 +++++++++++ .../{ => experiments}/serializerExtractors.kt | 3 ++- .../kotlin/dev.adamko.kxstsgen/processSerializers.kt | 1 - .../{ => experiments}/serializerExtractorsJvm.kt | 2 +- 6 files changed, 16 insertions(+), 15 deletions(-) create mode 100644 modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/_annotations.kt rename modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/{ => experiments}/serializerExtractors.kt (95%) rename modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/{ => experiments}/serializerExtractorsJvm.kt (98%) 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 87d321bc..3cd9af13 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 @@ -91,14 +91,3 @@ data class KxsTsConfig( } } - - -@Target( - AnnotationTarget.CLASS, - AnnotationTarget.PROPERTY, - AnnotationTarget.FUNCTION, - AnnotationTarget.TYPEALIAS -) -@RequiresOptIn(level = RequiresOptIn.Level.WARNING) -@MustBeDocumented -annotation class UnimplementedKxTsGenApi diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt index 00899761..9ba6ffb8 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt @@ -15,6 +15,7 @@ fun interface TsElementConverter { descriptor: SerialDescriptor, ): TsElement + open class Default( val elementIdConverter: TsElementIdConverter, val mapTypeConverter: TsMapTypeConverter, @@ -48,7 +49,6 @@ fun interface TsElementConverter { else -> convertInterface(descriptor, null) } - PolymorphicKind.SEALED -> convertPolymorphic(descriptor) // TODO handle contextual @@ -62,6 +62,7 @@ fun interface TsElementConverter { } } + fun convertPolymorphic( descriptor: SerialDescriptor, ): TsDeclaration { diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/_annotations.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/_annotations.kt new file mode 100644 index 00000000..837242f6 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/_annotations.kt @@ -0,0 +1,11 @@ +package dev.adamko.kxstsgen + +@Target( + AnnotationTarget.CLASS, + AnnotationTarget.PROPERTY, + AnnotationTarget.FUNCTION, + AnnotationTarget.TYPEALIAS +) +@RequiresOptIn(level = RequiresOptIn.Level.WARNING) +@MustBeDocumented +annotation class UnimplementedKxTsGenApi diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/serializerExtractors.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/experiments/serializerExtractors.kt similarity index 95% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/serializerExtractors.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/experiments/serializerExtractors.kt index d7945929..7fe8b147 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/serializerExtractors.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/experiments/serializerExtractors.kt @@ -1,7 +1,8 @@ @file:OptIn(InternalSerializationApi::class) -package dev.adamko.kxstsgen +package dev.adamko.kxstsgen.experiments +import dev.adamko.kxstsgen.KxsTsConfig import kotlinx.serialization.ContextualSerializer import kotlinx.serialization.DeserializationStrategy import kotlinx.serialization.InternalSerializationApi diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt index f0e81044..369e9239 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt @@ -73,6 +73,5 @@ fun interface SerializerDescriptorsExtractor { .flatMap { it.elementDescriptors } } } - } } diff --git a/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/serializerExtractorsJvm.kt b/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/experiments/serializerExtractorsJvm.kt similarity index 98% rename from modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/serializerExtractorsJvm.kt rename to modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/experiments/serializerExtractorsJvm.kt index 54c925d5..3f8cf326 100644 --- a/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/serializerExtractorsJvm.kt +++ b/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/experiments/serializerExtractorsJvm.kt @@ -1,5 +1,5 @@ @file:OptIn(InternalSerializationApi::class) // TODO make GitHub issue -package dev.adamko.kxstsgen +package dev.adamko.kxstsgen.experiments import kotlin.reflect.KClass import kotlin.reflect.* From c78b77a9e4fdb286025fb27e35a5cdb8c8203782 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 22:00:08 +0200 Subject: [PATCH 37/46] formatting, code tidying --- ...processSerializers.kt => SerializerDescriptorsExtractor.kt} | 3 +-- .../kotlin/dev.adamko.kxstsgen/TsElementConverter.kt | 2 +- .../kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt | 2 +- .../kotlin/dev.adamko.kxstsgen/TsMapTypeConverter.kt | 1 - .../kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt | 1 - .../src/commonMain/kotlin/dev.adamko.kxstsgen/_annotations.kt | 1 + .../src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt | 2 +- 7 files changed, 5 insertions(+), 7 deletions(-) rename modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/{processSerializers.kt => SerializerDescriptorsExtractor.kt} (95%) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/SerializerDescriptorsExtractor.kt similarity index 95% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/SerializerDescriptorsExtractor.kt index 369e9239..5780fe2b 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/processSerializers.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/SerializerDescriptorsExtractor.kt @@ -1,7 +1,6 @@ package dev.adamko.kxstsgen import dev.adamko.kxstsgen.util.MutableMapWithDefaultPut -import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind @@ -21,7 +20,7 @@ fun interface SerializerDescriptorsExtractor { ): Set - object Default : SerializerDescriptorsExtractor { + object Default : SerializerDescriptorsExtractor { override operator fun invoke( serializer: KSerializer<*> diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt index 9ba6ffb8..1e15742f 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt @@ -1,6 +1,5 @@ package dev.adamko.kxstsgen - import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor @@ -9,6 +8,7 @@ import kotlinx.serialization.descriptors.StructureKind import kotlinx.serialization.descriptors.elementDescriptors import kotlinx.serialization.descriptors.elementNames + fun interface TsElementConverter { operator fun invoke( diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt index 24258779..27ab0206 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt @@ -1,12 +1,12 @@ package dev.adamko.kxstsgen - import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor import kotlinx.serialization.descriptors.SerialKind import kotlinx.serialization.descriptors.StructureKind + fun interface TsElementIdConverter { operator fun invoke(descriptor: SerialDescriptor): TsElementId diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsMapTypeConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsMapTypeConverter.kt index 1ecf880e..3bdf82de 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsMapTypeConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsMapTypeConverter.kt @@ -1,6 +1,5 @@ package dev.adamko.kxstsgen - import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt index 73f9f3b5..9803e4b0 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsTypeRefConverter.kt @@ -1,6 +1,5 @@ package dev.adamko.kxstsgen - import kotlinx.serialization.descriptors.PolymorphicKind import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/_annotations.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/_annotations.kt index 837242f6..c739e0ab 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/_annotations.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/_annotations.kt @@ -1,5 +1,6 @@ package dev.adamko.kxstsgen + @Target( AnnotationTarget.CLASS, AnnotationTarget.PROPERTY, diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt index b85cc40c..8096be89 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/tsElements.kt @@ -128,7 +128,7 @@ sealed interface TsTypeRef { data class Literal( val element: TsLiteral, override val nullable: Boolean, - ) : TsTypeRef + ) : TsTypeRef data class Declaration( From 0889352587658c0ea356b07c9661e81f405dd39e Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 22:41:35 +0200 Subject: [PATCH 38/46] kmm gradle config tidy up --- .../src/main/kotlin/buildsrc/config/gradle.kt | 33 ------------- .../src/main/kotlin/buildsrc/config/kmm.kt | 47 +++++++++++++++++++ .../kotlin-multiplatform.gradle.kts | 13 +++++ modules/kxs-ts-gen-core/build.gradle.kts | 26 ++++++---- settings.gradle.kts | 5 ++ 5 files changed, 81 insertions(+), 43 deletions(-) create mode 100644 buildSrc/src/main/kotlin/buildsrc/config/kmm.kt diff --git a/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt b/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt index 399ee0d2..d897a509 100644 --- a/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt +++ b/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt @@ -27,36 +27,3 @@ fun IdeaModule.excludeGeneratedGradleDsl(layout: ProjectLayout) { ) ) } - - -/** - * `kotlin-js` adds a directory in the root-dir for the Yarn lock. - * That's a bit annoying. It's a little neater if it's in the - * gradle dir, next to the version-catalog. - */ -fun Project.relocateKotlinJsStore() { - - afterEvaluate { - rootProject.extensions.findByType()?.apply { - lockFileDirectory = project.rootDir.resolve("gradle/kotlin-js-store") - } - } - -} - -fun KotlinTargetContainerWithPresetFunctions.currentHostTarget( - targetName: String = "native", - configure: KotlinNativeTargetWithHostTests.() -> Unit, -): KotlinNativeTargetWithHostTests { - val hostOs = System.getProperty("os.name") - val isMingwX64 = hostOs.startsWith("Windows") - val hostTarget = when { - hostOs == "Mac OS X" -> macosX64(targetName) - hostOs == "Linux" -> linuxX64(targetName) - isMingwX64 -> mingwX64(targetName) - else -> throw GradleException("Preset for host OS '$hostOs' is undefined") - } - println("Current host target ${hostTarget.targetName}/${hostTarget.preset?.name}") - hostTarget.configure() - return hostTarget -} diff --git a/buildSrc/src/main/kotlin/buildsrc/config/kmm.kt b/buildSrc/src/main/kotlin/buildsrc/config/kmm.kt new file mode 100644 index 00000000..1ff3d9ef --- /dev/null +++ b/buildSrc/src/main/kotlin/buildsrc/config/kmm.kt @@ -0,0 +1,47 @@ +package buildsrc.config + +import org.gradle.api.GradleException +import org.gradle.api.Project +import org.gradle.kotlin.dsl.findByType +import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTargetWithHostTests +import org.jetbrains.kotlin.gradle.targets.js.yarn.YarnRootExtension + + +/** + * `kotlin-js` adds a directory in the root-dir for the Yarn lock. + * That's a bit annoying. It's a little neater if it's in the + * gradle dir, next to the version-catalog. + */ +fun Project.relocateKotlinJsStore() { + + afterEvaluate { + rootProject.extensions.findByType()?.apply { + lockFileDirectory = project.rootDir.resolve("gradle/kotlin-js-store") + } + } + +} + + +fun KotlinMultiplatformExtension.currentHostTarget( + targetName: String = "native", + configure: KotlinNativeTargetWithHostTests.() -> Unit, +): KotlinNativeTargetWithHostTests { + val hostOs = System.getProperty("os.name") + val hostTarget = when { + hostOs == "Mac OS X" -> macosX64(targetName) + hostOs == "Linux" -> linuxX64(targetName) + hostOs.startsWith("Windows") -> mingwX64(targetName) + else -> throw GradleException("Preset for host OS '$hostOs' is undefined") + } + + println("Current host target ${hostTarget.targetName}/${hostTarget.preset?.name}") + hostTarget.configure() + return hostTarget +} + + +fun KotlinMultiplatformExtension.publicationsFromMainHost(): List { + return listOf(jvm(), js()).map { it.name } + "kotlinMultiplatform" +} diff --git a/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts index 6dc154a1..aa900d74 100644 --- a/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts @@ -2,6 +2,7 @@ package buildsrc.convention import buildsrc.config.relocateKotlinJsStore + plugins { id("buildsrc.convention.subproject") kotlin("multiplatform") @@ -10,3 +11,15 @@ plugins { relocateKotlinJsStore() + + +kotlin { + targets.all { + compilations.all { + kotlinOptions { + languageVersion = "1.6" + apiVersion = "1.6" + } + } + } +} diff --git a/modules/kxs-ts-gen-core/build.gradle.kts b/modules/kxs-ts-gen-core/build.gradle.kts index 7b0408ad..d74a7f5d 100644 --- a/modules/kxs-ts-gen-core/build.gradle.kts +++ b/modules/kxs-ts-gen-core/build.gradle.kts @@ -1,3 +1,6 @@ +import buildsrc.config.publicationsFromMainHost + + plugins { buildsrc.convention.`kotlin-multiplatform` buildsrc.convention.`maven-publish` @@ -8,14 +11,6 @@ plugins { val kotlinxSerializationVersion = "1.3.2" kotlin { -// val hostOs = System.getProperty("os.name") -// val isMingwX64 = hostOs.startsWith("Windows") -// val nativeTarget = when { -// hostOs == "Mac OS X" -> macosX64("native") -// hostOs == "Linux" -> linuxX64("native") -// isMingwX64 -> mingwX64("native") -// else -> throw GradleException("Host OS is not supported in Kotlin/Native.") -// } // js(IR) { // binaries.executable() @@ -25,12 +20,11 @@ kotlin { // } // } // } + jvm { compilations.all { kotlinOptions { jvmTarget = "11" - languageVersion = "1.6" - apiVersion = "1.6" } } withJava() @@ -38,6 +32,18 @@ kotlin { useJUnitPlatform() } } + +// publishing { +// publications { +// matching { it.name in publicationsFromMainHost() }.all { +// val targetPublication = this@all +// tasks.withType() +// .matching { it.publication == targetPublication } +// .configureEach { onlyIf { findProperty("isMainHost") == "true" } } +// } +// } +// } + sourceSets { all { diff --git a/settings.gradle.kts b/settings.gradle.kts index 577556b0..a47f96ea 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -9,3 +9,8 @@ include( ) enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") + +dependencyResolutionManagement { + @Suppress("UnstableApiUsage") // Central declaration of repositories is an incubating feature + repositoriesMode.set(RepositoriesMode.PREFER_SETTINGS) +} From 759740b4b894cb6ff6454b0d73a52307a34b8519 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 22:47:26 +0200 Subject: [PATCH 39/46] bump gradle version --- build.gradle.kts | 2 +- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 59821 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew | 10 +++++----- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index f32ce3a0..711d6bcb 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -22,7 +22,7 @@ gitVersioning.apply { tasks.wrapper { - gradleVersion = "7.4.1" + gradleVersion = "7.4.2" distributionType = Wrapper.DistributionType.ALL } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..41d9927a4d4fb3f96a785543079b8df6723c946b 100644 GIT binary patch delta 8958 zcmY+KWl$VIlZIh&f(Hri?gR<$?iyT!TL`X;1^2~W7YVSq1qtqM!JWlDxLm%}UESUM zndj}Uny%^UnjhVhFb!8V3s(a#fIy>`VW15{5nuy;_V&a5O#0S&!a4dSkUMz_VHu3S zGA@p9Q$T|Sj}tYGWdjH;Mpp8m&yu&YURcrt{K;R|kM~(*{v%QwrBJIUF+K1kX5ZmF zty3i{d`y0;DgE+de>vN@yYqFPe1Ud{!&G*Q?iUc^V=|H%4~2|N zW+DM)W!`b&V2mQ0Y4u_)uB=P@-2`v|Wm{>CxER1P^ z>c}ZPZ)xxdOCDu59{X^~2id7+6l6x)U}C4Em?H~F`uOxS1?}xMxTV|5@}PlN%Cg$( zwY6c}r60=z5ZA1L zTMe;84rLtYvcm?M(H~ZqU;6F7Evo{P7!LGcdwO|qf1w+)MsnvK5^c@Uzj<{ zUoej1>95tuSvDJ|5K6k%&UF*uE6kBn47QJw^yE&#G;u^Z9oYWrK(+oL97hBsUMc_^ z;-lmxebwlB`Er_kXp2$`&o+rPJAN<`WX3ws2K{q@qUp}XTfV{t%KrsZ5vM!Q#4{V& zq>iO$MCiLq#%wXj%`W$_%FRg_WR*quv65TdHhdpV&jlq<=K^K`&!Kl5mA6p4n~p3u zWE{20^hYpn1M}}VmSHBXl1*-)2MP=0_k)EPr#>EoZukiXFDz?Di1I>2@Z^P$pvaF+ zN+qUy63jek2m59;YG)`r^F3-O)0RDIXPhf)XOOdkmu`3SMMSW(g+`Ajt{=h1dt~ks ztrhhP|L4G%5x79N#kwAHh5N){@{fzE7n&%dnisCm65Za<8r_hKvfx4Bg*`%-*-Mvn zFvn~)VP@}1sAyD+B{{8l{EjD10Av&Mz9^Xff*t`lU=q=S#(|>ls520;n3<}X#pyh& z*{CJf7$*&~!9jMnw_D~ikUKJ2+UnXmN6qak{xx%W;BKuXt7@ky!LPI1qk?gDwG@@o zkY+BkIie>{{q==5)kXw(*t#I?__Kwi>`=+s?Gq6X+vtSsaAO&Tf+Bl$vKnzc&%BHM z=loWOQq~n}>l=EL(5&6((ESsQC3^@4jlO5Od{qN#sWV)vqXw}aA>*uvwZopNN(|-T zRTF%5Y_k1R$;(d-)n;hWex{;7b6KgdAVE@&0pd(*qDzBO#YZV%kh%pYt1`hnQ(Fa& zYiDrOTDqk5M7hzp9kI2h!PxNnuJ&xl*zF8sx6!67bA49R1bmUF5bpK&&{eI0U~cH}PM z3aW1$lRb|ItkG5~_eBNu$|I|vYIdAA9a!pVq<+UTx*M}fG`23zxXp&E=FfnY- zEzKj;Cu_s4v>leO7M2-mE(UzKHL4c$c`3dS*19OpLV^4NI*hWWnJQ9lvzP4c;c?do zqrcsKT*i~eIHl0D3r4N{)+RsB6XhrC^;sp2cf_Eq#6*CV;t8v=V!ISe>>9kPgh}NI z=1UZutslxcT$Ad;_P^;Oouoa(cs!Ctpvi>%aQ+Zp=1d|h{W9Wmf7JWxa(~<#tSZ?C%wu4_5F!fc!<@PIBeJ)Nr^$bB6!_Gic_7}c3J{QI~Gg5g5jTp9}V6KYgrgaX>pJt}7$!wOht&KO|+z{Iw@YL|@~D zMww}+lG}rm2^peNx>58ME||ZQxFQeVSX8iogHLq_vXb`>RnoEKaTWBF-$JD#Q4BMv zt2(2Qb*x-?ur1Y(NsW8AdtX0#rDB?O(Vs4_xA(u-o!-tBG03OI!pQD+2UytbL5>lG z*(F)KacHqMa4?dxa(Vcrw>IIAeB$3cx#;;5r2X;HE8|}eYdAgCw#tpXNy7C3w1q`9 zGxZ6;@1G%8shz9e+!K2MO*{_RjO}Jo6eL3{TSZ>nY7)Qs`Dhi5><@oh0r)gT7H-?3 zLDsd^@m%JvrS8sta5`QiZNs^*GT}Hiy^zjK2^Ni%`Z|ma)D2 zuyumbvw$M8$haCTI~6M%d4+P)uX%u{Sfg4Al+F7c6;O-*)DKI7E8izSOKB#FcV{M+ zEvY0FBkq!$J0EW$Cxl}3{JwV^ki-T?q6C30Y5e&p@8Rd?$ST-Ghn*-`tB{k54W<>F z5I)TFpUC!E9298=sk>m#FI4sUDy_!8?51FqqW!9LN1(zuDnB3$!pEUjL>N>RNgAG~-9Xm|1lqHseW(%v&6K(DZ3Pano(1-Qe?3%J&>0`~w^Q-p&@ zg@HjvhJk?*hpF7$9P|gkzz`zBz_5Z!C4_-%fCcAgiSilzFQef!@amHDrW!YZS@?7C zs2Y9~>yqO+rkih?kXztzvnB^6W=f52*iyuZPv$c42$WK7>PHb z6%MYIr5D32KPdwL1hJf{_#jn?`k(taW?mwmZVvrr=y~fNcV$`}v(8};o9AjOJumS4 z`889O91^pkF+|@$d9wVoZ3;^j;^sUs&Ubo_qD&MTL%O z&*SE0ujG~zm;?x)8TLC&ft))nyI zcg44@*Q{cYT+qGrA=In_X{NNCD+B0w#;@g)jvBU;_8od6U>;7HIo@F*=g8CQUo(u^ z3r4FJ7#<@)MXO&5+DgKE&^>^`r!loe7CWE*1k0*0wLFzSOV8jvlX~WOQ?$1v zk$Or}!;ix0g78^6W;+<=J>z@CBs!<<)HvF(Ls-&`matpesJ5kkjC)6nGB@b{ii6-Uoho$BT%iJgugTOeZ$5Xo4D7Pd< zC*LJh5V@2#5%aBZCgzlQi3@<_!VfiL07ywc)ZbwKPfcR|ElQoS(8x|a7#IR}7#Io= zwg4$8S{egr-NffD)Fg&X9bJSoM25pF&%hf>(T&9bI}=#dPQyNYz;ZZ7EZ=u1n701SWKkZ9n(-qU ztN`sdWL1uxQ1mKS@x11;O|@^AD9!NeoPx}?EKIr!2>1Qq4gjfGU)tr6?Z5l7JAS3j zZeq{vG{rb%DFE4%$szK}d2UzB{4>L?Tv+NAlE*&Nq6g+XauaSI+N2Y8PJLw+aNg1p zbxr|hI8wcMP&&+(Cu|%+Jq|r>+BHk@{AvfBXKiVldN)@}TBS0LdIpnANCVE26WL-} zV}HJ^?m&$Rkq;Zf*i-hoasnpJVyTH__dbGWrB_R55d*>pTyl6(?$EO@>RCmTX1Hzr zT2)rOng?D4FfZ_C49hjMV*UonG2DlG$^+k=Y%|?Dqae4}JOU=8=fgY4Uh!pa9eEqf zFX&WLPu!jArN*^(>|H>dj~g`ONZhaaD%h_HHrHkk%d~TR_RrX{&eM#P@3x=S^%_6h zh=A)A{id16$zEFq@-D7La;kTuE!oopx^9{uA3y<}9 z^bQ@U<&pJV6kq7LRF47&!UAvgkBx=)KS_X!NY28^gQr27P=gKh0+E>$aCx&^vj2uc}ycsfSEP zedhTgUwPx%?;+dESs!g1z}5q9EC+fol}tAH9#fhZQ?q1GjyIaR@}lGCSpM-014T~l zEwriqt~ftwz=@2tn$xP&-rJt?nn5sy8sJ5Roy;pavj@O+tm}d_qmAlvhG(&k>(arz z;e|SiTr+0<&6(-An0*4{7akwUk~Yf4M!!YKj^swp9WOa%al`%R>V7mi z+5+UodFAaPdi4(8_FO&O!Ymb#@yxkuVMrog(7gkj$G@FLA#ENMxG)4f<}S%Fn?Up$+C%{02AgMKa^ z4SFGWp6U>{Q6VRJV}yjxXT*e`1XaX}(dW1F&RNhpTzvCtzuu;LMhMfJ2LBEy?{^GHG!OF!! zDvs64TG)?MX&9NCE#H3(M0K>O>`ca0WT2YR>PTe&tn?~0FV!MRtdb@v?MAUG&Ef7v zW%7>H(;Mm)RJkt18GXv!&np z?RUxOrCfs;m{fBz5MVlq59idhov21di5>WXWD-594L-X5;|@kyWi@N+(jLuh=o+5l zGGTi~)nflP_G}Yg5Pi%pl88U4+^*ihDoMP&zA*^xJE_X*Ah!jODrijCqQ^{=&hD7& z^)qv3;cu?olaT3pc{)Kcy9jA2E8I)#Kn8qO>70SQ5P8YSCN=_+_&)qg)OYBg|-k^d3*@jRAeB?;yd-O1A0wJ z?K*RDm|wE<(PBz~+C%2CTtzCTUohxP2*1kE8Of~{KRAvMrO_}NN&@P7SUO{;zx0iK z@or9R8ydYOFZf(cHASCAatL%;62IL27~SmASr(7F&NMr+#gNw@z1VM z_ALFwo3)SoANEwRerBdRV`>y`t72#aF2ConmWQp(Xy|msN9$yxhZ1jAQ67lq{vbC5 zujj|MlGo`6Bfn0TfKgi(k=gq0`K~W+X(@GzYlPI4g0M;owH3yG14rhK>lG8lS{`!K z+Nc@glT-DGz?Ym?v#Hq|_mEdPAlHH5jZuh*6glq!+>Lk$S%ED2@+ea6CE@&1-9a?s znglt|fmIK}fg<9@XgHe4*q!aO<-;Xj$T?IzB-{&2`#eA6rdtCi80mpP&vw(Uytxu$#YzNI_cB>LS zmim>ys;ir;*Dzbr22ZDxO2s;671&J0U<9(n1yj)J zHFNz=ufPcQVEG+ePjB<5C;=H0{>Mi*xD>hQq8`Vi7TjJ$V04$`h3EZGL|}a07oQdR z?{cR(z+d>arn^AUug&voOzzi$ZqaS)blz-z3zr;10x;oP2)|Cyb^WtN2*wNn`YX!Y z+$Pji<7|!XyMCEw4so}xXLU)p)BA~2fl>y2Tt}o9*BPm?AXA8UE8a;>rOgyCwZBFa zyl42y`bc3}+hiZL_|L_LY29vVerM+BVE@YxK>TGm@dHi@Uw*7AIq?QA9?THL603J% zIBJ4y3n8OFzsOI;NH%DZ!MDwMl<#$)d9eVVeqVl(5ZX$PPbt*p_(_9VSXhaUPa9Qu z7)q4vqYKX7ieVSjOmVEbLj4VYtnDpe*0Y&+>0dS^bJ<8s*eHq3tjRAw^+Mu4W^-E= z4;&namG4G;3pVDyPkUw#0kWEO1;HI6M51(1<0|*pa(I!sj}F^)avrE`ShVMKBz}nE zzKgOPMSEp6M>hJzyTHHcjV%W*;Tdb}1xJjCP#=iQuBk_Eho6yCRVp&e!}4IBJ&?ksVc&u#g3+G$oNlJ?mWfADjeBS-Ph3`DKk-~Z70XugH8sq2eba@4 zIC1H_J$`9b$K`J)sGX3d!&>OmC@@rx1TL~NinQOYy72Q_+^&Mg>Ku(fTgaXdr$p_V z#gav1o{k~c>#)u3r@~6v^o)Lf=C{rAlL@!s457pq)pO;Cojx7U{urO4cvXP|E>+dV zmr2?!-5)tk-&*ap^D^2x7NG6nOop2zNFQ9v8-EZ{WCz-h36C)<^|f{V#R_WE^@(T0+d-at5hXX{U?zak*ac-XnyINo+yBD~~3O1I=a z99|CI>502&s-Qi5bv>^2#cQ%ut<4d7KgQ^kE|=%6#VlGiY8$rdJUH{sra;P~cyb_i zeX(kS%w0C?mjhJl9TZp8RS;N~y3(EXEz13oPhOSE4WaTljGkVXWd~|#)vsG6_76I)Kb z8ro?;{j^lxNsaxE-cfP;g(e;mhh3)&ba}li?woV2#7ByioiD>s%L_D;?#;C#z;a(N z-_WY<=SH42m9bFQ>Nb z@4K$@4l8pD7AKxCR>t0%`Qoy9=hA?<<^Vcj8;-E+oBe3ReW1`el8np8E$k{LgFQ}2 z2t8a`wOXFdJ9!5$&mEfD1CnJ)TB+RJih88-Zos9@HZ# zL#{qfbF0ARTXkR@G{lwlOH~nnL)1jcyu!qv2`57S&%oKz0}r{~l9U_UHaJ5!8#nrs z?2FrL`mxnzu&{bweD&62)ilz*?pYIvt`T!XFVVA78})p1YEy7 z8fK#s?b~Yo$n7&_a?EBdXH-_W)Z44?!;DFx6pZ?~RArtBI*Qm4~6nX6Z_T*i$bQPE;Qz?DAPstpGSqr-AJ zo%m9cA`oDDm?&dTaoh_>@F>a?!y4qt_;NGN9Z<%SS;fX-cSu|>+Pba22`CRb#|HZa z;{)yHE>M-pc1C0mrnT~80!u&dvVTYFV8xTQ#g;6{c<9d!FDqU%TK5T6h*w*p980D~ zUyCb`y3{-?(mJFP)0*-Nt;mI$-gc4VQumh|rs&j_^R{sgTPF`1Xja2YWstsKFuQ(d zmZMxV$p$|qQUXchu&8%J(9|)B?`~rIx&)LqDS>ob5%gTeTP#Sbny#y*rnJ&?(l=!( zoV~}LJ1DPLnF8oyM(2ScrQ0{Q4m4-BWnS4wilgCW-~~;}pw=&<+HggRD_3c@3RQIr z9+-%!%}u_{`YS=&>h%kPO3ce}>y!d-zqiniNR-b5r97u;+K6HA2tS>Z#cV{+eFI`* zd8RMGAUtX1KWfPV;q<-5JAykS+2sY$2~UX+4461a(%{P#{rwFPu0xpIuYlbgD{C7C z=U{FUarVTYX6ZUq3wE@G^QT4H2Re;n$Fz9cJ>hABl)9T8pozqbA1)H-%1=WKm^QMu zjnUZ&Pu>q+X&6Co*y#@pxc-4waKMInEPGmE_>3@Ym3S*dedSradmc5mlJn`i0vMW6 zhBnGQD^Z;&S0lnS0curqDO@({J7kTtRE+Ra?nl^HP9<)W&C>~`!258f$XDbyQOQXG zP8hhySnarOpgu8xv8@WlXnm(Uk~)_3$Sg0vTbU3 z{W!5B(L3{Yy3K5PN<@jEarAtja`}@KYva&zFRF*s+_%jIXh$T(S=an8?=Ry3H*NRqWgsM`&!#|@kf1>=4q%bFw7^Rhz!z5I zyI^zU8_R1WN9`88Z=n>pIZQ`Ixr~_9G%Q}@A7rd#*%y7G zXl^Id=^ZL?Rx}}gWXCqzj9C6;x(~mAH|$JteXa1MH<6UQig@!Hf~t}B%tP0I|H&;y zO6N0}svOa1a^PyP9N5?4W6VF%=Bj{qHUgc8@siw4bafT=UPFSoQqKgyUX>sXTBZ=x zOh^Ad!{kOM9v{%5y}`-8u*T&C7Vq6mD%GR}UeU(*epO&qgC-CkD;%=l)ZuinSzHM` z{@`j&_vC6dDe{Yb9k@1zeV_K6!l(@=6ucoI=R^cH=6{i71%4W3$J-?<8Qn#$-DMtA z6Qqi)t?4ifrt%3jSA#6ji#{f(($KBL-iQh-xrC||3U3lq`9>r)>X%oLvtimuHW-)} zy}>9~|M>w4eES`g7;iBM%Se5-OP%1U6gNWp3AZqT8C6OlFFfQ$|7LL;tBV)(qlp4K zruar^K8FnJN3@_}B;G`a~H`t|3+6d>q3#`ctTkE-D^1#d9NalQ04lH*qUW2!V zhk7#z8OwHhSl8w14;KctfO8ubZJ4$dEdpXE78wABz=n5*=q9ex3S}`e7x~~V-jmHOhtX2*n+pBslo3uosdE7xABK=V#-t{1Hd~?i z{i~%Bw6NYF+F$aK$M`r#xe=NxhA5=p%i7!$);sd>Q}#`G?Q~fygrMXmZw?0#5#17W}6Tj+&kFexG{!mYl5FoA99}3G9l;3lVQ^ z48^~gsVppE*x91WheqI(A%F0Z#$#1UJP1R12Mj9r)y(A?a+iquX+d8WD4WAQJ_!oq z9rTISr7bPd(GTP57xm$}C}&kjMivi;zi^Y9g3&X0A;ovdJ?{%_wHgt%%9P&N4H z^XzV(uNA4 zAP`hgP6BEN5`YXh|DF~6Pud?~gWfhUKoPX4>z|}0aocC&K+AoV%|SX*N!wGq3|y< zg4lP(04XIPmt6}$N!dTk+pZv>u;MTB{L4hp9uXk7>aS!6jqM2lVr%{)H3$O127TSZ z0x9hi0k-P?nWFdQ0K`pykqUIT&jD~B0tHP{ffS(}fZ(aW$oBWTSfHO!A^><6vA?qar%tzN-5NQO zL&|F{nGiQyzNJ+bM$Y`n=Lx^3wTG^o2bGB@cwr1eb+6c-1tN=U+Db;bc~eJ!hwM{SbI=#g?$!PjDB+) zPgU_2EIxocr*EOJG52-~!gml&|D|C2OQ3Y(zAhL}iae4-Ut0F*!z!VEdfw8#`LAi# zhJ_EM*~;S|FMV6y%-SduHjPOI3cFM(GpH|HES<}*=vqY+64%dJYc|k?n6Br7)D#~# zEqO(xepfaf2F{>{E2`xb=AO%A<7RtUq6kU_Iu0m?@0K(+<}u3gVw5fy=Y4CC*{IE3 zLP3YBJ7x+U(os5=&NT%gKi23bbaZ`@;%ln)wp4GpDUT$J8NtFDHJzIe_-t}{!HAsh zJ4<^WovY};)9IKAskSebdQiXv$y5}THuJZ}ouoElIZRui=6lrupV|_Jz=9^&;@HwL;J#@23k?A;k`0Bgf;ioO>W`IQ+4? z7A)eKoY4%+g%=w;=Vm8}H>@U*=*AWNtPqgWRqib#5RTGA@Q=43FrQn3J`GkTUV5yp0U`EOTqjfp+-9;0F8!dMEwwcK%(6`8sDD^aR04 zd6O5vh|Xk?&3dy4f|1QK&Ulf{h6Iq;d-&*ti#Ck>wZFG;GHwc?b;X~eBITx49>2d8 z4HcK&1&DvEGT6kXdzAm4oO8%c}8OBt~8H956_;YP-ss*uMf==a+%w~F>Qkm7r)IAuxuoX}h92$gHqbFUun#8m zWHdy`Zrm#=Pa98x8cO0vd@Tgkr*lm0{dky+Gocr0P8y%HGEI#c3qLqIRc`Oq_C%*; zG+QTr(#Q|yHKv6R@!DmLlwJQ3FAB)Yor-I4zyDyqM4yp5n2TrQH>gRt*Zw0+WI-Sj`EgmYHh=t9! zF6lz^xpqGGpo6!5`sc0a^FVhy_Uxq|@~(1@IIzV)nTpY9sY`CV!?8e&bB8=M&sYEb z2i}fvKdhp9Hs68Y-!QJ<=wE(iQ5+49tqt;Rh|jhYrI5VW-mIz|UY{h8E=rC5sh#DU z?wGgk-Tn!I?+Zer7pHlF_Z^!Kd1qkS3&lv#%s6-<5Y%jQL${cge5=G5Ab?D&|9$Y~ zf%rJC2+=2vg;y0-SJb3<@3%}BO$T$C66q$L_H33a`VUbgW~N(4B=v5(<=My|#|J7q z*Ox4wL4kbJd_~EjLTABSu4U7Jk#`y(6O*U6(k6XxM}CtGZB(H@3~kh*zaGRXM}Iwp zQ%xFk2>@wiZrVCV_G4G~v;NebCQ%T7{SDyPpSv&dT@Cn)Mx@IK*IdNrj{*4pkV4wv z)y0J538h>cpB7iPSzA~x24T`{dzNkpvGIqvt1Dvdq@o-`B=$hkczX8$yFMhsWNK-X zxr$kR$tMD0@W)Vxe1^t9qVmsg&K^F@u84)(n2dttIEAZFN6VD$&tskpG%SI7whGL3 z)DeRiwe&?8m7U{G`oW8!SCi*dM>oYL%UKQnKxV_0RXAEBQg1kStExGEUVwLJ0orGGwb7uv+kPDl7_E2*iD|J*=8A@;XCvwq0aw5oJYN*Yh&o=l} z2z8YKb-fIAH5spql4eXqp*)o2*b>#1@DSt?zZi{GPj0gH&Nm+EI<3^z0w%YTEV4xw zI6$+=Faa|Y4o5i0zm5lOg|&tmnJ806DBovU@Ll6XsA;NRrTK~t*AAJIAS=v-UZ%Pr z$oddI@NRir&erzCwq|)ciJemr-E061j{0Vc@Ys7K(mW|JYj*$+i1Q8XlIK8T?TYS(AXu$`2U zQ@fHxc=AVHl_}cRZQ)w0anMEoqRKKIvS^`<-aMf*FM`NsG&Uowneo+Ji$7DUDYc7*Hjg;-&aHM%3 zXO6cz$$G};Uqh+iY7Wpme>PHG4cu(q;xyskNLs$^uRRMfEg?8Cj~aE-ajM%CXkx0F z>C?g3tIA#9sBQOpe`J+04{q7^TqhFk^F1jFtk4JDRO*`d-fx`GYHb=&(JiaM1b?Y^ zO3Kj3sj76ieol|N$;>j@t#tKj=@*gP+mv}KwlTcPYgR$+)2(gk)2JNE=jSauPq!$< z<|?Sb%W)wS)b>b6i{8!x!^!xIdU3{CJFVnTcw0j{M%DUCF=_>eYYEUWnA-|B(+KYL z_W_`JI&&u^@t0})@DH^1LDuT0s3dMpCHIbYBgOT4Zh_4yHbSqRbtIKndeT4Q*Jg91 z@>rO!^t-G~*AIW;FQ$3J=b;oGg8?CTa~qNCb>&cgp@e;?0AqA&paz~(%PYO+QBo4( zp?}ZdSMWx0iJm7HVNk9A#^9Osa#GPJ!_pYEW}($8>&2}fbr@&ygZ?${A7_9?X$(&5 z#~-hxdPQwCNEpf=^+WH-3`2LxrrBMTa}~qJC9S;VzhG!On^JLyW6WkF{8aAE$sM+( zxr8xLW(KIjI`Rm(24r3OJBk<3GF=G!uSP0-G&AY32mLm8q=#Xom&Pqv=1C{d3>1^ zAjsmV@XZ%BKq^eUfBpa8KvO8ob|F3hAjJv*yo2Bhl0)KUus{qA9m8jf)KnOGGTa6~4>3@J_VzkL|vYPl*uL+Ot*Q7W!f5rJw5+AsjP_IfL+-S*2p| zB7!FhjvkUTxQkGWGSg{X;h~dK>gAJivW?88Nu!3o>ySDaABn$rAYt086#27fbjPQS zhq>55ASvm*60qRdVOY9=bU^+{Pi#!OaZwENN;zy5?EztOHK-Q5;rCuiFl}BSc1YaQ zC-S{=KsGDz@Ji9O5W;XxE0xI|@3o6(2~i4b8Ii9VT;^G$*dRw(V?=br)D&q^XkeBX z+gl~+R@rVD-Hwv@7RHV?Bip5KMI)aV^&snt?H<$Nt=OPx#VxF&BGi?2A2+lNOYywNUGMeGL;|(=UjGDtLG0sN&LpGx;|U;xa13s z;W_|SPk^G}!M9_^pO zA3bt3-tca%^42sHeDtfcC0S3w3H1ny!Bxpa=*k?XRPpx9Bb-gx1J9Yvx)4J(8cG+q z(iCPZ9dsf3#QVyZgD_MW#G#qgV)olu$59&3(PzQfw@%4uZ~<5J=ABvdY43(Qnp{;G zHg3>@T#>DbTuhFl3)fb3TFqdh)V2aq7!;&JOHseTWukvA7}(iGUq;v-{2J0iHSNHq z;+)h!p6Ok^+Sp8-jgL($n6Qu47xyE`cFO5SdZR6;R!FET`tm#0D37z339Suxjpv+s z*=%2-N$N?X&0?x_uut3erF@aBGj;9$k9?3FlbDO{RQa1_qtxrh4!4#fjp4x~akvdTp@ zos?^Q&XE;3N93s4rHQGPrV7+au1$$aB6$hLy*Yz_kN$~dweb9PcB!eYVQTGjFuJP> zZCEwBtb>TIgIO^qAzq@Bv-qud_ZD-2W<_at&ml-gv`tPt$@DF5`HlA zM>DmmMkpv&Zm-8)Y#0bLQf4MpD4_-7M8eu6rh(tL8dq8onHs#R9J~dGd2IaXXMC~h z91pKhnQa%Fsn29nAA1;x(%oC zhca~qQDJaMf?wFrl-Pj;e$bZMYmMF!Y3Lv&Sb?Sjn#!NVx&NDyc^$b4uYyo2OmERa zRz;yDGd@JTykzFLe|Wk-y7#3x`6$wt$zR8r48mdUvfbeL+4D|Z``~7$PrE@qc7rZe zVsIoIbCwzjLZ@_M1*bD{HaYn();Z1-q*-I{tEnTZ(}Zmk&%MXSNBX>o| z-u*RNkAyKC-Srp7c-=@5f)xMWg>o2WWl}j6j9=8+D8;T z>0*0q#;qw8%U8i;6s0fu#I*%(g*@@a2Er@@nyI}{=@W{Z-;`=wN4N~>6Xrh&z#g}l zN1g5}0-#(nHUTv_rl2{yUZ;h#t&Fd?tY!7L%ClY)>uH-Ny2ET$lW$S)IQiN79H)D^ zb&0AXYkupy0~w8)*>Sj_p9}4L?lGTq%VG|2p`nWGhnM^!g|j-|O{%9Q%swOq63|*W zw$(N_laI}`ilB+o!a-wl?er~;;3+)$_akSQ!8YO_&-e*SI7n^(QQ;X0ZE`{4f!gAl z5$d+9CKVNonM!NO_frREICIAxOv)wm>}-k?iRisM`R7;=lyo|E_YR~FpS&PS`Lg0f zl-ON<0S%Uix8J%#yZdkCz4YNhcec<|7*P(JsM#>-L>+tYg_71q9~70FAc^6KW5jql zw!crdgVLH1G_eET=|SEc977;)ezVC|{PJZfra|}@rD;0s&@61mTEBJtILllg{%{vN zfhb&lq0yChaLhnJ-Qb62MB7`>M;|_ceHKZAeeh@#8tbrK!ArP6oXIhMK;dhEJTY`@ z0Tq>MIe0`7tGv)N*F0IGYSJv0vN?Az8g+4K9S!pW2~9F4W(_U_T=jCZrzuZ3*|__T zONp_UWmyePv8C~rckc?Xji;Z5OEqg zC*Um)i;Wh4TEwqReQdVVbUKT^2>Tpi6z_^-uF*adUFug4i@JhzpWT^Sk&E>CyP2?H zWf6x}ehuTs6wvzCnTU&gYzT029Nz19(In1WC z`(1IGmi!O%2AR|BjQa4Q0~u)kM%}?xQyjWuQ16^Gp++;`vr7!k--UZWM*~7Zl|ceO@I3`OpaRhD;YoCuo5IC0uHx>9 z478hu@H|e0Zlo)Zj@01#;8BDs@991xe~^9uG2}UXLM(m7fa}AMwX*tjioBeV&Q8Gx zSq$6wZFkRBK`cMI>R(@W@+lo2t)L+4q-negWRLWZBz*|%=W4v62JrmzNuOtA*x)QE z5L%=OH#@KMdB%Jp^r?0tE}5-*6oP`-lO7Sf)0)n*e<{HA=&qhLR)oD8-+V}Z4=md) z+k9lKf64DB2hAT)UaCP~di?-V3~JBH7itYyk~L6hrnxM%?RKntqd`=!b|e7eFnAcu z3*V;g{xr7TSTm$}DY%~SMpl>m{Sj!We+WfxSEor?YeiAxYUy25pn(?T()E>ByP^c@ zipwvWrhIK((R((VU+;@LmOnDu)ZXB3YArzzin!Z^0;PyJWnlfflo|q8(QY;o1*5CO z##hnkO{uynTMdk`~DOC#1 zdiYxQoy}=@7(ke#A8$YZZVtk4wo$8x28&I;cY3Ro-|kW=*yiiHgCLZeAr)UtVx>Tu z|LvL0hq|1-jC0I4x#>&QZCfrVB=zT!nR|~Uz`9%~2 znl{uZ{VEszW`Fad^q_HB!K9*|U-stK%?~;g?&&+12A}Rq$z($Bzuk^2X(Y=hF?-dQ ztc3DsQKI;qhWIV`99Q#R3xnU0AvY!i*BECj-z9l74|%O=V@nlv|qqC^r^-~C?E zGW%c|uYgnfJ(gjsTm_cIqcv*mYM{+i+&@F@+69ZQOK&u#v4oxUSQJ=tvqQ3W=*m;| z>SkBi8LYb-qRY7Sthh*0%3XAC%$z1rhOJzuX=PkTOa=DlocZUpE#KxVNH5)_4n=T( zGi3YrH7e~sPNYVBd~Grcq#CF~rN{p9Zza-Ntnwfma@TB)=3g36*0lSZg#ixEjFe%+ zX=&LDZ5zqculZ`=RYc^ln(~;nN|Qh6gN=!6f9-N2h+3NWbIxYud&;4SX*tWf5slk4 z{q@@l71UAZgj~*6edXb57fBUxvAS7s(RI=X868JM0+^DCn2yC>;v%S;qPOjB>YVsz(Zx9a>>BK&M zIQK>7_n)4ud0X5YM}^i*keH{ehLsiy9@NvOpsFeQjdI6anLGvVbBw_*fU1TzdVS$i z*4j7z!I5RF#rSz|8ibi$;qE{4`aqWYik7QB5U&F5C*;TO_x+gtzPGpzNt!7~nsBT7)Ckc(K~%uv&{{6A`mmBJVAk-{s~52Vu|HbCH7_W1~ZCX^RflOakGg=jo2Z z<*s;5-J+2@^LRDZ-7EV&Pq+FTErw@pfFqvx^i%E7Fx#^n(E`m2(c>K-O5`M`Yek9el zzTGs5qD6*G;y#~xu3>qWuO?-amKYtvRA}I9z#UspEeM;wOERYeot_n_EUMJf$4_u?E!6X~?q)tPoZb^_;8Y_Ox2h1m<+Le-fsRd|T8db<8#$bqez zua^Z|>h%zdnuU^ww$#-dZ9NTM`FN+!IlLkz*FqWb!x^Z|C{KyGjZ+>G;;7Mb@LY|H zc+Gp`L((Dw7pnDlHNm&;SfHedhx*kad$I^uGz{`0BYelq0yEUHpNKSkvj$|dpvY3{7*YGyhXA^LP0&wOw9oNoC=QoVx1<2Dne8qqZL zm>nFh5DX(-RnQwvHCZQwn^#Z=E!SPVlaRJ78Bo@}!!9dRt^qZy?-*`Pt4WSmgucJv zV1yFkcjlEM^uz-;b#Q7ZCP@Lk)m}uPX={R4B=56k7WNh11BN~0T*vr@!!ow^B0hOR zQ)4)&(e%>bNNL%bm<&8H{*l_L7s0$2GUgX2Vd;=4d9Dm2v3TaL+;L>{K7h7 zV#k?xDPm(NDE31$ z<}|X)pEY6myjK+^gaIMk&Yj2~F0rSKemNqlsVm4c|N7mp_C*L01s;GNx#D-*&gk!qQr}^?_r@q!8fuXw!)fA7xkd} zb>vHvdx~H$5qqAWrow7}+8zBM65-JOt5z za=T6f7MK`XJuQog8kIEboPdhcaVJeHy)5z7EBLK5NRr()E|#K0L0N^JD@pUA^Czb` zbUZ_558y+vqAGeyHCbrvOvLD67Ph}06959VzQ_|>RrXQAqE+AQ(-AaKdxoWaF8hdt z{O3W@b^*o#-f1VuU>YMV03ELF7zkCN4Q&b#prz%3Nne0lSbRo@@ z^ihv%oIl~Qyl6Q;a#$*jOC%x0_;eis*)J7=f@Ct*)xF5 zo}u~@-I}2|$b%5L7>@+Z?4o+1r&v6ceIy+vroK&jCQ<4q&45HP2wCol4hVm3pZtjf zHz1D7oyaSKJ~T{Gx}7ONLA)D5k(%%`WswrDyzX*rn}i}}TB4^y#@mAwPzoC)`?rYv zHgx|trUN#mu*VzUV~8TnJM2Qh*ZM5B{x&y>5An`(M7=Z*Q>TdiH@j*2=moNuOtvpz z+G`@~-`%~+AgPKgke@XiRPgndh@bp*-HRsh;HTtz@-y_uhb%7ylVOTqG0#u?Vn5c5 zEp*XRo|8hcgG^$#{$O9CJ&NE;TrfRpSnLmes&MO{m=N%zc`}gb!eQ7odl$oy1%PI} z#AIxx%oRVy&{O~9xnK4$EY>(eQj}!HKIV$Fz*H=-=Kn)N0D6u`(;iO|VraI4fu_W` z;b5{7;Lyx4za}DU#+U7}=H0dAS#YJJ&g2!P@Htu-AL&w=-)*%P9h2{wR|@?Ff9~)b z^+e_3Hetq7W%ls{!?<6&Y$Z;NNB41pvrv)|MET6AZXFXJeFqbFW5@i5WGzl?bP+~? z*&_puH;wKv2)9T_d+P`bLvJFqX#j&xa*-;0nGBbQf0DC>o~=J_Wmtf*2SZQr?{i~X z9-IbRH8{iy?<0v9Ir1?$66+igy|yDQ5J~A9sFX@Pe<*kCY8+MwH?I z`P}zfQ6l^AO8ehZ=l^ZR;R%uu4;BK*=?W9t|0{+-at(MQZ(CtG=EJFNaFMlKCMXu30(gJUqj5+ z`GM|!keqcj;FKTa_qq;{*dHRXAq157hlB@kL#8%yAm2AgfU|*rDKX@FLlp=HL8ddv zAWLCHe@DcDeB2}fl7#=0+#<05c3=VqM*O3bkr@9X4nO|)q0hU;Gye{L8ZN*NH8Id@mP-u;Fmb8YuorjLrW&ndip8CN%_qp982r w1WEnz9^$&s1hkp_3#lPJQ~!HI7WYYjA7>z!`?f%npAh2%rB@vD|Lau$2O)#1n*aa+ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index b1159fc5..92f06b50 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index c53aefaa..1b6c7873 100644 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ #!/bin/sh # -# Copyright 2015-2021 the original authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ # Busybox and similar reduced shells will NOT work, because this script # requires all of these POSIX shell features: # * functions; -# * expansions $var, ${var}, ${var:-default}, ${var+SET}, -# ${var#prefix}, ${var%suffix}, and $( cmd ); -# * compound commands having a testable exit status, especially case; -# * various built-in commands including command, set, and ulimit. +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». # # Important for patching: # From 43cfb94d4cc1ff98cfa5fba09732d152e3cef748 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 23:18:45 +0200 Subject: [PATCH 40/46] add jitpack button --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 42acef0e..ff5d09a6 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![](https://jitpack.io/v/adamko-dev/kotlinx-serialization-typescript-generator.svg)](https://jitpack.io/#adamko-dev/kotlinx-serialization-typescript-generator) + # Kotlinx Serialization TypeScript Generator Create TypeScript interfaces from Kotlinx Serialization classes. From 9e5cac526449df557a7c17391ae20cba4cf845b3 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 23:19:08 +0200 Subject: [PATCH 41/46] fix maven publishing --- .../convention/maven-publish.gradle.kts | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts index 34e1326a..678212bc 100644 --- a/buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts @@ -4,18 +4,21 @@ plugins { `maven-publish` } -plugins.withType(JavaPlugin::class.java) { - publishing { - publications { - create("mavenJava") { - from(components["java"]) - } - } - } -} +//plugins.withType(JavaPlugin::class.java) { +// publishing { +// publications { +// create("mavenJava") { +// from(components["java"]) +// } +// } +// } +//} tasks - .matching { it.name in listOf("publish", "publishToMavenLocal") } + .matching { + it.name.startsWith(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME) + && it.group == PublishingPlugin.PUBLISH_TASK_GROUP + } .configureEach { doLast { logger.lifecycle("[${this.name}] ${project.group}:${project.name}:${project.version}") From d7f6686c8819b4f99fe09fc493f778dff08bbb50 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 23:19:21 +0200 Subject: [PATCH 42/46] update project name & group --- build.gradle.kts | 2 +- settings.gradle.kts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 711d6bcb..8f3ce8ab 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } -project.group = "dev.adamko" +project.group = "dev.adamko.kxtsgen" project.version = "0.0.0-SNAPSHOT" gitVersioning.apply { refs { diff --git a/settings.gradle.kts b/settings.gradle.kts index a47f96ea..796f828d 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,4 +1,4 @@ -rootProject.name = "kxs-typescript-generator" +rootProject.name = "kotlinx-serialization-typescript-generator" apply(from = "./buildSrc/repositories.settings.gradle.kts") From bcb3c0ac8337ed4ea352fa5da49b5c592142c02a Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Sun, 3 Apr 2022 23:21:19 +0200 Subject: [PATCH 43/46] disable reflekt dependency --- buildSrc/build.gradle.kts | 6 +++--- buildSrc/repositories.settings.gradle.kts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 39aba186..0dab274f 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -36,9 +36,9 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-knit:${Versions.kotlinxKnit}") implementation("org.jetbrains.kotlinx:kover:${Versions.kotlinxKover}") - implementation("org.jetbrains.reflekt:gradle-plugin:1.6.10-1-SNAPSHOT") { - isChanging = true - } +// implementation("org.jetbrains.reflekt:gradle-plugin:1.6.10-1-SNAPSHOT") { +// isChanging = true +// } } diff --git a/buildSrc/repositories.settings.gradle.kts b/buildSrc/repositories.settings.gradle.kts index 3fd09331..060eeff6 100644 --- a/buildSrc/repositories.settings.gradle.kts +++ b/buildSrc/repositories.settings.gradle.kts @@ -24,12 +24,12 @@ fun RepositoryHandler.jitpack() { } -fun RepositoryHandler.myMavenLocal(enabled: Boolean = true) { +fun RepositoryHandler.myMavenLocal(enabled: Boolean = false) { if (enabled) { logger.lifecycle("Maven local is enabled") mavenLocal { content { - includeGroup("org.jetbrains.reflekt") +// includeGroup("org.jetbrains.reflekt") } } } From cad7b3091fba7ff76092dd945638b3e78247c838 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 4 Apr 2022 20:51:51 +0200 Subject: [PATCH 44/46] #1 add more tests/docs for lists and maps --- docs/code/example/example-list-objects-01.kt | 23 ++++++ docs/code/example/example-list-objects-02.kt | 22 +++++ .../code/example/example-list-primitive-01.kt | 4 +- .../code/example/example-list-primitive-02.kt | 23 ++++++ docs/code/example/example-map-primitive-03.kt | 6 +- docs/code/example/example-map-primitive-04.kt | 20 +++++ docs/code/example/example-map-primitive-05.kt | 16 ++++ docs/code/test/ListsTests.kt | 43 ++++++++++ docs/code/test/MapsTests.kt | 34 ++++++++ docs/lists.md | 80 ++++++++++++++++++- docs/maps.md | 58 +++++++++++++- 11 files changed, 321 insertions(+), 8 deletions(-) create mode 100644 docs/code/example/example-list-objects-01.kt create mode 100644 docs/code/example/example-list-objects-02.kt create mode 100644 docs/code/example/example-list-primitive-02.kt create mode 100644 docs/code/example/example-map-primitive-04.kt create mode 100644 docs/code/example/example-map-primitive-05.kt diff --git a/docs/code/example/example-list-objects-01.kt b/docs/code/example/example-list-objects-01.kt new file mode 100644 index 00000000..d51b2336 --- /dev/null +++ b/docs/code/example/example-list-objects-01.kt @@ -0,0 +1,23 @@ +// This file was automatically generated from lists.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleListObjects01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +data class Colour( + val rgb: String +) + +@Serializable +data class MyLists( + val colours: List, + val colourGroups: Set>, + val colourGroupGroups: LinkedHashSet>>, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(MyLists.serializer())) +} diff --git a/docs/code/example/example-list-objects-02.kt b/docs/code/example/example-list-objects-02.kt new file mode 100644 index 00000000..ca66335b --- /dev/null +++ b/docs/code/example/example-list-objects-02.kt @@ -0,0 +1,22 @@ +// This file was automatically generated from lists.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleListObjects02 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +data class Colour( + val rgb: String +) + +@Serializable +data class MyLists( + val listOfMaps: List>, + val listOfColourMaps: List>, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(MyLists.serializer())) +} diff --git a/docs/code/example/example-list-primitive-01.kt b/docs/code/example/example-list-primitive-01.kt index 2ec7c29d..9193237d 100644 --- a/docs/code/example/example-list-primitive-01.kt +++ b/docs/code/example/example-list-primitive-01.kt @@ -8,8 +8,8 @@ import dev.adamko.kxstsgen.* @Serializable data class MyLists( val strings: List, - val ints: List, - val longs: List, + val ints: Set, + val longs: Collection, ) fun main() { diff --git a/docs/code/example/example-list-primitive-02.kt b/docs/code/example/example-list-primitive-02.kt new file mode 100644 index 00000000..6005ff8c --- /dev/null +++ b/docs/code/example/example-list-primitive-02.kt @@ -0,0 +1,23 @@ +// This file was automatically generated from lists.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleListPrimitive02 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +data class Colour( + val rgb: String +) + +@Serializable +data class MyLists( + val colours: List, + val colourGroups: List>, + val colourGroupGroups: List>>, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(MyLists.serializer())) +} diff --git a/docs/code/example/example-map-primitive-03.kt b/docs/code/example/example-map-primitive-03.kt index 1fff4023..af81df8e 100644 --- a/docs/code/example/example-map-primitive-03.kt +++ b/docs/code/example/example-map-primitive-03.kt @@ -6,11 +6,11 @@ import kotlinx.serialization.* import dev.adamko.kxstsgen.* @Serializable -data class Config( - val properties: Map +class MapsWithLists( + val mapOfLists: Map> ) fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Config.serializer())) + println(tsGenerator.generate(MapsWithLists.serializer())) } diff --git a/docs/code/example/example-map-primitive-04.kt b/docs/code/example/example-map-primitive-04.kt new file mode 100644 index 00000000..12454628 --- /dev/null +++ b/docs/code/example/example-map-primitive-04.kt @@ -0,0 +1,20 @@ +// This file was automatically generated from maps.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleMapPrimitive04 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +@JvmInline +value class Data(val content: String) + +@Serializable +class MyDataClass( + val mapOfLists: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(MyDataClass.serializer())) +} diff --git a/docs/code/example/example-map-primitive-05.kt b/docs/code/example/example-map-primitive-05.kt new file mode 100644 index 00000000..83290304 --- /dev/null +++ b/docs/code/example/example-map-primitive-05.kt @@ -0,0 +1,16 @@ +// This file was automatically generated from maps.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleMapPrimitive05 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +data class Config( + val properties: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Config.serializer())) +} diff --git a/docs/code/test/ListsTests.kt b/docs/code/test/ListsTests.kt index 8ab6deb0..8c4655b3 100644 --- a/docs/code/test/ListsTests.kt +++ b/docs/code/test/ListsTests.kt @@ -25,4 +25,47 @@ class ListsTests { .normalize() ) } + + @Test + fun testExampleListObjects01() { + captureOutput("ExampleListObjects01") { + dev.adamko.kxstsgen.example.exampleListObjects01.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export interface MyLists { + | colours: Colour[]; + | colourGroups: Colour[][]; + | colourGroupGroups: Colour[][][]; + |} + | + |export interface Colour { + | rgb: string; + |} + """.trimMargin() + .normalize() + ) + } + + @Test + fun testExampleListObjects02() { + captureOutput("ExampleListObjects02") { + dev.adamko.kxstsgen.example.exampleListObjects02.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export interface MyLists { + | listOfMaps: { [key: string]: number }[]; + | listOfColourMaps: { [key: string]: Colour }[]; + |} + | + |export interface Colour { + | rgb: string; + |} + """.trimMargin() + .normalize() + ) + } } diff --git a/docs/code/test/MapsTests.kt b/docs/code/test/MapsTests.kt index 28dfb15f..5b5a45c3 100644 --- a/docs/code/test/MapsTests.kt +++ b/docs/code/test/MapsTests.kt @@ -49,6 +49,40 @@ class MapsTests { fun testExampleMapPrimitive03() { captureOutput("ExampleMapPrimitive03") { dev.adamko.kxstsgen.example.exampleMapPrimitive03.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export interface MapsWithLists { + | mapOfLists: { [key: string]: string[] }; + |} + """.trimMargin() + .normalize() + ) + } + + @Test + fun testExampleMapPrimitive04() { + captureOutput("ExampleMapPrimitive04") { + dev.adamko.kxstsgen.example.exampleMapPrimitive04.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export interface MyDataClass { + | mapOfLists: { [key: string]: Data }; + |} + | + |export type Data = string; + """.trimMargin() + .normalize() + ) + } + + @Test + fun testExampleMapPrimitive05() { + captureOutput("ExampleMapPrimitive05") { + dev.adamko.kxstsgen.example.exampleMapPrimitive05.main() }.normalizeJoin() .shouldBe( // language=TypeScript diff --git a/docs/lists.md b/docs/lists.md index b7992737..ed317da1 100644 --- a/docs/lists.md +++ b/docs/lists.md @@ -6,6 +6,8 @@ * [Introduction](#introduction) * [Primitive lists](#primitive-lists) + * [Lists of objects](#lists-of-objects) + * [Lists of collections](#lists-of-collections) @@ -19,12 +21,14 @@ import dev.adamko.kxstsgen.* ### Primitive lists +A collection will get converted to array. + ```kotlin @Serializable data class MyLists( val strings: List, - val ints: List, - val longs: List, + val ints: Set, + val longs: Collection, ) fun main() { @@ -44,3 +48,75 @@ export interface MyLists { ``` + +### Lists of objects + +```kotlin +@Serializable +data class Colour( + val rgb: String +) + +@Serializable +data class MyLists( + val colours: List, + val colourGroups: Set>, + val colourGroupGroups: LinkedHashSet>>, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(MyLists.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-list-objects-01.kt). + +```typescript +export interface MyLists { + colours: Colour[]; + colourGroups: Colour[][]; + colourGroupGroups: Colour[][][]; +} + +export interface Colour { + rgb: string; +} +``` + + + +### Lists of collections + +```kotlin +@Serializable +data class Colour( + val rgb: String +) + +@Serializable +data class MyLists( + val listOfMaps: List>, + val listOfColourMaps: List>, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(MyLists.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-list-objects-02.kt). + +```typescript +export interface MyLists { + listOfMaps: { [key: string]: number }[]; + listOfColourMaps: { [key: string]: Colour }[]; +} + +export interface Colour { + rgb: string; +} +``` + + diff --git a/docs/maps.md b/docs/maps.md index d77a3534..377174b4 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -7,6 +7,8 @@ * [Introduction](#introduction) * [Primitive maps](#primitive-maps) * [Enum keys](#enum-keys) + * [Maps with Collections](#maps-with-collections) + * [Maps with value classes](#maps-with-value-classes) * [Nullable keys and values](#nullable-keys-and-values) * [Maps with complex keys](#maps-with-complex-keys) * [ES6 Map](#es6-map) @@ -82,6 +84,60 @@ export enum SettingKeys { +### Maps with Collections + +```kotlin +@Serializable +class MapsWithLists( + val mapOfLists: Map> +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(MapsWithLists.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-map-primitive-03.kt). + +```typescript +export interface MapsWithLists { + mapOfLists: { [key: string]: string[] }; +} +``` + + + +### Maps with value classes + +```kotlin +@Serializable +@JvmInline +value class Data(val content: String) + +@Serializable +class MyDataClass( + val mapOfLists: Map +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(MyDataClass.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-map-primitive-04.kt). + +```typescript +export interface MyDataClass { + mapOfLists: { [key: string]: Data }; +} + +export type Data = string; +``` + + + ### Nullable keys and values ```kotlin @@ -96,7 +152,7 @@ fun main() { } ``` -> You can get the full code [here](./code/example/example-map-primitive-03.kt). +> You can get the full code [here](./code/example/example-map-primitive-05.kt). ```typescript export interface Config { From 8db48ce90490be65ce3a971597ff2504eef45faa Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 4 Apr 2022 21:24:28 +0200 Subject: [PATCH 45/46] add links to README --- README.md | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index ed241ccb..22c7453e 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,10 @@ # Kotlinx Serialization TypeScript Generator -Create TypeScript interfaces from Kotlinx Serialization classes. +[Kotlinx Serialization TypeScript Generator](https://github.com/adamko-dev/kotlinx-serialization-typescript-generator) +creates TypeScript interfaces from +[Kotlinx Serialization](https://github.com/Kotlin/kotlinx.serialization/) +classes. ```kotlin @Serializable @@ -52,17 +55,17 @@ See [the docs](./docs) for working examples. This is a proof-of-concept. -| | Status | Notes | -|---------------------------------------|----------------------------------------------------------|:-------------------------------------------------------------------------------------------------| -| Kotlin multiplatform | ❓ | The codebase is multiplatform, but only JVM has been tested | -| `@SerialName` | ✅/⚠ | The serial name is directly converted and might produce invalid TypeScript | -| Basic classes | ✅ [example](./docs/basic-classes.md) | | -| Nullable and default-value properties | ✅ [example](./docs/default-values.md) | | -| Value classes | ✅ [example](./docs/value-classes.md) | | -| Enums | ✅ [example](./docs/enums.md) | | -| Lists | ✅ [example](./docs/lists.md) | | -| Maps | ✅/⚠ [example](./docs/maps.md) | Maps with complex keys are converted to an ES6 Map, [see](./docs/maps.md#maps-with-complex-keys) | -| Polymorphism - Sealed classes | ✅/⚠ [example](./docs/polymorphism.md#sealed-classes) | Nested sealed classes are ignored, [see](./docs/polymorphism.md#nested-sealed-classes) | -| Polymorphism - Open classes | ❌ [example](./docs/abstract-classes.md) | Not implemented. Converted to `type MyClass = any` | -| `@JsonClassDiscriminator` | ❌ | Not implemented | -| Edge cases - circular dependencies | ✅ [example](./docs/edgecases.md) | | +| | Status | Notes | +|---------------------------------------|----------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------| +| Kotlin multiplatform | ❓ | The codebase is multiplatform, but only JVM has been tested | +| `@SerialName` | ✅/⚠ | The serial name is directly converted and might produce invalid TypeScript | +| Basic classes | ✅ [example](./docs/basic-classes.md) | | +| Nullable and default-value properties | ✅ [example](./docs/default-values.md) | | +| Value classes | ✅ [example](./docs/value-classes.md) | | +| Enums | ✅ [example](./docs/enums.md) | | +| Lists | ✅ [example](./docs/lists.md) | | +| Maps | ✅/⚠ [example](./docs/maps.md) | Maps with complex keys are converted to an ES6 Map, [see documentation](./docs/maps.md#maps-with-complex-keys) | +| Polymorphism - Sealed classes | ✅/⚠ [example](./docs/polymorphism.md#sealed-classes) | Nested sealed classes are ignored, [see documentation](./docs/polymorphism.md#nested-sealed-classes) | +| Polymorphism - Open classes | ❌ [example](./docs/abstract-classes.md) | Not implemented. Converted to `type MyClass = any` | +| `@JsonClassDiscriminator` | ❌ | Not implemented | +| Edge cases - circular dependencies | ✅ [example](./docs/edgecases.md) | | From 3ae531f465c1445b6cedc75c711227cc6b38aed8 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 4 Apr 2022 22:03:05 +0200 Subject: [PATCH 46/46] - workaround for json content polymorphic + test - fix element ID name extraction (handle other SerialDescriptors that have < > in them) - fix knit tests not running - fix generic test #11 --- README.md | 29 +++++----- docs/code/example/example-generics-01.kt | 2 +- .../example/example-json-polymorphic-01.kt | 31 ++++++++++ docs/code/test/PolymorphismTest.kt | 30 ++++++++++ docs/polymorphism.md | 58 ++++++++++++++++--- .../dev.adamko.kxstsgen/TsElementConverter.kt | 7 +++ .../TsElementIdConverter.kt | 40 +++++-------- 7 files changed, 148 insertions(+), 49 deletions(-) create mode 100644 docs/code/example/example-json-polymorphic-01.kt diff --git a/README.md b/README.md index 22c7453e..21f5dfc0 100644 --- a/README.md +++ b/README.md @@ -55,17 +55,18 @@ See [the docs](./docs) for working examples. This is a proof-of-concept. -| | Status | Notes | -|---------------------------------------|----------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------| -| Kotlin multiplatform | ❓ | The codebase is multiplatform, but only JVM has been tested | -| `@SerialName` | ✅/⚠ | The serial name is directly converted and might produce invalid TypeScript | -| Basic classes | ✅ [example](./docs/basic-classes.md) | | -| Nullable and default-value properties | ✅ [example](./docs/default-values.md) | | -| Value classes | ✅ [example](./docs/value-classes.md) | | -| Enums | ✅ [example](./docs/enums.md) | | -| Lists | ✅ [example](./docs/lists.md) | | -| Maps | ✅/⚠ [example](./docs/maps.md) | Maps with complex keys are converted to an ES6 Map, [see documentation](./docs/maps.md#maps-with-complex-keys) | -| Polymorphism - Sealed classes | ✅/⚠ [example](./docs/polymorphism.md#sealed-classes) | Nested sealed classes are ignored, [see documentation](./docs/polymorphism.md#nested-sealed-classes) | -| Polymorphism - Open classes | ❌ [example](./docs/abstract-classes.md) | Not implemented. Converted to `type MyClass = any` | -| `@JsonClassDiscriminator` | ❌ | Not implemented | -| Edge cases - circular dependencies | ✅ [example](./docs/edgecases.md) | | +| | Status | Notes | +|---------------------------------------|-----------------------------------------------------------------|:---------------------------------------------------------------------------------------------------------------| +| Kotlin multiplatform | ❓ | The codebase is multiplatform, but only JVM has been tested | +| `@SerialName` | ✅/⚠ | The serial name is directly converted and might produce invalid TypeScript | +| Basic classes | ✅ [example](./docs/basic-classes.md) | | +| Nullable and default-value properties | ✅ [example](./docs/default-values.md) | | +| Value classes | ✅ [example](./docs/value-classes.md) | | +| Enums | ✅ [example](./docs/enums.md) | | +| Lists | ✅ [example](./docs/lists.md) | | +| Maps | ✅/⚠ [example](./docs/maps.md) | Maps with complex keys are converted to an ES6 Map, [see documentation](./docs/maps.md#maps-with-complex-keys) | +| Polymorphism - Sealed classes | ✅/⚠ [example](./docs/polymorphism.md#sealed-classes) | Nested sealed classes are ignored, [see documentation](./docs/polymorphism.md#nested-sealed-classes) | +| Polymorphism - Open classes | ❌ [example](./docs/abstract-classes.md) | Not implemented. Converted to `type MyClass = any` | +| `@JsonClassDiscriminator` | ❌ | Not implemented | +| JSON Content polymorphism | ❌ [example](./docs/polymorphism.md#json-content-polymorphism) | Not implemented. Converted to `type MyClass = any` | +| Edge cases - circular dependencies | ✅ [example](./docs/edgecases.md) | | diff --git a/docs/code/example/example-generics-01.kt b/docs/code/example/example-generics-01.kt index 648137fa..493ed025 100644 --- a/docs/code/example/example-generics-01.kt +++ b/docs/code/example/example-generics-01.kt @@ -8,7 +8,7 @@ import dev.adamko.kxstsgen.* import kotlinx.serialization.builtins.serializer @Serializable -class Box( +class Box( val value: T, ) diff --git a/docs/code/example/example-json-polymorphic-01.kt b/docs/code/example/example-json-polymorphic-01.kt new file mode 100644 index 00000000..ab4035b7 --- /dev/null +++ b/docs/code/example/example-json-polymorphic-01.kt @@ -0,0 +1,31 @@ +// This file was automatically generated from polymorphism.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleJsonPolymorphic01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +import kotlinx.serialization.json.* + +@Serializable +abstract class Project { + abstract val name: String +} + +@Serializable +data class BasicProject(override val name: String) : Project() + +@Serializable +data class OwnedProject(override val name: String, val owner: String) : Project() + +object ProjectSerializer : JsonContentPolymorphicSerializer(Project::class) { + override fun selectDeserializer(element: JsonElement) = when { + "owner" in element.jsonObject -> OwnedProject.serializer() + else -> BasicProject.serializer() + } +} + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(ProjectSerializer)) +} diff --git a/docs/code/test/PolymorphismTest.kt b/docs/code/test/PolymorphismTest.kt index 271f41ec..2e2e6bd9 100644 --- a/docs/code/test/PolymorphismTest.kt +++ b/docs/code/test/PolymorphismTest.kt @@ -206,4 +206,34 @@ class PolymorphismTest { .normalize() ) } + + @Test + fun testExampleGenerics01() { + captureOutput("ExampleGenerics01") { + dev.adamko.kxstsgen.example.exampleGenerics01.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export interface Box { + | value: number; + |} + """.trimMargin() + .normalize() + ) + } + + @Test + fun testExampleJsonPolymorphic01() { + captureOutput("ExampleJsonPolymorphic01") { + dev.adamko.kxstsgen.example.exampleJsonPolymorphic01.main() + }.normalizeJoin() + .shouldBe( + // language=TypeScript + """ + |export type Project = any; + """.trimMargin() + .normalize() + ) + } } diff --git a/docs/polymorphism.md b/docs/polymorphism.md index d39828db..f14cf0a6 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -13,6 +13,7 @@ * [Objects](#objects) * [Open Polymorphism](#open-polymorphism) * [Generics](#generics) + * [JSON content polymorphism](#json-content-polymorphism) @@ -346,13 +347,14 @@ export namespace Response { ### Generics - +Kotlinx Serialization doesn't have 'generic' SerialDescriptors, so KxsTsGen can't generate generic +TypeScript classes. ```kotlin +import kotlinx.serialization.builtins.serializer + @Serializable -class Box( +class Box( val value: T, ) @@ -370,9 +372,51 @@ fun main() { > You can get the full code [here](./code/example/example-generics-01.kt). ```typescript -export type Double = number & { __kotlin_Double__: void } - export interface Box { - value: Double + value: number; } ``` + + + +### JSON content polymorphism + +Using a +[`JsonContentPolymorphicSerializer`](https://kotlin.github.io/kotlinx.serialization/kotlinx-serialization-json/kotlinx.serialization.json/-json-content-polymorphic-serializer/index.html) +means there's not enough data in the `SerialDescriptor` to generate a TypeScript interface. Instead, +a named type alias to 'any' will be created instead. + +```kotlin +import kotlinx.serialization.json.* + +@Serializable +abstract class Project { + abstract val name: String +} + +@Serializable +data class BasicProject(override val name: String) : Project() + +@Serializable +data class OwnedProject(override val name: String, val owner: String) : Project() + +object ProjectSerializer : JsonContentPolymorphicSerializer(Project::class) { + override fun selectDeserializer(element: JsonElement) = when { + "owner" in element.jsonObject -> OwnedProject.serializer() + else -> BasicProject.serializer() + } +} + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(ProjectSerializer)) +} +``` + +> You can get the full code [here](./code/example/example-json-polymorphic-01.kt). + +```typescript +export type Project = any; +``` + + diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt index 1e15742f..53219899 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementConverter.kt @@ -67,6 +67,13 @@ fun interface TsElementConverter { descriptor: SerialDescriptor, ): TsDeclaration { + if (descriptor.elementsCount == 0) { + return TsDeclaration.TsTypeAlias( + elementIdConverter(descriptor), + TsTypeRef.Literal(TsLiteral.Primitive.TsAny, false) + ) + } + val discriminatorIndex = descriptor.elementDescriptors .indexOfFirst { it.kind == PrimitiveKind.STRING } val discriminatorName = descriptor.getElementName(discriminatorIndex) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt index 27ab0206..216861e8 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev.adamko.kxstsgen/TsElementIdConverter.kt @@ -1,10 +1,6 @@ package dev.adamko.kxstsgen -import kotlinx.serialization.descriptors.PolymorphicKind -import kotlinx.serialization.descriptors.PrimitiveKind import kotlinx.serialization.descriptors.SerialDescriptor -import kotlinx.serialization.descriptors.SerialKind -import kotlinx.serialization.descriptors.StructureKind fun interface TsElementIdConverter { @@ -13,30 +9,20 @@ fun interface TsElementIdConverter { object Default : TsElementIdConverter { override operator fun invoke(descriptor: SerialDescriptor): TsElementId { - val targetId = TsElementId(descriptor.serialName.removeSuffix("?")) - - return when (descriptor.kind) { - PolymorphicKind.OPEN -> TsElementId( - targetId.namespace + "." + targetId.name.substringAfter("<").substringBeforeLast(">") - ) - PolymorphicKind.SEALED, - PrimitiveKind.BOOLEAN, - PrimitiveKind.BYTE, - PrimitiveKind.CHAR, - PrimitiveKind.DOUBLE, - PrimitiveKind.FLOAT, - PrimitiveKind.INT, - PrimitiveKind.LONG, - PrimitiveKind.SHORT, - PrimitiveKind.STRING, - SerialKind.CONTEXTUAL, - SerialKind.ENUM, - StructureKind.CLASS, - StructureKind.LIST, - StructureKind.MAP, - StructureKind.OBJECT -> targetId + + val serialName = descriptor.serialName.removeSuffix("?") + + val namespace = serialName.substringBeforeLast('.') + + val id = serialName + .substringAfterLast('.') + .substringAfter("<") + .substringBeforeLast(">") + + return when { + namespace.isBlank() -> TsElementId("$id") + else -> TsElementId("$namespace.$id") } } - } }