Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit fa87c3e

Browse files
committedApr 11, 2022
allow for overriding of elements (partial support for #23 and #22), and update knit to allow for TS Compile to be disabled
1 parent b2f134b commit fa87c3e

File tree

5 files changed

+228
-37
lines changed

5 files changed

+228
-37
lines changed
 
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// This file was automatically generated from customising-output.md by Knit tool. Do not edit.
2+
@file:Suppress("PackageDirectoryMismatch", "unused")
3+
package dev.adamko.kxstsgen.example.exampleCustomisingOutput01
4+
5+
import kotlinx.serialization.*
6+
import dev.adamko.kxstsgen.*
7+
8+
import kotlinx.serialization.builtins.serializer
9+
import dev.adamko.kxstsgen.core.*
10+
11+
@Serializable
12+
data class Item(
13+
val price: Double,
14+
val count: Int,
15+
)
16+
17+
fun main() {
18+
val tsGenerator = KxsTsGenerator()
19+
20+
tsGenerator.descriptorOverrides +=
21+
Double.serializer().descriptor to TsDeclaration.TsTypeAlias(
22+
id = TsElementId("Double"),
23+
typeRef = TsTypeRef.Declaration(
24+
id = TsElementId("double"),
25+
parent = null,
26+
nullable = false,
27+
)
28+
)
29+
30+
println(tsGenerator.generate(Item.serializer()))
31+
}

‎docs/code/knit-test.ftl

+10
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
<#--@formatter:off-->
12
<#-- @ftlvariable name="test.name" type="java.lang.String" -->
23
<#-- @ftlvariable name="test.package" type="java.lang.String" -->
34
// This file was automatically generated from ${file.name} by Knit tool. Do not edit.
@@ -24,7 +25,9 @@ class ${test.name} : FunSpec({
2425

2526
test("expect actual matches TypeScript") {
2627
actual.shouldBe(
28+
<#if case.param != "TS_COMPILE_OFF">
2729
// language=TypeScript
30+
</#if>
2831
"""
2932
<#list case.lines as line>
3033
|${line}
@@ -34,9 +37,16 @@ class ${test.name} : FunSpec({
3437
)
3538
}
3639

40+
<#if case.param == "TS_COMPILE_OFF">
41+
// TS_COMPILE_OFF
42+
// test("expect actual compiles").config(tags = tsCompile) {
43+
// actual.shouldTypeScriptCompile()
44+
// }
45+
<#else>
3746
test("expect actual compiles").config(tags = tsCompile) {
3847
actual.shouldTypeScriptCompile()
3948
}
49+
</#if>
4050
}
4151
<#sep>
4252

+38
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
// This file was automatically generated from customising-output.md by Knit tool. Do not edit.
2+
@file:Suppress("JSUnusedLocalSymbols")
3+
package dev.adamko.kxstsgen.example.test
4+
5+
import dev.adamko.kxstsgen.util.*
6+
import io.kotest.core.spec.style.*
7+
import io.kotest.matchers.*
8+
import kotlinx.knit.test.*
9+
10+
class CustomisingOutputTest : FunSpec({
11+
12+
tags(Knit)
13+
14+
context("ExampleCustomisingOutput01") {
15+
val actual = captureOutput("ExampleCustomisingOutput01") {
16+
dev.adamko.kxstsgen.example.exampleCustomisingOutput01.main()
17+
}.normalizeJoin()
18+
19+
test("expect actual matches TypeScript") {
20+
actual.shouldBe(
21+
"""
22+
|export interface Item {
23+
| price: Double;
24+
| count: number;
25+
|}
26+
|
27+
|export type Double = double; // assume that 'double' will be provided by another library
28+
""".trimMargin()
29+
.normalize()
30+
)
31+
}
32+
33+
// TS_COMPILE_OFF
34+
// test("expect actual compiles").config(tags = tsCompile) {
35+
// actual.shouldTypeScriptCompile()
36+
// }
37+
}
38+
})

‎docs/customising-output.md

+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<!--- TEST_NAME CustomisingOutputTest -->
2+
3+
4+
**Table of contents**
5+
6+
<!--- TOC -->
7+
8+
* [Introduction](#introduction)
9+
* [Overriding output](#overriding-output)
10+
11+
<!--- END -->
12+
13+
14+
<!--- INCLUDE .*\.kt
15+
import kotlinx.serialization.*
16+
import dev.adamko.kxstsgen.*
17+
-->
18+
19+
## Introduction
20+
21+
### Overriding output
22+
23+
If you want to override what KxsTsGen produces, then you can provide overrides.
24+
25+
By default, `Double` is transformed to `number`, but now we want to alias `Double` to `double`.
26+
27+
```kotlin
28+
import kotlinx.serialization.builtins.serializer
29+
import dev.adamko.kxstsgen.core.*
30+
31+
@Serializable
32+
data class Item(
33+
val price: Double,
34+
val count: Int,
35+
)
36+
37+
fun main() {
38+
val tsGenerator = KxsTsGenerator()
39+
40+
tsGenerator.descriptorOverrides +=
41+
Double.serializer().descriptor to TsDeclaration.TsTypeAlias(
42+
id = TsElementId("Double"),
43+
typeRef = TsTypeRef.Declaration(
44+
id = TsElementId("double"),
45+
parent = null,
46+
nullable = false,
47+
)
48+
)
49+
50+
println(tsGenerator.generate(Item.serializer()))
51+
}
52+
```
53+
54+
> You can get the full code [here](./code/example/example-customising-output-01.kt).
55+
56+
```typescript
57+
export interface Item {
58+
price: Double;
59+
count: number;
60+
}
61+
62+
export type Double = double; // assume that 'double' will be provided by another library
63+
```
64+
65+
<!--- TEST TS_COMPILE_OFF -->

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

+84-37
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import dev.adamko.kxstsgen.core.TsElement
77
import dev.adamko.kxstsgen.core.TsElementConverter
88
import dev.adamko.kxstsgen.core.TsElementId
99
import dev.adamko.kxstsgen.core.TsElementIdConverter
10+
import dev.adamko.kxstsgen.core.TsLiteral
1011
import dev.adamko.kxstsgen.core.TsMapTypeConverter
1112
import dev.adamko.kxstsgen.core.TsTypeRef
1213
import dev.adamko.kxstsgen.core.TsTypeRefConverter
@@ -18,71 +19,117 @@ import kotlinx.serialization.descriptors.SerialDescriptor
1819
* Generate TypeScript from [`@Serializable`][Serializable] Kotlin.
1920
*
2021
* The output can be controlled by the settings in [config],
21-
* or by setting hardcoded values in [serializerDescriptors] or [descriptorElements],
22+
* or by setting hardcoded values in [serializerDescriptorOverrides] or [descriptorOverrides],
2223
* or changed by overriding any converter.
2324
*
2425
* @param[config] General settings that affect how KxTsGen works
25-
* @param[descriptorsExtractor] Given a [KSerializer], extract all [SerialDescriptor]s
26-
* @param[elementIdConverter] Create an [TsElementId] from a [SerialDescriptor]
27-
* @param[mapTypeConverter] Decides how [Map]s should be converted
28-
* @param[typeRefConverter] Creates [TsTypeRef]s
29-
* @param[elementConverter] Converts [SerialDescriptor]s to [TsElement]s
3026
* @param[sourceCodeGenerator] Convert [TsElement]s to TypeScript source code
3127
*/
3228
open class KxsTsGenerator(
3329
open val config: KxsTsConfig = KxsTsConfig(),
3430

35-
open val descriptorsExtractor: SerializerDescriptorsExtractor = SerializerDescriptorsExtractor.Default,
31+
open val sourceCodeGenerator: KxsTsSourceCodeGenerator = KxsTsSourceCodeGenerator.Default(config),
32+
) {
33+
34+
35+
val serializerDescriptorOverrides: MutableMap<KSerializer<*>, Set<SerialDescriptor>> =
36+
mutableMapOf()
37+
38+
val descriptorOverrides: MutableMap<SerialDescriptor, TsElement> = mutableMapOf()
39+
40+
41+
open val descriptorsExtractor = object : SerializerDescriptorsExtractor {
42+
val extractor: SerializerDescriptorsExtractor = SerializerDescriptorsExtractor.Default
43+
val cache: MutableMap<KSerializer<*>, Set<SerialDescriptor>> = mutableMapOf()
44+
45+
override fun invoke(serializer: KSerializer<*>): Set<SerialDescriptor> =
46+
cache.getOrPut(serializer) {
47+
serializerDescriptorOverrides[serializer] ?: extractor(serializer)
48+
}
49+
}
50+
51+
52+
val elementIdConverter: TsElementIdConverter = object : TsElementIdConverter {
53+
private val converter: TsElementIdConverter = TsElementIdConverter.Default
54+
private val cache: MutableMap<SerialDescriptor, TsElementId> = mutableMapOf()
55+
56+
override fun invoke(descriptor: SerialDescriptor): TsElementId =
57+
cache.getOrPut(descriptor) {
58+
when (val override = descriptorOverrides[descriptor]) {
59+
is TsDeclaration -> override.id
60+
else -> converter(descriptor)
61+
}
62+
}
63+
}
3664

37-
open val elementIdConverter: TsElementIdConverter = TsElementIdConverter.Default,
3865

39-
open val mapTypeConverter: TsMapTypeConverter = TsMapTypeConverter.Default,
66+
val mapTypeConverter: TsMapTypeConverter = object : TsMapTypeConverter {
67+
private val converter = TsMapTypeConverter.Default
68+
private val cache: MutableMap<Pair<SerialDescriptor, SerialDescriptor>, TsLiteral.TsMap.Type> =
69+
mutableMapOf()
70+
71+
override fun invoke(
72+
keyDescriptor: SerialDescriptor,
73+
valDescriptor: SerialDescriptor,
74+
): TsLiteral.TsMap.Type =
75+
cache.getOrPut(keyDescriptor to valDescriptor) {
76+
when (val override = descriptorOverrides[keyDescriptor]) {
77+
is TsLiteral.TsMap -> override.type
78+
else -> converter(keyDescriptor, valDescriptor)
79+
}
80+
}
81+
}
82+
83+
84+
val typeRefConverter: TsTypeRefConverter = object : TsTypeRefConverter {
85+
private val converter = TsTypeRefConverter.Default(elementIdConverter, mapTypeConverter)
86+
val cache: MutableMap<SerialDescriptor, TsTypeRef> = mutableMapOf()
4087

41-
open val typeRefConverter: TsTypeRefConverter =
42-
TsTypeRefConverter.Default(elementIdConverter, mapTypeConverter),
88+
override fun invoke(descriptor: SerialDescriptor): TsTypeRef =
89+
cache.getOrPut(descriptor) {
90+
when (val override = descriptorOverrides[descriptor]) {
91+
null -> converter(descriptor)
92+
is TsLiteral -> TsTypeRef.Literal(override, descriptor.isNullable)
93+
is TsDeclaration -> TsTypeRef.Declaration(override.id, null, descriptor.isNullable)
94+
}
95+
}
96+
}
4397

44-
open val elementConverter: TsElementConverter =
45-
TsElementConverter.Default(
98+
99+
val elementConverter: TsElementConverter = object : TsElementConverter {
100+
private val converter = TsElementConverter.Default(
46101
elementIdConverter,
47102
mapTypeConverter,
48103
typeRefConverter,
49-
),
50-
51-
open val sourceCodeGenerator: KxsTsSourceCodeGenerator = KxsTsSourceCodeGenerator.Default(config),
52-
) {
104+
)
105+
val cache: MutableMap<SerialDescriptor, Set<TsElement>> = mutableMapOf()
106+
107+
override fun invoke(descriptor: SerialDescriptor): Set<TsElement> =
108+
cache.getOrPut(descriptor) {
109+
when (val override = descriptorOverrides[descriptor]) {
110+
null -> converter(descriptor)
111+
else -> setOf(override)
112+
}
113+
}
114+
}
53115

54-
/**
55-
* Stateful cache of all [descriptors][SerialDescriptor] extracted from a
56-
* [serializer][KSerializer].
57-
*
58-
* To customise the descriptors that a serializer produces, set value into this map.
59-
*/
60-
open val serializerDescriptors: MutableMap<KSerializer<*>, Set<SerialDescriptor>> = mutableMapOf()
61-
62-
/**
63-
* Cache of all [elements][TsElement] that are created from any [descriptor][SerialDescriptor].
64-
*
65-
* To customise the elements that a descriptor produces, set value into this map.
66-
*/
67-
open val descriptorElements: MutableMap<SerialDescriptor, Set<TsElement>> = mutableMapOf()
68116

69117
open fun generate(vararg serializers: KSerializer<*>): String {
70118
return serializers
71119
.toSet()
72120

73121
// 1. get all SerialDescriptors from a KSerializer
74-
.flatMap { serializer ->
75-
serializerDescriptors.getOrPut(serializer) { descriptorsExtractor(serializer) }
76-
}
122+
.flatMap { serializer -> descriptorsExtractor(serializer) }
77123
.toSet()
78124

79125
// 2. convert each SerialDescriptor to some TsElements
80-
.flatMap { descriptor ->
81-
descriptorElements.getOrPut(descriptor) { elementConverter(descriptor) }
82-
}
126+
.flatMap { descriptor -> elementConverter(descriptor) }
83127
.toSet()
84128

129+
// 3. group by namespaces
85130
.groupBy { element -> sourceCodeGenerator.groupElementsBy(element) }
131+
132+
// 4. convert to source code
86133
.mapValues { (_, elements) ->
87134
elements
88135
.filterIsInstance<TsDeclaration>()

0 commit comments

Comments
 (0)