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..70abfa8d --- /dev/null +++ b/docs/code/example/example-map-primitive-06.kt @@ -0,0 +1,42 @@ +// 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 Example( + val complex: Map, + val simple: Map, + val doubleSimple: Map, + val enum: Map, + val doubleEnum: Map, +) + +@Serializable +data class ComplexKey(val complex: String) + +@Serializable +@JvmInline +value class SimpleKey(val simple: String) + +@Serializable +@JvmInline +value class DoubleSimpleKey(val simple: SimpleKey) + +@Serializable +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() + println(tsGenerator.generate(Example.serializer())) +} diff --git a/docs/code/test/MapsTests.kt b/docs/code/test/MapsTests.kt index 623268b1..1e0263ae 100644 --- a/docs/code/test/MapsTests.kt +++ b/docs/code/test/MapsTests.kt @@ -130,6 +130,50 @@ 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 }; + | enum: { [key in EnumKey]: string }; + | doubleEnum: { [key in DoubleEnumKey]: string }; + |} + | + |export interface ComplexKey { + | complex: string; + |} + | + |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() + ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } + } + context("ExampleMapComplex01") { val actual = captureOutput("ExampleMapComplex01") { dev.adamko.kxstsgen.example.exampleMapComplex01.main() @@ -171,7 +215,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..9c9da784 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,80 @@ 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 Example( + val complex: Map, + val simple: Map, + val doubleSimple: Map, + val enum: Map, + val doubleEnum: Map, +) + +@Serializable +data class ComplexKey(val complex: String) + +@Serializable +@JvmInline +value class SimpleKey(val simple: String) + +@Serializable +@JvmInline +value class DoubleSimpleKey(val simple: SimpleKey) + +@Serializable +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() + 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 }; + enum: { [key in EnumKey]: string }; + doubleEnum: { [key in DoubleEnumKey]: string }; +} + +export interface ComplexKey { + complex: string; +} + +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", +} +``` + + + ### Maps with complex keys JSON maps **must** have keys that are either strings, positive integers, or enums. @@ -284,9 +359,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..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 @@ -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,19 @@ fun interface TsMapTypeConverter { PolymorphicKind.OPEN -> TsLiteral.TsMap.Type.MAP } } + + tailrec fun extractInlineType( + keyDescriptor: SerialDescriptor?, + valDescriptor: SerialDescriptor?, + ): TsLiteral.TsMap.Type { + return when { + keyDescriptor == null -> TsLiteral.TsMap.Type.MAP + !keyDescriptor.isInline -> this(keyDescriptor, valDescriptor) + else -> { + val inlineKeyDescriptor = keyDescriptor.elementDescriptors.firstOrNull() + extractInlineType(inlineKeyDescriptor, valDescriptor) + } + } + } } }