From 22bfd9147f27350cd5ce70fdcfaeaac4ed1a68e6 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 11 Apr 2022 00:23:43 +0200 Subject: [PATCH 1/3] #28 allow value classes as indexed type map keys --- docs/code/example/example-map-primitive-06.kt | 29 ++++++++++ docs/code/test/MapsTests.kt | 34 +++++++++++- docs/maps.md | 54 ++++++++++++++++++- .../kxstsgen/core/TsMapTypeConverter.kt | 16 ++++++ 4 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 docs/code/example/example-map-primitive-06.kt diff --git a/docs/code/example/example-map-primitive-06.kt b/docs/code/example/example-map-primitive-06.kt new file mode 100644 index 00000000..179b5e16 --- /dev/null +++ b/docs/code/example/example-map-primitive-06.kt @@ -0,0 +1,29 @@ +// This file was automatically generated from maps.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleMapPrimitive06 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +@Serializable +data class ComplexKey(val complex: String) + +@Serializable +@JvmInline +value class SimpleKey(val simple: String) + +@Serializable +@JvmInline +value class DoubleSimpleKey(val simple: SimpleKey) + +@Serializable +data class Example( + val complex: Map, + val simple: Map, + val doubleSimple: Map, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Example.serializer())) +} diff --git a/docs/code/test/MapsTests.kt b/docs/code/test/MapsTests.kt index 623268b1..5575c9c0 100644 --- a/docs/code/test/MapsTests.kt +++ b/docs/code/test/MapsTests.kt @@ -130,6 +130,38 @@ class MapsTests : FunSpec({ } } + context("ExampleMapPrimitive06") { + val actual = captureOutput("ExampleMapPrimitive06") { + dev.adamko.kxstsgen.example.exampleMapPrimitive06.main() + }.normalizeJoin() + + test("expect actual matches TypeScript") { + actual.shouldBe( + // language=TypeScript + """ + |export interface Example { + | complex: Map; + | simple: { [key: SimpleKey]: string }; + | doubleSimple: { [key: DoubleSimpleKey]: string }; + |} + | + |export interface ComplexKey { + | complex: string; + |} + | + |export type SimpleKey = string; + | + |export type DoubleSimpleKey = SimpleKey; + """.trimMargin() + .normalize() + ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } + } + context("ExampleMapComplex01") { val actual = captureOutput("ExampleMapComplex01") { dev.adamko.kxstsgen.example.exampleMapComplex01.main() @@ -171,7 +203,7 @@ class MapsTests : FunSpec({ // language=TypeScript """ |export interface CanvasProperties { - | colourNames: Map; + | colourNames: { [key: ColourMapKey]: string }; |} | |export type ColourMapKey = string; diff --git a/docs/maps.md b/docs/maps.md index 0a59758b..e0fa59d9 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -10,6 +10,7 @@ * [Maps with Collections](#maps-with-collections) * [Maps with value classes](#maps-with-value-classes) * [Nullable keys and values](#nullable-keys-and-values) + * [Type alias keys](#type-alias-keys) * [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) @@ -169,6 +170,55 @@ export interface Config { +### Type alias keys + +Type aliased keys should still use an indexed type, if the type alias is suitable. + +```kotlin +@Serializable +data class ComplexKey(val complex: String) + +@Serializable +@JvmInline +value class SimpleKey(val simple: String) + +@Serializable +@JvmInline +value class DoubleSimpleKey(val simple: SimpleKey) + +@Serializable +data class Example( + val complex: Map, + val simple: Map, + val doubleSimple: Map, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(Example.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-map-primitive-06.kt). + +```typescript +export interface Example { + complex: Map; + simple: { [key: SimpleKey]: string }; + doubleSimple: { [key: DoubleSimpleKey]: string }; +} + +export interface ComplexKey { + complex: string; +} + +export type SimpleKey = string; + +export type DoubleSimpleKey = SimpleKey; +``` + + + ### Maps with complex keys JSON maps **must** have keys that are either strings, positive integers, or enums. @@ -284,9 +334,11 @@ fun main() { > You can get the full code [here](./code/example/example-map-complex-02.kt). +Because the map now has a non-complex key, an 'indexed type' is generated. + ```typescript export interface CanvasProperties { - colourNames: Map; + colourNames: { [key: ColourMapKey]: string }; } export type ColourMapKey = string; diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsMapTypeConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsMapTypeConverter.kt index d97fe18f..6e568df8 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsMapTypeConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsMapTypeConverter.kt @@ -5,6 +5,7 @@ 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 TsMapTypeConverter { @@ -23,6 +24,8 @@ fun interface TsMapTypeConverter { if (keyDescriptor.isNullable) return TsLiteral.TsMap.Type.MAP + if (keyDescriptor.isInline) return extractInlineType(keyDescriptor, valDescriptor) + return when (keyDescriptor.kind) { SerialKind.ENUM -> TsLiteral.TsMap.Type.MAPPED_OBJECT @@ -45,5 +48,18 @@ fun interface TsMapTypeConverter { PolymorphicKind.OPEN -> TsLiteral.TsMap.Type.MAP } } + + tailrec fun extractInlineType( + keyDescriptor: SerialDescriptor, + valDescriptor: SerialDescriptor?, + ): TsLiteral.TsMap.Type { + if (!keyDescriptor.isInline) { + return this(keyDescriptor, valDescriptor) + } else { + val inlineKeyDescriptor = keyDescriptor.elementDescriptors.firstOrNull() + ?: return TsLiteral.TsMap.Type.MAP + return extractInlineType(inlineKeyDescriptor, valDescriptor) + } + } } } From 823431b3b57aff41a500fcea1f4afed9331db154 Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 11 Apr 2022 00:33:05 +0200 Subject: [PATCH 2/3] more tests for type alias enum map keys --- docs/code/example/example-map-primitive-06.kt | 23 +++++++++--- docs/code/test/MapsTests.kt | 12 +++++++ docs/maps.md | 35 ++++++++++++++++--- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/docs/code/example/example-map-primitive-06.kt b/docs/code/example/example-map-primitive-06.kt index 179b5e16..70abfa8d 100644 --- a/docs/code/example/example-map-primitive-06.kt +++ b/docs/code/example/example-map-primitive-06.kt @@ -5,6 +5,15 @@ package dev.adamko.kxstsgen.example.exampleMapPrimitive06 import kotlinx.serialization.* import dev.adamko.kxstsgen.* +@Serializable +data class Example( + val complex: Map, + val simple: Map, + val doubleSimple: Map, + val enum: Map, + val doubleEnum: Map, +) + @Serializable data class ComplexKey(val complex: String) @@ -17,11 +26,15 @@ value class SimpleKey(val simple: String) value class DoubleSimpleKey(val simple: SimpleKey) @Serializable -data class Example( - val complex: Map, - val simple: Map, - val doubleSimple: Map, -) +enum class ExampleEnum { A, B, C, } + +@Serializable +@JvmInline +value class EnumKey(val e: ExampleEnum) + +@Serializable +@JvmInline +value class DoubleEnumKey(val e: ExampleEnum) fun main() { val tsGenerator = KxsTsGenerator() diff --git a/docs/code/test/MapsTests.kt b/docs/code/test/MapsTests.kt index 5575c9c0..1e0263ae 100644 --- a/docs/code/test/MapsTests.kt +++ b/docs/code/test/MapsTests.kt @@ -143,6 +143,8 @@ class MapsTests : FunSpec({ | complex: Map; | simple: { [key: SimpleKey]: string }; | doubleSimple: { [key: DoubleSimpleKey]: string }; + | enum: { [key in EnumKey]: string }; + | doubleEnum: { [key in DoubleEnumKey]: string }; |} | |export interface ComplexKey { @@ -152,6 +154,16 @@ class MapsTests : FunSpec({ |export type SimpleKey = string; | |export type DoubleSimpleKey = SimpleKey; + | + |export type EnumKey = ExampleEnum; + | + |export type DoubleEnumKey = ExampleEnum; + | + |export enum ExampleEnum { + | A = "A", + | B = "B", + | C = "C", + |} """.trimMargin() .normalize() ) diff --git a/docs/maps.md b/docs/maps.md index e0fa59d9..9c9da784 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -175,6 +175,15 @@ export interface Config { Type aliased keys should still use an indexed type, if the type alias is suitable. ```kotlin +@Serializable +data class Example( + val complex: Map, + val simple: Map, + val doubleSimple: Map, + val enum: Map, + val doubleEnum: Map, +) + @Serializable data class ComplexKey(val complex: String) @@ -187,11 +196,15 @@ value class SimpleKey(val simple: String) value class DoubleSimpleKey(val simple: SimpleKey) @Serializable -data class Example( - val complex: Map, - val simple: Map, - val doubleSimple: Map, -) +enum class ExampleEnum { A, B, C, } + +@Serializable +@JvmInline +value class EnumKey(val e: ExampleEnum) + +@Serializable +@JvmInline +value class DoubleEnumKey(val e: ExampleEnum) fun main() { val tsGenerator = KxsTsGenerator() @@ -206,6 +219,8 @@ export interface Example { complex: Map; simple: { [key: SimpleKey]: string }; doubleSimple: { [key: DoubleSimpleKey]: string }; + enum: { [key in EnumKey]: string }; + doubleEnum: { [key in DoubleEnumKey]: string }; } export interface ComplexKey { @@ -215,6 +230,16 @@ export interface ComplexKey { export type SimpleKey = string; export type DoubleSimpleKey = SimpleKey; + +export type EnumKey = ExampleEnum; + +export type DoubleEnumKey = ExampleEnum; + +export enum ExampleEnum { + A = "A", + B = "B", + C = "C", +} ``` From a08894b87c51b73efb7d6d828bdda3b619df7c7b Mon Sep 17 00:00:00 2001 From: Adam <897017+aSemy@users.noreply.github.com> Date: Mon, 11 Apr 2022 00:33:29 +0200 Subject: [PATCH 3/3] tidy up inline type extractor --- .../adamko/kxstsgen/core/TsMapTypeConverter.kt | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsMapTypeConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsMapTypeConverter.kt index 6e568df8..f71daa5c 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsMapTypeConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsMapTypeConverter.kt @@ -50,15 +50,16 @@ fun interface TsMapTypeConverter { } tailrec fun extractInlineType( - keyDescriptor: SerialDescriptor, + keyDescriptor: SerialDescriptor?, valDescriptor: SerialDescriptor?, ): TsLiteral.TsMap.Type { - if (!keyDescriptor.isInline) { - return this(keyDescriptor, valDescriptor) - } else { - val inlineKeyDescriptor = keyDescriptor.elementDescriptors.firstOrNull() - ?: return TsLiteral.TsMap.Type.MAP - return extractInlineType(inlineKeyDescriptor, valDescriptor) + return when { + keyDescriptor == null -> TsLiteral.TsMap.Type.MAP + !keyDescriptor.isInline -> this(keyDescriptor, valDescriptor) + else -> { + val inlineKeyDescriptor = keyDescriptor.elementDescriptors.firstOrNull() + extractInlineType(inlineKeyDescriptor, valDescriptor) + } } } }