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) }