Skip to content

Commit b2f134b

Browse files
authored
Handle maps with type-alias keys (#29)
* #28 allow value classes as indexed type map keys * more tests for type alias enum map keys * tidy up inline type extractor
1 parent 2f2f184 commit b2f134b

File tree

4 files changed

+182
-2
lines changed

4 files changed

+182
-2
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// This file was automatically generated from maps.md by Knit tool. Do not edit.
2+
@file:Suppress("PackageDirectoryMismatch", "unused")
3+
package dev.adamko.kxstsgen.example.exampleMapPrimitive06
4+
5+
import kotlinx.serialization.*
6+
import dev.adamko.kxstsgen.*
7+
8+
@Serializable
9+
data class Example(
10+
val complex: Map<ComplexKey, String>,
11+
val simple: Map<SimpleKey, String>,
12+
val doubleSimple: Map<DoubleSimpleKey, String>,
13+
val enum: Map<EnumKey, String>,
14+
val doubleEnum: Map<DoubleEnumKey, String>,
15+
)
16+
17+
@Serializable
18+
data class ComplexKey(val complex: String)
19+
20+
@Serializable
21+
@JvmInline
22+
value class SimpleKey(val simple: String)
23+
24+
@Serializable
25+
@JvmInline
26+
value class DoubleSimpleKey(val simple: SimpleKey)
27+
28+
@Serializable
29+
enum class ExampleEnum { A, B, C, }
30+
31+
@Serializable
32+
@JvmInline
33+
value class EnumKey(val e: ExampleEnum)
34+
35+
@Serializable
36+
@JvmInline
37+
value class DoubleEnumKey(val e: ExampleEnum)
38+
39+
fun main() {
40+
val tsGenerator = KxsTsGenerator()
41+
println(tsGenerator.generate(Example.serializer()))
42+
}

docs/code/test/MapsTests.kt

+45-1
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,50 @@ class MapsTests : FunSpec({
130130
}
131131
}
132132

133+
context("ExampleMapPrimitive06") {
134+
val actual = captureOutput("ExampleMapPrimitive06") {
135+
dev.adamko.kxstsgen.example.exampleMapPrimitive06.main()
136+
}.normalizeJoin()
137+
138+
test("expect actual matches TypeScript") {
139+
actual.shouldBe(
140+
// language=TypeScript
141+
"""
142+
|export interface Example {
143+
| complex: Map<ComplexKey, string>;
144+
| simple: { [key: SimpleKey]: string };
145+
| doubleSimple: { [key: DoubleSimpleKey]: string };
146+
| enum: { [key in EnumKey]: string };
147+
| doubleEnum: { [key in DoubleEnumKey]: string };
148+
|}
149+
|
150+
|export interface ComplexKey {
151+
| complex: string;
152+
|}
153+
|
154+
|export type SimpleKey = string;
155+
|
156+
|export type DoubleSimpleKey = SimpleKey;
157+
|
158+
|export type EnumKey = ExampleEnum;
159+
|
160+
|export type DoubleEnumKey = ExampleEnum;
161+
|
162+
|export enum ExampleEnum {
163+
| A = "A",
164+
| B = "B",
165+
| C = "C",
166+
|}
167+
""".trimMargin()
168+
.normalize()
169+
)
170+
}
171+
172+
test("expect actual compiles").config(tags = tsCompile) {
173+
actual.shouldTypeScriptCompile()
174+
}
175+
}
176+
133177
context("ExampleMapComplex01") {
134178
val actual = captureOutput("ExampleMapComplex01") {
135179
dev.adamko.kxstsgen.example.exampleMapComplex01.main()
@@ -171,7 +215,7 @@ class MapsTests : FunSpec({
171215
// language=TypeScript
172216
"""
173217
|export interface CanvasProperties {
174-
| colourNames: Map<ColourMapKey, string>;
218+
| colourNames: { [key: ColourMapKey]: string };
175219
|}
176220
|
177221
|export type ColourMapKey = string;

docs/maps.md

+78-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
* [Maps with Collections](#maps-with-collections)
1111
* [Maps with value classes](#maps-with-value-classes)
1212
* [Nullable keys and values](#nullable-keys-and-values)
13+
* [Type alias keys](#type-alias-keys)
1314
* [Maps with complex keys](#maps-with-complex-keys)
1415
* [ES6 Map](#es6-map)
1516
* [Maps with complex keys - Map Key class](#maps-with-complex-keys---map-key-class)
@@ -169,6 +170,80 @@ export interface Config {
169170

170171
<!--- TEST -->
171172

173+
### Type alias keys
174+
175+
Type aliased keys should still use an indexed type, if the type alias is suitable.
176+
177+
```kotlin
178+
@Serializable
179+
data class Example(
180+
val complex: Map<ComplexKey, String>,
181+
val simple: Map<SimpleKey, String>,
182+
val doubleSimple: Map<DoubleSimpleKey, String>,
183+
val enum: Map<EnumKey, String>,
184+
val doubleEnum: Map<DoubleEnumKey, String>,
185+
)
186+
187+
@Serializable
188+
data class ComplexKey(val complex: String)
189+
190+
@Serializable
191+
@JvmInline
192+
value class SimpleKey(val simple: String)
193+
194+
@Serializable
195+
@JvmInline
196+
value class DoubleSimpleKey(val simple: SimpleKey)
197+
198+
@Serializable
199+
enum class ExampleEnum { A, B, C, }
200+
201+
@Serializable
202+
@JvmInline
203+
value class EnumKey(val e: ExampleEnum)
204+
205+
@Serializable
206+
@JvmInline
207+
value class DoubleEnumKey(val e: ExampleEnum)
208+
209+
fun main() {
210+
val tsGenerator = KxsTsGenerator()
211+
println(tsGenerator.generate(Example.serializer()))
212+
}
213+
```
214+
215+
> You can get the full code [here](./code/example/example-map-primitive-06.kt).
216+
217+
```typescript
218+
export interface Example {
219+
complex: Map<ComplexKey, string>;
220+
simple: { [key: SimpleKey]: string };
221+
doubleSimple: { [key: DoubleSimpleKey]: string };
222+
enum: { [key in EnumKey]: string };
223+
doubleEnum: { [key in DoubleEnumKey]: string };
224+
}
225+
226+
export interface ComplexKey {
227+
complex: string;
228+
}
229+
230+
export type SimpleKey = string;
231+
232+
export type DoubleSimpleKey = SimpleKey;
233+
234+
export type EnumKey = ExampleEnum;
235+
236+
export type DoubleEnumKey = ExampleEnum;
237+
238+
export enum ExampleEnum {
239+
A = "A",
240+
B = "B",
241+
C = "C",
242+
}
243+
```
244+
245+
<!--- TEST -->
246+
172247
### Maps with complex keys
173248

174249
JSON maps **must** have keys that are either strings, positive integers, or enums.
@@ -284,9 +359,11 @@ fun main() {
284359

285360
> You can get the full code [here](./code/example/example-map-complex-02.kt).
286361
362+
Because the map now has a non-complex key, an 'indexed type' is generated.
363+
287364
```typescript
288365
export interface CanvasProperties {
289-
colourNames: Map<ColourMapKey, string>;
366+
colourNames: { [key: ColourMapKey]: string };
290367
}
291368

292369
export type ColourMapKey = string;

modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsMapTypeConverter.kt

+17
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import kotlinx.serialization.descriptors.PrimitiveKind
55
import kotlinx.serialization.descriptors.SerialDescriptor
66
import kotlinx.serialization.descriptors.SerialKind
77
import kotlinx.serialization.descriptors.StructureKind
8+
import kotlinx.serialization.descriptors.elementDescriptors
89

910

1011
fun interface TsMapTypeConverter {
@@ -23,6 +24,8 @@ fun interface TsMapTypeConverter {
2324

2425
if (keyDescriptor.isNullable) return TsLiteral.TsMap.Type.MAP
2526

27+
if (keyDescriptor.isInline) return extractInlineType(keyDescriptor, valDescriptor)
28+
2629
return when (keyDescriptor.kind) {
2730
SerialKind.ENUM -> TsLiteral.TsMap.Type.MAPPED_OBJECT
2831

@@ -45,5 +48,19 @@ fun interface TsMapTypeConverter {
4548
PolymorphicKind.OPEN -> TsLiteral.TsMap.Type.MAP
4649
}
4750
}
51+
52+
tailrec fun extractInlineType(
53+
keyDescriptor: SerialDescriptor?,
54+
valDescriptor: SerialDescriptor?,
55+
): TsLiteral.TsMap.Type {
56+
return when {
57+
keyDescriptor == null -> TsLiteral.TsMap.Type.MAP
58+
!keyDescriptor.isInline -> this(keyDescriptor, valDescriptor)
59+
else -> {
60+
val inlineKeyDescriptor = keyDescriptor.elementDescriptors.firstOrNull()
61+
extractInlineType(inlineKeyDescriptor, valDescriptor)
62+
}
63+
}
64+
}
4865
}
4966
}

0 commit comments

Comments
 (0)