From f20ea577fadb5de895bd1199b43698f196f65867 Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Sat, 9 Apr 2022 20:42:18 +0200 Subject: [PATCH 1/9] Continuous improvements: Tuples, TypeUnions, descriptor extraction (#21) * change tuple constructor to use an iterator, so it's not indexed based (and so a little less fragile) * distinct the given serializers, before generating * make a distinction between a type alias (to one type) and type union (helps with the 'brand typing' option') - change the formatting of type unions - * add kotest * fix extracting SerialDescriptors from within subclasses * additional test for serializer extraction from sealed classes * document polymorphic descriptor extraction --- buildSrc/build.gradle.kts | 2 +- docs/code/example/example-tuple-01.kt | 12 +-- docs/code/example/example-tuple-02.kt | 2 +- docs/code/example/example-tuple-03.kt | 8 +- docs/code/test/PolymorphismTest.kt | 13 ++- docs/polymorphism.md | 13 ++- docs/tuples.md | 22 ++--- modules/kxs-ts-gen-core/build.gradle.kts | 15 +++- .../dev/adamko/kxstsgen/KxsTsGenerator.kt | 1 + .../kxstsgen/core/KxsTsSourceCodeGenerator.kt | 37 ++++++--- .../core/SerializerDescriptorsExtractor.kt | 23 +++++- .../kxstsgen/core/TsElementConverter.kt | 2 +- .../adamko/kxstsgen/core/experiments/tuple.kt | 4 +- .../dev/adamko/kxstsgen/core/tsElements.kt | 14 ++-- .../SerializerDescriptorsExtractorTest.kt | 80 +++++++++++++++++++ 15 files changed, 196 insertions(+), 52 deletions(-) create mode 100644 modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractorTest.kt diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 0dab274f..8a072be6 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -17,7 +17,7 @@ object Versions { const val kotlinxSerialization = "1.3.2" const val ksp = "1.6.10-1.0.4" - const val kotest = "5.1.0" + const val kotest = "5.2.3" } diff --git a/docs/code/example/example-tuple-01.kt b/docs/code/example/example-tuple-01.kt index cba8b0c0..250f0bfe 100644 --- a/docs/code/example/example-tuple-01.kt +++ b/docs/code/example/example-tuple-01.kt @@ -27,14 +27,14 @@ data class SimpleTypes( element(SimpleTypes::privateMember) } ) { - override fun tupleConstructor(elements: List<*>): SimpleTypes { + override fun tupleConstructor(elements: Iterator<*>): SimpleTypes { // When deserializing, the elements will be available as a list, in the order defined return SimpleTypes( - elements[0] as String, - elements[1] as Int, - elements[2] as Double, - elements[3] as Boolean, - elements[4] as String, + elements.next() as String, + elements.next() as Int, + elements.next() as Double, + elements.next() as Boolean, + elements.next() as String, ) } } diff --git a/docs/code/example/example-tuple-02.kt b/docs/code/example/example-tuple-02.kt index 6416fd3d..8dac896d 100644 --- a/docs/code/example/example-tuple-02.kt +++ b/docs/code/example/example-tuple-02.kt @@ -22,7 +22,7 @@ data class OptionalFields( element(OptionalFields::nullableOptionalString) } ) { - override fun tupleConstructor(elements: List<*>): OptionalFields { + override fun tupleConstructor(elements: Iterator<*>): OptionalFields { val iter = elements.iterator() return OptionalFields( iter.next() as String, diff --git a/docs/code/example/example-tuple-03.kt b/docs/code/example/example-tuple-03.kt index b81d7ae4..6838945d 100644 --- a/docs/code/example/example-tuple-03.kt +++ b/docs/code/example/example-tuple-03.kt @@ -20,11 +20,11 @@ data class Coordinates( element(Coordinates::z) } ) { - override fun tupleConstructor(elements: List<*>): Coordinates { + override fun tupleConstructor(elements: Iterator<*>): Coordinates { return Coordinates( - elements[0] as Int, - elements[1] as Int, - elements[2] as Int, + elements.next() as Int, + elements.next() as Int, + elements.next() as Int, ) } } diff --git a/docs/code/test/PolymorphismTest.kt b/docs/code/test/PolymorphismTest.kt index 2e2e6bd9..c55bc229 100644 --- a/docs/code/test/PolymorphismTest.kt +++ b/docs/code/test/PolymorphismTest.kt @@ -75,7 +75,9 @@ class PolymorphismTest { .shouldBe( // language=TypeScript """ - |export type Project = Project.DeprecatedProject | Project.OProj; + |export type Project = + | | Project.DeprecatedProject + | | Project.OProj; | |export namespace Project { | export enum Type { @@ -108,7 +110,10 @@ class PolymorphismTest { .shouldBe( // language=TypeScript """ - |export type Dog = Dog.Golden | Dog.Mutt | Dog.NovaScotia; + |export type Dog = + | | Dog.Golden + | | Dog.Mutt + | | Dog.NovaScotia; | |export namespace Dog { | export enum Type { @@ -185,7 +190,9 @@ class PolymorphismTest { .shouldBe( // language=TypeScript """ - |export type Response = Response.EmptyResponse | Response.TextResponse; + |export type Response = + | | Response.EmptyResponse + | | Response.TextResponse; | |export namespace Response { | export enum Type { diff --git a/docs/polymorphism.md b/docs/polymorphism.md index f14cf0a6..c1c47e8d 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -163,7 +163,9 @@ fun main() { > You can get the full code [here](./code/example/example-polymorphic-sealed-class-01.kt). ```typescript -export type Project = Project.DeprecatedProject | Project.OProj; +export type Project = + | Project.DeprecatedProject + | Project.OProj; export namespace Project { export enum Type { @@ -231,7 +233,10 @@ fun main() { > 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; +export type Dog = + | Dog.Golden + | Dog.Mutt + | Dog.NovaScotia; export namespace Dog { export enum Type { @@ -322,7 +327,9 @@ fun main() { > You can get the full code [here](./code/example/example-polymorphic-objects-01.kt). ```typescript -export type Response = Response.EmptyResponse | Response.TextResponse; +export type Response = + | Response.EmptyResponse + | Response.TextResponse; export namespace Response { export enum Type { diff --git a/docs/tuples.md b/docs/tuples.md index 81042d7b..306ccba2 100644 --- a/docs/tuples.md +++ b/docs/tuples.md @@ -58,14 +58,14 @@ data class SimpleTypes( element(SimpleTypes::privateMember) } ) { - override fun tupleConstructor(elements: List<*>): SimpleTypes { + override fun tupleConstructor(elements: Iterator<*>): SimpleTypes { // When deserializing, the elements will be available as a list, in the order defined return SimpleTypes( - elements[0] as String, - elements[1] as Int, - elements[2] as Double, - elements[3] as Boolean, - elements[4] as String, + elements.next() as String, + elements.next() as Int, + elements.next() as Double, + elements.next() as Boolean, + elements.next() as String, ) } } @@ -111,7 +111,7 @@ data class OptionalFields( element(OptionalFields::nullableOptionalString) } ) { - override fun tupleConstructor(elements: List<*>): OptionalFields { + override fun tupleConstructor(elements: Iterator<*>): OptionalFields { val iter = elements.iterator() return OptionalFields( iter.next() as String, @@ -154,11 +154,11 @@ data class Coordinates( element(Coordinates::z) } ) { - override fun tupleConstructor(elements: List<*>): Coordinates { + override fun tupleConstructor(elements: Iterator<*>): Coordinates { return Coordinates( - elements[0] as Int, - elements[1] as Int, - elements[2] as Int, + elements.next() as Int, + elements.next() as Int, + elements.next() as Int, ) } } diff --git a/modules/kxs-ts-gen-core/build.gradle.kts b/modules/kxs-ts-gen-core/build.gradle.kts index d74a7f5d..cac37a91 100644 --- a/modules/kxs-ts-gen-core/build.gradle.kts +++ b/modules/kxs-ts-gen-core/build.gradle.kts @@ -6,9 +6,11 @@ plugins { buildsrc.convention.`maven-publish` kotlin("plugin.serialization") // id("org.jetbrains.reflekt") + id("io.kotest.multiplatform") } val kotlinxSerializationVersion = "1.3.2" +val kotestVersion = "5.2.2" kotlin { @@ -70,6 +72,12 @@ kotlin { val commonTest by getting { dependencies { implementation(kotlin("test")) + + implementation("io.kotest:kotest-assertions-core:$kotestVersion") + implementation("io.kotest:kotest-assertions-json:$kotestVersion") + implementation("io.kotest:kotest-property:$kotestVersion") + implementation("io.kotest:kotest-framework-engine:$kotestVersion") + implementation("io.kotest:kotest-framework-datatest:$kotestVersion") } } // val nativeMain by getting @@ -81,6 +89,11 @@ kotlin { implementation(kotlin("reflect")) } } -// val jvmTest by getting + + val jvmTest by getting { + dependencies { + implementation("io.kotest:kotest-runner-junit5-jvm:$kotestVersion") + } + } } } 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 12320fa4..14d09102 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 @@ -68,6 +68,7 @@ open class KxsTsGenerator( open fun generate(vararg serializers: KSerializer<*>): String { return serializers + .toSet() // 1. get all SerialDescriptors from a KSerializer .flatMap { serializer -> diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt index 45801db1..9a57a52a 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt @@ -17,7 +17,8 @@ abstract class KxsTsSourceCodeGenerator( is TsDeclaration.TsEnum -> generateEnum(element) is TsDeclaration.TsInterface -> generateInterface(element) is TsDeclaration.TsNamespace -> generateNamespace(element) - is TsDeclaration.TsTypeAlias -> generateType(element) + is TsDeclaration.TsTypeUnion -> generateTypeUnion(element) + is TsDeclaration.TsTypeAlias -> generateTypeAlias(element) is TsDeclaration.TsTuple -> generateTuple(element) } } @@ -25,7 +26,8 @@ abstract class KxsTsSourceCodeGenerator( 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.TsTypeAlias): String + abstract fun generateTypeAlias(element: TsDeclaration.TsTypeAlias): String + abstract fun generateTypeUnion(element: TsDeclaration.TsTypeUnion): String abstract fun generateTuple(tuple: TsDeclaration.TsTuple): String abstract fun generateMapTypeReference(tsMap: TsLiteral.TsMap): String @@ -117,17 +119,13 @@ abstract class KxsTsSourceCodeGenerator( } - override fun generateType(element: TsDeclaration.TsTypeAlias): String { - val aliases = - element.typeRefs - .map { generateTypeReference(it) } - .sorted() - .joinToString(" | ") + override fun generateTypeAlias(element: TsDeclaration.TsTypeAlias): String { + val aliasedRef = generateTypeReference(element.typeRef) return when (config.typeAliasTyping) { KxsTsConfig.TypeAliasTypingConfig.None -> """ - |export type ${element.id.name} = ${aliases}; + |export type ${element.id.name} = ${aliasedRef}; """.trimMargin() KxsTsConfig.TypeAliasTypingConfig.BrandTyping -> { @@ -141,12 +139,31 @@ abstract class KxsTsSourceCodeGenerator( }.joinToString("") """ - |export type ${element.id.name} = $aliases & { __${brandType}__: void }; + |export type ${element.id.name} = $aliasedRef & { __${brandType}__: void }; """.trimMargin() } } } + override fun generateTypeUnion(element: TsDeclaration.TsTypeUnion): String { + return if (element.typeRefs.isEmpty()) { + """ + |export type ${element.id.name} = ${generatePrimitive(TsLiteral.Primitive.TsUnknown)}; + """.trimMargin() + } else { + val aliases = element.typeRefs + .map { "${config.indent}| ${generateTypeReference(it)}" } + .sorted() + .joinToString("\n") + + """ + ¦export type ${element.id.name} = + ¦$aliases; + """.trimMargin("¦") + } + } + + override fun generateTuple(tuple: TsDeclaration.TsTuple): String { val types = tuple.elements.joinToString(separator = ", ") { diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractor.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractor.kt index 41f6b0dd..d5b01c6b 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractor.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractor.kt @@ -66,10 +66,25 @@ fun interface SerializerDescriptorsExtractor { StructureKind.OBJECT -> descriptor.elementDescriptors PolymorphicKind.SEALED, - PolymorphicKind.OPEN -> descriptor - .elementDescriptors - .filter { it.kind is PolymorphicKind } - .flatMap { it.elementDescriptors } + PolymorphicKind.OPEN -> + // Polymorphic descriptors have 2 elements, the 'type' and 'value' - we don't need either + // for generation, they're metadata that will be used later. + // The elements of 'value' are similarly unneeded, but their elements might contain new + // descriptors - so extract them + descriptor.elementDescriptors + .flatMap { it.elementDescriptors } + .flatMap { it.elementDescriptors } + + // Example: + // com.application.Polymorphic + // ├── 'type' descriptor (ignore / it's a String, so check its elements, it doesn't hurt) + // └── 'value' descriptor (check elements...) + // ├── com.application.Polymorphic (ignore) + // │ ├── Double (extract!) + // │ └── com.application.SomeOtherClass (extract!) + // └── com.application.Polymorphic (ignore) + // ├── UInt (extract!) + // └── List( } private val indexedTupleElements = tupleElements.associateBy { it.index } - abstract fun tupleConstructor(elements: List<*>): T + abstract fun tupleConstructor(elements: Iterator<*>): T override val descriptor: SerialDescriptor = buildSerialDescriptor( serialName = serialName, @@ -123,7 +123,7 @@ abstract class TupleSerializer( generateSequence { val index = decodeElementIndex(descriptor) indexedTupleElements[index]?.decodeElement(this) - }.toList() + }.iterator() } return tupleConstructor(elements) } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt index 92b2e0e8..8961ded8 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt @@ -38,14 +38,18 @@ sealed interface TsDeclaration : TsElement { val id: TsElementId - /** A named reference to one or more other types. */ + /** A named reference to another type. */ data class TsTypeAlias( + override val id: TsElementId, + val typeRef: TsTypeRef, + ) : TsDeclaration + + + /** A named reference to one or more other types. */ + data class TsTypeUnion( override val id: TsElementId, val typeRefs: Set, - ) : TsDeclaration { - constructor(id: TsElementId, typeRef: TsTypeRef, vararg typeRefs: TsTypeRef) : - this(id, typeRefs.toSet() + typeRef) - } + ) : TsDeclaration /** A [tuple type](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types). */ diff --git a/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractorTest.kt b/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractorTest.kt new file mode 100644 index 00000000..4c1dd3b5 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/SerializerDescriptorsExtractorTest.kt @@ -0,0 +1,80 @@ +package dev.adamko.kxstsgen.core + +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.collections.shouldContainExactlyInAnyOrder +import kotlinx.serialization.Serializable +import kotlinx.serialization.builtins.serializer + +class SerializerDescriptorsExtractorTest : FunSpec({ + + test("Example1: given parent class, expect subclass property descriptor extracted") { + + val expected = listOf( + Example1.Parent.serializer().descriptor, + Example1.Nested.serializer().descriptor, + String.serializer().descriptor, + ) + + val actual = SerializerDescriptorsExtractor.Default(Example1.Parent.serializer()) + + withClue( + """ + expected: ${expected.map { it.serialName }.sorted().joinToString()} + actual: ${actual.map { it.serialName }.sorted().joinToString()} + """.trimIndent() + ) { + actual shouldContainExactlyInAnyOrder expected + } + } + + test("Example2: given parent class, expect subclass property descriptor extracted") { + + val expected = listOf( + Example2.Parent.serializer().descriptor, + Example2.Nested.serializer().descriptor, + String.serializer().descriptor, + ) + + val actual = SerializerDescriptorsExtractor.Default(Example2.Parent.serializer()) + + withClue( + """ + expected: ${expected.map { it.serialName }.sorted().joinToString()} + actual: ${actual.map { it.serialName }.sorted().joinToString()} + """.trimIndent() + ) { + actual shouldContainExactlyInAnyOrder expected + } + } + +}) + + +@Suppress("unused") +private object Example1 { + @Serializable + class Nested(val x: String) + + @Serializable + sealed class Parent + + @Serializable + class SubClass(val n: Nested) : Parent() +} + + +@Suppress("unused") +private object Example2 { + @Serializable + class Nested(val x: String) + + @Serializable + sealed class Parent + + @Serializable + sealed class SealedSub : Parent() + + @Serializable + class SubClass1(val n: Nested) : SealedSub() +} From 700917f39d3466e76b9c94f4da965968952e30d2 Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Sun, 10 Apr 2022 22:06:09 +0200 Subject: [PATCH 2/9] #24 TS Compilation testing (#25) * initial ts compile test example - setup Gradle Node plugin - run tsc in test * tidy up initial typescript compile test, and apply to all tests very slow! need to make them parallel...? * fix nullable values being used as indexed object keys (a good outcome from the ts compile testing! * apply typescript compilation tests to all knit tests - very slow, needs work to make them faster, or exclude the slow tests - migrate all tests to Kotest (works better with coroutines) - bump kotest version - tag tests * move test util to actual directory package * code tidy up: delete unused and some renames * exclude slow TSCompile tests --- buildSrc/build.gradle.kts | 5 +- buildSrc/repositories.settings.gradle.kts | 14 ++ .../buildsrc/convention/kotlin-jvm.gradle.kts | 4 +- .../buildsrc/convention/node.gradle.kts | 29 ++++ docs/code/build.gradle.kts | 23 +-- docs/code/example/example-map-primitive-05.kt | 3 +- docs/code/knit-test.ftl | 37 +++-- docs/code/test/AbstractClassesTest.kt | 59 +++++--- docs/code/test/BasicClassesTest.kt | 75 ++++++--- docs/code/test/DefaultValuesTest.kt | 59 +++++--- docs/code/test/EdgeCasesTest.kt | 59 +++++--- docs/code/test/EnumClassTest.kt | 43 ++++-- docs/code/test/ListsTests.kt | 59 +++++--- docs/code/test/MapsTests.kt | 143 ++++++++++++------ docs/code/test/PolymorphismTest.kt | 139 +++++++++++------ docs/code/test/TuplesTest.kt | 75 ++++++--- docs/code/test/ValueClassesTest.kt | 75 ++++++--- .../dev/adamko/kxstsgen/util/kotestConfig.kt | 18 +++ .../adamko/kxstsgen/util/processMatchers.kt | 56 +++++++ .../{ => dev/adamko/kxstsgen/util}/strings.kt | 2 + .../adamko/kxstsgen/util/typescriptCompile.kt | 46 ++++++ docs/knit.properties | 2 +- docs/maps.md | 11 +- modules/kxs-ts-gen-core/build.gradle.kts | 2 +- .../kxstsgen/core/TsElementConverter.kt | 2 +- .../kxstsgen/core/TsMapTypeConverter.kt | 15 +- .../kxstsgen/core/TsTypeRefConverter.kt | 2 +- 27 files changed, 766 insertions(+), 291 deletions(-) create mode 100644 buildSrc/src/main/kotlin/buildsrc/convention/node.gradle.kts create mode 100644 docs/code/util/dev/adamko/kxstsgen/util/kotestConfig.kt create mode 100644 docs/code/util/dev/adamko/kxstsgen/util/processMatchers.kt rename docs/code/util/{ => dev/adamko/kxstsgen/util}/strings.kt (99%) create mode 100644 docs/code/util/dev/adamko/kxstsgen/util/typescriptCompile.kt diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index 8a072be6..fa966abe 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -16,6 +16,7 @@ object Versions { const val kotlinxKover = "0.5.0" const val kotlinxSerialization = "1.3.2" const val ksp = "1.6.10-1.0.4" + const val gradleNodePlugin = "3.2.1" const val kotest = "5.2.3" } @@ -36,9 +37,7 @@ 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("com.github.node-gradle:gradle-node-plugin:${Versions.gradleNodePlugin}") } diff --git a/buildSrc/repositories.settings.gradle.kts b/buildSrc/repositories.settings.gradle.kts index 060eeff6..12ead969 100644 --- a/buildSrc/repositories.settings.gradle.kts +++ b/buildSrc/repositories.settings.gradle.kts @@ -6,6 +6,20 @@ dependencyResolutionManagement { mavenCentral() jitpack() gradlePluginPortal() + + // Declare the Node.js download repository + ivy("https://nodejs.org/dist/") { + name = "Node Distributions at $url" + patternLayout { artifact("v[revision]/[artifact](-v[revision]-[classifier]).[ext]") } + metadataSources { artifact() } + content { includeModule("org.nodejs", "node") } + } + ivy("https://github.com/yarnpkg/yarn/releases/download") { + name = "Yarn Distributions at $url" + patternLayout { artifact("v[revision]/[artifact](-v[revision]).[ext]") } + metadataSources { artifact() } + content { includeModule("com.yarnpkg", "yarn") } + } } pluginManagement { 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 b3580edd..a984ed9a 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.2.1")) + testImplementation(platform("io.kotest:kotest-bom:5.2.3")) testImplementation("io.kotest:kotest-runner-junit5") testImplementation("io.kotest:kotest-assertions-core") testImplementation("io.kotest:kotest-property") @@ -49,6 +49,6 @@ tasks.compileTestKotlin { kotlinOptions.freeCompilerArgs += "-opt-in=io.kotest.common.ExperimentalKotest" } -tasks.test { +tasks.withType().configureEach { useJUnitPlatform() } diff --git a/buildSrc/src/main/kotlin/buildsrc/convention/node.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/convention/node.gradle.kts new file mode 100644 index 00000000..8b8af429 --- /dev/null +++ b/buildSrc/src/main/kotlin/buildsrc/convention/node.gradle.kts @@ -0,0 +1,29 @@ +package buildsrc.convention + +plugins { + id("com.github.node-gradle.node") + id("buildsrc.convention.subproject") +} + +val rootGradleDir: Directory by extra { + rootProject.layout.projectDirectory.dir(".gradle") +} + +node { + download.set(true) + + distBaseUrl.set(null as String?) // set in repositories.settings.gradle.kts + + workDir.set(rootGradleDir.dir("nodejs")) + npmWorkDir.set(rootGradleDir.dir("npm")) + yarnWorkDir.set(rootGradleDir.dir("yarn")) +} + +tasks.withType { + val npmInstallDir = tasks.npmSetup.map { it.npmDir.get().asFile.canonicalPath } + inputs.dir(npmInstallDir) + + doFirst { + environment("NPM_INSTALL_DIR", npmInstallDir.get()) + } +} diff --git a/docs/code/build.gradle.kts b/docs/code/build.gradle.kts index 751b5b6a..fd0aec6e 100644 --- a/docs/code/build.gradle.kts +++ b/docs/code/build.gradle.kts @@ -1,10 +1,12 @@ +import org.jetbrains.kotlin.gradle.tasks.KotlinCompile + plugins { buildsrc.convention.`kotlin-jvm` + buildsrc.convention.node kotlin("plugin.serialization") id("org.jetbrains.kotlinx.knit") } - val kotlinxSerializationVersion = "1.3.2" dependencies { @@ -14,17 +16,20 @@ dependencies { implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") implementation("org.jetbrains.kotlinx:kotlinx-serialization-json") + implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") + 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") + testImplementation("com.github.pgreze:kotlin-process:1.3.1") } -tasks.withType { +tasks.withType { + mustRunAfter(tasks.knit) kotlinOptions.freeCompilerArgs += listOf( "-opt-in=kotlinx.serialization.ExperimentalSerializationApi", ) @@ -46,13 +51,9 @@ knit { } } +tasks.withType().configureEach { dependsOn(tasks.knit) } + tasks.test { - dependsOn(tasks.knit) -// finalizedBy(tasks.knitCheck) + // TSCompile tests are slow, they don't need to run every time + systemProperty("kotest.tags", "!TSCompile") } - -tasks.compileKotlin { mustRunAfter(tasks.knit) } - -//tasks.knitCheck { -// dependsOn(tasks.test) -//} diff --git a/docs/code/example/example-map-primitive-05.kt b/docs/code/example/example-map-primitive-05.kt index 83290304..496c0b0b 100644 --- a/docs/code/example/example-map-primitive-05.kt +++ b/docs/code/example/example-map-primitive-05.kt @@ -7,7 +7,8 @@ import dev.adamko.kxstsgen.* @Serializable data class Config( - val properties: Map + val nullableVals: Map, + val nullableKeys: Map, ) fun main() { diff --git a/docs/code/knit-test.ftl b/docs/code/knit-test.ftl index 78bac9ff..f20f4e61 100644 --- a/docs/code/knit-test.ftl +++ b/docs/code/knit-test.ftl @@ -4,32 +4,41 @@ @file:Suppress("JSUnusedLocalSymbols") package ${test.package} +import dev.adamko.kxstsgen.util.* +<#--import io.kotest.assertions.*--> +<#--import io.kotest.core.*--> +import io.kotest.core.spec.style.* import io.kotest.matchers.* +<#--import io.kotest.matchers.string.*--> import kotlinx.knit.test.* -import org.junit.jupiter.api.Test -import dev.adamko.kxstsgen.util.* -class ${test.name} { +class ${test.name} : FunSpec({ + + tags(Knit) + <#list cases as case><#assign method = test["mode.${case.param}"]!"custom"> - @Test - fun test${case.name}() { - captureOutput("${case.name}") { + context("${case.name}") { + val actual = captureOutput("${case.name}") { ${case.knit.package}.${case.knit.name}.main() - }<#if method != "custom">.${method}( + }.normalizeJoin() + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ -<#list case.lines as line> + <#list case.lines as line> |${line} - + """.trimMargin() - .normalize() + .normalize() ) -<#else>.also { lines -> - check(${case.param}) } - + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } <#sep> -} +}) diff --git a/docs/code/test/AbstractClassesTest.kt b/docs/code/test/AbstractClassesTest.kt index 3c71a4aa..5fea9a45 100644 --- a/docs/code/test/AbstractClassesTest.kt +++ b/docs/code/test/AbstractClassesTest.kt @@ -2,18 +2,22 @@ @file:Suppress("JSUnusedLocalSymbols") package dev.adamko.kxstsgen.example.test +import dev.adamko.kxstsgen.util.* +import io.kotest.core.spec.style.* 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") { +class AbstractClassesTest : FunSpec({ + + tags(Knit) + + context("ExampleAbstractClassSingleField01") { + val actual = captureOutput("ExampleAbstractClassSingleField01") { dev.adamko.kxstsgen.example.exampleAbstractClassSingleField01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type Color = any; @@ -21,16 +25,22 @@ class AbstractClassesTest { |// rgb: number; |// } """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleAbstractClassPrimitiveFields01() { - captureOutput("ExampleAbstractClassPrimitiveFields01") { + context("ExampleAbstractClassPrimitiveFields01") { + val actual = captureOutput("ExampleAbstractClassPrimitiveFields01") { dev.adamko.kxstsgen.example.exampleAbstractClassPrimitiveFields01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type SimpleTypes = any; @@ -42,16 +52,22 @@ class AbstractClassesTest { |// privateMember: string; |// } """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleAbstractClassAbstractField01() { - captureOutput("ExampleAbstractClassAbstractField01") { + context("ExampleAbstractClassAbstractField01") { + val actual = captureOutput("ExampleAbstractClassAbstractField01") { dev.adamko.kxstsgen.example.exampleAbstractClassAbstractField01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type AbstractSimpleTypes = any; @@ -59,7 +75,12 @@ class AbstractClassesTest { |// rgb: number; |// } """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } -} +}) diff --git a/docs/code/test/BasicClassesTest.kt b/docs/code/test/BasicClassesTest.kt index 9daaa133..28c4872a 100644 --- a/docs/code/test/BasicClassesTest.kt +++ b/docs/code/test/BasicClassesTest.kt @@ -2,34 +2,44 @@ @file:Suppress("JSUnusedLocalSymbols") package dev.adamko.kxstsgen.example.test +import dev.adamko.kxstsgen.util.* +import io.kotest.core.spec.style.* 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") { +class BasicClassesTest : FunSpec({ + + tags(Knit) + + context("ExamplePlainClassSingleField01") { + val actual = captureOutput("ExamplePlainClassSingleField01") { dev.adamko.kxstsgen.example.examplePlainClassSingleField01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface Color { | rgb: number; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExamplePlainClassPrimitiveFields01() { - captureOutput("ExamplePlainClassPrimitiveFields01") { + context("ExamplePlainClassPrimitiveFields01") { + val actual = captureOutput("ExamplePlainClassPrimitiveFields01") { dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface SimpleTypes { @@ -40,16 +50,22 @@ class BasicClassesTest { | privateMember: string; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExamplePlainDataClass01() { - captureOutput("ExamplePlainDataClass01") { + context("ExamplePlainDataClass01") { + val actual = captureOutput("ExamplePlainDataClass01") { dev.adamko.kxstsgen.example.examplePlainDataClass01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface SomeDataClass { @@ -60,22 +76,33 @@ class BasicClassesTest { | privateMember: string; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExamplePlainClassPrimitiveFields02() { - captureOutput("ExamplePlainClassPrimitiveFields02") { + context("ExamplePlainClassPrimitiveFields02") { + val actual = captureOutput("ExamplePlainClassPrimitiveFields02") { dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields02.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface SimpleTypes { |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } -} +}) diff --git a/docs/code/test/DefaultValuesTest.kt b/docs/code/test/DefaultValuesTest.kt index 9ccdcf08..476429dd 100644 --- a/docs/code/test/DefaultValuesTest.kt +++ b/docs/code/test/DefaultValuesTest.kt @@ -2,50 +2,66 @@ @file:Suppress("JSUnusedLocalSymbols") package dev.adamko.kxstsgen.example.test +import dev.adamko.kxstsgen.util.* +import io.kotest.core.spec.style.* 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") { +class DefaultValuesTest : FunSpec({ + + tags(Knit) + + context("ExampleDefaultValuesSingleField01") { + val actual = captureOutput("ExampleDefaultValuesSingleField01") { dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface Colour { | rgb?: number; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleDefaultValuesSingleField02() { - captureOutput("ExampleDefaultValuesSingleField02") { + context("ExampleDefaultValuesSingleField02") { + val actual = captureOutput("ExampleDefaultValuesSingleField02") { dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField02.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface Colour { | rgb: number | null; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleDefaultValuesPrimitiveFields01() { - captureOutput("ExampleDefaultValuesPrimitiveFields01") { + context("ExampleDefaultValuesPrimitiveFields01") { + val actual = captureOutput("ExampleDefaultValuesPrimitiveFields01") { dev.adamko.kxstsgen.example.exampleDefaultValuesPrimitiveFields01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface ContactDetails { @@ -55,7 +71,12 @@ class DefaultValuesTest { | phoneNumber?: string | null; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } -} +}) diff --git a/docs/code/test/EdgeCasesTest.kt b/docs/code/test/EdgeCasesTest.kt index 09d30de0..8c01e95e 100644 --- a/docs/code/test/EdgeCasesTest.kt +++ b/docs/code/test/EdgeCasesTest.kt @@ -2,18 +2,22 @@ @file:Suppress("JSUnusedLocalSymbols") package dev.adamko.kxstsgen.example.test +import dev.adamko.kxstsgen.util.* +import io.kotest.core.spec.style.* 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") { +class EdgeCasesTest : FunSpec({ + + tags(Knit) + + context("ExampleEdgecaseRecursiveReferences01") { + val actual = captureOutput("ExampleEdgecaseRecursiveReferences01") { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface A { @@ -24,16 +28,22 @@ class EdgeCasesTest { | a: A; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleEdgecaseRecursiveReferences02() { - captureOutput("ExampleEdgecaseRecursiveReferences02") { + context("ExampleEdgecaseRecursiveReferences02") { + val actual = captureOutput("ExampleEdgecaseRecursiveReferences02") { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences02.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface A { @@ -44,16 +54,22 @@ class EdgeCasesTest { | list: A[]; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleEdgecaseRecursiveReferences03() { - captureOutput("ExampleEdgecaseRecursiveReferences03") { + context("ExampleEdgecaseRecursiveReferences03") { + val actual = captureOutput("ExampleEdgecaseRecursiveReferences03") { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences03.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface A { @@ -64,7 +80,12 @@ class EdgeCasesTest { | map: { [key: string]: A }; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } -} +}) diff --git a/docs/code/test/EnumClassTest.kt b/docs/code/test/EnumClassTest.kt index 0d1f82ba..8a575f1d 100644 --- a/docs/code/test/EnumClassTest.kt +++ b/docs/code/test/EnumClassTest.kt @@ -2,18 +2,22 @@ @file:Suppress("JSUnusedLocalSymbols") package dev.adamko.kxstsgen.example.test +import dev.adamko.kxstsgen.util.* +import io.kotest.core.spec.style.* 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") { +class EnumClassTest : FunSpec({ + + tags(Knit) + + context("ExampleEnumClass01") { + val actual = captureOutput("ExampleEnumClass01") { dev.adamko.kxstsgen.example.exampleEnumClass01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export enum SomeType { @@ -22,16 +26,22 @@ class EnumClassTest { | Gamma = "Gamma", |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleEnumClass02() { - captureOutput("ExampleEnumClass02") { + context("ExampleEnumClass02") { + val actual = captureOutput("ExampleEnumClass02") { dev.adamko.kxstsgen.example.exampleEnumClass02.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export enum SomeType2 { @@ -40,7 +50,12 @@ class EnumClassTest { | Gamma = "Gamma", |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } -} +}) diff --git a/docs/code/test/ListsTests.kt b/docs/code/test/ListsTests.kt index 8c4655b3..801a36b1 100644 --- a/docs/code/test/ListsTests.kt +++ b/docs/code/test/ListsTests.kt @@ -2,18 +2,22 @@ @file:Suppress("JSUnusedLocalSymbols") package dev.adamko.kxstsgen.example.test +import dev.adamko.kxstsgen.util.* +import io.kotest.core.spec.style.* 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") { +class ListsTests : FunSpec({ + + tags(Knit) + + context("ExampleListPrimitive01") { + val actual = captureOutput("ExampleListPrimitive01") { dev.adamko.kxstsgen.example.exampleListPrimitive01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface MyLists { @@ -22,16 +26,22 @@ class ListsTests { | longs: number[]; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleListObjects01() { - captureOutput("ExampleListObjects01") { + context("ExampleListObjects01") { + val actual = captureOutput("ExampleListObjects01") { dev.adamko.kxstsgen.example.exampleListObjects01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface MyLists { @@ -44,16 +54,22 @@ class ListsTests { | rgb: string; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleListObjects02() { - captureOutput("ExampleListObjects02") { + context("ExampleListObjects02") { + val actual = captureOutput("ExampleListObjects02") { dev.adamko.kxstsgen.example.exampleListObjects02.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface MyLists { @@ -65,7 +81,12 @@ class ListsTests { | rgb: string; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } -} +}) diff --git a/docs/code/test/MapsTests.kt b/docs/code/test/MapsTests.kt index 5b5a45c3..623268b1 100644 --- a/docs/code/test/MapsTests.kt +++ b/docs/code/test/MapsTests.kt @@ -2,34 +2,44 @@ @file:Suppress("JSUnusedLocalSymbols") package dev.adamko.kxstsgen.example.test +import dev.adamko.kxstsgen.util.* +import io.kotest.core.spec.style.* 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") { +class MapsTests : FunSpec({ + + tags(Knit) + + context("ExampleMapPrimitive01") { + val actual = captureOutput("ExampleMapPrimitive01") { dev.adamko.kxstsgen.example.exampleMapPrimitive01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface Config { | properties: { [key: string]: string }; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleMapPrimitive02() { - captureOutput("ExampleMapPrimitive02") { + context("ExampleMapPrimitive02") { + val actual = captureOutput("ExampleMapPrimitive02") { dev.adamko.kxstsgen.example.exampleMapPrimitive02.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface Application { @@ -41,32 +51,44 @@ class MapsTests { | MAX_MEMORY = "MAX_MEMORY", |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleMapPrimitive03() { - captureOutput("ExampleMapPrimitive03") { + context("ExampleMapPrimitive03") { + val actual = captureOutput("ExampleMapPrimitive03") { dev.adamko.kxstsgen.example.exampleMapPrimitive03.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface MapsWithLists { | mapOfLists: { [key: string]: string[] }; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleMapPrimitive04() { - captureOutput("ExampleMapPrimitive04") { + context("ExampleMapPrimitive04") { + val actual = captureOutput("ExampleMapPrimitive04") { dev.adamko.kxstsgen.example.exampleMapPrimitive04.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface MyDataClass { @@ -75,32 +97,46 @@ class MapsTests { | |export type Data = string; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleMapPrimitive05() { - captureOutput("ExampleMapPrimitive05") { + context("ExampleMapPrimitive05") { + val actual = captureOutput("ExampleMapPrimitive05") { dev.adamko.kxstsgen.example.exampleMapPrimitive05.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface Config { - | properties: { [key: string | null]: string | null }; + | nullableVals: { [key: string]: string | null }; + | // [key: string | null] is not allowed + | nullableKeys: Map; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleMapComplex01() { - captureOutput("ExampleMapComplex01") { + context("ExampleMapComplex01") { + val actual = captureOutput("ExampleMapComplex01") { dev.adamko.kxstsgen.example.exampleMapComplex01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface CanvasProperties { @@ -116,16 +152,22 @@ class MapsTests { | |export type UByte = number; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleMapComplex02() { - captureOutput("ExampleMapComplex02") { + context("ExampleMapComplex02") { + val actual = captureOutput("ExampleMapComplex02") { dev.adamko.kxstsgen.example.exampleMapComplex02.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface CanvasProperties { @@ -134,23 +176,34 @@ class MapsTests { | |export type ColourMapKey = string; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleMapComplex03() { - captureOutput("ExampleMapComplex03") { + context("ExampleMapComplex03") { + val actual = captureOutput("ExampleMapComplex03") { dev.adamko.kxstsgen.example.exampleMapComplex03.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface CanvasProperties { | colourNames: { [key: string]: string }; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } -} +}) diff --git a/docs/code/test/PolymorphismTest.kt b/docs/code/test/PolymorphismTest.kt index c55bc229..941ec3b3 100644 --- a/docs/code/test/PolymorphismTest.kt +++ b/docs/code/test/PolymorphismTest.kt @@ -2,18 +2,22 @@ @file:Suppress("JSUnusedLocalSymbols") package dev.adamko.kxstsgen.example.test +import dev.adamko.kxstsgen.util.* +import io.kotest.core.spec.style.* 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") { +class PolymorphismTest : FunSpec({ + + tags(Knit) + + context("ExamplePolymorphicAbstractClassPrimitiveFields01") { + val actual = captureOutput("ExamplePolymorphicAbstractClassPrimitiveFields01") { dev.adamko.kxstsgen.example.examplePolymorphicAbstractClassPrimitiveFields01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type SimpleTypes = any; @@ -25,32 +29,44 @@ class PolymorphismTest { |// privateMember: string; |// } """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExamplePolymorphicStaticTypes01() { - captureOutput("ExamplePolymorphicStaticTypes01") { + context("ExamplePolymorphicStaticTypes01") { + val actual = captureOutput("ExamplePolymorphicStaticTypes01") { dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface Project { | name: string; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExamplePolymorphicStaticTypes02() { - captureOutput("ExamplePolymorphicStaticTypes02") { + context("ExamplePolymorphicStaticTypes02") { + val actual = captureOutput("ExamplePolymorphicStaticTypes02") { dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes02.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type Project = any; @@ -63,16 +79,22 @@ class PolymorphismTest { |// owner: string; |// } """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExamplePolymorphicSealedClass01() { - captureOutput("ExamplePolymorphicSealedClass01") { + context("ExamplePolymorphicSealedClass01") { + val actual = captureOutput("ExamplePolymorphicSealedClass01") { dev.adamko.kxstsgen.example.examplePolymorphicSealedClass01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type Project = @@ -98,16 +120,22 @@ class PolymorphismTest { | } |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExamplePolymorphicSealedClass02() { - captureOutput("ExamplePolymorphicSealedClass02") { + context("ExamplePolymorphicSealedClass02") { + val actual = captureOutput("ExamplePolymorphicSealedClass02") { dev.adamko.kxstsgen.example.examplePolymorphicSealedClass02.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type Dog = @@ -178,16 +206,22 @@ class PolymorphismTest { |// } |// } """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExamplePolymorphicObjects01() { - captureOutput("ExamplePolymorphicObjects01") { + context("ExamplePolymorphicObjects01") { + val actual = captureOutput("ExamplePolymorphicObjects01") { dev.adamko.kxstsgen.example.examplePolymorphicObjects01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type Response = @@ -210,37 +244,54 @@ class PolymorphismTest { | } |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleGenerics01() { - captureOutput("ExampleGenerics01") { + context("ExampleGenerics01") { + val actual = captureOutput("ExampleGenerics01") { dev.adamko.kxstsgen.example.exampleGenerics01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface Box { | value: number; |} """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleJsonPolymorphic01() { - captureOutput("ExampleJsonPolymorphic01") { + context("ExampleJsonPolymorphic01") { + val actual = captureOutput("ExampleJsonPolymorphic01") { dev.adamko.kxstsgen.example.exampleJsonPolymorphic01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type Project = any; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } -} +}) diff --git a/docs/code/test/TuplesTest.kt b/docs/code/test/TuplesTest.kt index 9b237478..3d1310ed 100644 --- a/docs/code/test/TuplesTest.kt +++ b/docs/code/test/TuplesTest.kt @@ -2,60 +2,82 @@ @file:Suppress("JSUnusedLocalSymbols") package dev.adamko.kxstsgen.example.test +import dev.adamko.kxstsgen.util.* +import io.kotest.core.spec.style.* import io.kotest.matchers.* import kotlinx.knit.test.* -import org.junit.jupiter.api.Test -import dev.adamko.kxstsgen.util.* -class TuplesTest { - @Test - fun testExampleTuple01() { - captureOutput("ExampleTuple01") { +class TuplesTest : FunSpec({ + + tags(Knit) + + context("ExampleTuple01") { + val actual = captureOutput("ExampleTuple01") { dev.adamko.kxstsgen.example.exampleTuple01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type SimpleTypes = [string, number, number | null, boolean, string]; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleTuple02() { - captureOutput("ExampleTuple02") { + context("ExampleTuple02") { + val actual = captureOutput("ExampleTuple02") { dev.adamko.kxstsgen.example.exampleTuple02.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type OptionalFields = [string, string, string | null, string | null]; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleTuple03() { - captureOutput("ExampleTuple03") { + context("ExampleTuple03") { + val actual = captureOutput("ExampleTuple03") { dev.adamko.kxstsgen.example.exampleTuple03.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type Coordinates = [number, number, number]; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleTuple04() { - captureOutput("ExampleTuple04") { + context("ExampleTuple04") { + val actual = captureOutput("ExampleTuple04") { dev.adamko.kxstsgen.example.exampleTuple04.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export interface GameLocations { @@ -66,7 +88,12 @@ class TuplesTest { | |export type Coordinates = [number, number, number]; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } -} +}) diff --git a/docs/code/test/ValueClassesTest.kt b/docs/code/test/ValueClassesTest.kt index 845c6b6f..d98b5602 100644 --- a/docs/code/test/ValueClassesTest.kt +++ b/docs/code/test/ValueClassesTest.kt @@ -2,32 +2,42 @@ @file:Suppress("JSUnusedLocalSymbols") package dev.adamko.kxstsgen.example.test +import dev.adamko.kxstsgen.util.* +import io.kotest.core.spec.style.* 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") { +class ValueClassesTest : FunSpec({ + + tags(Knit) + + context("ExampleValueClasses01") { + val actual = captureOutput("ExampleValueClasses01") { dev.adamko.kxstsgen.example.exampleValueClasses01.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type AuthToken = string; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleValueClasses02() { - captureOutput("ExampleValueClasses02") { + context("ExampleValueClasses02") { + val actual = captureOutput("ExampleValueClasses02") { dev.adamko.kxstsgen.example.exampleValueClasses02.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type UByte = number; @@ -38,37 +48,54 @@ class ValueClassesTest { | |export type ULong = number; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleValueClasses03() { - captureOutput("ExampleValueClasses03") { + context("ExampleValueClasses03") { + val actual = captureOutput("ExampleValueClasses03") { dev.adamko.kxstsgen.example.exampleValueClasses03.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type ULong = number & { __ULong__: void }; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } - @Test - fun testExampleValueClasses04() { - captureOutput("ExampleValueClasses04") { + context("ExampleValueClasses04") { + val actual = captureOutput("ExampleValueClasses04") { dev.adamko.kxstsgen.example.exampleValueClasses04.main() }.normalizeJoin() - .shouldBe( + + test("expect actual matches TypeScript") { + actual.shouldBe( // language=TypeScript """ |export type UserCount = UInt; | |export type UInt = number; """.trimMargin() - .normalize() + .normalize() ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } } -} +}) diff --git a/docs/code/util/dev/adamko/kxstsgen/util/kotestConfig.kt b/docs/code/util/dev/adamko/kxstsgen/util/kotestConfig.kt new file mode 100644 index 00000000..95a9b3cc --- /dev/null +++ b/docs/code/util/dev/adamko/kxstsgen/util/kotestConfig.kt @@ -0,0 +1,18 @@ +package dev.adamko.kxstsgen.util + +import io.kotest.core.Tag +import io.kotest.core.config.AbstractProjectConfig +import io.kotest.core.spec.IsolationMode + + +@Suppress("unused") // picked up by Kotest scanning +object KotestConfig : AbstractProjectConfig() { + // override val parallelism = 10 // causes tests failures... + override val isolationMode = IsolationMode.InstancePerLeaf +} + +object Knit : Tag() + +object TSCompile : Tag() + +val tsCompile = setOf(TSCompile) diff --git a/docs/code/util/dev/adamko/kxstsgen/util/processMatchers.kt b/docs/code/util/dev/adamko/kxstsgen/util/processMatchers.kt new file mode 100644 index 00000000..cce1f135 --- /dev/null +++ b/docs/code/util/dev/adamko/kxstsgen/util/processMatchers.kt @@ -0,0 +1,56 @@ +package dev.adamko.kxstsgen.util + +import io.kotest.assertions.asClue +import io.kotest.assertions.withClue +import io.kotest.matchers.ints.shouldBeExactly +import io.kotest.matchers.nulls.shouldNotBeNull +import io.kotest.matchers.string.shouldContainIgnoringCase +import io.kotest.matchers.string.shouldNotBeEmpty +import io.kotest.matchers.string.shouldNotContain +import java.nio.file.Path +import kotlin.coroutines.CoroutineContext +import kotlin.io.path.createDirectory +import kotlin.io.path.createTempDirectory +import kotlin.io.path.createTempFile +import kotlin.io.path.exists +import kotlin.io.path.invariantSeparatorsPathString +import kotlin.io.path.writeText +import kotlinx.coroutines.CoroutineName +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.withContext + + +private val processContext: CoroutineContext = + Dispatchers.IO + SupervisorJob() + CoroutineName("TscCompile") + + +private val tempDir: Path by lazy { + createTempDirectory().resolveSibling("kxstsgen").apply { + if (!exists()) createDirectory() + println("""Test output dir file:///${this.invariantSeparatorsPathString}""") + } +} + + +suspend fun String?.shouldTypeScriptCompile(): String = withContext(processContext) { + val src = this@shouldTypeScriptCompile + src.shouldNotBeNull() + + val file: Path = createTempFile( + directory = tempDir, + prefix = src.filter { it.isLetterOrDigit() }.take(20), + suffix = ".ts", + ) + file.writeText(src) + + val (process, output) = typescriptCompile(file) + output.joinToString("\n").asClue { log -> + withClue("exit code should be 0") { process shouldBeExactly 0 } + log.shouldNotBeEmpty() + log shouldContainIgnoringCase "npx: installed" + log shouldNotContain "error TS" + } + + src +} diff --git a/docs/code/util/strings.kt b/docs/code/util/dev/adamko/kxstsgen/util/strings.kt similarity index 99% rename from docs/code/util/strings.kt rename to docs/code/util/dev/adamko/kxstsgen/util/strings.kt index 08a8fa76..e1c3c7e9 100644 --- a/docs/code/util/strings.kt +++ b/docs/code/util/dev/adamko/kxstsgen/util/strings.kt @@ -1,5 +1,6 @@ package dev.adamko.kxstsgen.util + /** * * filter out lines that are `//` comments * * convert whitespace-only lines to an empty string @@ -15,6 +16,7 @@ fun String.normalize(): String = .ifBlank { "" } } + /** [normalize] each String, then [join][joinToString] them */ fun Iterable.normalizeJoin(): String = joinToString("\n") { it.normalize() } diff --git a/docs/code/util/dev/adamko/kxstsgen/util/typescriptCompile.kt b/docs/code/util/dev/adamko/kxstsgen/util/typescriptCompile.kt new file mode 100644 index 00000000..ef2c9880 --- /dev/null +++ b/docs/code/util/dev/adamko/kxstsgen/util/typescriptCompile.kt @@ -0,0 +1,46 @@ +package dev.adamko.kxstsgen.util + +import com.github.pgreze.process.ProcessResult +import com.github.pgreze.process.Redirect +import com.github.pgreze.process.process +import java.nio.file.Path +import kotlin.io.path.invariantSeparatorsPathString +import kotlinx.coroutines.ExperimentalCoroutinesApi + + +@OptIn(ExperimentalCoroutinesApi::class) +suspend fun typescriptCompile( + typeScriptFile: Path, +): ProcessResult { + return process( + npxCmd, + "-p", "typescript", + "tsc", + typeScriptFile.invariantSeparatorsPathString, + "--noEmit", + "--listFiles", // so we can check $file is compiled + "--pretty", "false", // doesn't work? + "--strict", + "--target", "esnext", // required for Map<> + + stdout = Redirect.CAPTURE, + stderr = Redirect.CAPTURE, + ) +} + + +private val npxCmd: String by lazy { + // this is untested on non-Windows + val hostOS = System.getProperty("os.name").lowercase() + val cmd = when { + "windows" in hostOS -> "npx.cmd" + else -> "npx" + } + npmInstallDir.resolve(cmd).invariantSeparatorsPathString +} + + +// must be set by Gradle +private val npmInstallDir: Path by lazy { + Path.of(System.getenv("NPM_INSTALL_DIR")) +} diff --git a/docs/knit.properties b/docs/knit.properties index 5b8bc032..a166b855 100644 --- a/docs/knit.properties +++ b/docs/knit.properties @@ -6,4 +6,4 @@ test.package=dev.adamko.kxstsgen.example.test test.template=./code/knit-test.ftl test.language=typescript knit.include=./code/knit-include.ftl -test.mode.=normalizeJoin()\n .shouldBe +#test.mode.=\n .shouldBe diff --git a/docs/maps.md b/docs/maps.md index 377174b4..0a59758b 100644 --- a/docs/maps.md +++ b/docs/maps.md @@ -140,10 +140,15 @@ export type Data = string; ### Nullable keys and values +Nullable keys are not allowed, so are convert to an ES6 Map. + +> An index signature parameter type must be 'string', 'number', 'symbol', or a template literal type + ```kotlin @Serializable data class Config( - val properties: Map + val nullableVals: Map, + val nullableKeys: Map, ) fun main() { @@ -156,7 +161,9 @@ fun main() { ```typescript export interface Config { - properties: { [key: string | null]: string | null }; + nullableVals: { [key: string]: string | null }; + // [key: string | null] is not allowed + nullableKeys: Map; } ``` diff --git a/modules/kxs-ts-gen-core/build.gradle.kts b/modules/kxs-ts-gen-core/build.gradle.kts index cac37a91..7a2c18f9 100644 --- a/modules/kxs-ts-gen-core/build.gradle.kts +++ b/modules/kxs-ts-gen-core/build.gradle.kts @@ -10,7 +10,7 @@ plugins { } val kotlinxSerializationVersion = "1.3.2" -val kotestVersion = "5.2.2" +val kotestVersion = "5.2.3" kotlin { diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt index 6a582cfd..a8dece17 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt @@ -217,7 +217,7 @@ fun interface TsElementConverter { val keyTypeRef = typeRefConverter(keyDescriptor) val valueTypeRef = typeRefConverter(valueDescriptor) - val type = mapTypeConverter(keyDescriptor) + val type = mapTypeConverter(keyDescriptor, valueDescriptor) return TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) } 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 6621cfc8..d97fe18f 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 @@ -9,12 +9,21 @@ import kotlinx.serialization.descriptors.StructureKind fun interface TsMapTypeConverter { - operator fun invoke(descriptor: SerialDescriptor): TsLiteral.TsMap.Type + operator fun invoke( + keyDescriptor: SerialDescriptor, + valDescriptor: SerialDescriptor?, + ): TsLiteral.TsMap.Type object Default : TsMapTypeConverter { - override operator fun invoke(descriptor: SerialDescriptor): TsLiteral.TsMap.Type { - return when (descriptor.kind) { + override operator fun invoke( + keyDescriptor: SerialDescriptor, + valDescriptor: SerialDescriptor?, + ): TsLiteral.TsMap.Type { + + if (keyDescriptor.isNullable) return TsLiteral.TsMap.Type.MAP + + return when (keyDescriptor.kind) { SerialKind.ENUM -> TsLiteral.TsMap.Type.MAPPED_OBJECT PrimitiveKind.STRING -> TsLiteral.TsMap.Type.INDEX_SIGNATURE diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsTypeRefConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsTypeRefConverter.kt index ea23ea4f..d6e511d4 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsTypeRefConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsTypeRefConverter.kt @@ -64,7 +64,7 @@ fun interface TsTypeRefConverter { val (keyDescriptor, valueDescriptor) = descriptor.elementDescriptors.toList() val keyTypeRef = this(keyDescriptor) val valueTypeRef = this(valueDescriptor) - val type = mapTypeConverter(keyDescriptor) + val type = mapTypeConverter(keyDescriptor, valueDescriptor) val map = TsLiteral.TsMap(keyTypeRef, valueTypeRef, type) return TsTypeRef.Literal(map, descriptor.isNullable) } From 2f2f1842ae81d4d7a282bf0182c2ca08159ddb67 Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Sun, 10 Apr 2022 23:23:35 +0200 Subject: [PATCH 3/9] #26 named tuples (#27) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implement labelled tuple elements, add tests, fix existing - simplify 'TSProperty', it only needs to be one class - TSCompile tests passed ✅ --- docs/code/example/example-tuple-02.kt | 38 ++++---- docs/code/example/example-tuple-03.kt | 36 +++---- docs/code/example/example-tuple-04.kt | 33 +++++-- docs/code/example/example-tuple-05.kt | 21 +++++ docs/code/test/TuplesTest.kt | 51 +++++++++- docs/tuples.md | 93 +++++++++++++++++-- .../kxstsgen/core/KxsTsSourceCodeGenerator.kt | 15 +-- .../kxstsgen/core/TsElementConverter.kt | 27 +++--- .../adamko/kxstsgen/core/experiments/tuple.kt | 15 ++- .../dev/adamko/kxstsgen/core/tsElements.kt | 26 ++---- 10 files changed, 257 insertions(+), 98 deletions(-) create mode 100644 docs/code/example/example-tuple-05.kt diff --git a/docs/code/example/example-tuple-02.kt b/docs/code/example/example-tuple-02.kt index 8dac896d..07282f63 100644 --- a/docs/code/example/example-tuple-02.kt +++ b/docs/code/example/example-tuple-02.kt @@ -6,29 +6,27 @@ import dev.adamko.kxstsgen.* import dev.adamko.kxstsgen.core.experiments.TupleSerializer import kotlinx.serialization.* -@Serializable(with = OptionalFields.Serializer::class) -data class OptionalFields( - val requiredString: String, - val optionalString: String = "", - val nullableString: String?, - val nullableOptionalString: String? = "", +@Serializable(with = PostalAddressUSA.Serializer::class) +data class PostalAddressUSA( + @SerialName("num") // 'SerialName' will be ignored in 'Tuple' form + val houseNumber: String, + val streetName: String, + val postcode: String, ) { - object Serializer : TupleSerializer( - "OptionalFields", + object Serializer : TupleSerializer( + "PostalAddressUSA", { - element(OptionalFields::requiredString) - element(OptionalFields::optionalString) - element(OptionalFields::nullableString) - element(OptionalFields::nullableOptionalString) + element(PostalAddressUSA::houseNumber) + // custom labels for 'streetName', 'postcode' + element("street", PostalAddressUSA::streetName) + element("zip", PostalAddressUSA::postcode) } ) { - override fun tupleConstructor(elements: Iterator<*>): OptionalFields { - val iter = elements.iterator() - return OptionalFields( - iter.next() as String, - iter.next() as String, - iter.next() as String, - iter.next() as String, + override fun tupleConstructor(elements: Iterator<*>): PostalAddressUSA { + return PostalAddressUSA( + elements.next() as String, + elements.next() as String, + elements.next() as String, ) } } @@ -36,5 +34,5 @@ data class OptionalFields( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(OptionalFields.serializer())) + println(tsGenerator.generate(PostalAddressUSA.serializer())) } diff --git a/docs/code/example/example-tuple-03.kt b/docs/code/example/example-tuple-03.kt index 6838945d..4fa0fb11 100644 --- a/docs/code/example/example-tuple-03.kt +++ b/docs/code/example/example-tuple-03.kt @@ -6,25 +6,29 @@ import dev.adamko.kxstsgen.* import dev.adamko.kxstsgen.core.experiments.TupleSerializer import kotlinx.serialization.* -@Serializable(with = Coordinates.Serializer::class) -data class Coordinates( - val x: Int, - val y: Int, - val z: Int, +@Serializable(with = OptionalFields.Serializer::class) +data class OptionalFields( + val requiredString: String, + val optionalString: String = "", + val nullableString: String?, + val nullableOptionalString: String? = "", ) { - object Serializer : TupleSerializer( - "Coordinates", + object Serializer : TupleSerializer( + "OptionalFields", { - element(Coordinates::x) - element(Coordinates::y) - element(Coordinates::z) + element(OptionalFields::requiredString) + element(OptionalFields::optionalString) + element(OptionalFields::nullableString) + element(OptionalFields::nullableOptionalString) } ) { - override fun tupleConstructor(elements: Iterator<*>): Coordinates { - return Coordinates( - elements.next() as Int, - elements.next() as Int, - elements.next() as Int, + override fun tupleConstructor(elements: Iterator<*>): OptionalFields { + val iter = elements.iterator() + return OptionalFields( + iter.next() as String, + iter.next() as String, + iter.next() as String, + iter.next() as String, ) } } @@ -32,5 +36,5 @@ data class Coordinates( fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(Coordinates.serializer())) + println(tsGenerator.generate(OptionalFields.serializer())) } diff --git a/docs/code/example/example-tuple-04.kt b/docs/code/example/example-tuple-04.kt index bae81b03..a8026af9 100644 --- a/docs/code/example/example-tuple-04.kt +++ b/docs/code/example/example-tuple-04.kt @@ -6,16 +6,31 @@ import dev.adamko.kxstsgen.* import dev.adamko.kxstsgen.core.experiments.TupleSerializer import kotlinx.serialization.* -import dev.adamko.kxstsgen.example.exampleTuple03.Coordinates - -@Serializable -class GameLocations( - val homeLocation: Coordinates, - val allLocations: List, - val namedLocations: Map, -) +@Serializable(with = Coordinates.Serializer::class) +data class Coordinates( + val x: Int, + val y: Int, + val z: Int, +) { + object Serializer : TupleSerializer( + "Coordinates", + { + element(Coordinates::x) + element(Coordinates::y) + element(Coordinates::z) + } + ) { + override fun tupleConstructor(elements: Iterator<*>): Coordinates { + return Coordinates( + elements.next() as Int, + elements.next() as Int, + elements.next() as Int, + ) + } + } +} fun main() { val tsGenerator = KxsTsGenerator() - println(tsGenerator.generate(GameLocations.serializer())) + println(tsGenerator.generate(Coordinates.serializer())) } diff --git a/docs/code/example/example-tuple-05.kt b/docs/code/example/example-tuple-05.kt new file mode 100644 index 00000000..87b33f36 --- /dev/null +++ b/docs/code/example/example-tuple-05.kt @@ -0,0 +1,21 @@ +// This file was automatically generated from tuples.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleTuple05 + +import dev.adamko.kxstsgen.* +import dev.adamko.kxstsgen.core.experiments.TupleSerializer +import kotlinx.serialization.* + +import dev.adamko.kxstsgen.example.exampleTuple04.Coordinates + +@Serializable +class GameLocations( + val homeLocation: Coordinates, + val allLocations: List, + val namedLocations: Map, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(GameLocations.serializer())) +} diff --git a/docs/code/test/TuplesTest.kt b/docs/code/test/TuplesTest.kt index 3d1310ed..cdb87d3b 100644 --- a/docs/code/test/TuplesTest.kt +++ b/docs/code/test/TuplesTest.kt @@ -20,7 +20,13 @@ class TuplesTest : FunSpec({ actual.shouldBe( // language=TypeScript """ - |export type SimpleTypes = [string, number, number | null, boolean, string]; + |export type SimpleTypes = [ + | aString: string, + | anInt: number, + | aDouble: number | null, + | bool: boolean, + | privateMember: string, + |]; """.trimMargin() .normalize() ) @@ -40,7 +46,11 @@ class TuplesTest : FunSpec({ actual.shouldBe( // language=TypeScript """ - |export type OptionalFields = [string, string, string | null, string | null]; + |export type PostalAddressUSA = [ + | houseNumber: string, // @SerialName("num") was ignored + | street: string, // custom name + | zip: string, // custom name + |]; """.trimMargin() .normalize() ) @@ -60,7 +70,12 @@ class TuplesTest : FunSpec({ actual.shouldBe( // language=TypeScript """ - |export type Coordinates = [number, number, number]; + |export type OptionalFields = [ + | requiredString: string, + | optionalString: string, + | nullableString: string | null, + | nullableOptionalString: string | null, + |]; """.trimMargin() .normalize() ) @@ -76,6 +91,30 @@ class TuplesTest : FunSpec({ dev.adamko.kxstsgen.example.exampleTuple04.main() }.normalizeJoin() + test("expect actual matches TypeScript") { + actual.shouldBe( + // language=TypeScript + """ + |export type Coordinates = [ + | x: number, + | y: number, + | z: number, + |]; + """.trimMargin() + .normalize() + ) + } + + test("expect actual compiles").config(tags = tsCompile) { + actual.shouldTypeScriptCompile() + } + } + + context("ExampleTuple05") { + val actual = captureOutput("ExampleTuple05") { + dev.adamko.kxstsgen.example.exampleTuple05.main() + }.normalizeJoin() + test("expect actual matches TypeScript") { actual.shouldBe( // language=TypeScript @@ -86,7 +125,11 @@ class TuplesTest : FunSpec({ | namedLocations: { [key: string]: Coordinates }; |} | - |export type Coordinates = [number, number, number]; + |export type Coordinates = [ + | x: number, + | y: number, + | z: number, + |]; """.trimMargin() .normalize() ) diff --git a/docs/tuples.md b/docs/tuples.md index 306ccba2..10cd274d 100644 --- a/docs/tuples.md +++ b/docs/tuples.md @@ -7,9 +7,10 @@ * [Tuples](#tuples) * [Tuple example](#tuple-example) + * [Tuple labels](#tuple-labels) * [Optional elements in tuples](#optional-elements-in-tuples) * [Properties all the same type](#properties-all-the-same-type) - * [Tuples as interface properties](#tuples-as-interface-properties) + * [Tuples as interface properties](#tuples-as-interface-properties) @@ -80,7 +81,66 @@ fun main() { > You can get the full code [here](./code/example/example-tuple-01.kt). ```typescript -export type SimpleTypes = [string, number, number | null, boolean, string]; +export type SimpleTypes = [ + aString: string, + anInt: number, + aDouble: number | null, + bool: boolean, + privateMember: string, +]; +``` + + + +### Tuple labels + +By default, the tuple elements are labelled with the names of properties, not the `@SerialName`, +which will be ignored. This isn't important for serialization because the tuple will be serialized +without the name of the property. + +The name of the label can be overridden if desired while defining the elements. + +```kotlin +@Serializable(with = PostalAddressUSA.Serializer::class) +data class PostalAddressUSA( + @SerialName("num") // 'SerialName' will be ignored in 'Tuple' form + val houseNumber: String, + val streetName: String, + val postcode: String, +) { + object Serializer : TupleSerializer( + "PostalAddressUSA", + { + element(PostalAddressUSA::houseNumber) + // custom labels for 'streetName', 'postcode' + element("street", PostalAddressUSA::streetName) + element("zip", PostalAddressUSA::postcode) + } + ) { + override fun tupleConstructor(elements: Iterator<*>): PostalAddressUSA { + return PostalAddressUSA( + elements.next() as String, + elements.next() as String, + elements.next() as String, + ) + } + } +} + +fun main() { + val tsGenerator = KxsTsGenerator() + println(tsGenerator.generate(PostalAddressUSA.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-tuple-02.kt). + +```typescript +export type PostalAddressUSA = [ + houseNumber: string, // @SerialName("num") was ignored + street: string, // custom name + zip: string, // custom name +]; ``` @@ -129,10 +189,15 @@ fun main() { } ``` -> You can get the full code [here](./code/example/example-tuple-02.kt). +> You can get the full code [here](./code/example/example-tuple-03.kt). ```typescript -export type OptionalFields = [string, string, string | null, string | null]; +export type OptionalFields = [ + requiredString: string, + optionalString: string, + nullableString: string | null, + nullableOptionalString: string | null, +]; ``` @@ -170,18 +235,22 @@ fun main() { } ``` -> You can get the full code [here](./code/example/example-tuple-03.kt). +> You can get the full code [here](./code/example/example-tuple-04.kt). ```typescript -export type Coordinates = [number, number, number]; +export type Coordinates = [ + x: number, + y: number, + z: number, +]; ``` -#### Tuples as interface properties +### Tuples as interface properties ```kotlin -import dev.adamko.kxstsgen.example.exampleTuple03.Coordinates +import dev.adamko.kxstsgen.example.exampleTuple04.Coordinates @Serializable class GameLocations( @@ -196,7 +265,7 @@ fun main() { } ``` -> You can get the full code [here](./code/example/example-tuple-04.kt). +> You can get the full code [here](./code/example/example-tuple-05.kt). ```typescript export interface GameLocations { @@ -205,7 +274,11 @@ export interface GameLocations { namedLocations: { [key: string]: Coordinates }; } -export type Coordinates = [number, number, number]; +export type Coordinates = [ + x: number, + y: number, + z: number, +]; ``` diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt index 9a57a52a..cbcc2b89 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt @@ -111,7 +111,7 @@ abstract class KxsTsSourceCodeGenerator( * ``` */ open fun generateInterfaceProperty( - property: TsProperty.Named + property: TsProperty ): String { val separator = if (property.optional) "?: " else ": " val propertyType = generateTypeReference(property.typeRef) @@ -166,14 +166,17 @@ abstract class KxsTsSourceCodeGenerator( override fun generateTuple(tuple: TsDeclaration.TsTuple): String { - val types = tuple.elements.joinToString(separator = ", ") { - val optionalMarker = if (it.optional) "?" else "" - generateTypeReference(it.typeRef) + optionalMarker + val types = tuple.elements.joinToString(separator = "\n") { property -> + val optionalMarker = if (property.optional) "?" else "" + val typeRef = generateTypeReference(property.typeRef) + "${config.indent}${property.name}: $typeRef$optionalMarker," } return """ - |export type ${tuple.id.name} = [$types]; - """.trimMargin() + ¦export type ${tuple.id.name} = [ + ¦$types + ¦]; + """.trimMargin("¦") } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt index a8dece17..ee978554 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt @@ -126,7 +126,7 @@ fun interface TsElementConverter { false, ) - val literalTypeProperty = TsProperty.Named(discriminatorName, false, literalTypeRef) + val literalTypeProperty = TsProperty(discriminatorName, literalTypeRef, false) subclass.copy(properties = setOf(literalTypeProperty) + subclass.properties) } @@ -165,12 +165,7 @@ fun interface TsElementConverter { ): TsDeclaration { val resultId = elementIdConverter(descriptor) - val properties = descriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> - val name = descriptor.getElementName(index) - val fieldTypeRef = typeRefConverter(fieldDescriptor) - val optional = descriptor.isElementOptional(index) - TsProperty.Named(name, optional, fieldTypeRef) - }.toSet() + val properties = convertProperties(descriptor) return TsDeclaration.TsInterface(resultId, properties) } @@ -181,16 +176,24 @@ fun interface TsElementConverter { ): TsDeclaration.TsTuple { val resultId = elementIdConverter(descriptor) - val properties = descriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> - val fieldTypeRef = typeRefConverter(fieldDescriptor) - val optional = descriptor.isElementOptional(index) - TsProperty.Unnamed(optional, fieldTypeRef) - } + val properties = convertProperties(descriptor) return TsDeclaration.TsTuple(resultId, properties) } + open fun convertProperties( + descriptor: SerialDescriptor, + ): Set { + return descriptor.elementDescriptors.mapIndexed { index, fieldDescriptor -> + val name = descriptor.getElementName(index) + val fieldTypeRef = typeRefConverter(fieldDescriptor) + val optional = descriptor.isElementOptional(index) + TsProperty(name, fieldTypeRef, optional) + }.toSet() + } + + open fun convertEnum( enumDescriptor: SerialDescriptor, ): TsDeclaration.TsEnum { diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/tuple.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/tuple.kt index 1423442a..ee8bc98f 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/tuple.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/tuple.kt @@ -2,6 +2,7 @@ package dev.adamko.kxstsgen.core.experiments +import kotlin.reflect.KProperty1 import kotlinx.serialization.InternalSerializationApi import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.SerialDescriptor @@ -17,6 +18,7 @@ import kotlinx.serialization.serializer open class TupleElement( + open val name: String, open val index: Int, open val elementSerializer: KSerializer, open val elementAccessor: T.() -> E, @@ -45,10 +47,12 @@ open class TupleElement( inline fun tupleElement( index: Int, + name: String, noinline elementAccessor: T.() -> E, serializer: KSerializer = serializer(), ): TupleElement { return TupleElement( + name, index, serializer, elementAccessor, @@ -73,9 +77,16 @@ class TupleElementsBuilder { val elementsSize by _elements::size inline fun element( + property: KProperty1 + ) { + element(property.name, property) + } + + inline fun element( + name: String, noinline elementAccessor: T.() -> E, ) { - element(tupleElement(elementsSize, elementAccessor)) + element(tupleElement(elementsSize, name, elementAccessor)) } fun element(element: TupleElement) { @@ -106,7 +117,7 @@ abstract class TupleSerializer( tupleElements .sortedBy { it.index } .forEach { tupleElement -> - element("element${tupleElement.index}", tupleElement.descriptor) + element(tupleElement.name, tupleElement.descriptor) } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt index 8961ded8..45011488 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt @@ -55,13 +55,13 @@ sealed interface TsDeclaration : TsElement { /** A [tuple type](https://www.typescriptlang.org/docs/handbook/2/objects.html#tuple-types). */ data class TsTuple( override val id: TsElementId, - val elements: List, + val elements: Set, ) : TsDeclaration data class TsInterface( override val id: TsElementId, - val properties: Set, + val properties: Set, ) : TsDeclaration @@ -158,26 +158,14 @@ sealed interface TsTypeRef { * A property within an [interface][TsDeclaration.TsInterface] * or [tuple][TsDeclaration.TsTuple]. */ -sealed interface TsProperty { - val typeRef: TsTypeRef +data class TsProperty( + val name: String, + val typeRef: TsTypeRef, /** * A property may be required or optional. See the TypeScript docs: * ['Optional Properties'](https://www.typescriptlang.org/docs/handbook/2/objects.html#optional-properties) * * Optionality is different to nullability, which is defined by [TsTypeRef.nullable]. */ - val optional: Boolean - - - data class Named( - val name: String, - override val optional: Boolean, - override val typeRef: TsTypeRef, - ) : TsProperty - - - data class Unnamed( - override val optional: Boolean, - override val typeRef: TsTypeRef, - ) : TsProperty -} + val optional: Boolean, +) From b2f134b22f961636a552de656cc911a0be9596b0 Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Mon, 11 Apr 2022 00:35:20 +0200 Subject: [PATCH 4/9] 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 --- docs/code/example/example-map-primitive-06.kt | 42 ++++++++++ docs/code/test/MapsTests.kt | 46 ++++++++++- docs/maps.md | 79 ++++++++++++++++++- .../kxstsgen/core/TsMapTypeConverter.kt | 17 ++++ 4 files changed, 182 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..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) + } + } + } } } From 611f64220fd805add1a8f2c647f0df529394c43a Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Wed, 13 Apr 2022 21:16:36 +0200 Subject: [PATCH 5/9] Element overriding (#30) * allow for overriding of elements (partial support for #23 and #22), and update knit to allow for TS Compile to be disabled * make TsMapTypeConverter more clear, split inline/non-inline * code tidy * fix bug where overrides weren't consistently found and applied if the target was nullable --- .../example/example-customising-output-01.kt | 31 ++++ .../example/example-customising-output-02.kt | 32 ++++ .../example/example-customising-output-03.kt | 39 ++++ docs/code/knit-test.ftl | 10 + docs/code/test/CustomisingOutputTest.kt | 97 ++++++++++ docs/customising-output.md | 175 ++++++++++++++++++ .../dev/adamko/kxstsgen/KxsTsGenerator.kt | 125 +++++++++---- .../kxstsgen/core/KxsTsSourceCodeGenerator.kt | 5 +- .../kxstsgen/core/TsElementIdConverter.kt | 2 +- .../kxstsgen/core/TsMapTypeConverter.kt | 36 ++-- 10 files changed, 496 insertions(+), 56 deletions(-) create mode 100644 docs/code/example/example-customising-output-01.kt create mode 100644 docs/code/example/example-customising-output-02.kt create mode 100644 docs/code/example/example-customising-output-03.kt create mode 100644 docs/code/test/CustomisingOutputTest.kt create mode 100644 docs/customising-output.md diff --git a/docs/code/example/example-customising-output-01.kt b/docs/code/example/example-customising-output-01.kt new file mode 100644 index 00000000..6b89589c --- /dev/null +++ b/docs/code/example/example-customising-output-01.kt @@ -0,0 +1,31 @@ +// This file was automatically generated from customising-output.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleCustomisingOutput01 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +import kotlinx.serialization.builtins.serializer +import dev.adamko.kxstsgen.core.* + +@Serializable +data class Item( + val price: Double, + val count: Int, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + + tsGenerator.descriptorOverrides += + Double.serializer().descriptor to TsDeclaration.TsTypeAlias( + id = TsElementId("Double"), + typeRef = TsTypeRef.Declaration( + id = TsElementId("double"), + parent = null, + nullable = false, + ) + ) + + println(tsGenerator.generate(Item.serializer())) +} diff --git a/docs/code/example/example-customising-output-02.kt b/docs/code/example/example-customising-output-02.kt new file mode 100644 index 00000000..28b63047 --- /dev/null +++ b/docs/code/example/example-customising-output-02.kt @@ -0,0 +1,32 @@ +// This file was automatically generated from customising-output.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleCustomisingOutput02 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +import kotlinx.serialization.builtins.serializer +import dev.adamko.kxstsgen.core.* + +@Serializable +data class ItemHolder( + val item: Item, +) + +@Serializable +data class Item( + val count: UInt? = 0u, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + + tsGenerator.descriptorOverrides += + UInt.serializer().descriptor to TsDeclaration.TsTypeAlias( + id = TsElementId("kotlin.UInt"), + typeRef = TsTypeRef.Declaration(id = TsElementId("uint"), parent = null, nullable = false) + ) + + println(tsGenerator.generate(ItemHolder.serializer())) +} + diff --git a/docs/code/example/example-customising-output-03.kt b/docs/code/example/example-customising-output-03.kt new file mode 100644 index 00000000..b87d2d16 --- /dev/null +++ b/docs/code/example/example-customising-output-03.kt @@ -0,0 +1,39 @@ +// This file was automatically generated from customising-output.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleCustomisingOutput03 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +import kotlinx.serialization.builtins.serializer +import dev.adamko.kxstsgen.core.* + + +@Serializable +@JvmInline +value class Tick(val value: UInt) + +@Serializable +data class ItemHolder( + val item: Item, + val tick: Tick?, +) + +@Serializable +data class Item( + val count: UInt? = 0u, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + + tsGenerator.descriptorOverrides += + UInt.serializer().descriptor to TsDeclaration.TsTypeAlias( + id = TsElementId("kotlin.UInt"), + typeRef = TsTypeRef.Declaration(id = TsElementId("uint"), parent = null, nullable = false) + ) + + println(tsGenerator.generate(ItemHolder.serializer())) +} + + diff --git a/docs/code/knit-test.ftl b/docs/code/knit-test.ftl index f20f4e61..564f5876 100644 --- a/docs/code/knit-test.ftl +++ b/docs/code/knit-test.ftl @@ -1,3 +1,4 @@ +<#--@formatter:off--> <#-- @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. @@ -24,7 +25,9 @@ class ${test.name} : FunSpec({ test("expect actual matches TypeScript") { actual.shouldBe( + <#if case.param != "TS_COMPILE_OFF"> // language=TypeScript + """ <#list case.lines as line> |${line} @@ -34,9 +37,16 @@ class ${test.name} : FunSpec({ ) } + <#if case.param == "TS_COMPILE_OFF"> + // TS_COMPILE_OFF + // test("expect actual compiles").config(tags = tsCompile) { + // actual.shouldTypeScriptCompile() + // } + <#else> test("expect actual compiles").config(tags = tsCompile) { actual.shouldTypeScriptCompile() } + } <#sep> diff --git a/docs/code/test/CustomisingOutputTest.kt b/docs/code/test/CustomisingOutputTest.kt new file mode 100644 index 00000000..bc792157 --- /dev/null +++ b/docs/code/test/CustomisingOutputTest.kt @@ -0,0 +1,97 @@ +// This file was automatically generated from customising-output.md by Knit tool. Do not edit. +@file:Suppress("JSUnusedLocalSymbols") +package dev.adamko.kxstsgen.example.test + +import dev.adamko.kxstsgen.util.* +import io.kotest.core.spec.style.* +import io.kotest.matchers.* +import kotlinx.knit.test.* + +class CustomisingOutputTest : FunSpec({ + + tags(Knit) + + context("ExampleCustomisingOutput01") { + val actual = captureOutput("ExampleCustomisingOutput01") { + dev.adamko.kxstsgen.example.exampleCustomisingOutput01.main() + }.normalizeJoin() + + test("expect actual matches TypeScript") { + actual.shouldBe( + """ + |export interface Item { + | price: Double; + | count: number; + |} + | + |export type Double = double; // assume that 'double' will be provided by another library + """.trimMargin() + .normalize() + ) + } + + // TS_COMPILE_OFF + // test("expect actual compiles").config(tags = tsCompile) { + // actual.shouldTypeScriptCompile() + // } + } + + context("ExampleCustomisingOutput02") { + val actual = captureOutput("ExampleCustomisingOutput02") { + dev.adamko.kxstsgen.example.exampleCustomisingOutput02.main() + }.normalizeJoin() + + test("expect actual matches TypeScript") { + actual.shouldBe( + """ + |export interface ItemHolder { + | item: Item; + |} + | + |export interface Item { + | count?: UInt | null; + |} + | + |export type UInt = uint; + """.trimMargin() + .normalize() + ) + } + + // TS_COMPILE_OFF + // test("expect actual compiles").config(tags = tsCompile) { + // actual.shouldTypeScriptCompile() + // } + } + + context("ExampleCustomisingOutput03") { + val actual = captureOutput("ExampleCustomisingOutput03") { + dev.adamko.kxstsgen.example.exampleCustomisingOutput03.main() + }.normalizeJoin() + + test("expect actual matches TypeScript") { + actual.shouldBe( + """ + |export interface ItemHolder { + | item: Item; + | tick: Tick | null; + |} + | + |export interface Item { + | count?: UInt | null; + |} + | + |export type Tick = UInt; + | + |export type UInt = uint; + """.trimMargin() + .normalize() + ) + } + + // TS_COMPILE_OFF + // test("expect actual compiles").config(tags = tsCompile) { + // actual.shouldTypeScriptCompile() + // } + } +}) diff --git a/docs/customising-output.md b/docs/customising-output.md new file mode 100644 index 00000000..63771015 --- /dev/null +++ b/docs/customising-output.md @@ -0,0 +1,175 @@ + + + +**Table of contents** + + + +* [Introduction](#introduction) + * [Overriding output](#overriding-output) + * [Override nullable elements](#override-nullable-elements) + * [Override both nullable and non-nullable descriptors](#override-both-nullable-and-non-nullable-descriptors) + + + + + + +## Introduction + +### Overriding output + +If you want to override what KxsTsGen produces, then you can provide overrides. + +By default, `Double` is transformed to `number`, but now we want to alias `Double` to `double`. + +```kotlin +import kotlinx.serialization.builtins.serializer +import dev.adamko.kxstsgen.core.* + +@Serializable +data class Item( + val price: Double, + val count: Int, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + + tsGenerator.descriptorOverrides += + Double.serializer().descriptor to TsDeclaration.TsTypeAlias( + id = TsElementId("Double"), + typeRef = TsTypeRef.Declaration( + id = TsElementId("double"), + parent = null, + nullable = false, + ) + ) + + println(tsGenerator.generate(Item.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-customising-output-01.kt). + +```typescript +export interface Item { + price: Double; + count: number; +} + +export type Double = double; // assume that 'double' will be provided by another library +``` + + + +### Override nullable elements + +Even though UInt is nullable, it should be overridden by the UInt defined in `descriptorOverrides`. + +```kotlin +import kotlinx.serialization.builtins.serializer +import dev.adamko.kxstsgen.core.* + +@Serializable +data class ItemHolder( + val item: Item, +) + +@Serializable +data class Item( + val count: UInt? = 0u, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + + tsGenerator.descriptorOverrides += + UInt.serializer().descriptor to TsDeclaration.TsTypeAlias( + id = TsElementId("kotlin.UInt"), + typeRef = TsTypeRef.Declaration(id = TsElementId("uint"), parent = null, nullable = false) + ) + + println(tsGenerator.generate(ItemHolder.serializer())) +} + +``` + +> You can get the full code [here](./code/example/example-customising-output-02.kt). + +```typescript +export interface ItemHolder { + item: Item; +} + +export interface Item { + count?: UInt | null; +} + +export type UInt = uint; +``` + + + +### Override both nullable and non-nullable descriptors + +`Tick` has a non-nullable UInt, while `Item` has a nullable UInt. Also, in `ItemHolder`, `Tick` is +nullable. Even though a non-nullable override for UInt is supplied, the output shouldn't have +conflicting overrides. + +```kotlin +import kotlinx.serialization.builtins.serializer +import dev.adamko.kxstsgen.core.* + + +@Serializable +@JvmInline +value class Tick(val value: UInt) + +@Serializable +data class ItemHolder( + val item: Item, + val tick: Tick?, +) + +@Serializable +data class Item( + val count: UInt? = 0u, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + + tsGenerator.descriptorOverrides += + UInt.serializer().descriptor to TsDeclaration.TsTypeAlias( + id = TsElementId("kotlin.UInt"), + typeRef = TsTypeRef.Declaration(id = TsElementId("uint"), parent = null, nullable = false) + ) + + println(tsGenerator.generate(ItemHolder.serializer())) +} + + +``` + +> You can get the full code [here](./code/example/example-customising-output-03.kt). + +```typescript +export interface ItemHolder { + item: Item; + tick: Tick | null; +} + +export interface Item { + count?: UInt | null; +} + +export type Tick = UInt; + +export type UInt = uint; +``` + + 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 14d09102..cb284a2b 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 @@ -7,82 +7,133 @@ import dev.adamko.kxstsgen.core.TsElement import dev.adamko.kxstsgen.core.TsElementConverter import dev.adamko.kxstsgen.core.TsElementId import dev.adamko.kxstsgen.core.TsElementIdConverter +import dev.adamko.kxstsgen.core.TsLiteral import dev.adamko.kxstsgen.core.TsMapTypeConverter import dev.adamko.kxstsgen.core.TsTypeRef import dev.adamko.kxstsgen.core.TsTypeRefConverter import kotlinx.serialization.KSerializer import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.descriptors.nullable /** * Generate TypeScript from [`@Serializable`][Serializable] Kotlin. * * The output can be controlled by the settings in [config], - * or by setting hardcoded values in [serializerDescriptors] or [descriptorElements], + * or by setting hardcoded values in [serializerDescriptorOverrides] or [descriptorOverrides], * or changed by overriding any converter. * * @param[config] General settings that affect how KxTsGen works - * @param[descriptorsExtractor] Given a [KSerializer], extract all [SerialDescriptor]s - * @param[elementIdConverter] Create an [TsElementId] from a [SerialDescriptor] - * @param[mapTypeConverter] Decides how [Map]s should be converted - * @param[typeRefConverter] Creates [TsTypeRef]s - * @param[elementConverter] Converts [SerialDescriptor]s to [TsElement]s * @param[sourceCodeGenerator] Convert [TsElement]s to TypeScript source code */ open class KxsTsGenerator( open val config: KxsTsConfig = KxsTsConfig(), - open val descriptorsExtractor: SerializerDescriptorsExtractor = SerializerDescriptorsExtractor.Default, + open val sourceCodeGenerator: KxsTsSourceCodeGenerator = KxsTsSourceCodeGenerator.Default(config), +) { + - open val elementIdConverter: TsElementIdConverter = TsElementIdConverter.Default, + val serializerDescriptorOverrides: MutableMap, Set> = + mutableMapOf() - open val mapTypeConverter: TsMapTypeConverter = TsMapTypeConverter.Default, + val descriptorOverrides: MutableMap = mutableMapOf() + + private fun findOverride(descriptor: SerialDescriptor): TsElement? { + return descriptorOverrides.entries.run { + firstOrNull { it.key == descriptor } ?: firstOrNull { it.key.nullable == descriptor.nullable } + }?.value + } - open val typeRefConverter: TsTypeRefConverter = - TsTypeRefConverter.Default(elementIdConverter, mapTypeConverter), - open val elementConverter: TsElementConverter = - TsElementConverter.Default( + open val descriptorsExtractor = object : SerializerDescriptorsExtractor { + val extractor: SerializerDescriptorsExtractor = SerializerDescriptorsExtractor.Default + val cache: MutableMap, Set> = mutableMapOf() + + override fun invoke(serializer: KSerializer<*>): Set = + cache.getOrPut(serializer) { + serializerDescriptorOverrides[serializer] ?: extractor(serializer) + } + } + + + val elementIdConverter: TsElementIdConverter = object : TsElementIdConverter { + private val converter: TsElementIdConverter = TsElementIdConverter.Default + private val cache: MutableMap = mutableMapOf() + + override fun invoke(descriptor: SerialDescriptor): TsElementId = + cache.getOrPut(descriptor) { + when (val override = findOverride(descriptor)) { + is TsDeclaration -> override.id + else -> converter(descriptor) + } + } + } + + + val mapTypeConverter: TsMapTypeConverter = object : TsMapTypeConverter { + private val converter = TsMapTypeConverter.Default + private val cache: MutableMap, TsLiteral.TsMap.Type> = + mutableMapOf() + + override fun invoke( + keyDescriptor: SerialDescriptor, + valDescriptor: SerialDescriptor, + ): TsLiteral.TsMap.Type = + cache.getOrPut(keyDescriptor to valDescriptor) { + converter(keyDescriptor, valDescriptor) + } + } + + + val typeRefConverter: TsTypeRefConverter = object : TsTypeRefConverter { + private val converter = TsTypeRefConverter.Default(elementIdConverter, mapTypeConverter) + val cache: MutableMap = mutableMapOf() + + override fun invoke(descriptor: SerialDescriptor): TsTypeRef = + cache.getOrPut(descriptor) { + when (val override = findOverride(descriptor)) { + null -> converter(descriptor) + is TsLiteral -> TsTypeRef.Literal(override, descriptor.isNullable) + is TsDeclaration -> TsTypeRef.Declaration(override.id, null, descriptor.isNullable) + } + } + } + + + val elementConverter: TsElementConverter = object : TsElementConverter { + private val converter = TsElementConverter.Default( elementIdConverter, mapTypeConverter, typeRefConverter, - ), - - open val sourceCodeGenerator: KxsTsSourceCodeGenerator = KxsTsSourceCodeGenerator.Default(config), -) { + ) + val cache: MutableMap> = mutableMapOf() + + override fun invoke(descriptor: SerialDescriptor): Set = + cache.getOrPut(descriptor) { + when (val override = findOverride(descriptor)) { + null -> converter(descriptor) + else -> setOf(override) + } + } + } - /** - * Stateful cache of all [descriptors][SerialDescriptor] extracted from a - * [serializer][KSerializer]. - * - * To customise the descriptors that a serializer produces, set value into this map. - */ - open val serializerDescriptors: MutableMap, Set> = mutableMapOf() - - /** - * Cache of all [elements][TsElement] that are created from any [descriptor][SerialDescriptor]. - * - * To customise the elements that a descriptor produces, set value into this map. - */ - open val descriptorElements: MutableMap> = mutableMapOf() open fun generate(vararg serializers: KSerializer<*>): String { return serializers .toSet() // 1. get all SerialDescriptors from a KSerializer - .flatMap { serializer -> - serializerDescriptors.getOrPut(serializer) { descriptorsExtractor(serializer) } - } + .flatMap { serializer -> descriptorsExtractor(serializer) } .toSet() // 2. convert each SerialDescriptor to some TsElements - .flatMap { descriptor -> - descriptorElements.getOrPut(descriptor) { elementConverter(descriptor) } - } + .flatMap { descriptor -> elementConverter(descriptor) } .toSet() + // 3. group by namespaces .groupBy { element -> sourceCodeGenerator.groupElementsBy(element) } + + // 4. convert to source code .mapValues { (_, elements) -> elements .filterIsInstance() diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt index cbcc2b89..041bcd82 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt @@ -86,6 +86,7 @@ abstract class KxsTsSourceCodeGenerator( """.trimMargin() } + override fun generateInterface(element: TsDeclaration.TsInterface): String { val properties = element.properties @@ -100,6 +101,7 @@ abstract class KxsTsSourceCodeGenerator( } } + /** * Generate * ```typescript @@ -145,6 +147,7 @@ abstract class KxsTsSourceCodeGenerator( } } + override fun generateTypeUnion(element: TsDeclaration.TsTypeUnion): String { return if (element.typeRefs.isEmpty()) { """ @@ -248,7 +251,5 @@ abstract class KxsTsSourceCodeGenerator( TsLiteral.TsMap.Type.MAP -> "Map<$keyTypeRef, $valueTypeRef>" } } - } - } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementIdConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementIdConverter.kt index 4e623544..db29efb0 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementIdConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementIdConverter.kt @@ -20,7 +20,7 @@ fun interface TsElementIdConverter { .substringBeforeLast(">") return when { - namespace.isBlank() -> TsElementId("$id") + namespace.isBlank() -> TsElementId(id) else -> TsElementId("$namespace.$id") } } 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 f71daa5c..cbb99f8d 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 @@ -12,21 +12,27 @@ fun interface TsMapTypeConverter { operator fun invoke( keyDescriptor: SerialDescriptor, - valDescriptor: SerialDescriptor?, + valDescriptor: SerialDescriptor, ): TsLiteral.TsMap.Type object Default : TsMapTypeConverter { override operator fun invoke( keyDescriptor: SerialDescriptor, - valDescriptor: SerialDescriptor?, + valDescriptor: SerialDescriptor, ): TsLiteral.TsMap.Type { + return when { + keyDescriptor.isNullable -> TsLiteral.TsMap.Type.MAP + keyDescriptor.isInline -> extractInlineType(keyDescriptor) + else -> serialKindMapType(keyDescriptor.kind) + } + } - if (keyDescriptor.isNullable) return TsLiteral.TsMap.Type.MAP - - if (keyDescriptor.isInline) return extractInlineType(keyDescriptor, valDescriptor) - - return when (keyDescriptor.kind) { + /** Determine a map type based on [kind] */ + fun serialKindMapType( + kind: SerialKind, + ): TsLiteral.TsMap.Type { + return when (kind) { SerialKind.ENUM -> TsLiteral.TsMap.Type.MAPPED_OBJECT PrimitiveKind.STRING -> TsLiteral.TsMap.Type.INDEX_SIGNATURE @@ -49,16 +55,14 @@ fun interface TsMapTypeConverter { } } - tailrec fun extractInlineType( - keyDescriptor: SerialDescriptor?, - valDescriptor: SerialDescriptor?, - ): TsLiteral.TsMap.Type { + + tailrec fun extractInlineType(keyDescriptor: 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) + !keyDescriptor.isInline + || keyDescriptor.elementsCount == 0 -> serialKindMapType(keyDescriptor.kind) + else -> { + val inlineKeyDescriptor = keyDescriptor.elementDescriptors.first() + extractInlineType(inlineKeyDescriptor) } } } From 43df261a46a873477cdb321533136c7977e27ab4 Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Wed, 13 Apr 2022 23:44:16 +0200 Subject: [PATCH 6/9] allow for custom literals, for better overriding #23 (#31) --- .../example/example-customising-output-02.kt | 16 ++---- .../example/example-customising-output-03.kt | 10 +--- .../example/example-customising-output-04.kt | 47 ++++++++++++++++ docs/code/test/CustomisingOutputTest.kt | 34 +++++++++++- docs/customising-output.md | 55 ++++++++++++++++++- modules/kxs-ts-gen-core/build.gradle.kts | 2 +- .../kxstsgen/core/KxsTsSourceCodeGenerator.kt | 2 + .../dev/adamko/kxstsgen/core/tsElements.kt | 2 + 8 files changed, 143 insertions(+), 25 deletions(-) create mode 100644 docs/code/example/example-customising-output-04.kt diff --git a/docs/code/example/example-customising-output-02.kt b/docs/code/example/example-customising-output-02.kt index 28b63047..f5359aa0 100644 --- a/docs/code/example/example-customising-output-02.kt +++ b/docs/code/example/example-customising-output-02.kt @@ -8,25 +8,17 @@ import dev.adamko.kxstsgen.* import kotlinx.serialization.builtins.serializer import dev.adamko.kxstsgen.core.* -@Serializable -data class ItemHolder( - val item: Item, -) - @Serializable data class Item( - val count: UInt? = 0u, + val price: Double, + val count: Int, ) fun main() { val tsGenerator = KxsTsGenerator() tsGenerator.descriptorOverrides += - UInt.serializer().descriptor to TsDeclaration.TsTypeAlias( - id = TsElementId("kotlin.UInt"), - typeRef = TsTypeRef.Declaration(id = TsElementId("uint"), parent = null, nullable = false) - ) + Double.serializer().descriptor to TsLiteral.Custom("customDouble") - println(tsGenerator.generate(ItemHolder.serializer())) + println(tsGenerator.generate(Item.serializer())) } - diff --git a/docs/code/example/example-customising-output-03.kt b/docs/code/example/example-customising-output-03.kt index b87d2d16..c99d80f4 100644 --- a/docs/code/example/example-customising-output-03.kt +++ b/docs/code/example/example-customising-output-03.kt @@ -8,20 +8,15 @@ import dev.adamko.kxstsgen.* import kotlinx.serialization.builtins.serializer import dev.adamko.kxstsgen.core.* - -@Serializable -@JvmInline -value class Tick(val value: UInt) - @Serializable data class ItemHolder( val item: Item, - val tick: Tick?, ) @Serializable data class Item( val count: UInt? = 0u, + val score: Int? = 0, ) fun main() { @@ -33,7 +28,8 @@ fun main() { typeRef = TsTypeRef.Declaration(id = TsElementId("uint"), parent = null, nullable = false) ) + tsGenerator.descriptorOverrides += Int.serializer().descriptor to TsLiteral.Custom("customInt") + println(tsGenerator.generate(ItemHolder.serializer())) } - diff --git a/docs/code/example/example-customising-output-04.kt b/docs/code/example/example-customising-output-04.kt new file mode 100644 index 00000000..e2e1551f --- /dev/null +++ b/docs/code/example/example-customising-output-04.kt @@ -0,0 +1,47 @@ +// This file was automatically generated from customising-output.md by Knit tool. Do not edit. +@file:Suppress("PackageDirectoryMismatch", "unused") +package dev.adamko.kxstsgen.example.exampleCustomisingOutput04 + +import kotlinx.serialization.* +import dev.adamko.kxstsgen.* + +import kotlinx.serialization.builtins.serializer +import dev.adamko.kxstsgen.core.* + + +@Serializable +@JvmInline +value class Tick(val value: UInt) + +@Serializable +@JvmInline +value class Phase(val value: Int) + +@Serializable +data class ItemHolder( + val item: Item, + val tick: Tick?, + val phase: Phase?, +) + +@Serializable +data class Item( + val count: UInt? = 0u, + val score: Int? = 0, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + + tsGenerator.descriptorOverrides += + UInt.serializer().descriptor to TsDeclaration.TsTypeAlias( + id = TsElementId("kotlin.UInt"), + typeRef = TsTypeRef.Declaration(id = TsElementId("uint"), parent = null, nullable = false) + ) + + tsGenerator.descriptorOverrides += Int.serializer().descriptor to TsLiteral.Custom("customInt") + + println(tsGenerator.generate(ItemHolder.serializer())) +} + + diff --git a/docs/code/test/CustomisingOutputTest.kt b/docs/code/test/CustomisingOutputTest.kt index bc792157..9e4eca8e 100644 --- a/docs/code/test/CustomisingOutputTest.kt +++ b/docs/code/test/CustomisingOutputTest.kt @@ -41,6 +41,29 @@ class CustomisingOutputTest : FunSpec({ dev.adamko.kxstsgen.example.exampleCustomisingOutput02.main() }.normalizeJoin() + test("expect actual matches TypeScript") { + actual.shouldBe( + """ + |export interface Item { + | price: customDouble; + | count: number; + |} + """.trimMargin() + .normalize() + ) + } + + // TS_COMPILE_OFF + // test("expect actual compiles").config(tags = tsCompile) { + // actual.shouldTypeScriptCompile() + // } + } + + context("ExampleCustomisingOutput03") { + val actual = captureOutput("ExampleCustomisingOutput03") { + dev.adamko.kxstsgen.example.exampleCustomisingOutput03.main() + }.normalizeJoin() + test("expect actual matches TypeScript") { actual.shouldBe( """ @@ -50,6 +73,7 @@ class CustomisingOutputTest : FunSpec({ | |export interface Item { | count?: UInt | null; + | score?: customInt | null; |} | |export type UInt = uint; @@ -64,9 +88,9 @@ class CustomisingOutputTest : FunSpec({ // } } - context("ExampleCustomisingOutput03") { - val actual = captureOutput("ExampleCustomisingOutput03") { - dev.adamko.kxstsgen.example.exampleCustomisingOutput03.main() + context("ExampleCustomisingOutput04") { + val actual = captureOutput("ExampleCustomisingOutput04") { + dev.adamko.kxstsgen.example.exampleCustomisingOutput04.main() }.normalizeJoin() test("expect actual matches TypeScript") { @@ -75,14 +99,18 @@ class CustomisingOutputTest : FunSpec({ |export interface ItemHolder { | item: Item; | tick: Tick | null; + | phase: Phase | null; |} | |export interface Item { | count?: UInt | null; + | score?: customInt | null; |} | |export type Tick = UInt; | + |export type Phase = customInt; + | |export type UInt = uint; """.trimMargin() .normalize() diff --git a/docs/customising-output.md b/docs/customising-output.md index 63771015..83f40657 100644 --- a/docs/customising-output.md +++ b/docs/customising-output.md @@ -66,6 +66,41 @@ export type Double = double; // assume that 'double' will be provided by another +Instead of changing the output to be a type alias, a custom 'literal' type can be set instead. + +```kotlin +import kotlinx.serialization.builtins.serializer +import dev.adamko.kxstsgen.core.* + +@Serializable +data class Item( + val price: Double, + val count: Int, +) + +fun main() { + val tsGenerator = KxsTsGenerator() + + tsGenerator.descriptorOverrides += + Double.serializer().descriptor to TsLiteral.Custom("customDouble") + + println(tsGenerator.generate(Item.serializer())) +} +``` + +> You can get the full code [here](./code/example/example-customising-output-02.kt). + +This produces no type alias, and `Double` is overridden to be `customDouble`. + +```typescript +export interface Item { + price: customDouble; + count: number; +} +``` + + + ### Override nullable elements Even though UInt is nullable, it should be overridden by the UInt defined in `descriptorOverrides`. @@ -82,6 +117,7 @@ data class ItemHolder( @Serializable data class Item( val count: UInt? = 0u, + val score: Int? = 0, ) fun main() { @@ -93,12 +129,14 @@ fun main() { typeRef = TsTypeRef.Declaration(id = TsElementId("uint"), parent = null, nullable = false) ) + tsGenerator.descriptorOverrides += Int.serializer().descriptor to TsLiteral.Custom("customInt") + println(tsGenerator.generate(ItemHolder.serializer())) } ``` -> You can get the full code [here](./code/example/example-customising-output-02.kt). +> You can get the full code [here](./code/example/example-customising-output-03.kt). ```typescript export interface ItemHolder { @@ -107,6 +145,7 @@ export interface ItemHolder { export interface Item { count?: UInt | null; + score?: customInt | null; } export type UInt = uint; @@ -129,15 +168,21 @@ import dev.adamko.kxstsgen.core.* @JvmInline value class Tick(val value: UInt) +@Serializable +@JvmInline +value class Phase(val value: Int) + @Serializable data class ItemHolder( val item: Item, val tick: Tick?, + val phase: Phase?, ) @Serializable data class Item( val count: UInt? = 0u, + val score: Int? = 0, ) fun main() { @@ -149,26 +194,32 @@ fun main() { typeRef = TsTypeRef.Declaration(id = TsElementId("uint"), parent = null, nullable = false) ) + tsGenerator.descriptorOverrides += Int.serializer().descriptor to TsLiteral.Custom("customInt") + println(tsGenerator.generate(ItemHolder.serializer())) } ``` -> You can get the full code [here](./code/example/example-customising-output-03.kt). +> You can get the full code [here](./code/example/example-customising-output-04.kt). ```typescript export interface ItemHolder { item: Item; tick: Tick | null; + phase: Phase | null; } export interface Item { count?: UInt | null; + score?: customInt | null; } export type Tick = UInt; +export type Phase = customInt; + export type UInt = uint; ``` diff --git a/modules/kxs-ts-gen-core/build.gradle.kts b/modules/kxs-ts-gen-core/build.gradle.kts index 7a2c18f9..c1fd24e0 100644 --- a/modules/kxs-ts-gen-core/build.gradle.kts +++ b/modules/kxs-ts-gen-core/build.gradle.kts @@ -26,7 +26,7 @@ kotlin { jvm { compilations.all { kotlinOptions { - jvmTarget = "11" + jvmTarget = "1.8" } } withJava() diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt index 041bcd82..cf021f4f 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt @@ -200,6 +200,8 @@ abstract class KxsTsSourceCodeGenerator( } is TsLiteral.TsMap -> generateMapTypeReference(typeRef.element) + + is TsLiteral.Custom -> typeRef.element.value } is TsTypeRef.Declaration -> { diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt index 45011488..5f68bed5 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt @@ -120,6 +120,8 @@ sealed interface TsLiteral : TsElement { } } + @JvmInline + value class Custom(val value: String) : TsLiteral } From 1617a47d903ace92ce29bc9c5756daaf55f12280 Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Wed, 1 Jun 2022 20:52:55 +0200 Subject: [PATCH 7/9] Continuous development (#32) * re-enable kotlin js * re-enable kotlin js * additional test for tuple as map key * make jvm target 1.8 (better compatibility) * rename source code generate to match names of other classes * version bumps kotlin 1.6.20 * spelling/grammar * fix sealed interface enums not using the correct serial name * rm extra line breaks at end of code block * set up centralised versions and versions platform * fix buildsrc gradle config * fix publishing java platform * map type improvements - allow numbers as indexed type keys - map type will check for TsElement overrides - add option to override the generated map type * re-use test name for the TS file name * fix tuple serializer * tidy up tuple serializer * fix tuple deserialization * rm debug print * bump kotlin 1.6.21 * start trying to set up maven central publishing * enable gradle caching * enable gradle parallel --- buildSrc/build.gradle.kts | 45 +- buildSrc/settings.gradle.kts | 9 + .../main/kotlin/buildsrc/config/publishing.kt | 44 ++ .../buildsrc/convention/kotlin-jvm.gradle.kts | 4 +- .../convention/maven-publish.gradle.kts | 96 ++- docs/code/build.gradle.kts | 17 +- .../example/example-customising-output-03.kt | 1 - .../example/example-customising-output-04.kt | 2 - .../example-polymorphic-sealed-class-02.kt | 3 + docs/code/example/example-tuple-01.kt | 2 +- docs/code/example/example-tuple-05.kt | 1 + docs/code/knit-test.ftl | 12 +- docs/code/test/AbstractClassesTest.kt | 19 +- docs/code/test/BasicClassesTest.kt | 25 +- docs/code/test/CustomisingOutputTest.kt | 25 +- docs/code/test/DefaultValuesTest.kt | 19 +- docs/code/test/EdgeCasesTest.kt | 19 +- docs/code/test/EnumClassTest.kt | 13 +- docs/code/test/ListsTests.kt | 19 +- docs/code/test/MapsTests.kt | 55 +- docs/code/test/PolymorphismTest.kt | 61 +- docs/code/test/TuplesTest.kt | 32 +- docs/code/test/ValueClassesTest.kt | 25 +- .../adamko/kxstsgen/util/processMatchers.kt | 6 +- docs/customising-output.md | 3 - docs/polymorphism.md | 15 +- docs/tuples.md | 8 +- gradle.properties | 6 + gradle/kotlin-js-store/yarn.lock | 567 ++++++++++++++++++ gradle/libs.versions.toml | 73 +++ jitpack.yml | 2 +- modules/kxs-ts-gen-core/build.gradle.kts | 60 +- .../dev/adamko/kxstsgen/KxsTsGenerator.kt | 20 +- .../kxstsgen/core/TsElementConverter.kt | 78 ++- .../kxstsgen/core/TsMapTypeConverter.kt | 4 +- ...eGenerator.kt => TsSourceCodeGenerator.kt} | 7 +- .../core/experiments/serializerExtractors.kt | 106 ++-- .../adamko/kxstsgen/core/experiments/tuple.kt | 55 +- .../dev/adamko/kxstsgen/core/tsElements.kt | 9 +- .../kxstsgen/core/experiments/TupleTest.kt | 142 +++++ .../adamko/kxstsgen/core/test/kxsBinary.kt | 160 +++++ .../experiments/serializerExtractorsJvm.kt | 104 ++-- modules/kxs-ts-gen-processor/build.gradle.kts | 19 +- modules/versions-platform/build.gradle.kts | 27 + settings.gradle.kts | 2 + 45 files changed, 1629 insertions(+), 392 deletions(-) create mode 100644 buildSrc/src/main/kotlin/buildsrc/config/publishing.kt create mode 100644 gradle.properties create mode 100644 gradle/kotlin-js-store/yarn.lock create mode 100644 gradle/libs.versions.toml rename modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/{KxsTsSourceCodeGenerator.kt => TsSourceCodeGenerator.kt} (97%) create mode 100644 modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/experiments/TupleTest.kt create mode 100644 modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/test/kxsBinary.kt create mode 100644 modules/versions-platform/build.gradle.kts diff --git a/buildSrc/build.gradle.kts b/buildSrc/build.gradle.kts index fa966abe..c5aa19e4 100644 --- a/buildSrc/build.gradle.kts +++ b/buildSrc/build.gradle.kts @@ -3,50 +3,37 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { idea `kotlin-dsl` - kotlin("jvm") version "1.6.10" + kotlin("jvm") version "1.6.20" `project-report` } -object Versions { - const val jvmTarget = "11" - const val kotlin = "1.6.10" - 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 gradleNodePlugin = "3.2.1" - - const val kotest = "5.2.3" -} - - dependencies { - implementation(enforcedPlatform("org.jetbrains.kotlin:kotlin-bom:${Versions.kotlin}")) + implementation(enforcedPlatform(libs.kotlin.bom)) implementation("org.jetbrains.kotlin:kotlin-serialization") - implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${Versions.kotlin}") +// implementation("org.jetbrains.kotlin:kotlin-reflect") - implementation("com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin:${Versions.ksp}") + implementation(libs.kotlin.gradlePlugin) + implementation(libs.kotlinSymbolProcessing.gradlePlugin) - implementation(platform("org.jetbrains.kotlinx:kotlinx-serialization-bom:${Versions.kotlinxSerialization}")) + implementation(libs.kotest.gradlePlugin) - implementation("io.kotest:kotest-framework-multiplatform-plugin-gradle:${Versions.kotest}") + implementation(libs.kotlinx.kover.gradlePlugin) - implementation("org.jetbrains.kotlinx:kotlinx-knit:${Versions.kotlinxKnit}") - implementation("org.jetbrains.kotlinx:kover:${Versions.kotlinxKover}") + implementation(libs.kotlinx.knit.gradlePlugin) - implementation("com.github.node-gradle:gradle-node-plugin:${Versions.gradleNodePlugin}") + implementation(libs.gradleNodePlugin) } +val gradleJvmTarget = "11" +val gradleKotlinTarget = "1.6" tasks.withType().configureEach { kotlinOptions { - jvmTarget = Versions.jvmTarget - apiVersion = Versions.kotlinTarget - languageVersion = Versions.kotlinTarget + jvmTarget = gradleJvmTarget + apiVersion = gradleKotlinTarget + languageVersion = gradleKotlinTarget } kotlinOptions.freeCompilerArgs += listOf( @@ -58,11 +45,11 @@ tasks.withType().configureEach { kotlin { jvmToolchain { - (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(Versions.jvmTarget)) + (this as JavaToolchainSpec).languageVersion.set(JavaLanguageVersion.of(gradleJvmTarget)) } kotlinDslPluginOptions { - jvmTarget.set(Versions.jvmTarget) + jvmTarget.set(gradleJvmTarget) } } diff --git a/buildSrc/settings.gradle.kts b/buildSrc/settings.gradle.kts index cb641bd9..eb552c75 100644 --- a/buildSrc/settings.gradle.kts +++ b/buildSrc/settings.gradle.kts @@ -1 +1,10 @@ apply(from = "./repositories.settings.gradle.kts") + +dependencyResolutionManagement { + @Suppress("UnstableApiUsage") + versionCatalogs { + create("libs") { + from(files("../gradle/libs.versions.toml")) + } + } +} diff --git a/buildSrc/src/main/kotlin/buildsrc/config/publishing.kt b/buildSrc/src/main/kotlin/buildsrc/config/publishing.kt new file mode 100644 index 00000000..bcdba2ec --- /dev/null +++ b/buildSrc/src/main/kotlin/buildsrc/config/publishing.kt @@ -0,0 +1,44 @@ +package buildsrc.config + +import org.gradle.api.Project +import org.gradle.api.publish.PublishingExtension +import org.gradle.api.publish.maven.MavenPublication +import org.gradle.kotlin.dsl.configure +import org.gradle.kotlin.dsl.getByType +import org.gradle.plugins.signing.SigningExtension + + +fun MavenPublication.kxsTsGenPom() = pom { + url.set("https://github.com/adamko-dev/kotlinx-serialization-typescript-generator") + packaging = "jar" + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + scm { + connection.set("scm:git:git://github.com/adamko-dev/kotlinx-serialization-typescript-generator.git") + url.set("https://github.com/adamko-dev/kotlinx-serialization-typescript-generator") + } +} + + + + + +// hacks because IntelliJ still doesn't properly load DSL accessors for buildSrc + +/** Configure [PublishingExtension] */ +fun Project.publishing(action: PublishingExtension.() -> Unit): Unit = + extensions.configure(action) + +val Project.publishing: PublishingExtension + get() = extensions.getByType() + +/** Configure [SigningExtension] */ +fun Project.signing(action: SigningExtension.() -> Unit): Unit = + extensions.configure(action) + +val Project.signing: SigningExtension + get() = extensions.getByType() 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 a984ed9a..75722654 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.2.3")) + // versions provided by versions-platform subproject testImplementation("io.kotest:kotest-runner-junit5") testImplementation("io.kotest:kotest-assertions-core") testImplementation("io.kotest:kotest-property") @@ -33,7 +33,7 @@ java { tasks.withType { kotlinOptions { - jvmTarget = "11" + jvmTarget = "1.8" apiVersion = "1.6" languageVersion = "1.6" } 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 678212bc..b2286ad0 100644 --- a/buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts @@ -1,26 +1,88 @@ package buildsrc.convention +import buildsrc.config.publishing +import buildsrc.config.signing +import org.gradle.api.credentials.PasswordCredentials +import org.gradle.internal.credentials.DefaultPasswordCredentials + plugins { + id("buildsrc.convention.subproject") `maven-publish` + signing +} + + +//val sonatypeRepositoryCredentials: Provider = +// providers.credentials(PasswordCredentials::class, "sonatypeRepositoryCredentials") + +val sonatypeRepositoryUsername: String? by project.extra +val sonatypeRepositoryPassword: String? by project.extra +val sonatypeRepositoryCredentials: Provider = providers.provider { + if (sonatypeRepositoryUsername.isNullOrBlank() || sonatypeRepositoryPassword.isNullOrBlank()) { + null + } else { + DefaultPasswordCredentials(sonatypeRepositoryUsername, sonatypeRepositoryPassword) + } +} + + +val sonatypeRepositoryId: String by project.extra + +val sonatypeRepositoryReleaseUrl: Provider = provider { + if (version.toString().endsWith("SNAPSHOT")) { + "https://oss.sonatype.org/content/repositories/snapshots/" + } else { + "https://oss.sonatype.org/service/local/staging/deployByRepositoryId/$sonatypeRepositoryId/" + } } -//plugins.withType(JavaPlugin::class.java) { -// publishing { -// publications { -// create("mavenJava") { -// from(components["java"]) -// } -// } -// } -//} - -tasks - .matching { - it.name.startsWith(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME) - && it.group == PublishingPlugin.PUBLISH_TASK_GROUP + +tasks.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}") } - .configureEach { - doLast { - logger.lifecycle("[${this.name}] ${project.group}:${project.name}:${project.version}") +} + + + +publishing { + repositories { + if (sonatypeRepositoryCredentials.isPresent) { + maven(sonatypeRepositoryReleaseUrl) { + name = "oss" + credentials { + username = sonatypeRepositoryCredentials.get().username + password = sonatypeRepositoryCredentials.get().password + } + } } } +} + +signing { + val signingKeyId: String? by project + val signingKey: String? by project + val signingPassword: String? by project + useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) + setRequired(false) +} + + +plugins.withType { + if (!plugins.hasPlugin(KotlinMultiplatformPlugin::class)) { + val publication = publishing.publications.create("mavenJava") { + from(components["java"]) + } + signing { sign(publication) } + } +} + +plugins.withType { + val publication = publishing.publications.create("mavenJavaPlatform") { + from(components["javaPlatform"]) + } + signing { sign(publication) } +} diff --git a/docs/code/build.gradle.kts b/docs/code/build.gradle.kts index fd0aec6e..1b8b74c7 100644 --- a/docs/code/build.gradle.kts +++ b/docs/code/build.gradle.kts @@ -7,25 +7,24 @@ plugins { id("org.jetbrains.kotlinx.knit") } -val kotlinxSerializationVersion = "1.3.2" - dependencies { + implementation(platform(projects.modules.versionsPlatform)) + 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") + implementation(libs.kotlinx.serialization.core) + implementation(libs.kotlinx.serialization.json) - implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1") + implementation(libs.kotlinx.coroutines.core) - implementation("org.jetbrains.kotlinx:kotlinx-knit:0.3.0") + implementation(libs.kotlinx.knit) implementation(kotlin("reflect")) testImplementation(kotlin("test")) - testImplementation("org.jetbrains.kotlinx:kotlinx-knit-test:0.3.0") - testImplementation("com.github.pgreze:kotlin-process:1.3.1") + testImplementation(libs.kotlinx.knit.test) + testImplementation(libs.kotlinProcess) } tasks.withType { diff --git a/docs/code/example/example-customising-output-03.kt b/docs/code/example/example-customising-output-03.kt index c99d80f4..2223bf84 100644 --- a/docs/code/example/example-customising-output-03.kt +++ b/docs/code/example/example-customising-output-03.kt @@ -32,4 +32,3 @@ fun main() { println(tsGenerator.generate(ItemHolder.serializer())) } - diff --git a/docs/code/example/example-customising-output-04.kt b/docs/code/example/example-customising-output-04.kt index e2e1551f..1ba5d93b 100644 --- a/docs/code/example/example-customising-output-04.kt +++ b/docs/code/example/example-customising-output-04.kt @@ -43,5 +43,3 @@ fun main() { println(tsGenerator.generate(ItemHolder.serializer())) } - - diff --git a/docs/code/example/example-polymorphic-sealed-class-02.kt b/docs/code/example/example-polymorphic-sealed-class-02.kt index dd93fbcc..15824517 100644 --- a/docs/code/example/example-polymorphic-sealed-class-02.kt +++ b/docs/code/example/example-polymorphic-sealed-class-02.kt @@ -10,6 +10,7 @@ sealed class Dog { abstract val name: String @Serializable + @SerialName("Dog.Mutt") class Mutt(override val name: String, val loveable: Boolean = true) : Dog() @Serializable @@ -17,6 +18,7 @@ sealed class Dog { abstract val colour: String @Serializable + @SerialName("Dog.Retriever.Golden") data class Golden( override val name: String, override val colour: String, @@ -24,6 +26,7 @@ sealed class Dog { ) : Retriever() @Serializable + @SerialName("Dog.Retriever.NovaScotia") data class NovaScotia( override val name: String, override val colour: String, diff --git a/docs/code/example/example-tuple-01.kt b/docs/code/example/example-tuple-01.kt index 250f0bfe..6bf791b3 100644 --- a/docs/code/example/example-tuple-01.kt +++ b/docs/code/example/example-tuple-01.kt @@ -28,7 +28,7 @@ data class SimpleTypes( } ) { override fun tupleConstructor(elements: Iterator<*>): SimpleTypes { - // When deserializing, the elements will be available as a list, in the order defined + // When deserializing, the elements will be available as a list, in the order defined above return SimpleTypes( elements.next() as String, elements.next() as Int, diff --git a/docs/code/example/example-tuple-05.kt b/docs/code/example/example-tuple-05.kt index 87b33f36..6c5168e7 100644 --- a/docs/code/example/example-tuple-05.kt +++ b/docs/code/example/example-tuple-05.kt @@ -13,6 +13,7 @@ class GameLocations( val homeLocation: Coordinates, val allLocations: List, val namedLocations: Map, + val locationsInfo: Map, ) fun main() { diff --git a/docs/code/knit-test.ftl b/docs/code/knit-test.ftl index 564f5876..d0d08c86 100644 --- a/docs/code/knit-test.ftl +++ b/docs/code/knit-test.ftl @@ -16,10 +16,12 @@ import kotlinx.knit.test.* class ${test.name} : FunSpec({ tags(Knit) - -<#list cases as case><#assign method = test["mode.${case.param}"]!"custom"> +<#--<#assign method = test["mode.${case.param}"]!"custom">--> +<#list cases as case> context("${case.name}") { - val actual = captureOutput("${case.name}") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { ${case.knit.package}.${case.knit.name}.main() }.normalizeJoin() @@ -40,11 +42,11 @@ class ${test.name} : FunSpec({ <#if case.param == "TS_COMPILE_OFF"> // TS_COMPILE_OFF // test("expect actual compiles").config(tags = tsCompile) { - // actual.shouldTypeScriptCompile() + // actual.shouldTypeScriptCompile(caseName) // } <#else> test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } diff --git a/docs/code/test/AbstractClassesTest.kt b/docs/code/test/AbstractClassesTest.kt index 5fea9a45..41e05f4b 100644 --- a/docs/code/test/AbstractClassesTest.kt +++ b/docs/code/test/AbstractClassesTest.kt @@ -10,9 +10,10 @@ import kotlinx.knit.test.* class AbstractClassesTest : FunSpec({ tags(Knit) - context("ExampleAbstractClassSingleField01") { - val actual = captureOutput("ExampleAbstractClassSingleField01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleAbstractClassSingleField01.main() }.normalizeJoin() @@ -30,12 +31,14 @@ class AbstractClassesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleAbstractClassPrimitiveFields01") { - val actual = captureOutput("ExampleAbstractClassPrimitiveFields01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleAbstractClassPrimitiveFields01.main() }.normalizeJoin() @@ -57,12 +60,14 @@ class AbstractClassesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleAbstractClassAbstractField01") { - val actual = captureOutput("ExampleAbstractClassAbstractField01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleAbstractClassAbstractField01.main() }.normalizeJoin() @@ -80,7 +85,7 @@ class AbstractClassesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } }) diff --git a/docs/code/test/BasicClassesTest.kt b/docs/code/test/BasicClassesTest.kt index 28c4872a..73c206ec 100644 --- a/docs/code/test/BasicClassesTest.kt +++ b/docs/code/test/BasicClassesTest.kt @@ -10,9 +10,10 @@ import kotlinx.knit.test.* class BasicClassesTest : FunSpec({ tags(Knit) - context("ExamplePlainClassSingleField01") { - val actual = captureOutput("ExamplePlainClassSingleField01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.examplePlainClassSingleField01.main() }.normalizeJoin() @@ -29,12 +30,14 @@ class BasicClassesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExamplePlainClassPrimitiveFields01") { - val actual = captureOutput("ExamplePlainClassPrimitiveFields01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields01.main() }.normalizeJoin() @@ -55,12 +58,14 @@ class BasicClassesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExamplePlainDataClass01") { - val actual = captureOutput("ExamplePlainDataClass01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.examplePlainDataClass01.main() }.normalizeJoin() @@ -81,12 +86,14 @@ class BasicClassesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExamplePlainClassPrimitiveFields02") { - val actual = captureOutput("ExamplePlainClassPrimitiveFields02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.examplePlainClassPrimitiveFields02.main() }.normalizeJoin() @@ -102,7 +109,7 @@ class BasicClassesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } }) diff --git a/docs/code/test/CustomisingOutputTest.kt b/docs/code/test/CustomisingOutputTest.kt index 9e4eca8e..740291a3 100644 --- a/docs/code/test/CustomisingOutputTest.kt +++ b/docs/code/test/CustomisingOutputTest.kt @@ -10,9 +10,10 @@ import kotlinx.knit.test.* class CustomisingOutputTest : FunSpec({ tags(Knit) - context("ExampleCustomisingOutput01") { - val actual = captureOutput("ExampleCustomisingOutput01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleCustomisingOutput01.main() }.normalizeJoin() @@ -32,12 +33,14 @@ class CustomisingOutputTest : FunSpec({ // TS_COMPILE_OFF // test("expect actual compiles").config(tags = tsCompile) { - // actual.shouldTypeScriptCompile() + // actual.shouldTypeScriptCompile(caseName) // } } context("ExampleCustomisingOutput02") { - val actual = captureOutput("ExampleCustomisingOutput02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleCustomisingOutput02.main() }.normalizeJoin() @@ -55,12 +58,14 @@ class CustomisingOutputTest : FunSpec({ // TS_COMPILE_OFF // test("expect actual compiles").config(tags = tsCompile) { - // actual.shouldTypeScriptCompile() + // actual.shouldTypeScriptCompile(caseName) // } } context("ExampleCustomisingOutput03") { - val actual = captureOutput("ExampleCustomisingOutput03") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleCustomisingOutput03.main() }.normalizeJoin() @@ -84,12 +89,14 @@ class CustomisingOutputTest : FunSpec({ // TS_COMPILE_OFF // test("expect actual compiles").config(tags = tsCompile) { - // actual.shouldTypeScriptCompile() + // actual.shouldTypeScriptCompile(caseName) // } } context("ExampleCustomisingOutput04") { - val actual = captureOutput("ExampleCustomisingOutput04") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleCustomisingOutput04.main() }.normalizeJoin() @@ -119,7 +126,7 @@ class CustomisingOutputTest : FunSpec({ // TS_COMPILE_OFF // test("expect actual compiles").config(tags = tsCompile) { - // actual.shouldTypeScriptCompile() + // actual.shouldTypeScriptCompile(caseName) // } } }) diff --git a/docs/code/test/DefaultValuesTest.kt b/docs/code/test/DefaultValuesTest.kt index 476429dd..d4c30508 100644 --- a/docs/code/test/DefaultValuesTest.kt +++ b/docs/code/test/DefaultValuesTest.kt @@ -10,9 +10,10 @@ import kotlinx.knit.test.* class DefaultValuesTest : FunSpec({ tags(Knit) - context("ExampleDefaultValuesSingleField01") { - val actual = captureOutput("ExampleDefaultValuesSingleField01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField01.main() }.normalizeJoin() @@ -29,12 +30,14 @@ class DefaultValuesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleDefaultValuesSingleField02") { - val actual = captureOutput("ExampleDefaultValuesSingleField02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleDefaultValuesSingleField02.main() }.normalizeJoin() @@ -51,12 +54,14 @@ class DefaultValuesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleDefaultValuesPrimitiveFields01") { - val actual = captureOutput("ExampleDefaultValuesPrimitiveFields01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleDefaultValuesPrimitiveFields01.main() }.normalizeJoin() @@ -76,7 +81,7 @@ class DefaultValuesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } }) diff --git a/docs/code/test/EdgeCasesTest.kt b/docs/code/test/EdgeCasesTest.kt index 8c01e95e..ea86bfa8 100644 --- a/docs/code/test/EdgeCasesTest.kt +++ b/docs/code/test/EdgeCasesTest.kt @@ -10,9 +10,10 @@ import kotlinx.knit.test.* class EdgeCasesTest : FunSpec({ tags(Knit) - context("ExampleEdgecaseRecursiveReferences01") { - val actual = captureOutput("ExampleEdgecaseRecursiveReferences01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences01.main() }.normalizeJoin() @@ -33,12 +34,14 @@ class EdgeCasesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleEdgecaseRecursiveReferences02") { - val actual = captureOutput("ExampleEdgecaseRecursiveReferences02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences02.main() }.normalizeJoin() @@ -59,12 +62,14 @@ class EdgeCasesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleEdgecaseRecursiveReferences03") { - val actual = captureOutput("ExampleEdgecaseRecursiveReferences03") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleEdgecaseRecursiveReferences03.main() }.normalizeJoin() @@ -85,7 +90,7 @@ class EdgeCasesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } }) diff --git a/docs/code/test/EnumClassTest.kt b/docs/code/test/EnumClassTest.kt index 8a575f1d..c3949915 100644 --- a/docs/code/test/EnumClassTest.kt +++ b/docs/code/test/EnumClassTest.kt @@ -10,9 +10,10 @@ import kotlinx.knit.test.* class EnumClassTest : FunSpec({ tags(Knit) - context("ExampleEnumClass01") { - val actual = captureOutput("ExampleEnumClass01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleEnumClass01.main() }.normalizeJoin() @@ -31,12 +32,14 @@ class EnumClassTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleEnumClass02") { - val actual = captureOutput("ExampleEnumClass02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleEnumClass02.main() }.normalizeJoin() @@ -55,7 +58,7 @@ class EnumClassTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } }) diff --git a/docs/code/test/ListsTests.kt b/docs/code/test/ListsTests.kt index 801a36b1..a142cb51 100644 --- a/docs/code/test/ListsTests.kt +++ b/docs/code/test/ListsTests.kt @@ -10,9 +10,10 @@ import kotlinx.knit.test.* class ListsTests : FunSpec({ tags(Knit) - context("ExampleListPrimitive01") { - val actual = captureOutput("ExampleListPrimitive01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleListPrimitive01.main() }.normalizeJoin() @@ -31,12 +32,14 @@ class ListsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleListObjects01") { - val actual = captureOutput("ExampleListObjects01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleListObjects01.main() }.normalizeJoin() @@ -59,12 +62,14 @@ class ListsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleListObjects02") { - val actual = captureOutput("ExampleListObjects02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleListObjects02.main() }.normalizeJoin() @@ -86,7 +91,7 @@ class ListsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } }) diff --git a/docs/code/test/MapsTests.kt b/docs/code/test/MapsTests.kt index 1e0263ae..a3090d14 100644 --- a/docs/code/test/MapsTests.kt +++ b/docs/code/test/MapsTests.kt @@ -10,9 +10,10 @@ import kotlinx.knit.test.* class MapsTests : FunSpec({ tags(Knit) - context("ExampleMapPrimitive01") { - val actual = captureOutput("ExampleMapPrimitive01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleMapPrimitive01.main() }.normalizeJoin() @@ -29,12 +30,14 @@ class MapsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleMapPrimitive02") { - val actual = captureOutput("ExampleMapPrimitive02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleMapPrimitive02.main() }.normalizeJoin() @@ -56,12 +59,14 @@ class MapsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleMapPrimitive03") { - val actual = captureOutput("ExampleMapPrimitive03") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleMapPrimitive03.main() }.normalizeJoin() @@ -78,12 +83,14 @@ class MapsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleMapPrimitive04") { - val actual = captureOutput("ExampleMapPrimitive04") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleMapPrimitive04.main() }.normalizeJoin() @@ -102,12 +109,14 @@ class MapsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleMapPrimitive05") { - val actual = captureOutput("ExampleMapPrimitive05") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleMapPrimitive05.main() }.normalizeJoin() @@ -126,12 +135,14 @@ class MapsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleMapPrimitive06") { - val actual = captureOutput("ExampleMapPrimitive06") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleMapPrimitive06.main() }.normalizeJoin() @@ -170,12 +181,14 @@ class MapsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleMapComplex01") { - val actual = captureOutput("ExampleMapComplex01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleMapComplex01.main() }.normalizeJoin() @@ -201,12 +214,14 @@ class MapsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleMapComplex02") { - val actual = captureOutput("ExampleMapComplex02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleMapComplex02.main() }.normalizeJoin() @@ -225,12 +240,14 @@ class MapsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleMapComplex03") { - val actual = captureOutput("ExampleMapComplex03") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleMapComplex03.main() }.normalizeJoin() @@ -247,7 +264,7 @@ class MapsTests : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } }) diff --git a/docs/code/test/PolymorphismTest.kt b/docs/code/test/PolymorphismTest.kt index 941ec3b3..99bdb813 100644 --- a/docs/code/test/PolymorphismTest.kt +++ b/docs/code/test/PolymorphismTest.kt @@ -10,9 +10,10 @@ import kotlinx.knit.test.* class PolymorphismTest : FunSpec({ tags(Knit) - context("ExamplePolymorphicAbstractClassPrimitiveFields01") { - val actual = captureOutput("ExamplePolymorphicAbstractClassPrimitiveFields01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.examplePolymorphicAbstractClassPrimitiveFields01.main() }.normalizeJoin() @@ -34,12 +35,14 @@ class PolymorphismTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExamplePolymorphicStaticTypes01") { - val actual = captureOutput("ExamplePolymorphicStaticTypes01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes01.main() }.normalizeJoin() @@ -56,12 +59,14 @@ class PolymorphismTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExamplePolymorphicStaticTypes02") { - val actual = captureOutput("ExamplePolymorphicStaticTypes02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.examplePolymorphicStaticTypes02.main() }.normalizeJoin() @@ -84,12 +89,14 @@ class PolymorphismTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExamplePolymorphicSealedClass01") { - val actual = captureOutput("ExamplePolymorphicSealedClass01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.examplePolymorphicSealedClass01.main() }.normalizeJoin() @@ -104,7 +111,7 @@ class PolymorphismTest : FunSpec({ |export namespace Project { | export enum Type { | OProj = "OProj", - | DeprecatedProject = "DeprecatedProject", + | DeprecatedProject = "dev.adamko.kxstsgen.example.examplePolymorphicSealedClass01.DeprecatedProject", | } | | export interface OProj { @@ -125,12 +132,14 @@ class PolymorphismTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExamplePolymorphicSealedClass02") { - val actual = captureOutput("ExamplePolymorphicSealedClass02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.examplePolymorphicSealedClass02.main() }.normalizeJoin() @@ -145,9 +154,9 @@ class PolymorphismTest : FunSpec({ | |export namespace Dog { | export enum Type { - | Mutt = "Mutt", - | Golden = "Golden", - | NovaScotia = "NovaScotia", + | Mutt = "Dog.Mutt", + | Golden = "Dog.Retriever.Golden", + | NovaScotia = "Dog.Retriever.NovaScotia", | } | | export interface Mutt { @@ -211,12 +220,14 @@ class PolymorphismTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExamplePolymorphicObjects01") { - val actual = captureOutput("ExamplePolymorphicObjects01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.examplePolymorphicObjects01.main() }.normalizeJoin() @@ -230,8 +241,8 @@ class PolymorphismTest : FunSpec({ | |export namespace Response { | export enum Type { - | EmptyResponse = "EmptyResponse", - | TextResponse = "TextResponse", + | EmptyResponse = "dev.adamko.kxstsgen.example.examplePolymorphicObjects01.EmptyResponse", + | TextResponse = "dev.adamko.kxstsgen.example.examplePolymorphicObjects01.TextResponse", | } | | export interface EmptyResponse { @@ -249,12 +260,14 @@ class PolymorphismTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleGenerics01") { - val actual = captureOutput("ExampleGenerics01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleGenerics01.main() }.normalizeJoin() @@ -271,12 +284,14 @@ class PolymorphismTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleJsonPolymorphic01") { - val actual = captureOutput("ExampleJsonPolymorphic01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleJsonPolymorphic01.main() }.normalizeJoin() @@ -291,7 +306,7 @@ class PolymorphismTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } }) diff --git a/docs/code/test/TuplesTest.kt b/docs/code/test/TuplesTest.kt index cdb87d3b..9d0d4b91 100644 --- a/docs/code/test/TuplesTest.kt +++ b/docs/code/test/TuplesTest.kt @@ -10,9 +10,10 @@ import kotlinx.knit.test.* class TuplesTest : FunSpec({ tags(Knit) - context("ExampleTuple01") { - val actual = captureOutput("ExampleTuple01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleTuple01.main() }.normalizeJoin() @@ -33,12 +34,14 @@ class TuplesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleTuple02") { - val actual = captureOutput("ExampleTuple02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleTuple02.main() }.normalizeJoin() @@ -57,12 +60,14 @@ class TuplesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleTuple03") { - val actual = captureOutput("ExampleTuple03") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleTuple03.main() }.normalizeJoin() @@ -82,12 +87,14 @@ class TuplesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleTuple04") { - val actual = captureOutput("ExampleTuple04") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleTuple04.main() }.normalizeJoin() @@ -106,12 +113,14 @@ class TuplesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleTuple05") { - val actual = captureOutput("ExampleTuple05") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleTuple05.main() }.normalizeJoin() @@ -123,6 +132,7 @@ class TuplesTest : FunSpec({ | homeLocation: Coordinates; | allLocations: Coordinates[]; | namedLocations: { [key: string]: Coordinates }; + | locationsInfo: Map; |} | |export type Coordinates = [ @@ -136,7 +146,7 @@ class TuplesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } }) diff --git a/docs/code/test/ValueClassesTest.kt b/docs/code/test/ValueClassesTest.kt index d98b5602..4e41e7a3 100644 --- a/docs/code/test/ValueClassesTest.kt +++ b/docs/code/test/ValueClassesTest.kt @@ -10,9 +10,10 @@ import kotlinx.knit.test.* class ValueClassesTest : FunSpec({ tags(Knit) - context("ExampleValueClasses01") { - val actual = captureOutput("ExampleValueClasses01") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleValueClasses01.main() }.normalizeJoin() @@ -27,12 +28,14 @@ class ValueClassesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleValueClasses02") { - val actual = captureOutput("ExampleValueClasses02") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleValueClasses02.main() }.normalizeJoin() @@ -53,12 +56,14 @@ class ValueClassesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleValueClasses03") { - val actual = captureOutput("ExampleValueClasses03") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleValueClasses03.main() }.normalizeJoin() @@ -73,12 +78,14 @@ class ValueClassesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } context("ExampleValueClasses04") { - val actual = captureOutput("ExampleValueClasses04") { + val caseName = testCase.name.testName + + val actual = captureOutput(caseName) { dev.adamko.kxstsgen.example.exampleValueClasses04.main() }.normalizeJoin() @@ -95,7 +102,7 @@ class ValueClassesTest : FunSpec({ } test("expect actual compiles").config(tags = tsCompile) { - actual.shouldTypeScriptCompile() + actual.shouldTypeScriptCompile(caseName) } } }) diff --git a/docs/code/util/dev/adamko/kxstsgen/util/processMatchers.kt b/docs/code/util/dev/adamko/kxstsgen/util/processMatchers.kt index cce1f135..5cacf484 100644 --- a/docs/code/util/dev/adamko/kxstsgen/util/processMatchers.kt +++ b/docs/code/util/dev/adamko/kxstsgen/util/processMatchers.kt @@ -33,13 +33,15 @@ private val tempDir: Path by lazy { } -suspend fun String?.shouldTypeScriptCompile(): String = withContext(processContext) { +suspend fun String?.shouldTypeScriptCompile( + testName: String +): String = withContext(processContext) { val src = this@shouldTypeScriptCompile src.shouldNotBeNull() val file: Path = createTempFile( directory = tempDir, - prefix = src.filter { it.isLetterOrDigit() }.take(20), + prefix = testName, suffix = ".ts", ) file.writeText(src) diff --git a/docs/customising-output.md b/docs/customising-output.md index 83f40657..5c126945 100644 --- a/docs/customising-output.md +++ b/docs/customising-output.md @@ -133,7 +133,6 @@ fun main() { println(tsGenerator.generate(ItemHolder.serializer())) } - ``` > You can get the full code [here](./code/example/example-customising-output-03.kt). @@ -198,8 +197,6 @@ fun main() { println(tsGenerator.generate(ItemHolder.serializer())) } - - ``` > You can get the full code [here](./code/example/example-customising-output-04.kt). diff --git a/docs/polymorphism.md b/docs/polymorphism.md index c1c47e8d..d70d597c 100644 --- a/docs/polymorphism.md +++ b/docs/polymorphism.md @@ -170,7 +170,7 @@ export type Project = export namespace Project { export enum Type { OProj = "OProj", - DeprecatedProject = "DeprecatedProject", + DeprecatedProject = "dev.adamko.kxstsgen.example.examplePolymorphicSealedClass01.DeprecatedProject", } export interface OProj { @@ -202,6 +202,7 @@ sealed class Dog { abstract val name: String @Serializable + @SerialName("Dog.Mutt") class Mutt(override val name: String, val loveable: Boolean = true) : Dog() @Serializable @@ -209,6 +210,7 @@ sealed class Dog { abstract val colour: String @Serializable + @SerialName("Dog.Retriever.Golden") data class Golden( override val name: String, override val colour: String, @@ -216,6 +218,7 @@ sealed class Dog { ) : Retriever() @Serializable + @SerialName("Dog.Retriever.NovaScotia") data class NovaScotia( override val name: String, override val colour: String, @@ -240,9 +243,9 @@ export type Dog = export namespace Dog { export enum Type { - Mutt = "Mutt", - Golden = "Golden", - NovaScotia = "NovaScotia", + Mutt = "Dog.Mutt", + Golden = "Dog.Retriever.Golden", + NovaScotia = "Dog.Retriever.NovaScotia", } export interface Mutt { @@ -333,8 +336,8 @@ export type Response = export namespace Response { export enum Type { - EmptyResponse = "EmptyResponse", - TextResponse = "TextResponse", + EmptyResponse = "dev.adamko.kxstsgen.example.examplePolymorphicObjects01.EmptyResponse", + TextResponse = "dev.adamko.kxstsgen.example.examplePolymorphicObjects01.TextResponse", } export interface EmptyResponse { diff --git a/docs/tuples.md b/docs/tuples.md index 10cd274d..2aca0f0f 100644 --- a/docs/tuples.md +++ b/docs/tuples.md @@ -34,8 +34,8 @@ deserializing. ### Tuple example -Let's say we have a class, `SimpleTypes`, that we want to serializer. We need to create a bespoke -tuple serializer for it, so override the plugin-generated serializer. +Let's say we have a class, `SimpleTypes`, that we want to serialize. We need to create a bespoke +tuple serializer for it, and override the plugin-generated serializer. ```kotlin @Serializable(with = SimpleTypes.SimpleTypesSerializer::class) @@ -60,7 +60,7 @@ data class SimpleTypes( } ) { override fun tupleConstructor(elements: Iterator<*>): SimpleTypes { - // When deserializing, the elements will be available as a list, in the order defined + // When deserializing, the elements will be available as a list, in the order defined above return SimpleTypes( elements.next() as String, elements.next() as Int, @@ -257,6 +257,7 @@ class GameLocations( val homeLocation: Coordinates, val allLocations: List, val namedLocations: Map, + val locationsInfo: Map, ) fun main() { @@ -272,6 +273,7 @@ export interface GameLocations { homeLocation: Coordinates; allLocations: Coordinates[]; namedLocations: { [key: string]: Coordinates }; + locationsInfo: Map; } export type Coordinates = [ diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..24f1058f --- /dev/null +++ b/gradle.properties @@ -0,0 +1,6 @@ +org.gradle.parallel=true +org.gradle.caching=true +org.gradle.unsafe.configuration-cache=true +org.gradle.unsafe.configuration-cache-problems=warn + +org.gradle.kotlin.dsl.precompiled.accessors.strict=true diff --git a/gradle/kotlin-js-store/yarn.lock b/gradle/kotlin-js-store/yarn.lock new file mode 100644 index 00000000..aa75c185 --- /dev/null +++ b/gradle/kotlin-js-store/yarn.lock @@ -0,0 +1,567 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@ungap/promise-all-settled@1.1.2": + version "1.1.2" + resolved "https://registry.yarnpkg.com/@ungap/promise-all-settled/-/promise-all-settled-1.1.2.tgz#aa58042711d6e3275dd37dc597e5d31e8c290a44" + integrity sha512-sL/cEvJWAnClXw0wHk85/2L0G6Sj8UB0Ctc1TEMbKSsmpRosqhwj9gWgFRZSrBr2f9tiXISwNhCPmlfqUqyb9Q== + +ansi-colors@4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" + integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + +ansi-regex@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" + integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== + +ansi-styles@^4.0.0, ansi-styles@^4.1.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" + integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== + dependencies: + color-convert "^2.0.1" + +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +balanced-match@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" + integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== + +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + +brace-expansion@^1.1.7: + version "1.1.11" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@~3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" + integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== + dependencies: + fill-range "^7.0.1" + +browser-stdout@1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" + integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== + +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + +camelcase@^6.0.0: + version "6.3.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" + integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== + +chalk@^4.1.0: + version "4.1.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01" + integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA== + dependencies: + ansi-styles "^4.1.0" + supports-color "^7.1.0" + +chokidar@3.5.2: + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +cliui@^7.0.2: + version "7.0.4" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" + integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^7.0.0" + +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + +debug@4.3.2: + version "4.3.2" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.2.tgz#f0a49c18ac8779e31d4a0c6029dfb76873c7428b" + integrity sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw== + dependencies: + ms "2.1.2" + +decamelize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" + integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== + +diff@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" + integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== + +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + +escalade@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" + integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== + +escape-string-regexp@4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" + integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== + +fill-range@^7.0.1: + version "7.0.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" + integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== + dependencies: + to-regex-range "^5.0.1" + +find-up@5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + +flat@^5.0.2: + version "5.0.2" + resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" + integrity sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ== + +format-util@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" + integrity sha512-varLbTj0e0yVyRpqQhuWV+8hlePAgaoFRhNFj50BNjEIrw1/DphHSObtqwskVCPWNgzwPoQrZAbfa/SBiicNeg== + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + +get-caller-file@^2.0.5: + version "2.0.5" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" + integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== + +glob-parent@~5.1.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== + dependencies: + is-glob "^4.0.1" + +glob@7.1.7: + version "7.1.7" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.7.tgz#3b193e9233f01d42d0b3f78294bbeeb418f94a90" + integrity sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +growl@1.10.5: + version "1.10.5" + resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" + integrity sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +he@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" + integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" + integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== + +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + +is-glob@^4.0.1, is-glob@~4.0.1: + version "4.0.3" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +is-number@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" + integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== + +is-plain-obj@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-2.1.0.tgz#45e42e37fccf1f40da8e5f76ee21515840c09287" + integrity sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA== + +is-unicode-supported@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" + integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= + +js-yaml@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== + dependencies: + argparse "^2.0.1" + +locate-path@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286" + integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw== + dependencies: + p-locate "^5.0.0" + +log-symbols@4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" + integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== + dependencies: + chalk "^4.1.0" + is-unicode-supported "^0.1.0" + +minimatch@3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== + dependencies: + brace-expansion "^1.1.7" + +minimatch@^3.0.4: + version "3.1.2" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" + integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== + dependencies: + brace-expansion "^1.1.7" + +mocha@9.1.2: + version "9.1.2" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-9.1.2.tgz#93f53175b0f0dc4014bd2d612218fccfcf3534d3" + integrity sha512-ta3LtJ+63RIBP03VBjMGtSqbe6cWXRejF9SyM9Zyli1CKZJZ+vfCTj3oW24V7wAphMJdpOFLoMI3hjJ1LWbs0w== + dependencies: + "@ungap/promise-all-settled" "1.1.2" + ansi-colors "4.1.1" + browser-stdout "1.3.1" + chokidar "3.5.2" + debug "4.3.2" + diff "5.0.0" + escape-string-regexp "4.0.0" + find-up "5.0.0" + glob "7.1.7" + growl "1.10.5" + he "1.2.0" + js-yaml "4.1.0" + log-symbols "4.1.0" + minimatch "3.0.4" + ms "2.1.3" + nanoid "3.1.25" + serialize-javascript "6.0.0" + strip-json-comments "3.1.1" + supports-color "8.1.1" + which "2.0.2" + workerpool "6.1.5" + yargs "16.2.0" + yargs-parser "20.2.4" + yargs-unparser "2.0.0" + +ms@2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" + integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== + +ms@2.1.3: + version "2.1.3" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" + integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== + +nanoid@3.1.25: + version "3.1.25" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.1.25.tgz#09ca32747c0e543f0e1814b7d3793477f9c8e152" + integrity sha512-rdwtIXaXCLFAQbnfqDRnI6jaRHp9fTcYBjtFKE8eezcZ7LuLjhUaQGNeMXf1HmRoCH32CLz6XwX0TtxEOS/A3Q== + +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + +once@^1.3.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + dependencies: + wrappy "1" + +p-limit@^3.0.2: + version "3.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b" + integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ== + dependencies: + yocto-queue "^0.1.0" + +p-locate@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834" + integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw== + dependencies: + p-limit "^3.0.2" + +path-exists@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" + integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + +picomatch@^2.0.4, picomatch@^2.2.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" + integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== + +randombytes@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== + dependencies: + safe-buffer "^5.1.0" + +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= + +safe-buffer@^5.1.0: + version "5.2.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" + integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== + +serialize-javascript@6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" + integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== + dependencies: + randombytes "^2.1.0" + +source-map-support@0.5.20: + version "0.5.20" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.20.tgz#12166089f8f5e5e8c56926b377633392dd2cb6c9" + integrity sha512-n1lZZ8Ve4ksRqizaBQgxXDgKwttHDhyfQjA6YZZn8+AroHbsIz+JjwxQDxbp+7y5OYCI8t1Yk7etjD9CRd2hIw== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-json-comments@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" + integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== + +supports-color@8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +supports-color@^7.1.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" + integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw== + dependencies: + has-flag "^4.0.0" + +to-regex-range@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" + integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== + dependencies: + is-number "^7.0.0" + +which@2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1" + integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA== + dependencies: + isexe "^2.0.0" + +workerpool@6.1.5: + version "6.1.5" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.1.5.tgz#0f7cf076b6215fd7e1da903ff6f22ddd1886b581" + integrity sha512-XdKkCK0Zqc6w3iTxLckiuJ81tiD/o5rBE/m+nXpRCB+/Sq4DqkfXZ/x0jW02DG1tGsfUGXbTJyZDP+eu67haSw== + +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + +y18n@^5.0.5: + version "5.0.8" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" + integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== + +yargs-parser@20.2.4: + version "20.2.4" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" + integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== + +yargs-parser@^20.2.2: + version "20.2.9" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" + integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== + +yargs-unparser@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" + integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== + dependencies: + camelcase "^6.0.0" + decamelize "^4.0.0" + flat "^5.0.2" + is-plain-obj "^2.1.0" + +yargs@16.2.0: + version "16.2.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" + integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.0" + y18n "^5.0.5" + yargs-parser "^20.2.2" + +yocto-queue@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" + integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q== diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..84586404 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,73 @@ +[versions] + +jvmTarget = "1.8" +kotlinTarget = "1.6" + +kotlin = "1.6.21" + +kotlinSymbolProcessing = "1.6.20-1.0.5" +kotlinCompileTesting = "1.4.8" + +kotlinx-serialization = "1.3.2" +kotlinx-knit = "0.4.0" +kotlinx-coroutines = "1.6.1" +kotlinx-kover = "0.5.0" + +okio = "3.1.0" + +kotest = "5.2.3" + +kotlinProcess = "1.3.1" + +classgraph = "4.8.143" + +gradleNodePlugin = "3.2.1" + +[libraries] + +kotlin-bom = { group = "org.jetbrains.kotlin", name = "kotlin-bom", version.ref = "kotlin" } +kotlin-gradlePlugin = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" } + +kotlinSymbolProcessing = { group = "com.google.devtools.ksp", name = "symbol-processing-api", version.ref = "kotlinSymbolProcessing" } +kotlinSymbolProcessing-gradlePlugin = { group = "com.google.devtools.ksp", name = "com.google.devtools.ksp.gradle.plugin", version.ref = "kotlinSymbolProcessing" } + +kotlinx-coroutines-bom = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-bom", version.ref = "kotlinx-coroutines" } +kotlinx-coroutines-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core" } + +kotlinx-serialization-bom = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-bom", version.ref = "kotlinx-serialization" } +kotlinx-serialization-core = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core" } +kotlinx-serialization-json = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json" } +kotlinx-serialization-cbor = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-cbor" } + +okio-bom = { group = "com.squareup.okio", name = "okio-bom", version.ref = "okio" } +okio-core = { group = "com.squareup.okio", name = "okio" } + +kotlinx-kover-gradlePlugin = { group = "org.jetbrains.kotlinx", name = "kover", version.ref = "kotlinx-kover" } + +gradleNodePlugin = { group = "com.github.node-gradle", name = "gradle-node-plugin", version.ref = "gradleNodePlugin" } + +classgraph = { group = "io.github.classgraph", name = "classgraph", version.ref = "classgraph" } + +kotlinProcess = { group = "com.github.pgreze", name = "kotlin-process", version.ref = "kotlinProcess" } + +kotlinCompileTesting = { group = "com.github.tschuchortdev", name = "kotlin-compile-testing", version.ref = "kotlinCompileTesting" } +kotlinCompileTesting-ksp = { group = "com.github.tschuchortdev", name = "kotlin-compile-testing-ksp", version.ref = "kotlinCompileTesting" } + + +kotest-bom = { group = "io.kotest", name = "kotest-bom", version.ref = "kotest" } +kotest-assertionsCore = { group = "io.kotest", name = "kotest-assertions-core" } +kotest-assertionsJson = { group = "io.kotest", name = "kotest-assertions-json" } +kotest-property = { group = "io.kotest", name = "kotest-property" } +kotest-frameworkEngine = { group = "io.kotest", name = "kotest-framework-engine" } +kotest-frameworkDatatest = { group = "io.kotest", name = "kotest-framework-datatest" } +kotest-runnerJUnit5 = { group = "io.kotest", name = "kotest-runner-junit5" } +kotest-gradlePlugin = { group = "io.kotest", name = "kotest-framework-multiplatform-plugin-gradle", version.ref = "kotest" } + + +kotlinx-knit = { group = "org.jetbrains.kotlinx", name = "kotlinx-knit", version.ref = "kotlinx-knit" } +kotlinx-knit-test = { group = "org.jetbrains.kotlinx", name = "kotlinx-knit-test", version.ref = "kotlinx-knit" } +kotlinx-knit-gradlePlugin = { group = "org.jetbrains.kotlinx", name = "kotlinx-knit", version.ref = "kotlinx-knit" } + +[bundles] + +[plugins] diff --git a/jitpack.yml b/jitpack.yml index a5396c12..8136a0c8 100644 --- a/jitpack.yml +++ b/jitpack.yml @@ -2,4 +2,4 @@ # https://jitpack.io/docs/BUILDING/#java-version jdk: - - openjdk11 + - openjdk8 diff --git a/modules/kxs-ts-gen-core/build.gradle.kts b/modules/kxs-ts-gen-core/build.gradle.kts index c1fd24e0..2f67133e 100644 --- a/modules/kxs-ts-gen-core/build.gradle.kts +++ b/modules/kxs-ts-gen-core/build.gradle.kts @@ -1,11 +1,7 @@ -import buildsrc.config.publicationsFromMainHost - - plugins { buildsrc.convention.`kotlin-multiplatform` buildsrc.convention.`maven-publish` kotlin("plugin.serialization") -// id("org.jetbrains.reflekt") id("io.kotest.multiplatform") } @@ -14,14 +10,15 @@ val kotestVersion = "5.2.3" kotlin { -// js(IR) { -// binaries.executable() + js(IR) { + binaries.executable() // browser { // commonWebpackConfig { // cssSupport.enabled = true // } // } -// } + nodejs() + } jvm { compilations.all { @@ -49,7 +46,7 @@ kotlin { sourceSets { all { - languageSettings.apply { + languageSettings { optIn("kotlin.RequiresOptIn") optIn("kotlin.ExperimentalStdlibApi") optIn("kotlin.time.ExperimentalTime") @@ -59,41 +56,56 @@ kotlin { val commonMain by getting { dependencies { - implementation( - project.dependencies.platform( - "org.jetbrains.kotlinx:kotlinx-serialization-bom:${kotlinxSerializationVersion}" - ) - ) - implementation("org.jetbrains.kotlinx:kotlinx-serialization-core") - implementation("org.jetbrains.kotlinx:kotlinx-serialization-json") - implementation(kotlin("reflect")) + implementation(project.dependencies.platform(projects.modules.versionsPlatform)) + implementation(libs.kotlinx.serialization.core) + implementation(libs.kotlinx.serialization.json) } } val commonTest by getting { dependencies { implementation(kotlin("test")) - implementation("io.kotest:kotest-assertions-core:$kotestVersion") - implementation("io.kotest:kotest-assertions-json:$kotestVersion") - implementation("io.kotest:kotest-property:$kotestVersion") - implementation("io.kotest:kotest-framework-engine:$kotestVersion") - implementation("io.kotest:kotest-framework-datatest:$kotestVersion") + implementation(libs.kotest.assertionsCore) + implementation(libs.kotest.assertionsJson) + implementation(libs.kotest.property) + implementation(libs.kotest.frameworkEngine) + implementation(libs.kotest.frameworkDatatest) + + implementation(libs.kotlinx.serialization.cbor) + implementation(libs.okio.core) } } // val nativeMain by getting // val nativeTest by getting -// val jsMain by getting -// val jsTest by getting + val jsMain by getting + val jsTest by getting val jvmMain by getting { dependencies { + implementation(project.dependencies.platform(projects.modules.versionsPlatform)) implementation(kotlin("reflect")) } } val jvmTest by getting { dependencies { - implementation("io.kotest:kotest-runner-junit5-jvm:$kotestVersion") + implementation(libs.kotest.frameworkEngine) + implementation(libs.kotest.runnerJUnit5) } } } } + +// +//val javadocJar by tasks.creating(Jar::class) { +// group = JavaBasePlugin.DOCUMENTATION_GROUP +// description = "Assembles java doc to jar" +// archiveClassifier.set("javadoc") +// from(tasks.javadoc) +//} +// + +//publishing { +// publications.withType().configureEach { +//// artifact(javadocJar) +// } +//} 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 cb284a2b..9ed94cdd 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,6 +1,5 @@ package dev.adamko.kxstsgen -import dev.adamko.kxstsgen.core.KxsTsSourceCodeGenerator import dev.adamko.kxstsgen.core.SerializerDescriptorsExtractor import dev.adamko.kxstsgen.core.TsDeclaration import dev.adamko.kxstsgen.core.TsElement @@ -9,6 +8,7 @@ import dev.adamko.kxstsgen.core.TsElementId import dev.adamko.kxstsgen.core.TsElementIdConverter import dev.adamko.kxstsgen.core.TsLiteral import dev.adamko.kxstsgen.core.TsMapTypeConverter +import dev.adamko.kxstsgen.core.TsSourceCodeGenerator import dev.adamko.kxstsgen.core.TsTypeRef import dev.adamko.kxstsgen.core.TsTypeRefConverter import kotlinx.serialization.KSerializer @@ -29,7 +29,7 @@ import kotlinx.serialization.descriptors.nullable open class KxsTsGenerator( open val config: KxsTsConfig = KxsTsConfig(), - open val sourceCodeGenerator: KxsTsSourceCodeGenerator = KxsTsSourceCodeGenerator.Default(config), + open val sourceCodeGenerator: TsSourceCodeGenerator = TsSourceCodeGenerator.Default(config), ) { @@ -44,6 +44,20 @@ open class KxsTsGenerator( }?.value } + open fun findMapTypeOverride(descriptor: SerialDescriptor): TsLiteral.TsMap.Type? { + return when (findOverride(descriptor)) { + null -> null + + is TsDeclaration.TsEnum -> TsLiteral.TsMap.Type.MAPPED_OBJECT + + is TsLiteral.Custom, + TsLiteral.Primitive.TsNumber, + TsLiteral.Primitive.TsString -> TsLiteral.TsMap.Type.INDEX_SIGNATURE + + else -> TsLiteral.TsMap.Type.MAP + } + } + open val descriptorsExtractor = object : SerializerDescriptorsExtractor { val extractor: SerializerDescriptorsExtractor = SerializerDescriptorsExtractor.Default @@ -80,7 +94,7 @@ open class KxsTsGenerator( valDescriptor: SerialDescriptor, ): TsLiteral.TsMap.Type = cache.getOrPut(keyDescriptor to valDescriptor) { - converter(keyDescriptor, valDescriptor) + findMapTypeOverride(keyDescriptor) ?: converter(keyDescriptor, valDescriptor) } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt index ee978554..9d9c4b7b 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsElementConverter.kt @@ -70,7 +70,7 @@ fun interface TsElementConverter { /** * Handle sealed-polymorphic descriptors. * - * Generate + * Generate... * * 1. a namespace that contains * a. a 'type' enum, for each subclass @@ -89,39 +89,50 @@ fun interface TsElementConverter { .indexOfFirst { it.kind == PrimitiveKind.STRING } val discriminatorName = descriptor.elementNames.elementAtOrNull(discriminatorIndex) - // subclasses details - val subclassInterfaces = descriptor - .elementDescriptors - .firstOrNull { it.kind == SerialKind.CONTEXTUAL } - ?.elementDescriptors - ?.flatMap { this(it) } - ?.filterIsInstance() - ?.map { it.copy(id = TsElementId("${descriptor.serialName}.${it.id.name}")) } - ?.toSet() - ?: emptySet() - - val subInterfaceRefs: Map = - subclassInterfaces.associateBy { subclass -> - val subclassId = TsElementId(namespaceId.toString() + "." + subclass.id.name) - TsTypeRef.Declaration(subclassId, namespaceRef, false) - } + val subclassesDescriptorToInterface: Map = + descriptor.elementDescriptors + .firstOrNull { it.kind == SerialKind.CONTEXTUAL } + ?.elementDescriptors + ?.associateWith { this(it) } + ?.mapValues { (_, v) -> + v.filterIsInstance() + .map { + it.copy(id = TsElementId("${descriptor.serialName}.${it.id.name}")) + }.single() + } ?: emptyMap() // verify a discriminated interface can be created - if (subInterfaceRefs.isEmpty() || discriminatorName.isNullOrBlank()) { + if (subclassesDescriptorToInterface.isEmpty() || discriminatorName.isNullOrBlank()) { + // fallback: a type alias to 'any', same as for open-polymorphism return setOf(createTypeAliasAny(descriptor)) } else { // discriminator enum - val discriminatorEnum = TsDeclaration.TsEnum( - TsElementId("${namespaceId.namespace}.${discriminatorName.replaceFirstChar { it.uppercaseChar() }}"), - subInterfaceRefs.keys.map { it.id.name }.toSet(), - ) + val discriminatorEnum = run { + val id = TsElementId( + "${namespaceId.namespace}.${discriminatorName.replaceFirstChar { it.uppercaseChar() }}" + ) + + val members = subclassesDescriptorToInterface.entries.map { (descriptor, tsInterface) -> + val enumMemberName = tsInterface.id.name + val enumMemberValue = TsTypeRef.Literal( + TsLiteral.Custom(descriptor.serialName), + false + ) + TsProperty(enumMemberName, enumMemberValue, false) + }.toSet() + + TsDeclaration.TsEnum(id, members) + } + val discriminatorEnumRef = TsTypeRef.Declaration(discriminatorEnum.id, namespaceRef, false) // add discriminator property to subclasses - val subInterfacesWithTypeProp = subInterfaceRefs.map { (subInterfaceRef, subclass) -> + val subInterfacesWithTypeProp = subclassesDescriptorToInterface.map { (_, subclass) -> + + val subclassId = TsElementId(namespaceId.toString() + "." + subclass.id.name) val literalTypeRef = TsTypeRef.Declaration( - TsElementId("${discriminatorEnum.id.name}.${subInterfaceRef.id.name}"), + TsElementId("${discriminatorEnum.id.name}.${subclassId.name}"), discriminatorEnumRef, false, ) @@ -132,10 +143,18 @@ fun interface TsElementConverter { } // create type union and namespace - val subInterfaceTypeUnion = TsDeclaration.TsTypeUnion( - namespaceId, - subInterfaceRefs.keys - ) + val subInterfaceTypeUnion = run { + val subInterfaceRefs = + subclassesDescriptorToInterface.entries.map { (_, subclass) -> + val subclassId = TsElementId(namespaceId.toString() + "." + subclass.id.name) + TsTypeRef.Declaration(subclassId, namespaceRef, false) + }.toSet() + + TsDeclaration.TsTypeUnion( + namespaceId, + subInterfaceRefs + ) + } val namespace = TsDeclaration.TsNamespace( namespaceId, @@ -198,7 +217,8 @@ fun interface TsElementConverter { enumDescriptor: SerialDescriptor, ): TsDeclaration.TsEnum { val resultId = elementIdConverter(enumDescriptor) - return TsDeclaration.TsEnum(resultId, enumDescriptor.elementNames.toSet()) + val members = convertProperties(enumDescriptor) + return TsDeclaration.TsEnum(resultId, members) } 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 cbb99f8d..2fa71280 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 @@ -35,6 +35,8 @@ fun interface TsMapTypeConverter { return when (kind) { SerialKind.ENUM -> TsLiteral.TsMap.Type.MAPPED_OBJECT + PrimitiveKind.INT, + PrimitiveKind.LONG, PrimitiveKind.STRING -> TsLiteral.TsMap.Type.INDEX_SIGNATURE SerialKind.CONTEXTUAL, @@ -42,8 +44,6 @@ fun interface TsMapTypeConverter { PrimitiveKind.BYTE, PrimitiveKind.CHAR, PrimitiveKind.SHORT, - PrimitiveKind.INT, - PrimitiveKind.LONG, PrimitiveKind.FLOAT, PrimitiveKind.DOUBLE, StructureKind.CLASS, diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsSourceCodeGenerator.kt similarity index 97% rename from modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt rename to modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsSourceCodeGenerator.kt index cf021f4f..6fb157af 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/KxsTsSourceCodeGenerator.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/TsSourceCodeGenerator.kt @@ -6,7 +6,7 @@ import dev.adamko.kxstsgen.KxsTsConfig /** * Writes [TsElement]s as TypeScript source code. */ -abstract class KxsTsSourceCodeGenerator( +abstract class TsSourceCodeGenerator( val config: KxsTsConfig = KxsTsConfig(), ) { @@ -37,7 +37,7 @@ abstract class KxsTsSourceCodeGenerator( open class Default( config: KxsTsConfig, - ) : KxsTsSourceCodeGenerator(config) { + ) : TsSourceCodeGenerator(config) { override fun groupElementsBy(element: TsElement): String { @@ -75,8 +75,9 @@ abstract class KxsTsSourceCodeGenerator( val enumMembers = enum.members.joinToString("\n") { member -> """ - |${config.indent}$member = "$member", + |${member.name} = "${generateTypeReference(member.typeRef)}", """.trimMargin() + .prependIndent(config.indent) } return """ diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/serializerExtractors.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/serializerExtractors.kt index 3c7bf66d..d260b2ce 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/serializerExtractors.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/serializerExtractors.kt @@ -1,56 +1,56 @@ -@file:OptIn(InternalSerializationApi::class) +//@file:OptIn(InternalSerializationApi::class) package dev.adamko.kxstsgen.core.experiments -import dev.adamko.kxstsgen.KxsTsConfig -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<*>, - kxsTsConfig: KxsTsConfig, -): KSerializer<*>? { - return try { - val decoder = ContextualSerializerCaptorDecoder(kxsTsConfig.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 +//import dev.adamko.kxstsgen.KxsTsConfig +//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<*>, +// kxsTsConfig: KxsTsConfig, +//): KSerializer<*>? { +// return try { +// val decoder = ContextualSerializerCaptorDecoder(kxsTsConfig.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/core/experiments/tuple.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/tuple.kt index ee8bc98f..faac62c2 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/tuple.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/experiments/tuple.kt @@ -17,25 +17,25 @@ import kotlinx.serialization.encoding.encodeCollection import kotlinx.serialization.serializer -open class TupleElement( - open val name: String, - open val index: Int, - open val elementSerializer: KSerializer, - open val elementAccessor: T.() -> E, +data class TupleElement( + val name: String, + val index: Int, + val elementSerializer: KSerializer, + val elementAccessor: T.() -> E, ) { - val descriptor: SerialDescriptor + internal val descriptor: SerialDescriptor get() = elementSerializer.descriptor - open fun encodeElement(encoder: CompositeEncoder, value: T) { + fun encodeElement(encoder: CompositeEncoder, value: T) { encoder.encodeSerializableElement( descriptor, index, elementSerializer, - value.elementAccessor() + value.elementAccessor(), ) } - open fun decodeElement(decoder: CompositeDecoder): E { + fun decodeElement(decoder: CompositeDecoder): E { return decoder.decodeSerializableElement( descriptor, index, @@ -74,7 +74,9 @@ class TupleElementsBuilder { private val _elements: ArrayDeque> = ArrayDeque() val elements: List> get() = _elements.toList() - val elementsSize by _elements::size + + @PublishedApi + internal val elementsSize by _elements::size inline fun element( property: KProperty1 @@ -129,13 +131,32 @@ abstract class TupleSerializer( } } - override fun deserialize(decoder: Decoder): T { - val elements = decoder.decodeStructure(descriptor) { - generateSequence { - val index = decodeElementIndex(descriptor) - indexedTupleElements[index]?.decodeElement(this) - }.iterator() + override fun deserialize(decoder: Decoder): T = decoder.decodeStructure(descriptor) { + + // the collection size isn't required here, but we need to decode it to get it out of the way + decodeCollectionSize(descriptor) + + val elements = if (decodeSequentially()) { + tupleElements.asSequence().map { + it.decodeElement(this@decodeStructure) + } + } else { + generateSequence { decodeElementIndex(descriptor) } + .takeWhile { index -> + when (index) { + CompositeDecoder.UNKNOWN_NAME -> error("unknown name at index:$index") + CompositeDecoder.DECODE_DONE -> false + !in indexedTupleElements.keys.indices -> error("unexpected index:$index") + else -> true + } + }.map { index -> + val tupleElement = indexedTupleElements.getOrElse(index) { + error("no tuple element at index:$index") + } + tupleElement.decodeElement(this@decodeStructure) + } } - return tupleConstructor(elements) + // elements sequence *must* be collected inside 'decodeStructure' + tupleConstructor(elements.iterator()) } } diff --git a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt index 5f68bed5..5bc8267f 100644 --- a/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt +++ b/modules/kxs-ts-gen-core/src/commonMain/kotlin/dev/adamko/kxstsgen/core/tsElements.kt @@ -67,7 +67,7 @@ sealed interface TsDeclaration : TsElement { data class TsEnum( override val id: TsElementId, - val members: Set, + val members: Set, ) : TsDeclaration @@ -120,6 +120,7 @@ sealed interface TsLiteral : TsElement { } } + /** https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#literal-types */ @JvmInline value class Custom(val value: String) : TsLiteral } @@ -157,8 +158,10 @@ sealed interface TsTypeRef { /** - * A property within an [interface][TsDeclaration.TsInterface] - * or [tuple][TsDeclaration.TsTuple]. + * A property within an + * [interface][TsDeclaration.TsInterface], + * [tuple][TsDeclaration.TsTuple], + * or [enum][TsDeclaration.TsEnum]. */ data class TsProperty( val name: String, diff --git a/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/experiments/TupleTest.kt b/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/experiments/TupleTest.kt new file mode 100644 index 00000000..0637ce39 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/experiments/TupleTest.kt @@ -0,0 +1,142 @@ +package dev.adamko.kxstsgen.core.experiments + +import dev.adamko.kxstsgen.core.test.kxsBinary +import io.kotest.assertions.withClue +import io.kotest.core.spec.style.FunSpec +import io.kotest.matchers.ints.shouldBeExactly +import io.kotest.matchers.longs.shouldBeExactly +import io.kotest.matchers.shouldBe +import io.kotest.matchers.types.shouldNotBeSameInstanceAs +import io.kotest.property.Arb +import io.kotest.property.arbitrary.Codepoint +import io.kotest.property.arbitrary.az +import io.kotest.property.arbitrary.boolean +import io.kotest.property.arbitrary.int +import io.kotest.property.arbitrary.long +import io.kotest.property.arbitrary.string +import io.kotest.property.checkAll +import kotlinx.serialization.Serializable +import kotlinx.serialization.cbor.Cbor +import kotlinx.serialization.decodeFromByteArray +import kotlinx.serialization.decodeFromString +import kotlinx.serialization.encodeToByteArray +import kotlinx.serialization.encodeToString +import kotlinx.serialization.json.Json + +class TupleTest : FunSpec({ + + context("Coordinates") { + test("json round trip") { + checkAll( + Arb.int(), + Arb.long(), + Arb.string(5, Codepoint.az()), + Arb.boolean(), + ) { x, y, d, b -> + val initial = Coordinates(x, y, d, b) + + val encoded: String = Json.encodeToString(initial) + + val decoded: Coordinates = Json.decodeFromString(encoded) + + withClue( + """ + initial: $initial + decoded: $decoded + """.trimIndent() + ) { + initial.x shouldBeExactly decoded.x + initial.y shouldBeExactly decoded.y + initial.data shouldBe d + initial.active shouldBe b + initial shouldNotBeSameInstanceAs decoded + } + } + } + + test("Cbor round trip") { + checkAll( + Arb.int(), + Arb.long(), + Arb.string(5, Codepoint.az()), + Arb.boolean(), + ) { x, y, d, b -> + val initial = Coordinates(x, y, d, b) + + val encoded: ByteArray = Cbor.encodeToByteArray(initial) +// println("encoded: ${encoded.joinToString { it.toString().padStart(4, ' ') }}") + + val decoded: Coordinates = Cbor.decodeFromByteArray(encoded) + + withClue( + """ + initial: $initial + decoded: $decoded + """.trimIndent() + ) { + initial.x shouldBeExactly decoded.x + initial.y shouldBeExactly decoded.y + initial.data shouldBe d + initial.active shouldBe b + initial shouldNotBeSameInstanceAs decoded + } + } + } + + test("kxsBinary round trip") { + checkAll( + Arb.int(-50..50), + Arb.long(-50L..50L), + Arb.string(5, Codepoint.az()), + Arb.boolean(), + ) { x, y, d, b -> + val initial = Coordinates(x, y, d, b) + + val encoded: ByteArray = kxsBinary.encodeToByteArray(initial) + + val decoded: Coordinates = kxsBinary.decodeFromByteArray(encoded) + + withClue( + """ + initial: $initial + decoded: $decoded + """.trimIndent() + ) { + initial.x shouldBeExactly decoded.x + initial.y shouldBeExactly decoded.y + initial.data shouldBe d + initial.active shouldBe b + initial shouldNotBeSameInstanceAs decoded + } + } + } + } +}) { + + + @Serializable(with = Coordinates.Serializer::class) + data class Coordinates( + val x: Int, + val y: Long, + val data: String, + val active: Boolean + ) { + object Serializer : TupleSerializer( + "Coordinates", + { + element(Coordinates::x) + element(Coordinates::y) + element(Coordinates::data) + element(Coordinates::active) + } + ) { + override fun tupleConstructor(elements: Iterator): Coordinates { + val x = requireNotNull(elements.next() as? Int) + val y = requireNotNull(elements.next() as? Long) + val data = requireNotNull(elements.next() as? String) + val active = requireNotNull(elements.next() as? Boolean) + return Coordinates(x, y, data, active) + } + } + } +} diff --git a/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/test/kxsBinary.kt b/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/test/kxsBinary.kt new file mode 100644 index 00000000..582fcfc8 --- /dev/null +++ b/modules/kxs-ts-gen-core/src/commonTest/kotlin/dev/adamko/kxstsgen/core/test/kxsBinary.kt @@ -0,0 +1,160 @@ +package dev.adamko.kxstsgen.core.test + +import kotlinx.serialization.BinaryFormat +import kotlinx.serialization.DeserializationStrategy +import kotlinx.serialization.SerializationStrategy +import kotlinx.serialization.builtins.ByteArraySerializer +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.AbstractDecoder +import kotlinx.serialization.encoding.AbstractEncoder +import kotlinx.serialization.encoding.CompositeDecoder +import kotlinx.serialization.encoding.CompositeEncoder +import kotlinx.serialization.modules.EmptySerializersModule +import kotlinx.serialization.modules.SerializersModule +import okio.Buffer +import okio.BufferedSink +import okio.BufferedSource + + +val kxsBinary: KxsBinary = KxsBinary() + +/** [`https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/formats.md#efficient-binary-format`](https://github.com/Kotlin/kotlinx.serialization/blob/master/docs/formats.md#efficient-binary-format) */ +class KxsBinary( + override val serializersModule: SerializersModule = EmptySerializersModule +) : BinaryFormat { + + override fun encodeToByteArray(serializer: SerializationStrategy, value: T): ByteArray { + val output = Buffer() + val encoder = KxsDataOutputEncoder(output, serializersModule) + encoder.encodeSerializableValue(serializer, value) + return output.readByteArray() + } + + override fun decodeFromByteArray( + deserializer: DeserializationStrategy, + bytes: ByteArray + ): T { + val input = Buffer().write(bytes) + val decoder = KxsDataInputDecoder(input, serializersModule) + return decoder.decodeSerializableValue(deserializer) + } +} + +private val byteArraySerializer = ByteArraySerializer() + +class KxsDataOutputEncoder( + private val output: BufferedSink, + override val serializersModule: SerializersModule = EmptySerializersModule, +) : AbstractEncoder() { + + private operator fun BufferedSink.invoke(action: BufferedSink.() -> Unit): Unit = action() + + override fun encodeBoolean(value: Boolean) = output { writeByte(if (value) 1 else 0) } + override fun encodeByte(value: Byte) = output { writeByte(value.toInt()) } + override fun encodeChar(value: Char) = output { writeUtf8CodePoint(value.code) } + override fun encodeDouble(value: Double) = output { writeLong(value.toRawBits()) } + override fun encodeEnum(enumDescriptor: SerialDescriptor, index: Int) = output { writeInt(index) } + override fun encodeFloat(value: Float) = output { writeInt(value.toRawBits()) } + override fun encodeInt(value: Int) = output { writeInt(value) } + override fun encodeLong(value: Long) = output { writeLong(value) } + override fun encodeShort(value: Short) = output { writeShort(value.toInt()) } + override fun encodeString(value: String) = output { + encodeCompactSize(value.length) + writeUtf8(value) + } + + override fun beginCollection( + descriptor: SerialDescriptor, + collectionSize: Int + ): CompositeEncoder { + encodeCompactSize(collectionSize) + return this + } + + override fun encodeNull() = encodeBoolean(false) + override fun encodeNotNullMark() = encodeBoolean(true) + + override fun encodeSerializableValue(serializer: SerializationStrategy, value: T) { + when (serializer.descriptor) { + byteArraySerializer.descriptor -> encodeByteArray(value as ByteArray) + else -> super.encodeSerializableValue(serializer, value) + } + } + + private fun encodeByteArray(bytes: ByteArray) { + encodeCompactSize(bytes.size) + output.write(bytes) + } + + private fun encodeCompactSize(value: Int) { + if (value < 0xff) { + output.writeByte(value) + } else { + output.writeByte(0xff) + output.writeInt(value) + } + } +} + + +class KxsDataInputDecoder( + private val input: BufferedSource, + override val serializersModule: SerializersModule = EmptySerializersModule, + private var elementsCount: Int = 0, +) : AbstractDecoder() { + + private var elementIndex = 0 + + override fun decodeBoolean(): Boolean = input.readByte().toInt() != 0 + override fun decodeByte(): Byte = input.readByte() + override fun decodeChar(): Char = input.readUtf8CodePoint().toChar() + override fun decodeDouble(): Double = Double.fromBits(input.readLong()) + override fun decodeEnum(enumDescriptor: SerialDescriptor): Int = input.readInt() + override fun decodeFloat(): Float = Float.fromBits(input.readInt()) + override fun decodeInt(): Int = input.readInt() + override fun decodeLong(): Long = input.readLong() + override fun decodeShort(): Short = input.readShort() + override fun decodeString(): String { + val size = decodeCompactSize() + return input.readUtf8(size.toLong()) + } + + override fun decodeElementIndex(descriptor: SerialDescriptor): Int { + if (elementIndex == elementsCount) return CompositeDecoder.DECODE_DONE + return elementIndex++ + } + + override fun beginStructure(descriptor: SerialDescriptor): CompositeDecoder = + KxsDataInputDecoder(input, serializersModule, descriptor.elementsCount) + + override fun decodeSequentially(): Boolean = true + + override fun decodeCollectionSize(descriptor: SerialDescriptor): Int = + decodeCompactSize().also { elementsCount = it } + + override fun decodeNotNullMark(): Boolean = decodeBoolean() + + override fun decodeSerializableValue( + deserializer: DeserializationStrategy, + previousValue: T? + ): T { + @Suppress("UNCHECKED_CAST") + return when (deserializer.descriptor) { + byteArraySerializer.descriptor -> decodeByteArray() as T + else -> super.decodeSerializableValue(deserializer, previousValue) + } + } + + private fun decodeByteArray(): ByteArray { + val bytes = ByteArray(decodeCompactSize()) + input.readFully(bytes) + return bytes + } + + private fun decodeCompactSize(): Int { + val byte = input.readByte().toInt() and 0xff + if (byte < 0xff) return byte + return input.readInt() + } + +} diff --git a/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/core/experiments/serializerExtractorsJvm.kt b/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/core/experiments/serializerExtractorsJvm.kt index 579e47b6..49042015 100644 --- a/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/core/experiments/serializerExtractorsJvm.kt +++ b/modules/kxs-ts-gen-core/src/jvmMain/kotlin/dev/adamko/kxstsgen/core/experiments/serializerExtractorsJvm.kt @@ -1,54 +1,54 @@ -@file:OptIn(InternalSerializationApi::class) // TODO make GitHub issue +//@file:OptIn(InternalSerializationApi::class) // TODO make GitHub issue package dev.adamko.kxstsgen.core.experiments -import kotlin.reflect.KClass -import kotlin.reflect.* -import kotlin.reflect.full.declaredMemberFunctions -import kotlin.reflect.full.declaredMemberProperties -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: KFunction1> = - (ContextualSerializer::class - .declaredMemberFunctions - .firstOrNull { it.name == "serializer" } - ?.apply { isAccessible = true } - as KFunction1> - ) - ?: error("Can't access ContextualSerializer.serializer()") -// as (SerializersModule) -> KSerializer<*> +//import kotlin.reflect.KClass +//import kotlin.reflect.* +//import kotlin.reflect.full.declaredMemberFunctions +//import kotlin.reflect.full.declaredMemberProperties +//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: KFunction1> = +// (ContextualSerializer::class +// .declaredMemberFunctions +// .firstOrNull { it.name == "serializer" } +// ?.apply { isAccessible = true } +// as KFunction1> +// ) +// ?: error("Can't access ContextualSerializer.serializer()") +//// as (SerializersModule) -> KSerializer<*> diff --git a/modules/kxs-ts-gen-processor/build.gradle.kts b/modules/kxs-ts-gen-processor/build.gradle.kts index 81c344f3..22e76ae4 100644 --- a/modules/kxs-ts-gen-processor/build.gradle.kts +++ b/modules/kxs-ts-gen-processor/build.gradle.kts @@ -2,28 +2,23 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { buildsrc.convention.`kotlin-jvm` -// kotlin("plugin.serialization") - -// 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 dependencies { - implementation("com.google.devtools.ksp:symbol-processing-api:$kspVersion") + implementation(platform(projects.modules.versionsPlatform)) + + implementation(libs.kotlinSymbolProcessing) - testImplementation("com.github.tschuchortdev:kotlin-compile-testing:$kotlinCompileTestingVersion") - testImplementation("com.github.tschuchortdev:kotlin-compile-testing-ksp:$kotlinCompileTestingVersion") + testImplementation(libs.kotlinCompileTesting) + testImplementation(libs.kotlinCompileTesting.ksp) 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") + implementation(libs.kotlinx.serialization.core) + implementation(libs.kotlinx.serialization.json) testImplementation(kotlin("test")) } diff --git a/modules/versions-platform/build.gradle.kts b/modules/versions-platform/build.gradle.kts new file mode 100644 index 00000000..fac39624 --- /dev/null +++ b/modules/versions-platform/build.gradle.kts @@ -0,0 +1,27 @@ +plugins { + buildsrc.convention.subproject + buildsrc.convention.`maven-publish` + + `java-platform` +} + +javaPlatform { + allowDependencies() +} + +dependencies { + + api(platform(libs.kotlin.bom)) + api(platform(libs.kotlinx.serialization.bom)) + api(platform(libs.kotlinx.coroutines.bom)) + api(platform(libs.okio.bom)) + + api(platform(libs.kotest.bom)) + + constraints { + api(libs.classgraph) + api(libs.kotlinSymbolProcessing) + api(libs.kotlinCompileTesting) + api(libs.kotlinCompileTesting.ksp) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index 796f828d..ed425227 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -4,7 +4,9 @@ apply(from = "./buildSrc/repositories.settings.gradle.kts") include( ":modules:kxs-ts-gen-core", + ":modules:kxs-ts-gen-gradle-plugin", ":modules:kxs-ts-gen-processor", + ":modules:versions-platform", ":docs:code", ) From 04c1cf025c168364217202a88e6a4d41644ea677 Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Wed, 1 Jun 2022 21:05:29 +0200 Subject: [PATCH 8/9] bump some versions (#34) --- gradle/libs.versions.toml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 84586404..493a8962 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,23 +5,23 @@ kotlinTarget = "1.6" kotlin = "1.6.21" -kotlinSymbolProcessing = "1.6.20-1.0.5" +kotlinSymbolProcessing = "1.6.21-1.0.5" kotlinCompileTesting = "1.4.8" -kotlinx-serialization = "1.3.2" +kotlinx-serialization = "1.3.3" kotlinx-knit = "0.4.0" kotlinx-coroutines = "1.6.1" kotlinx-kover = "0.5.0" okio = "3.1.0" -kotest = "5.2.3" +kotest = "5.3.0" kotlinProcess = "1.3.1" classgraph = "4.8.143" -gradleNodePlugin = "3.2.1" +gradleNodePlugin = "3.3.0" [libraries] From 05ecef3bc747b04eaa907b8d61275d42cd5abd84 Mon Sep 17 00:00:00 2001 From: aSemy <897017+aSemy@users.noreply.github.com> Date: Thu, 2 Jun 2022 12:51:56 +0200 Subject: [PATCH 9/9] Publish to Maven Central (#35) * setup maven publish convention, fix project group kxtsgen -> kxstsgen, rename kotlin-mpp convention plugin to avoid clash with the actual KotlinMultiplatformPlugin * tidy-up Maven publishing config --- build.gradle.kts | 2 +- .../src/main/kotlin/buildsrc/config/gradle.kt | 6 - ...tform.gradle.kts => kotlin-mpp.gradle.kts} | 0 .../convention/maven-publish.gradle.kts | 138 ++++++++++++------ modules/kxs-ts-gen-core/build.gradle.kts | 5 +- 5 files changed, 97 insertions(+), 54 deletions(-) rename buildSrc/src/main/kotlin/buildsrc/convention/{kotlin-multiplatform.gradle.kts => kotlin-mpp.gradle.kts} (100%) diff --git a/build.gradle.kts b/build.gradle.kts index 8f3ce8ab..23a84af9 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -8,7 +8,7 @@ plugins { } -project.group = "dev.adamko.kxtsgen" +project.group = "dev.adamko.kxstsgen" project.version = "0.0.0-SNAPSHOT" gitVersioning.apply { refs { diff --git a/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt b/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt index d897a509..e13da441 100644 --- a/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt +++ b/buildSrc/src/main/kotlin/buildsrc/config/gradle.kt @@ -1,13 +1,7 @@ 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 */ diff --git a/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts b/buildSrc/src/main/kotlin/buildsrc/convention/kotlin-mpp.gradle.kts similarity index 100% rename from buildSrc/src/main/kotlin/buildsrc/convention/kotlin-multiplatform.gradle.kts rename to buildSrc/src/main/kotlin/buildsrc/convention/kotlin-mpp.gradle.kts 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 b2286ad0..a1e51884 100644 --- a/buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts +++ b/buildSrc/src/main/kotlin/buildsrc/convention/maven-publish.gradle.kts @@ -2,41 +2,38 @@ package buildsrc.convention import buildsrc.config.publishing import buildsrc.config.signing -import org.gradle.api.credentials.PasswordCredentials -import org.gradle.internal.credentials.DefaultPasswordCredentials plugins { - id("buildsrc.convention.subproject") `maven-publish` signing } - -//val sonatypeRepositoryCredentials: Provider = -// providers.credentials(PasswordCredentials::class, "sonatypeRepositoryCredentials") - -val sonatypeRepositoryUsername: String? by project.extra -val sonatypeRepositoryPassword: String? by project.extra -val sonatypeRepositoryCredentials: Provider = providers.provider { - if (sonatypeRepositoryUsername.isNullOrBlank() || sonatypeRepositoryPassword.isNullOrBlank()) { - null - } else { - DefaultPasswordCredentials(sonatypeRepositoryUsername, sonatypeRepositoryPassword) +val sonatypeRepositoryCredentials: Provider> = providers + .credentials(PasswordCredentials::class, "sonatypeRepository") + .map { credentials -> + Action { + username = credentials.username + password = credentials.password + } } -} - - -val sonatypeRepositoryId: String by project.extra val sonatypeRepositoryReleaseUrl: Provider = provider { if (version.toString().endsWith("SNAPSHOT")) { - "https://oss.sonatype.org/content/repositories/snapshots/" + "https://s01.oss.sonatype.org/content/repositories/snapshots/" } else { - "https://oss.sonatype.org/service/local/staging/deployByRepositoryId/$sonatypeRepositoryId/" + "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" } } +val signingKeyId: Provider = + providers.gradleProperty("signing.keyId") +val signingPassword: Provider = + providers.gradleProperty("signing.password") +val signingSecretKeyRingFile: Provider = + providers.gradleProperty("signing.secretKeyRingFile") + + tasks.matching { it.name.startsWith(PublishingPlugin.PUBLISH_LIFECYCLE_TASK_NAME) && it.group == PublishingPlugin.PUBLISH_TASK_GROUP @@ -47,42 +44,97 @@ tasks.matching { } - publishing { repositories { - if (sonatypeRepositoryCredentials.isPresent) { - maven(sonatypeRepositoryReleaseUrl) { - name = "oss" - credentials { - username = sonatypeRepositoryCredentials.get().username - password = sonatypeRepositoryCredentials.get().password - } - } + maven(sonatypeRepositoryReleaseUrl) { + name = "sonatype" + credentials(sonatypeRepositoryCredentials.get()) } } + publications.withType().configureEach { + createKxTsGenPom() + } } + signing { - val signingKeyId: String? by project - val signingKey: String? by project - val signingPassword: String? by project - useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) - setRequired(false) + +// if ( +// signingKeyId.isPresent() && +// signingPassword.isPresent() && +// signingSecretKeyRingFile.isPresent() +// ) { +// useInMemoryPgpKeys(signingKeyId, signingKey, signingPassword) +// } else { +// useGpgCmd() +// } + + useGpgCmd() + + // sign all publications + sign(publishing.publications) } -plugins.withType { - if (!plugins.hasPlugin(KotlinMultiplatformPlugin::class)) { - val publication = publishing.publications.create("mavenJava") { - from(components["java"]) +plugins.configureEach { + when (this) { + // not necessary? It looks like the plugin creates publications correctly? +// is KotlinMultiplatformPlugin -> { +// +// // Stub javadoc.jar artifact (required by Maven Central?) +// val javadocJar by tasks.registering(Jar::class) { +// archiveClassifier.set("javadoc") +// } +// +// publishing.publications.create("mavenKotlinMpp") { +// from(components["kotlin"]) +// artifact(javadocJar) +// artifact(tasks["sourcesJar"]) +// } +// } + + // JavaPlugin clashes with KotlinMultiplatformPlugin? + // causes error + // Artifact kxs-ts-gen-core-jvm-maven-publish-SNAPSHOT.jar wasn't produced by this build +// is JavaPlugin -> afterEvaluate { +// if (!plugins.hasPlugin(KotlinMultiplatformPlugin::class)) { +// publishing.publications.create("mavenJava") { +// from(components["java"]) +// artifact(tasks["sourcesJar"]) +// } +// } +// } + + is JavaPlatformPlugin -> { + publishing.publications.create("mavenJavaPlatform") { + from(components["javaPlatform"]) + } } - signing { sign(publication) } } } -plugins.withType { - val publication = publishing.publications.create("mavenJavaPlatform") { - from(components["javaPlatform"]) + +fun MavenPublication.createKxTsGenPom(): Unit = pom { + name.set("Kotlinx Serialization Typescript Generator") + description.set("KxTsGen creates TypeScript interfaces from Kotlinx Serialization @Serializable classes") + url.set("https://github.com/adamko-dev/kotlinx-serialization-typescript-generator") + + licenses { + license { + name.set("The Apache License, Version 2.0") + url.set("http://www.apache.org/licenses/LICENSE-2.0.txt") + } + } + + developers { + developer { + email.set("adam@adamko.dev") + } + } + + scm { + connection.set("scm:git:git://github.com/adamko-dev/kotlinx-serialization-typescript-generator.git") + developerConnection.set("scm:git:ssh://github.com:adamko-dev/kotlinx-serialization-typescript-generator.git") + url.set("https://github.com/adamko-dev/kotlinx-serialization-typescript-generator") } - signing { sign(publication) } } diff --git a/modules/kxs-ts-gen-core/build.gradle.kts b/modules/kxs-ts-gen-core/build.gradle.kts index 2f67133e..3ed2898d 100644 --- a/modules/kxs-ts-gen-core/build.gradle.kts +++ b/modules/kxs-ts-gen-core/build.gradle.kts @@ -1,13 +1,10 @@ plugins { - buildsrc.convention.`kotlin-multiplatform` + buildsrc.convention.`kotlin-mpp` buildsrc.convention.`maven-publish` kotlin("plugin.serialization") id("io.kotest.multiplatform") } -val kotlinxSerializationVersion = "1.3.2" -val kotestVersion = "5.2.3" - kotlin { js(IR) {