From ec8901f322e1e54bb48a85622252767a4ba9963d Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Fri, 4 Jul 2025 15:01:38 +0200 Subject: [PATCH 1/8] Writing kdocs for generateCode --- .../kotlinx/dataframe/api/generateCode.kt | 154 +++++++++++++++++- .../kotlinx/dataframe/codeGen/Marker.kt | 7 + .../documentation/DocumentationUrls.kt | 3 + .../dataframe/examples/exposed/tables.kt | 2 +- 4 files changed, 159 insertions(+), 7 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt index 8ce81d03b6..2c8475dec7 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt @@ -1,14 +1,89 @@ package org.jetbrains.kotlinx.dataframe.api import org.jetbrains.kotlinx.dataframe.DataFrame +import org.jetbrains.kotlinx.dataframe.annotations.ColumnName +import org.jetbrains.kotlinx.dataframe.annotations.DataSchema import org.jetbrains.kotlinx.dataframe.codeGen.CodeGenerator import org.jetbrains.kotlinx.dataframe.codeGen.MarkerVisibility import org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer +import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup +import org.jetbrains.kotlinx.dataframe.columns.FrameColumn +import org.jetbrains.kotlinx.dataframe.documentation.DocumentationUrls +import org.jetbrains.kotlinx.dataframe.documentation.ExcludeFromSources import org.jetbrains.kotlinx.dataframe.impl.codeGen.from import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema +// region Docs + +/** + * Generates a [CodeString] containing generated [@DataSchema][DataSchema] $[TYPES] + * for the given $[RECEIVER] + * (including all nested [frame columns][FrameColumn] and [column groups][ColumnGroup]). + * + * These generated declarations can also be called "markers". + * + * This is useful when working with the compiler plugin in cases where the schema + * cannot be inferred automatically from the source. + * + * This function is a simplified wrapper for the more advanced and customizable + * [CodeGenerator] API. + * For more customizability, have a look at [CodeGenerator.create()][CodeGenerator.create]. + * + * For more information: {@include [DocumentationUrls.DataSchemaGeneration]} + * + * @return [CodeString] – A value class wrapper for [String], containing + * the generated Kotlin code of data schema declarations (markers). + */ +@ExcludeFromSources +private interface CommonGenerateCodeDocs { + + // "interfaces" or "data classes" + interface TYPES + + // "DataFrameSchema" or "DataFrame" + interface RECEIVER +} + +@ExcludeFromSources +private interface Params { + + /** @param markerName The base name to use for generated data schema declarations (markers). */ + interface MarkerName + + /** @include [MarkerName] If not specified, generates a name from type [T]. */ + interface MarkerNameOptional + + /** @param fields Whether to generate fields (`val ...:`) inside the generated data schema declarations (markers). */ + interface Fields + + /** @param extensionProperties Whether to generate extension properties in addition to data schema declarations (markers). */ + interface ExtensionProperties + + /** @param visibility Visibility modifier for the generated declarations (markers). */ + interface Visibility + + /** @param useFqNames If `true`, fully qualified type names will be used in generated code. */ + interface UseFqNames + + /** + * @param nameNormalizer Strategy for converting column names (with spaces, underscores, etc.) to valid Kotlin identifiers. + * Columns will keep their original name inside the dataframe via [@ColumnName][ColumnName]. + */ + interface NameNormalizer +} + +// endregion + // region DataFrame +/** + * @include [CommonGenerateCodeDocs] + * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema]} + * {@set [CommonGenerateCodeDocs.TYPES] interfaces and extension properties} + * + * @include [Params.Fields] Default is `true`. + * @include [Params.ExtensionProperties] Default is `true`. + */ public inline fun DataFrame.generateCode( fields: Boolean = true, extensionProperties: Boolean = true, @@ -19,6 +94,16 @@ public inline fun DataFrame.generateCode( extensionProperties = extensionProperties, ) +/** + * @include [CommonGenerateCodeDocs] + * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema]} + * {@set [CommonGenerateCodeDocs.TYPES] interfaces and extension properties} + * + * @include [Params.MarkerName] + * @include [Params.Fields] Default is `true`. + * @include [Params.ExtensionProperties] Default is `true`. + * @include [Params.Visibility] Default is [MarkerVisibility.IMPLICIT_PUBLIC]. + */ public fun DataFrame.generateCode( markerName: String, fields: Boolean = true, @@ -32,11 +117,39 @@ public fun DataFrame.generateCode( visibility = visibility, ) +/** + * @include [CommonGenerateCodeDocs] + * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema]} + * {@set [CommonGenerateCodeDocs.TYPES] interfaces} + * + * @include [Params.MarkerNameOptional] + */ public inline fun DataFrame.generateInterfaces(): CodeString = schema().generateInterfaces( markerName = markerName(), ) +/** + * @include [CommonGenerateCodeDocs] + * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema]} + * {@set [CommonGenerateCodeDocs.TYPES] interfaces} + * + * @include [Params.MarkerNameOptional] + */ +public fun DataFrame.generateInterfaces(markerName: String): CodeString = + schema().generateInterfaces(markerName = markerName) + +/** + * @include [CommonGenerateCodeDocs] + * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema]} + * {@set [CommonGenerateCodeDocs.TYPES] data classes} + * + * @include [Params.MarkerNameOptional] + * @include [Params.ExtensionProperties] Default is `false`. + * @include [Params.Visibility] Default is [MarkerVisibility.IMPLICIT_PUBLIC]. + * @include [Params.UseFqNames] Default is `false`. + * @include [Params.NameNormalizer] Default is [NameNormalizer.default][NameNormalizer.Companion.default]. + */ public inline fun DataFrame.generateDataClasses( markerName: String? = null, extensionProperties: Boolean = false, @@ -45,20 +158,27 @@ public inline fun DataFrame.generateDataClasses( nameNormalizer: NameNormalizer = NameNormalizer.default, ): CodeString = schema().generateDataClasses( - name = markerName ?: markerName(), + markerName = markerName ?: markerName(), extensionProperties = extensionProperties, visibility = visibility, useFqNames = useFqNames, nameNormalizer = nameNormalizer, ) -public fun DataFrame.generateInterfaces(markerName: String): CodeString = - schema().generateInterfaces(markerName = markerName) - // endregion // region DataFrameSchema +/** + * @include [CommonGenerateCodeDocs] + * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrameSchema][this]} + * {@set [CommonGenerateCodeDocs.TYPES] interfaces and extension properties} + * + * @include [Params.MarkerName] + * @include [Params.Fields] Default is `true`. + * @include [Params.ExtensionProperties] Default is `true`. + * @include [Params.Visibility] Default is [MarkerVisibility.IMPLICIT_PUBLIC]. + */ @JvmName("generateCodeForSchema") public fun DataFrameSchema.generateCode( markerName: String, @@ -77,6 +197,13 @@ public fun DataFrameSchema.generateCode( ).code.declarations.toCodeString() } +/** + * @include [CommonGenerateCodeDocs] + * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrameSchema][this]} + * {@set [CommonGenerateCodeDocs.TYPES] interfaces} + * + * @include [Params.MarkerName] + */ @JvmName("generateInterfacesForSchema") public fun DataFrameSchema.generateInterfaces(markerName: String): CodeString = generateCode( @@ -85,9 +212,20 @@ public fun DataFrameSchema.generateInterfaces(markerName: String): CodeString = extensionProperties = false, ) +/** + * @include [CommonGenerateCodeDocs] + * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrameSchema][this]} + * {@set [CommonGenerateCodeDocs.TYPES] data classes} + * + * @include [Params.MarkerName] + * @include [Params.ExtensionProperties] Default is `false`. + * @include [Params.Visibility] Default is [MarkerVisibility.IMPLICIT_PUBLIC]. + * @include [Params.UseFqNames] Default is `false`. + * @include [Params.NameNormalizer] Default is [NameNormalizer.default][NameNormalizer.Companion.default]. + */ @JvmName("generateDataClassesForSchema") public fun DataFrameSchema.generateDataClasses( - name: String, + markerName: String, extensionProperties: Boolean = false, visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, useFqNames: Boolean = false, @@ -96,7 +234,7 @@ public fun DataFrameSchema.generateDataClasses( val codeGen = CodeGenerator.create(useFqNames) return codeGen.generate( schema = this, - name = name, + name = markerName, fields = true, extensionProperties = extensionProperties, isOpen = false, @@ -121,6 +259,10 @@ internal inline fun markerName(): String = */ public val NameNormalizer.Companion.default: NameNormalizer get() = NameNormalizer.from(setOf('\t', ' ', '_')) +/** + * A value class wrapper for [String], containing + * generated Kotlin code of data schema declarations (markers) and optionally extension properties. + */ @JvmInline public value class CodeString(public val value: String) { override fun toString(): String = value diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/Marker.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/Marker.kt index d52bd63051..1731f27fc8 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/Marker.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/Marker.kt @@ -4,6 +4,13 @@ import org.jetbrains.kotlinx.dataframe.impl.schema.DataFrameSchemaImpl import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema import kotlin.reflect.KClass +/** + * Which type of visibility the marker (generated class-like code declaration) + * will have: + * - [INTERNAL][INTERNAL]: "internal" + * - [IMPLICIT_PUBLIC][IMPLICIT_PUBLIC]: "" + * - [EXPLICIT_PUBLIC][EXPLICIT_PUBLIC]: "public" + */ public enum class MarkerVisibility { INTERNAL, IMPLICIT_PUBLIC, diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt index 771133046e..15e7772fdb 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt @@ -116,4 +116,7 @@ internal interface DocumentationUrls { /** [See `explode` on the documentation website.]({@include [Url]}/explode.html) */ interface Explode + + /** [See `Data Schemas/Data Classes Generation` on the documentation website.]({@include [Url]}/dataschema-data-classes-generation.html) */ + interface DataSchemaGeneration } diff --git a/examples/idea-examples/unsupported-data-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/examples/exposed/tables.kt b/examples/idea-examples/unsupported-data-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/examples/exposed/tables.kt index 5e0b549d8b..ee623c99ea 100644 --- a/examples/idea-examples/unsupported-data-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/examples/exposed/tables.kt +++ b/examples/idea-examples/unsupported-data-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/examples/exposed/tables.kt @@ -59,7 +59,7 @@ fun main() { // we use a NameNormalizer to let DataFrame generate the same accessors as in the Table // while keeping the correct column names schema.generateDataClasses( - name = "DfCustomers", + markerName = "DfCustomers", nameNormalizer = nameNormalizer, ).print() } From ca2360c85d22cad9c6b1df4bac2632eee66de249 Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Fri, 4 Jul 2025 16:43:48 +0200 Subject: [PATCH 2/8] adding more tests for generateX family of functions --- .../dataframe/codeGen/CodeGenerationTests.kt | 384 +++++++++++++++++- 1 file changed, 374 insertions(+), 10 deletions(-) diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt index 8398f7c0d9..0ef9f1fefc 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt @@ -1,21 +1,26 @@ package org.jetbrains.kotlinx.dataframe.codeGen import io.kotest.matchers.shouldBe +import org.intellij.lang.annotations.Language import org.jetbrains.kotlinx.dataframe.AnyRow import org.jetbrains.kotlinx.dataframe.ColumnsScope import org.jetbrains.kotlinx.dataframe.DataColumn import org.jetbrains.kotlinx.dataframe.DataRow import org.jetbrains.kotlinx.dataframe.api.dataFrameOf import org.jetbrains.kotlinx.dataframe.api.dropNulls +import org.jetbrains.kotlinx.dataframe.api.generateCode import org.jetbrains.kotlinx.dataframe.api.generateDataClasses +import org.jetbrains.kotlinx.dataframe.api.generateInterfaces import org.jetbrains.kotlinx.dataframe.api.groupBy import org.jetbrains.kotlinx.dataframe.api.move import org.jetbrains.kotlinx.dataframe.api.schema import org.jetbrains.kotlinx.dataframe.api.toCodeString import org.jetbrains.kotlinx.dataframe.api.under +import org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup import org.jetbrains.kotlinx.dataframe.impl.codeGen.ReplCodeGenerator import org.jetbrains.kotlinx.dataframe.impl.codeGen.ReplCodeGeneratorImpl +import org.jetbrains.kotlinx.dataframe.impl.toCamelCaseByDelimiters import org.jetbrains.kotlinx.dataframe.testSets.person.BaseTest import org.jetbrains.kotlinx.dataframe.testSets.person.Person import org.junit.Test @@ -348,11 +353,32 @@ class CodeGenerationTests : BaseTest() { } } + @Test + fun `check name normalization for generated data classes`() { + dataFrameOf("my_name")(1).generateDataClasses() shouldBe + """ + @DataSchema + data class DataEntry( + @ColumnName("my_name") + val myName: Int + ) + """.trimIndent().toCodeString() + } + + @Test + fun patterns() { + """^[\d]""".toRegex().matches("3fds") + } + + // region Tests for generateX functions + @Test fun `check method generateDataClasses`() { - val code = typed.groupBy { name }.toDataFrame().generateDataClasses() + val code1 = typed.groupBy { name }.toDataFrame().generateDataClasses() + val code2 = typed.groupBy { name }.toDataFrame().schema().generateDataClasses("Person") - code shouldBe + @Language("kotlin") + val expected = """ @DataSchema data class Person1( @@ -368,22 +394,360 @@ class CodeGenerationTests : BaseTest() { val name: String ) """.trimIndent().toCodeString() + + code1 shouldBe expected + code2 shouldBe expected } @Test - fun `check name normalization for generated data classes`() { - dataFrameOf("my_name")(1).generateDataClasses() shouldBe + fun `DataFrame generateCode - default parameters`() { + val code = typed.generateCode() + + val packageName = "org.jetbrains.kotlinx.dataframe" + + @Language("kotlin") + val expected = """ @DataSchema - data class DataEntry( - @ColumnName("my_name") - val myName: Int + interface Person { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + + val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("Person_age") get() = this["age"] as $packageName.DataColumn + val $packageName.DataRow.age: kotlin.Int @JvmName("Person_age") get() = this["age"] as kotlin.Int + val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("Person_city") get() = this["city"] as $packageName.DataColumn + val $packageName.DataRow.city: kotlin.String? @JvmName("Person_city") get() = this["city"] as kotlin.String? + val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("Person_name") get() = this["name"] as $packageName.DataColumn + val $packageName.DataRow.name: kotlin.String @JvmName("Person_name") get() = this["name"] as kotlin.String + val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("Person_weight") get() = this["weight"] as $packageName.DataColumn + val $packageName.DataRow.weight: kotlin.Int? @JvmName("Person_weight") get() = this["weight"] as kotlin.Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateCode - custom parameters`() { + val code = typed.generateCode(fields = false, extensionProperties = true) + + val packageName = "org.jetbrains.kotlinx.dataframe" + + @Language("kotlin") + val expected = + """ + @DataSchema + interface Person { } + + val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("Person_age") get() = this["age"] as $packageName.DataColumn + val $packageName.DataRow.age: kotlin.Int @JvmName("Person_age") get() = this["age"] as kotlin.Int + val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("Person_city") get() = this["city"] as $packageName.DataColumn + val $packageName.DataRow.city: kotlin.String? @JvmName("Person_city") get() = this["city"] as kotlin.String? + val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("Person_name") get() = this["name"] as $packageName.DataColumn + val $packageName.DataRow.name: kotlin.String @JvmName("Person_name") get() = this["name"] as kotlin.String + val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("Person_weight") get() = this["weight"] as $packageName.DataColumn + val $packageName.DataRow.weight: kotlin.Int? @JvmName("Person_weight") get() = this["weight"] as kotlin.Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateCode - with marker name and default parameters`() { + val code = typed.generateCode("CustomMarker") + + val packageName = "org.jetbrains.kotlinx.dataframe" + + @Language("kotlin") + val expected = + """ + @DataSchema + interface CustomMarker { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + + val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("CustomMarker_age") get() = this["age"] as $packageName.DataColumn + val $packageName.DataRow.age: kotlin.Int @JvmName("CustomMarker_age") get() = this["age"] as kotlin.Int + val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("CustomMarker_city") get() = this["city"] as $packageName.DataColumn + val $packageName.DataRow.city: kotlin.String? @JvmName("CustomMarker_city") get() = this["city"] as kotlin.String? + val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("CustomMarker_name") get() = this["name"] as $packageName.DataColumn + val $packageName.DataRow.name: kotlin.String @JvmName("CustomMarker_name") get() = this["name"] as kotlin.String + val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("CustomMarker_weight") get() = this["weight"] as $packageName.DataColumn + val $packageName.DataRow.weight: kotlin.Int? @JvmName("CustomMarker_weight") get() = this["weight"] as kotlin.Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateCode - with marker name and custom parameters`() { + val code = typed.generateCode( + markerName = "CustomMarker", + fields = false, + extensionProperties = false, + visibility = MarkerVisibility.INTERNAL, + ) + + @Language("kotlin") + val expected = + """ + @DataSchema + internal interface CustomMarker { } + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateInterfaces`() { + val code = typed.generateInterfaces() + + @Language("kotlin") + val expected = + """ + @DataSchema + interface Person { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateInterfaces - with marker name`() { + val code = typed.generateInterfaces("CustomInterface") + + @Language("kotlin") + val expected = + """ + @DataSchema + interface CustomInterface { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateDataClasses - with default parameters`() { + val code = typed.generateDataClasses() + + @Language("kotlin") + val expected = + """ + @DataSchema + data class Person( + val age: Int, + val city: String?, + val name: String, + val weight: Int? ) - """.trimIndent().toCodeString() + """.trimIndent() + + code.value shouldBe expected } @Test - fun patterns() { - """^[\d]""".toRegex().matches("3fds") + fun `DataFrame generateDataClasses - with custom parameters`() { + val code = typed.generateDataClasses( + markerName = "CustomDataClass", + extensionProperties = true, + visibility = MarkerVisibility.INTERNAL, + useFqNames = true, + ) + + val packageName = "org.jetbrains.kotlinx.dataframe" + + @Language("kotlin") + val expected = + """ + @DataSchema + internal data class CustomDataClass( + val age: kotlin.Int, + val city: kotlin.String?, + val name: kotlin.String, + val weight: kotlin.Int? + ) + + internal val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("CustomDataClass_age") get() = this["age"] as $packageName.DataColumn + internal val $packageName.DataRow.age: kotlin.Int @JvmName("CustomDataClass_age") get() = this["age"] as kotlin.Int + internal val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("CustomDataClass_city") get() = this["city"] as $packageName.DataColumn + internal val $packageName.DataRow.city: kotlin.String? @JvmName("CustomDataClass_city") get() = this["city"] as kotlin.String? + internal val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("CustomDataClass_name") get() = this["name"] as $packageName.DataColumn + internal val $packageName.DataRow.name: kotlin.String @JvmName("CustomDataClass_name") get() = this["name"] as kotlin.String + internal val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("CustomDataClass_weight") get() = this["weight"] as $packageName.DataColumn + internal val $packageName.DataRow.weight: kotlin.Int? @JvmName("CustomDataClass_weight") get() = this["weight"] as kotlin.Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrameSchema generateCode - with default parameters`() { + val schema = typed.schema() + val code = schema.generateCode("SchemaMarker") + + val packageName = "org.jetbrains.kotlinx.dataframe" + + @Language("kotlin") + val expected = + """ + @DataSchema + interface SchemaMarker { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + + val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("SchemaMarker_age") get() = this["age"] as $packageName.DataColumn + val $packageName.DataRow.age: kotlin.Int @JvmName("SchemaMarker_age") get() = this["age"] as kotlin.Int + val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("SchemaMarker_city") get() = this["city"] as $packageName.DataColumn + val $packageName.DataRow.city: kotlin.String? @JvmName("SchemaMarker_city") get() = this["city"] as kotlin.String? + val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("SchemaMarker_name") get() = this["name"] as $packageName.DataColumn + val $packageName.DataRow.name: kotlin.String @JvmName("SchemaMarker_name") get() = this["name"] as kotlin.String + val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("SchemaMarker_weight") get() = this["weight"] as $packageName.DataColumn + val $packageName.DataRow.weight: kotlin.Int? @JvmName("SchemaMarker_weight") get() = this["weight"] as kotlin.Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrameSchema generateCode - with custom parameters`() { + val schema = typed.schema() + val code = schema.generateCode( + markerName = "SchemaMarker", + fields = true, + extensionProperties = false, + visibility = MarkerVisibility.EXPLICIT_PUBLIC, + ) + + @Language("kotlin") + val expected = + """ + @DataSchema + public interface SchemaMarker { + public val age: kotlin.Int + public val city: kotlin.String? + public val name: kotlin.String + public val weight: kotlin.Int? + } + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrameSchema generateInterfaces`() { + val schema = typed.schema() + val code = schema.generateInterfaces("SchemaInterface") + + @Language("kotlin") + val expected = + """ + @DataSchema + interface SchemaInterface { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrameSchema generateDataClasses - with default parameters`() { + val schema = typed.schema() + val code = schema.generateDataClasses("SchemaDataClass") + + @Language("kotlin") + val expected = + """ + @DataSchema + data class SchemaDataClass( + val age: Int, + val city: String?, + val name: String, + val weight: Int? + ) + """.trimIndent() + + code.value shouldBe expected } + + @Test + fun `DataFrameSchema generateDataClasses - with custom parameters`() { + val schema = typed.schema() + val code = schema.generateDataClasses( + markerName = "SchemaDataClass", + extensionProperties = true, + visibility = MarkerVisibility.EXPLICIT_PUBLIC, + useFqNames = false, + ) + + @Language("kotlin") + val expected = + """ + @DataSchema + public data class SchemaDataClass( + public val age: Int, + public val city: String?, + public val name: String, + public val weight: Int? + ) + + public val ColumnsScope.age: DataColumn @JvmName("SchemaDataClass_age") get() = this["age"] as DataColumn + public val DataRow.age: Int @JvmName("SchemaDataClass_age") get() = this["age"] as Int + public val ColumnsScope.city: DataColumn @JvmName("SchemaDataClass_city") get() = this["city"] as DataColumn + public val DataRow.city: String? @JvmName("SchemaDataClass_city") get() = this["city"] as String? + public val ColumnsScope.name: DataColumn @JvmName("SchemaDataClass_name") get() = this["name"] as DataColumn + public val DataRow.name: String @JvmName("SchemaDataClass_name") get() = this["name"] as String + public val ColumnsScope.weight: DataColumn @JvmName("SchemaDataClass_weight") get() = this["weight"] as DataColumn + public val DataRow.weight: Int? @JvmName("SchemaDataClass_weight") get() = this["weight"] as Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateDataClasses - with name normalizer`() { + val dfWithSpecialNames = dataFrameOf("my_column", "another column", "third-column")(1, "test", 3.14) + val code = dfWithSpecialNames.generateDataClasses( + nameNormalizer = NameNormalizer { it.toCamelCaseByDelimiters() + "1" }, + ) + + @Language("kotlin") + val expected = + """ + @DataSchema + data class DataEntry( + @ColumnName("another column") + val anotherColumn1: String, + @ColumnName("my_column") + val myColumn1: Int, + @ColumnName("third-column") + val thirdColumn1: Double + ) + """.trimIndent() + + code.value shouldBe expected + } + + // endregion } From 43b7d2bfdfeebba95d83ebddfc71ea50db4b5234 Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Tue, 22 Jul 2025 15:23:32 +0200 Subject: [PATCH 3/8] deprecated generateCode in favor of generateInterfaces. Reworked generateCode.kt from feedback --- .../kotlinx/dataframe/api/generateCode.kt | 402 +++++++++++++++--- .../kotlinx/dataframe/codeGen/Marker.kt | 7 + .../dataframe/util/deprecationMessages.kt | 9 + .../dataframe/codeGen/CodeGenerationTests.kt | 384 ++++++++++++++++- .../kotlinx/dataframe/api/generateCode.kt | 315 +++++++++----- .../documentation/DocumentationUrls.kt | 5 +- .../dataframe/util/deprecationMessages.kt | 9 + .../dataframe/codeGen/CodeGenerationTests.kt | 162 ------- 8 files changed, 951 insertions(+), 342 deletions(-) diff --git a/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt b/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt index 8ce81d03b6..9781034a96 100644 --- a/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt +++ b/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt @@ -1,110 +1,325 @@ package org.jetbrains.kotlinx.dataframe.api import org.jetbrains.kotlinx.dataframe.DataFrame +import org.jetbrains.kotlinx.dataframe.annotations.ColumnName +import org.jetbrains.kotlinx.dataframe.annotations.DataSchema import org.jetbrains.kotlinx.dataframe.codeGen.CodeGenerator import org.jetbrains.kotlinx.dataframe.codeGen.MarkerVisibility import org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer +import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup +import org.jetbrains.kotlinx.dataframe.columns.FrameColumn +import org.jetbrains.kotlinx.dataframe.documentation.AccessApi.ExtensionPropertiesApi import org.jetbrains.kotlinx.dataframe.impl.codeGen.from import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema +import org.jetbrains.kotlinx.dataframe.util.GENERATE_CODE +import org.jetbrains.kotlinx.dataframe.util.GENERATE_CODE_REPLACE1 +import org.jetbrains.kotlinx.dataframe.util.GENERATE_CODE_REPLACE2 +import org.jetbrains.kotlinx.dataframe.util.GENERATE_INTERFACES + +// region Docs + +// endregion // region DataFrame -public inline fun DataFrame.generateCode( - fields: Boolean = true, - extensionProperties: Boolean = true, +/** + * Generates a [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] containing generated [@DataSchema][org.jetbrains.kotlinx.dataframe.annotations.DataSchema] interfaces + * for the given [DataFrame's][this] [schema][DataFrameSchema] + * (including all nested [frame columns][org.jetbrains.kotlinx.dataframe.columns.FrameColumn] and [column groups][org.jetbrains.kotlinx.dataframe.columns.ColumnGroup]). + * + * These generated declarations can also be called "markers". + * + * This is useful when working with the compiler plugin in cases where the schema + * cannot be inferred automatically from the source. [See Compiler Plugin on the documentation website.](https://kotlin.github.io/dataframe/compiler-plugin.html) + * + * This function is a simplified wrapper for the more advanced and customizable + * [CodeGenerator][org.jetbrains.kotlinx.dataframe.codeGen.CodeGenerator] API. + * For more customizability, have a look at [CodeGenerator.create()][CodeGenerator.create]. + * + * For more information: [See `Data Schemas/Data Classes Generation` on the documentation website.](https://kotlin.github.io/dataframe/dataschemagenerationmethods.html) + * + * @return [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] – A value class wrapper for [String], containing + * the generated Kotlin code of data schema declarations (markers). + * @param markerName The base name to use for generated data schema declarations (markers). If not specified, it generates a name from type [T]. + * @param extensionProperties Whether to generate [extension properties (column accessors)][org.jetbrains.kotlinx.dataframe.documentation.AccessApi.ExtensionPropertiesApi] + * in addition to data schema declarations (markers). + * Useful if you don't use the compiler plugin, otherwise they are not needed; + * the compiler plugin and older Gradle/KSP plugin generate them automatically. + * Default is `false`. + * @param visibility Visibility modifier for the generated declarations (markers). Default is [MarkerVisibility.IMPLICIT_PUBLIC][org.jetbrains.kotlinx.dataframe.codeGen.MarkerVisibility.IMPLICIT_PUBLIC]. + * @param useFqNames If `true`, fully qualified type names will be used in generated code. Default is `false`. + * @param nameNormalizer Strategy for converting column names (with spaces, underscores, etc.) + * to Kotlin-style identifiers. + * Generated properties will still refer to columns by their actual name + * using the [@ColumnName][org.jetbrains.kotlinx.dataframe.annotations.ColumnName] annotation. + * Default is [NameNormalizer.default][org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer.Companion.default]. + */ +public fun DataFrame.generateInterfaces( + markerName: String, + extensionProperties: Boolean = false, + visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, + useFqNames: Boolean = false, + nameNormalizer: NameNormalizer = NameNormalizer.default, +): CodeString = + schema().generateCodeImpl( + markerName = markerName, + extensionProperties = extensionProperties, + visibility = visibility, + useFqNames = useFqNames, + nameNormalizer = nameNormalizer, + asDataClass = false, + ) + +/** Generates a [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] containing generated [@DataSchema][org.jetbrains.kotlinx.dataframe.annotations.DataSchema] interfaces + * for the given [DataFrame's][this] [schema][org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema] + * (including all nested [frame columns][org.jetbrains.kotlinx.dataframe.columns.FrameColumn] and [column groups][org.jetbrains.kotlinx.dataframe.columns.ColumnGroup]). + * + * These generated declarations can also be called "markers". + * + * This is useful when working with the compiler plugin in cases where the schema + * cannot be inferred automatically from the source. [See Compiler Plugin on the documentation website.](https://kotlin.github.io/dataframe/compiler-plugin.html) + * + * This function is a simplified wrapper for the more advanced and customizable + * [CodeGenerator][org.jetbrains.kotlinx.dataframe.codeGen.CodeGenerator] API. + * For more customizability, have a look at [CodeGenerator.create()][CodeGenerator.create]. + * + * For more information: [See `Data Schemas/Data Classes Generation` on the documentation website.](https://kotlin.github.io/dataframe/dataschemagenerationmethods.html) + * + * @return [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] – A value class wrapper for [String], containing + * the generated Kotlin code of data schema declarations (markers). + * @param markerName The base name to use for generated data schema declarations (markers). If not specified, it generates a name from type [T]. + * @param extensionProperties Whether to generate [extension properties (column accessors)][org.jetbrains.kotlinx.dataframe.documentation.AccessApi.ExtensionPropertiesApi] + * in addition to data schema declarations (markers). + * Useful if you don't use the compiler plugin, otherwise they are not needed; + * the compiler plugin and older Gradle/KSP plugin generate them automatically. + * Default is `false`. + * @param visibility Visibility modifier for the generated declarations (markers). Default is [MarkerVisibility.IMPLICIT_PUBLIC][org.jetbrains.kotlinx.dataframe.codeGen.MarkerVisibility.IMPLICIT_PUBLIC]. + * @param useFqNames If `true`, fully qualified type names will be used in generated code. Default is `false`. + * @param nameNormalizer Strategy for converting column names (with spaces, underscores, etc.) + * to Kotlin-style identifiers. + * Generated properties will still refer to columns by their actual name + * using the [@ColumnName][org.jetbrains.kotlinx.dataframe.annotations.ColumnName] annotation. + * Default is [NameNormalizer.default][org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer.Companion.default]. */ +public inline fun DataFrame.generateInterfaces( + extensionProperties: Boolean = false, + visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, + useFqNames: Boolean = false, + nameNormalizer: NameNormalizer = NameNormalizer.default, ): CodeString = - schema().generateCode( + schema().generateCodeImpl( markerName = markerName(), - fields = fields, extensionProperties = extensionProperties, + visibility = visibility, + useFqNames = useFqNames, + nameNormalizer = nameNormalizer, + asDataClass = false, ) -public fun DataFrame.generateCode( +/** + * Generates a [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] containing generated [@DataSchema][org.jetbrains.kotlinx.dataframe.annotations.DataSchema] data classes + * for the given [DataFrame's][this] [schema][DataFrameSchema] + * (including all nested [frame columns][org.jetbrains.kotlinx.dataframe.columns.FrameColumn] and [column groups][org.jetbrains.kotlinx.dataframe.columns.ColumnGroup]). + * + * These generated declarations can also be called "markers". + * + * This is useful when you want to: + * - Work with the data as regular Kotlin data classes. + * - Convert a dataframe to instantiated data classes with [`df.toListOf()`][DataFrame.toListOf]. + * - Work with data classes serialization. + * - Extract structured types for further use in your application. + * + * This function is a simplified wrapper for the more advanced and customizable + * [CodeGenerator][org.jetbrains.kotlinx.dataframe.codeGen.CodeGenerator] API. + * For more customizability, have a look at [CodeGenerator.create()][CodeGenerator.create]. + * + * For more information: [See `Data Schemas/Data Classes Generation` on the documentation website.](https://kotlin.github.io/dataframe/dataschemagenerationmethods.html) + * + * @return [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] – A value class wrapper for [String], containing + * the generated Kotlin code of data schema declarations (markers). + * @param markerName The base name to use for generated data schema declarations (markers). If not specified, it generates a name from type [T]. + * @param extensionProperties Whether to generate [extension properties (column accessors)][org.jetbrains.kotlinx.dataframe.documentation.AccessApi.ExtensionPropertiesApi] + * in addition to data schema declarations (markers). + * Useful if you don't use the compiler plugin, otherwise they are not needed; + * the compiler plugin and older Gradle/KSP plugin generate them automatically. + * Default is `false`. + * @param visibility Visibility modifier for the generated declarations (markers). Default is [MarkerVisibility.IMPLICIT_PUBLIC][org.jetbrains.kotlinx.dataframe.codeGen.MarkerVisibility.IMPLICIT_PUBLIC]. + * @param useFqNames If `true`, fully qualified type names will be used in generated code. Default is `false`. + * @param nameNormalizer Strategy for converting column names (with spaces, underscores, etc.) + * to Kotlin-style identifiers. + * Generated properties will still refer to columns by their actual name + * using the [@ColumnName][org.jetbrains.kotlinx.dataframe.annotations.ColumnName] annotation. + * Default is [NameNormalizer.default][org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer.Companion.default]. + */ +public fun DataFrame.generateDataClasses( markerName: String, - fields: Boolean = true, - extensionProperties: Boolean = true, + extensionProperties: Boolean = false, visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, + useFqNames: Boolean = false, + nameNormalizer: NameNormalizer = NameNormalizer.default, ): CodeString = - schema().generateCode( + schema().generateCodeImpl( markerName = markerName, - fields = fields, extensionProperties = extensionProperties, visibility = visibility, + useFqNames = useFqNames, + nameNormalizer = nameNormalizer, + asDataClass = true, ) -public inline fun DataFrame.generateInterfaces(): CodeString = - schema().generateInterfaces( - markerName = markerName(), - ) - +/** Generates a [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] containing generated [@DataSchema][org.jetbrains.kotlinx.dataframe.annotations.DataSchema] data classes + * for the given [DataFrame's][this] [schema][org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema] + * (including all nested [frame columns][org.jetbrains.kotlinx.dataframe.columns.FrameColumn] and [column groups][org.jetbrains.kotlinx.dataframe.columns.ColumnGroup]). + * + * These generated declarations can also be called "markers". + * + * This is useful when you want to: + * - Work with the data as regular Kotlin data classes. + * - Convert a dataframe to instantiated data classes with [`df.toListOf()`][org.jetbrains.kotlinx.dataframe.DataFrame.toListOf]. + * - Work with data classes serialization. + * - Extract structured types for further use in your application. + * + * This function is a simplified wrapper for the more advanced and customizable + * [CodeGenerator][org.jetbrains.kotlinx.dataframe.codeGen.CodeGenerator] API. + * For more customizability, have a look at [CodeGenerator.create()][CodeGenerator.create]. + * + * For more information: [See `Data Schemas/Data Classes Generation` on the documentation website.](https://kotlin.github.io/dataframe/dataschemagenerationmethods.html) + * + * @return [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] – A value class wrapper for [String], containing + * the generated Kotlin code of data schema declarations (markers). + * @param markerName The base name to use for generated data schema declarations (markers). If not specified, it generates a name from type [T]. + * @param extensionProperties Whether to generate [extension properties (column accessors)][org.jetbrains.kotlinx.dataframe.documentation.AccessApi.ExtensionPropertiesApi] + * in addition to data schema declarations (markers). + * Useful if you don't use the compiler plugin, otherwise they are not needed; + * the compiler plugin and older Gradle/KSP plugin generate them automatically. + * Default is `false`. + * @param visibility Visibility modifier for the generated declarations (markers). Default is [MarkerVisibility.IMPLICIT_PUBLIC][org.jetbrains.kotlinx.dataframe.codeGen.MarkerVisibility.IMPLICIT_PUBLIC]. + * @param useFqNames If `true`, fully qualified type names will be used in generated code. Default is `false`. + * @param nameNormalizer Strategy for converting column names (with spaces, underscores, etc.) + * to Kotlin-style identifiers. + * Generated properties will still refer to columns by their actual name + * using the [@ColumnName][org.jetbrains.kotlinx.dataframe.annotations.ColumnName] annotation. + * Default is [NameNormalizer.default][org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer.Companion.default]. */ public inline fun DataFrame.generateDataClasses( - markerName: String? = null, extensionProperties: Boolean = false, visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, useFqNames: Boolean = false, nameNormalizer: NameNormalizer = NameNormalizer.default, ): CodeString = - schema().generateDataClasses( - name = markerName ?: markerName(), + schema().generateCodeImpl( + markerName = markerName(), extensionProperties = extensionProperties, visibility = visibility, useFqNames = useFqNames, nameNormalizer = nameNormalizer, + asDataClass = true, ) -public fun DataFrame.generateInterfaces(markerName: String): CodeString = - schema().generateInterfaces(markerName = markerName) - // endregion // region DataFrameSchema -@JvmName("generateCodeForSchema") -public fun DataFrameSchema.generateCode( +/** + * Generates a [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] containing generated [@DataSchema][org.jetbrains.kotlinx.dataframe.annotations.DataSchema] interfaces + * for the given [DataFrameSchema][this] + * (including all nested [frame columns][org.jetbrains.kotlinx.dataframe.columns.FrameColumn] and [column groups][org.jetbrains.kotlinx.dataframe.columns.ColumnGroup]). + * + * These generated declarations can also be called "markers". + * + * This is useful when working with the compiler plugin in cases where the schema + * cannot be inferred automatically from the source. [See Compiler Plugin on the documentation website.](https://kotlin.github.io/dataframe/compiler-plugin.html) + * + * This function is a simplified wrapper for the more advanced and customizable + * [CodeGenerator][org.jetbrains.kotlinx.dataframe.codeGen.CodeGenerator] API. + * For more customizability, have a look at [CodeGenerator.create()][CodeGenerator.create]. + * + * For more information: [See `Data Schemas/Data Classes Generation` on the documentation website.](https://kotlin.github.io/dataframe/dataschemagenerationmethods.html) + * + * @return [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] – A value class wrapper for [String], containing + * the generated Kotlin code of data schema declarations (markers). + * @param markerName The base name to use for generated data schema declarations (markers). + * @param extensionProperties Whether to generate [extension properties (column accessors)][org.jetbrains.kotlinx.dataframe.documentation.AccessApi.ExtensionPropertiesApi] + * in addition to data schema declarations (markers). + * Useful if you don't use the compiler plugin, otherwise they are not needed; + * the compiler plugin and older Gradle/KSP plugin generate them automatically. + * Default is `false`. + * @param visibility Visibility modifier for the generated declarations (markers). Default is [MarkerVisibility.IMPLICIT_PUBLIC][org.jetbrains.kotlinx.dataframe.codeGen.MarkerVisibility.IMPLICIT_PUBLIC]. + * @param useFqNames If `true`, fully qualified type names will be used in generated code. Default is `false`. + * @param nameNormalizer Strategy for converting column names (with spaces, underscores, etc.) + * to Kotlin-style identifiers. + * Generated properties will still refer to columns by their actual name + * using the [@ColumnName][org.jetbrains.kotlinx.dataframe.annotations.ColumnName] annotation. + * Default is [NameNormalizer.default][org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer.Companion.default]. + */ +@JvmName("generateInterfacesForSchema") +public fun DataFrameSchema.generateInterfaces( markerName: String, - fields: Boolean = true, - extensionProperties: Boolean = true, + extensionProperties: Boolean = false, visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, -): CodeString { - val codeGen = CodeGenerator.create() - return codeGen.generate( - schema = this, - name = markerName, - fields = fields, + useFqNames: Boolean = false, + nameNormalizer: NameNormalizer = NameNormalizer.default, +): CodeString = + generateCodeImpl( + markerName = markerName, extensionProperties = extensionProperties, - isOpen = true, visibility = visibility, - ).code.declarations.toCodeString() -} - -@JvmName("generateInterfacesForSchema") -public fun DataFrameSchema.generateInterfaces(markerName: String): CodeString = - generateCode( - markerName = markerName, - fields = true, - extensionProperties = false, + useFqNames = useFqNames, + nameNormalizer = nameNormalizer, + asDataClass = false, ) +/** + * Generates a [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] containing generated [@DataSchema][org.jetbrains.kotlinx.dataframe.annotations.DataSchema] data classes + * for the given [DataFrameSchema][this] + * (including all nested [frame columns][org.jetbrains.kotlinx.dataframe.columns.FrameColumn] and [column groups][org.jetbrains.kotlinx.dataframe.columns.ColumnGroup]). + * + * These generated declarations can also be called "markers". + * + * This is useful when you want to: + * - Work with the data as regular Kotlin data classes. + * - Convert a dataframe to instantiated data classes with [`df.toListOf()`][DataFrame.toListOf]. + * - Work with data classes serialization. + * - Extract structured types for further use in your application. + * + * This function is a simplified wrapper for the more advanced and customizable + * [CodeGenerator][org.jetbrains.kotlinx.dataframe.codeGen.CodeGenerator] API. + * For more customizability, have a look at [CodeGenerator.create()][CodeGenerator.create]. + * + * For more information: [See `Data Schemas/Data Classes Generation` on the documentation website.](https://kotlin.github.io/dataframe/dataschemagenerationmethods.html) + * + * @return [CodeString][org.jetbrains.kotlinx.dataframe.api.CodeString] – A value class wrapper for [String], containing + * the generated Kotlin code of data schema declarations (markers). + * + * + * @param markerName The base name to use for generated data schema declarations (markers). + * @param extensionProperties Whether to generate [extension properties (column accessors)][org.jetbrains.kotlinx.dataframe.documentation.AccessApi.ExtensionPropertiesApi] + * in addition to data schema declarations (markers). + * Useful if you don't use the compiler plugin, otherwise they are not needed; + * the compiler plugin and older Gradle/KSP plugin generate them automatically. + * Default is `false`. + * @param visibility Visibility modifier for the generated declarations (markers). Default is [MarkerVisibility.IMPLICIT_PUBLIC][org.jetbrains.kotlinx.dataframe.codeGen.MarkerVisibility.IMPLICIT_PUBLIC]. + * @param useFqNames If `true`, fully qualified type names will be used in generated code. Default is `false`. + * @param nameNormalizer Strategy for converting column names (with spaces, underscores, etc.) + * to Kotlin-style identifiers. + * Generated properties will still refer to columns by their actual name + * using the [@ColumnName][org.jetbrains.kotlinx.dataframe.annotations.ColumnName] annotation. + * Default is [NameNormalizer.default][org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer.Companion.default]. + */ @JvmName("generateDataClassesForSchema") public fun DataFrameSchema.generateDataClasses( - name: String, + markerName: String, extensionProperties: Boolean = false, visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, useFqNames: Boolean = false, nameNormalizer: NameNormalizer = NameNormalizer.default, -): CodeString { - val codeGen = CodeGenerator.create(useFqNames) - return codeGen.generate( - schema = this, - name = name, - fields = true, +): CodeString = + generateCodeImpl( + markerName = markerName, extensionProperties = extensionProperties, - isOpen = false, visibility = visibility, + useFqNames = useFqNames, + nameNormalizer = nameNormalizer, asDataClass = true, - fieldNameNormalizer = nameNormalizer, - ).code.declarations.toCodeString() -} + ) // endregion @@ -121,6 +336,12 @@ internal inline fun markerName(): String = */ public val NameNormalizer.Companion.default: NameNormalizer get() = NameNormalizer.from(setOf('\t', ' ', '_')) +/** + * A value class wrapper for [String], containing + * generated Kotlin code of data schema declarations (markers) and optionally extension properties. + * + * @see CodeString.print + */ @JvmInline public value class CodeString(public val value: String) { override fun toString(): String = value @@ -128,3 +349,80 @@ public value class CodeString(public val value: String) { @PublishedApi internal fun String.toCodeString(): CodeString = CodeString(this) + +/** + * Generates a [CodeString] containing generated [@DataSchema][DataSchema] declarations (markers). + */ +@PublishedApi +internal fun DataFrameSchema.generateCodeImpl( + markerName: String, + extensionProperties: Boolean, + visibility: MarkerVisibility, + useFqNames: Boolean, + nameNormalizer: NameNormalizer, + asDataClass: Boolean, +): CodeString { + val codeGen = CodeGenerator.create(useFqNames) + return codeGen.generate( + schema = this, + name = markerName, + fields = true, + extensionProperties = extensionProperties, + isOpen = false, + visibility = visibility, + asDataClass = asDataClass, + fieldNameNormalizer = nameNormalizer, + ).code.declarations.toCodeString() +} + +// region Deprecated + +@Deprecated( + message = GENERATE_CODE, + replaceWith = ReplaceWith(GENERATE_CODE_REPLACE1), + level = DeprecationLevel.WARNING, +) +public inline fun DataFrame.generateCode( + fields: Boolean = true, + extensionProperties: Boolean = true, +): CodeString { + val codeGen = CodeGenerator.create() + return codeGen.generate( + schema = this.schema(), + name = markerName(), + fields = fields, + extensionProperties = extensionProperties, + isOpen = true, + visibility = MarkerVisibility.IMPLICIT_PUBLIC, + ).code.declarations.toCodeString() +} + +@Deprecated( + message = GENERATE_CODE, + replaceWith = ReplaceWith(GENERATE_CODE_REPLACE2), + level = DeprecationLevel.WARNING, +) +public fun DataFrame.generateCode( + markerName: String, + fields: Boolean = true, + extensionProperties: Boolean = true, + visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, +): CodeString { + val codeGen = CodeGenerator.create() + return codeGen.generate( + schema = this.schema(), + name = markerName, + fields = fields, + extensionProperties = extensionProperties, + isOpen = true, + visibility = visibility, + ).code.declarations.toCodeString() +} + +@Deprecated(message = GENERATE_INTERFACES, level = DeprecationLevel.HIDDEN) +public inline fun DataFrame.generateInterfaces(): CodeString = generateInterfaces() + +@Deprecated(message = GENERATE_INTERFACES, level = DeprecationLevel.HIDDEN) +public fun DataFrame.generateInterfaces(markerName: String): CodeString = generateInterfaces(markerName) + +// endregion diff --git a/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/Marker.kt b/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/Marker.kt index d52bd63051..1731f27fc8 100644 --- a/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/Marker.kt +++ b/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/Marker.kt @@ -4,6 +4,13 @@ import org.jetbrains.kotlinx.dataframe.impl.schema.DataFrameSchemaImpl import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema import kotlin.reflect.KClass +/** + * Which type of visibility the marker (generated class-like code declaration) + * will have: + * - [INTERNAL][INTERNAL]: "internal" + * - [IMPLICIT_PUBLIC][IMPLICIT_PUBLIC]: "" + * - [EXPLICIT_PUBLIC][EXPLICIT_PUBLIC]: "public" + */ public enum class MarkerVisibility { INTERNAL, IMPLICIT_PUBLIC, diff --git a/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt b/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt index ea5f4b78cc..76e2c58222 100644 --- a/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt +++ b/core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt @@ -113,6 +113,15 @@ internal const val TO_URL_REPLACE = "toUrl()" internal const val FILTER_BY = "This function is deprecated in favor of `filter { }`. $MESSAGE_1_0" internal const val FILTER_BY_REPLACE = "filter { column }" +internal const val GENERATE_CODE = + "This function has been deprecated in favor of the more explicit `generateInterfaces()`. The `fields` parameter has also been removed. Use `CodeGenerator` explicitly, if you need it. $MESSAGE_1_0" + +internal const val GENERATE_CODE_REPLACE1 = "this.generateInterfaces(extensionProperties = extensionProperties)" +internal const val GENERATE_CODE_REPLACE2 = + "this.generateInterfaces(markerName = markerName, extensionProperties = extensionProperties, visibility = visibility)" + +internal const val GENERATE_INTERFACES = "This function is just here for binary compatibility. $MESSAGE_1_0" + // endregion // region WARNING in 1.0, ERROR in 1.1 diff --git a/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt b/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt index 8398f7c0d9..5d1f44d415 100644 --- a/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt +++ b/core/generated-sources/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt @@ -1,13 +1,17 @@ package org.jetbrains.kotlinx.dataframe.codeGen import io.kotest.matchers.shouldBe +import org.intellij.lang.annotations.Language import org.jetbrains.kotlinx.dataframe.AnyRow import org.jetbrains.kotlinx.dataframe.ColumnsScope import org.jetbrains.kotlinx.dataframe.DataColumn import org.jetbrains.kotlinx.dataframe.DataRow import org.jetbrains.kotlinx.dataframe.api.dataFrameOf import org.jetbrains.kotlinx.dataframe.api.dropNulls +import org.jetbrains.kotlinx.dataframe.api.generateCode +import org.jetbrains.kotlinx.dataframe.api.generateCodeImpl import org.jetbrains.kotlinx.dataframe.api.generateDataClasses +import org.jetbrains.kotlinx.dataframe.api.generateInterfaces import org.jetbrains.kotlinx.dataframe.api.groupBy import org.jetbrains.kotlinx.dataframe.api.move import org.jetbrains.kotlinx.dataframe.api.schema @@ -16,6 +20,7 @@ import org.jetbrains.kotlinx.dataframe.api.under import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup import org.jetbrains.kotlinx.dataframe.impl.codeGen.ReplCodeGenerator import org.jetbrains.kotlinx.dataframe.impl.codeGen.ReplCodeGeneratorImpl +import org.jetbrains.kotlinx.dataframe.impl.toCamelCaseByDelimiters import org.jetbrains.kotlinx.dataframe.testSets.person.BaseTest import org.jetbrains.kotlinx.dataframe.testSets.person.Person import org.junit.Test @@ -348,11 +353,32 @@ class CodeGenerationTests : BaseTest() { } } + @Test + fun `check name normalization for generated data classes`() { + dataFrameOf("my_name")(1).generateDataClasses() shouldBe + """ + @DataSchema + data class DataEntry( + @ColumnName("my_name") + val myName: Int + ) + """.trimIndent().toCodeString() + } + + @Test + fun patterns() { + """^[\d]""".toRegex().matches("3fds") + } + + // region Tests for generateX functions + @Test fun `check method generateDataClasses`() { - val code = typed.groupBy { name }.toDataFrame().generateDataClasses() + val code1 = typed.groupBy { name }.toDataFrame().generateDataClasses() + val code2 = typed.groupBy { name }.toDataFrame().schema().generateDataClasses("Person") - code shouldBe + @Language("kotlin") + val expected = """ @DataSchema data class Person1( @@ -368,22 +394,360 @@ class CodeGenerationTests : BaseTest() { val name: String ) """.trimIndent().toCodeString() + + code1 shouldBe expected + code2 shouldBe expected } @Test - fun `check name normalization for generated data classes`() { - dataFrameOf("my_name")(1).generateDataClasses() shouldBe + fun `DataFrame generateCode - default parameters`() { + val code = typed.generateCode() + + val packageName = "org.jetbrains.kotlinx.dataframe" + + @Language("kotlin") + val expected = """ @DataSchema - data class DataEntry( - @ColumnName("my_name") - val myName: Int + interface Person { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + + val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("Person_age") get() = this["age"] as $packageName.DataColumn + val $packageName.DataRow.age: kotlin.Int @JvmName("Person_age") get() = this["age"] as kotlin.Int + val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("Person_city") get() = this["city"] as $packageName.DataColumn + val $packageName.DataRow.city: kotlin.String? @JvmName("Person_city") get() = this["city"] as kotlin.String? + val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("Person_name") get() = this["name"] as $packageName.DataColumn + val $packageName.DataRow.name: kotlin.String @JvmName("Person_name") get() = this["name"] as kotlin.String + val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("Person_weight") get() = this["weight"] as $packageName.DataColumn + val $packageName.DataRow.weight: kotlin.Int? @JvmName("Person_weight") get() = this["weight"] as kotlin.Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateCode - custom parameters`() { + val code = typed.generateCode(fields = false, extensionProperties = true) + + val packageName = "org.jetbrains.kotlinx.dataframe" + + @Language("kotlin") + val expected = + """ + @DataSchema + interface Person { } + + val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("Person_age") get() = this["age"] as $packageName.DataColumn + val $packageName.DataRow.age: kotlin.Int @JvmName("Person_age") get() = this["age"] as kotlin.Int + val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("Person_city") get() = this["city"] as $packageName.DataColumn + val $packageName.DataRow.city: kotlin.String? @JvmName("Person_city") get() = this["city"] as kotlin.String? + val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("Person_name") get() = this["name"] as $packageName.DataColumn + val $packageName.DataRow.name: kotlin.String @JvmName("Person_name") get() = this["name"] as kotlin.String + val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("Person_weight") get() = this["weight"] as $packageName.DataColumn + val $packageName.DataRow.weight: kotlin.Int? @JvmName("Person_weight") get() = this["weight"] as kotlin.Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateCode - with marker name and default parameters`() { + val code = typed.generateCode("CustomMarker") + + val packageName = "org.jetbrains.kotlinx.dataframe" + + @Language("kotlin") + val expected = + """ + @DataSchema + interface CustomMarker { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + + val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("CustomMarker_age") get() = this["age"] as $packageName.DataColumn + val $packageName.DataRow.age: kotlin.Int @JvmName("CustomMarker_age") get() = this["age"] as kotlin.Int + val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("CustomMarker_city") get() = this["city"] as $packageName.DataColumn + val $packageName.DataRow.city: kotlin.String? @JvmName("CustomMarker_city") get() = this["city"] as kotlin.String? + val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("CustomMarker_name") get() = this["name"] as $packageName.DataColumn + val $packageName.DataRow.name: kotlin.String @JvmName("CustomMarker_name") get() = this["name"] as kotlin.String + val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("CustomMarker_weight") get() = this["weight"] as $packageName.DataColumn + val $packageName.DataRow.weight: kotlin.Int? @JvmName("CustomMarker_weight") get() = this["weight"] as kotlin.Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateCode - with marker name and custom parameters`() { + val code = typed.generateCode( + markerName = "CustomMarker", + fields = false, + extensionProperties = false, + visibility = MarkerVisibility.INTERNAL, + ) + + @Language("kotlin") + val expected = + """ + @DataSchema + internal interface CustomMarker { } + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateInterfaces`() { + val code = typed.generateInterfaces() + + @Language("kotlin") + val expected = + """ + @DataSchema + interface Person { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateInterfaces - with marker name`() { + val code = typed.generateInterfaces("CustomInterface") + + @Language("kotlin") + val expected = + """ + @DataSchema + interface CustomInterface { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateDataClasses - with default parameters`() { + val code = typed.generateDataClasses() + + @Language("kotlin") + val expected = + """ + @DataSchema + data class Person( + val age: Int, + val city: String?, + val name: String, + val weight: Int? ) - """.trimIndent().toCodeString() + """.trimIndent() + + code.value shouldBe expected } @Test - fun patterns() { - """^[\d]""".toRegex().matches("3fds") + fun `DataFrame generateDataClasses - with custom parameters`() { + val code = typed.generateDataClasses( + markerName = "CustomDataClass", + extensionProperties = true, + visibility = MarkerVisibility.INTERNAL, + useFqNames = true, + ) + + val packageName = "org.jetbrains.kotlinx.dataframe" + + @Language("kotlin") + val expected = + """ + @DataSchema + internal data class CustomDataClass( + val age: kotlin.Int, + val city: kotlin.String?, + val name: kotlin.String, + val weight: kotlin.Int? + ) + + internal val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("CustomDataClass_age") get() = this["age"] as $packageName.DataColumn + internal val $packageName.DataRow.age: kotlin.Int @JvmName("CustomDataClass_age") get() = this["age"] as kotlin.Int + internal val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("CustomDataClass_city") get() = this["city"] as $packageName.DataColumn + internal val $packageName.DataRow.city: kotlin.String? @JvmName("CustomDataClass_city") get() = this["city"] as kotlin.String? + internal val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("CustomDataClass_name") get() = this["name"] as $packageName.DataColumn + internal val $packageName.DataRow.name: kotlin.String @JvmName("CustomDataClass_name") get() = this["name"] as kotlin.String + internal val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("CustomDataClass_weight") get() = this["weight"] as $packageName.DataColumn + internal val $packageName.DataRow.weight: kotlin.Int? @JvmName("CustomDataClass_weight") get() = this["weight"] as kotlin.Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrameSchema generateCode - with default parameters`() { + val schema = typed.schema() + val code = schema.generateCodeImpl("SchemaMarker") + + val packageName = "org.jetbrains.kotlinx.dataframe" + + @Language("kotlin") + val expected = + """ + @DataSchema + interface SchemaMarker { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + + val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("SchemaMarker_age") get() = this["age"] as $packageName.DataColumn + val $packageName.DataRow.age: kotlin.Int @JvmName("SchemaMarker_age") get() = this["age"] as kotlin.Int + val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("SchemaMarker_city") get() = this["city"] as $packageName.DataColumn + val $packageName.DataRow.city: kotlin.String? @JvmName("SchemaMarker_city") get() = this["city"] as kotlin.String? + val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("SchemaMarker_name") get() = this["name"] as $packageName.DataColumn + val $packageName.DataRow.name: kotlin.String @JvmName("SchemaMarker_name") get() = this["name"] as kotlin.String + val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("SchemaMarker_weight") get() = this["weight"] as $packageName.DataColumn + val $packageName.DataRow.weight: kotlin.Int? @JvmName("SchemaMarker_weight") get() = this["weight"] as kotlin.Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrameSchema generateCode - with custom parameters`() { + val schema = typed.schema() + val code = schema.generateCodeImpl( + markerName = "SchemaMarker", + fields = true, + extensionProperties = false, + visibility = MarkerVisibility.EXPLICIT_PUBLIC, + ) + + @Language("kotlin") + val expected = + """ + @DataSchema + public interface SchemaMarker { + public val age: kotlin.Int + public val city: kotlin.String? + public val name: kotlin.String + public val weight: kotlin.Int? + } + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrameSchema generateInterfaces`() { + val schema = typed.schema() + val code = schema.generateInterfaces("SchemaInterface") + + @Language("kotlin") + val expected = + """ + @DataSchema + interface SchemaInterface { + val age: kotlin.Int + val city: kotlin.String? + val name: kotlin.String + val weight: kotlin.Int? + } + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrameSchema generateDataClasses - with default parameters`() { + val schema = typed.schema() + val code = schema.generateDataClasses("SchemaDataClass") + + @Language("kotlin") + val expected = + """ + @DataSchema + data class SchemaDataClass( + val age: Int, + val city: String?, + val name: String, + val weight: Int? + ) + """.trimIndent() + + code.value shouldBe expected } + + @Test + fun `DataFrameSchema generateDataClasses - with custom parameters`() { + val schema = typed.schema() + val code = schema.generateDataClasses( + markerName = "SchemaDataClass", + extensionProperties = true, + visibility = MarkerVisibility.EXPLICIT_PUBLIC, + useFqNames = false, + ) + + @Language("kotlin") + val expected = + """ + @DataSchema + public data class SchemaDataClass( + public val age: Int, + public val city: String?, + public val name: String, + public val weight: Int? + ) + + public val ColumnsScope.age: DataColumn @JvmName("SchemaDataClass_age") get() = this["age"] as DataColumn + public val DataRow.age: Int @JvmName("SchemaDataClass_age") get() = this["age"] as Int + public val ColumnsScope.city: DataColumn @JvmName("SchemaDataClass_city") get() = this["city"] as DataColumn + public val DataRow.city: String? @JvmName("SchemaDataClass_city") get() = this["city"] as String? + public val ColumnsScope.name: DataColumn @JvmName("SchemaDataClass_name") get() = this["name"] as DataColumn + public val DataRow.name: String @JvmName("SchemaDataClass_name") get() = this["name"] as String + public val ColumnsScope.weight: DataColumn @JvmName("SchemaDataClass_weight") get() = this["weight"] as DataColumn + public val DataRow.weight: Int? @JvmName("SchemaDataClass_weight") get() = this["weight"] as Int? + """.trimIndent() + + code.value shouldBe expected + } + + @Test + fun `DataFrame generateDataClasses - with name normalizer`() { + val dfWithSpecialNames = dataFrameOf("my_column", "another column", "third-column")(1, "test", 3.14) + val code = dfWithSpecialNames.generateDataClasses( + nameNormalizer = NameNormalizer { it.toCamelCaseByDelimiters() + "1" }, + ) + + @Language("kotlin") + val expected = + """ + @DataSchema + data class DataEntry( + @ColumnName("another column") + val anotherColumn1: String, + @ColumnName("my_column") + val myColumn1: Int, + @ColumnName("third-column") + val thirdColumn1: Double + ) + """.trimIndent() + + code.value shouldBe expected + } + + // endregion } diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt index 2c8475dec7..d05c285477 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt @@ -8,10 +8,15 @@ import org.jetbrains.kotlinx.dataframe.codeGen.MarkerVisibility import org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup import org.jetbrains.kotlinx.dataframe.columns.FrameColumn +import org.jetbrains.kotlinx.dataframe.documentation.AccessApi.ExtensionPropertiesApi import org.jetbrains.kotlinx.dataframe.documentation.DocumentationUrls import org.jetbrains.kotlinx.dataframe.documentation.ExcludeFromSources import org.jetbrains.kotlinx.dataframe.impl.codeGen.from import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema +import org.jetbrains.kotlinx.dataframe.util.GENERATE_CODE +import org.jetbrains.kotlinx.dataframe.util.GENERATE_CODE_REPLACE1 +import org.jetbrains.kotlinx.dataframe.util.GENERATE_CODE_REPLACE2 +import org.jetbrains.kotlinx.dataframe.util.GENERATE_INTERFACES // region Docs @@ -22,8 +27,7 @@ import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema * * These generated declarations can also be called "markers". * - * This is useful when working with the compiler plugin in cases where the schema - * cannot be inferred automatically from the source. + * $[USEFUL_WHEN] * * This function is a simplified wrapper for the more advanced and customizable * [CodeGenerator] API. @@ -34,6 +38,7 @@ import org.jetbrains.kotlinx.dataframe.schema.DataFrameSchema * @return [CodeString] – A value class wrapper for [String], containing * the generated Kotlin code of data schema declarations (markers). */ +@Suppress("ClassName") @ExcludeFromSources private interface CommonGenerateCodeDocs { @@ -42,6 +47,9 @@ private interface CommonGenerateCodeDocs { // "DataFrameSchema" or "DataFrame" interface RECEIVER + + // "Useful when statement" or "How to use" + interface USEFUL_WHEN } @ExcludeFromSources @@ -50,24 +58,30 @@ private interface Params { /** @param markerName The base name to use for generated data schema declarations (markers). */ interface MarkerName - /** @include [MarkerName] If not specified, generates a name from type [T]. */ + /** @include [MarkerName] If not specified, it generates a name from type [T]. */ interface MarkerNameOptional - /** @param fields Whether to generate fields (`val ...:`) inside the generated data schema declarations (markers). */ - interface Fields - - /** @param extensionProperties Whether to generate extension properties in addition to data schema declarations (markers). */ + /** + * @param extensionProperties Whether to generate [extension properties (column accessors)][ExtensionPropertiesApi] + * in addition to data schema declarations (markers). + * Useful if you don't use the compiler plugin, otherwise they are not needed; + * the compiler plugin and older Gradle/KSP plugin generate them automatically. + * Default is `false`. + */ interface ExtensionProperties - /** @param visibility Visibility modifier for the generated declarations (markers). */ + /** @param visibility Visibility modifier for the generated declarations (markers). Default is [MarkerVisibility.IMPLICIT_PUBLIC]. */ interface Visibility - /** @param useFqNames If `true`, fully qualified type names will be used in generated code. */ + /** @param useFqNames If `true`, fully qualified type names will be used in generated code. Default is `false`. */ interface UseFqNames /** - * @param nameNormalizer Strategy for converting column names (with spaces, underscores, etc.) to valid Kotlin identifiers. - * Columns will keep their original name inside the dataframe via [@ColumnName][ColumnName]. + * @param nameNormalizer Strategy for converting column names (with spaces, underscores, etc.) + * to Kotlin-style identifiers. + * Generated properties will still refer to columns by their actual name + * using the [@ColumnName][ColumnName] annotation. + * Default is [NameNormalizer.default][NameNormalizer.Companion.default]. */ interface NameNormalizer } @@ -78,91 +92,93 @@ private interface Params { /** * @include [CommonGenerateCodeDocs] - * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema]} - * {@set [CommonGenerateCodeDocs.TYPES] interfaces and extension properties} - * - * @include [Params.Fields] Default is `true`. - * @include [Params.ExtensionProperties] Default is `true`. - */ -public inline fun DataFrame.generateCode( - fields: Boolean = true, - extensionProperties: Boolean = true, -): CodeString = - schema().generateCode( - markerName = markerName(), - fields = fields, - extensionProperties = extensionProperties, - ) - -/** - * @include [CommonGenerateCodeDocs] - * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema]} - * {@set [CommonGenerateCodeDocs.TYPES] interfaces and extension properties} - * - * @include [Params.MarkerName] - * @include [Params.Fields] Default is `true`. - * @include [Params.ExtensionProperties] Default is `true`. - * @include [Params.Visibility] Default is [MarkerVisibility.IMPLICIT_PUBLIC]. + * @set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema] + * @set [CommonGenerateCodeDocs.TYPES] interfaces + * @set [CommonGenerateCodeDocs.USEFUL_WHEN] This is useful when working with the compiler plugin in cases where the schema + * cannot be inferred automatically from the source. {@include [DocumentationUrls.CompilerPlugin]} + * @include [Params.MarkerNameOptional] + * @include [Params.ExtensionProperties] + * @include [Params.Visibility] + * @include [Params.UseFqNames] + * @include [Params.NameNormalizer] */ -public fun DataFrame.generateCode( +public fun DataFrame.generateInterfaces( markerName: String, - fields: Boolean = true, - extensionProperties: Boolean = true, + extensionProperties: Boolean = false, visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, + useFqNames: Boolean = false, + nameNormalizer: NameNormalizer = NameNormalizer.default, ): CodeString = - schema().generateCode( + schema().generateCodeImpl( markerName = markerName, - fields = fields, extensionProperties = extensionProperties, visibility = visibility, + useFqNames = useFqNames, + nameNormalizer = nameNormalizer, + asDataClass = false, ) -/** - * @include [CommonGenerateCodeDocs] - * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema]} - * {@set [CommonGenerateCodeDocs.TYPES] interfaces} - * - * @include [Params.MarkerNameOptional] - */ -public inline fun DataFrame.generateInterfaces(): CodeString = - schema().generateInterfaces( +/** @include [DataFrame.generateInterfaces] */ +public inline fun DataFrame.generateInterfaces( + extensionProperties: Boolean = false, + visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, + useFqNames: Boolean = false, + nameNormalizer: NameNormalizer = NameNormalizer.default, +): CodeString = + schema().generateCodeImpl( markerName = markerName(), + extensionProperties = extensionProperties, + visibility = visibility, + useFqNames = useFqNames, + nameNormalizer = nameNormalizer, + asDataClass = false, ) /** * @include [CommonGenerateCodeDocs] - * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema]} - * {@set [CommonGenerateCodeDocs.TYPES] interfaces} - * + * @set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema] + * @set [CommonGenerateCodeDocs.TYPES] data classes + * @set [CommonGenerateCodeDocs.USEFUL_WHEN] This is useful when you want to: + * - Work with the data as regular Kotlin data classes. + * - Convert a dataframe to instantiated data classes with [`df.toListOf()`][DataFrame.toListOf]. + * - Work with data classes serialization. + * - Extract structured types for further use in your application. * @include [Params.MarkerNameOptional] + * @include [Params.ExtensionProperties] + * @include [Params.Visibility] + * @include [Params.UseFqNames] + * @include [Params.NameNormalizer] */ -public fun DataFrame.generateInterfaces(markerName: String): CodeString = - schema().generateInterfaces(markerName = markerName) +public fun DataFrame.generateDataClasses( + markerName: String, + extensionProperties: Boolean = false, + visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, + useFqNames: Boolean = false, + nameNormalizer: NameNormalizer = NameNormalizer.default, +): CodeString = + schema().generateCodeImpl( + markerName = markerName, + extensionProperties = extensionProperties, + visibility = visibility, + useFqNames = useFqNames, + nameNormalizer = nameNormalizer, + asDataClass = true, + ) -/** - * @include [CommonGenerateCodeDocs] - * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrame's][this] [schema][DataFrameSchema]} - * {@set [CommonGenerateCodeDocs.TYPES] data classes} - * - * @include [Params.MarkerNameOptional] - * @include [Params.ExtensionProperties] Default is `false`. - * @include [Params.Visibility] Default is [MarkerVisibility.IMPLICIT_PUBLIC]. - * @include [Params.UseFqNames] Default is `false`. - * @include [Params.NameNormalizer] Default is [NameNormalizer.default][NameNormalizer.Companion.default]. - */ +/** @include [DataFrame.generateDataClasses] */ public inline fun DataFrame.generateDataClasses( - markerName: String? = null, extensionProperties: Boolean = false, visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, useFqNames: Boolean = false, nameNormalizer: NameNormalizer = NameNormalizer.default, ): CodeString = - schema().generateDataClasses( - markerName = markerName ?: markerName(), + schema().generateCodeImpl( + markerName = markerName(), extensionProperties = extensionProperties, visibility = visibility, useFqNames = useFqNames, nameNormalizer = nameNormalizer, + asDataClass = true, ) // endregion @@ -171,57 +187,47 @@ public inline fun DataFrame.generateDataClasses( /** * @include [CommonGenerateCodeDocs] - * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrameSchema][this]} - * {@set [CommonGenerateCodeDocs.TYPES] interfaces and extension properties} - * + * @set [CommonGenerateCodeDocs.RECEIVER] [DataFrameSchema][this] + * @set [CommonGenerateCodeDocs.TYPES] interfaces + * @set [CommonGenerateCodeDocs.USEFUL_WHEN] This is useful when working with the compiler plugin in cases where the schema + * cannot be inferred automatically from the source. {@include [DocumentationUrls.CompilerPlugin]} * @include [Params.MarkerName] - * @include [Params.Fields] Default is `true`. - * @include [Params.ExtensionProperties] Default is `true`. - * @include [Params.Visibility] Default is [MarkerVisibility.IMPLICIT_PUBLIC]. + * @include [Params.ExtensionProperties] + * @include [Params.Visibility] + * @include [Params.UseFqNames] + * @include [Params.NameNormalizer] */ -@JvmName("generateCodeForSchema") -public fun DataFrameSchema.generateCode( +@JvmName("generateInterfacesForSchema") +public fun DataFrameSchema.generateInterfaces( markerName: String, - fields: Boolean = true, - extensionProperties: Boolean = true, + extensionProperties: Boolean = false, visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, -): CodeString { - val codeGen = CodeGenerator.create() - return codeGen.generate( - schema = this, - name = markerName, - fields = fields, + useFqNames: Boolean = false, + nameNormalizer: NameNormalizer = NameNormalizer.default, +): CodeString = + generateCodeImpl( + markerName = markerName, extensionProperties = extensionProperties, - isOpen = true, visibility = visibility, - ).code.declarations.toCodeString() -} - -/** - * @include [CommonGenerateCodeDocs] - * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrameSchema][this]} - * {@set [CommonGenerateCodeDocs.TYPES] interfaces} - * - * @include [Params.MarkerName] - */ -@JvmName("generateInterfacesForSchema") -public fun DataFrameSchema.generateInterfaces(markerName: String): CodeString = - generateCode( - markerName = markerName, - fields = true, - extensionProperties = false, + useFqNames = useFqNames, + nameNormalizer = nameNormalizer, + asDataClass = false, ) /** * @include [CommonGenerateCodeDocs] * {@set [CommonGenerateCodeDocs.RECEIVER] [DataFrameSchema][this]} * {@set [CommonGenerateCodeDocs.TYPES] data classes} - * + * @set [CommonGenerateCodeDocs.USEFUL_WHEN] This is useful when you want to: + * - Work with the data as regular Kotlin data classes. + * - Convert a dataframe to instantiated data classes with [`df.toListOf()`][DataFrame.toListOf]. + * - Work with data classes serialization. + * - Extract structured types for further use in your application. * @include [Params.MarkerName] - * @include [Params.ExtensionProperties] Default is `false`. - * @include [Params.Visibility] Default is [MarkerVisibility.IMPLICIT_PUBLIC]. - * @include [Params.UseFqNames] Default is `false`. - * @include [Params.NameNormalizer] Default is [NameNormalizer.default][NameNormalizer.Companion.default]. + * @include [Params.ExtensionProperties] + * @include [Params.Visibility] + * @include [Params.UseFqNames] + * @include [Params.NameNormalizer] */ @JvmName("generateDataClassesForSchema") public fun DataFrameSchema.generateDataClasses( @@ -230,19 +236,15 @@ public fun DataFrameSchema.generateDataClasses( visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, useFqNames: Boolean = false, nameNormalizer: NameNormalizer = NameNormalizer.default, -): CodeString { - val codeGen = CodeGenerator.create(useFqNames) - return codeGen.generate( - schema = this, - name = markerName, - fields = true, +): CodeString = + generateCodeImpl( + markerName = markerName, extensionProperties = extensionProperties, - isOpen = false, visibility = visibility, + useFqNames = useFqNames, + nameNormalizer = nameNormalizer, asDataClass = true, - fieldNameNormalizer = nameNormalizer, - ).code.declarations.toCodeString() -} + ) // endregion @@ -262,6 +264,8 @@ public val NameNormalizer.Companion.default: NameNormalizer get() = NameNormaliz /** * A value class wrapper for [String], containing * generated Kotlin code of data schema declarations (markers) and optionally extension properties. + * + * @see CodeString.print */ @JvmInline public value class CodeString(public val value: String) { @@ -270,3 +274,80 @@ public value class CodeString(public val value: String) { @PublishedApi internal fun String.toCodeString(): CodeString = CodeString(this) + +/** + * Generates a [CodeString] containing generated [@DataSchema][DataSchema] declarations (markers). + */ +@PublishedApi +internal fun DataFrameSchema.generateCodeImpl( + markerName: String, + extensionProperties: Boolean, + visibility: MarkerVisibility, + useFqNames: Boolean, + nameNormalizer: NameNormalizer, + asDataClass: Boolean, +): CodeString { + val codeGen = CodeGenerator.create(useFqNames) + return codeGen.generate( + schema = this, + name = markerName, + fields = true, + extensionProperties = extensionProperties, + isOpen = false, + visibility = visibility, + asDataClass = asDataClass, + fieldNameNormalizer = nameNormalizer, + ).code.declarations.toCodeString() +} + +// region Deprecated + +@Deprecated( + message = GENERATE_CODE, + replaceWith = ReplaceWith(GENERATE_CODE_REPLACE1), + level = DeprecationLevel.WARNING, +) +public inline fun DataFrame.generateCode( + fields: Boolean = true, + extensionProperties: Boolean = true, +): CodeString { + val codeGen = CodeGenerator.create() + return codeGen.generate( + schema = this.schema(), + name = markerName(), + fields = fields, + extensionProperties = extensionProperties, + isOpen = true, + visibility = MarkerVisibility.IMPLICIT_PUBLIC, + ).code.declarations.toCodeString() +} + +@Deprecated( + message = GENERATE_CODE, + replaceWith = ReplaceWith(GENERATE_CODE_REPLACE2), + level = DeprecationLevel.WARNING, +) +public fun DataFrame.generateCode( + markerName: String, + fields: Boolean = true, + extensionProperties: Boolean = true, + visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, +): CodeString { + val codeGen = CodeGenerator.create() + return codeGen.generate( + schema = this.schema(), + name = markerName, + fields = fields, + extensionProperties = extensionProperties, + isOpen = true, + visibility = visibility, + ).code.declarations.toCodeString() +} + +@Deprecated(message = GENERATE_INTERFACES, level = DeprecationLevel.HIDDEN) +public inline fun DataFrame.generateInterfaces(): CodeString = generateInterfaces() + +@Deprecated(message = GENERATE_INTERFACES, level = DeprecationLevel.HIDDEN) +public fun DataFrame.generateInterfaces(markerName: String): CodeString = generateInterfaces(markerName) + +// endregion diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt index 15e7772fdb..772b647fe9 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt @@ -26,6 +26,9 @@ internal interface DocumentationUrls { /** [See Column Selectors on the documentation website.]({@include [Url]}/columnselectors.html) */ interface ColumnSelectors + /** [See Compiler Plugin on the documentation website.]({@include [Url]}/compiler-plugin.html) */ + interface CompilerPlugin + interface DataRow { /** [See Row Expressions on the documentation website.]({@include [Url]}/datarow.html#row-expressions) */ interface RowExpressions @@ -117,6 +120,6 @@ internal interface DocumentationUrls { /** [See `explode` on the documentation website.]({@include [Url]}/explode.html) */ interface Explode - /** [See `Data Schemas/Data Classes Generation` on the documentation website.]({@include [Url]}/dataschema-data-classes-generation.html) */ + /** [See `Data Schemas/Data Classes Generation` on the documentation website.]({@include [Url]}/dataschemagenerationmethods.html) */ interface DataSchemaGeneration } diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt index ea5f4b78cc..76e2c58222 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt @@ -113,6 +113,15 @@ internal const val TO_URL_REPLACE = "toUrl()" internal const val FILTER_BY = "This function is deprecated in favor of `filter { }`. $MESSAGE_1_0" internal const val FILTER_BY_REPLACE = "filter { column }" +internal const val GENERATE_CODE = + "This function has been deprecated in favor of the more explicit `generateInterfaces()`. The `fields` parameter has also been removed. Use `CodeGenerator` explicitly, if you need it. $MESSAGE_1_0" + +internal const val GENERATE_CODE_REPLACE1 = "this.generateInterfaces(extensionProperties = extensionProperties)" +internal const val GENERATE_CODE_REPLACE2 = + "this.generateInterfaces(markerName = markerName, extensionProperties = extensionProperties, visibility = visibility)" + +internal const val GENERATE_INTERFACES = "This function is just here for binary compatibility. $MESSAGE_1_0" + // endregion // region WARNING in 1.0, ERROR in 1.1 diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt index 0ef9f1fefc..b9398089eb 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt @@ -8,7 +8,6 @@ import org.jetbrains.kotlinx.dataframe.DataColumn import org.jetbrains.kotlinx.dataframe.DataRow import org.jetbrains.kotlinx.dataframe.api.dataFrameOf import org.jetbrains.kotlinx.dataframe.api.dropNulls -import org.jetbrains.kotlinx.dataframe.api.generateCode import org.jetbrains.kotlinx.dataframe.api.generateDataClasses import org.jetbrains.kotlinx.dataframe.api.generateInterfaces import org.jetbrains.kotlinx.dataframe.api.groupBy @@ -16,7 +15,6 @@ import org.jetbrains.kotlinx.dataframe.api.move import org.jetbrains.kotlinx.dataframe.api.schema import org.jetbrains.kotlinx.dataframe.api.toCodeString import org.jetbrains.kotlinx.dataframe.api.under -import org.jetbrains.kotlinx.dataframe.codeGen.NameNormalizer import org.jetbrains.kotlinx.dataframe.columns.ColumnGroup import org.jetbrains.kotlinx.dataframe.impl.codeGen.ReplCodeGenerator import org.jetbrains.kotlinx.dataframe.impl.codeGen.ReplCodeGeneratorImpl @@ -399,110 +397,6 @@ class CodeGenerationTests : BaseTest() { code2 shouldBe expected } - @Test - fun `DataFrame generateCode - default parameters`() { - val code = typed.generateCode() - - val packageName = "org.jetbrains.kotlinx.dataframe" - - @Language("kotlin") - val expected = - """ - @DataSchema - interface Person { - val age: kotlin.Int - val city: kotlin.String? - val name: kotlin.String - val weight: kotlin.Int? - } - - val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("Person_age") get() = this["age"] as $packageName.DataColumn - val $packageName.DataRow.age: kotlin.Int @JvmName("Person_age") get() = this["age"] as kotlin.Int - val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("Person_city") get() = this["city"] as $packageName.DataColumn - val $packageName.DataRow.city: kotlin.String? @JvmName("Person_city") get() = this["city"] as kotlin.String? - val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("Person_name") get() = this["name"] as $packageName.DataColumn - val $packageName.DataRow.name: kotlin.String @JvmName("Person_name") get() = this["name"] as kotlin.String - val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("Person_weight") get() = this["weight"] as $packageName.DataColumn - val $packageName.DataRow.weight: kotlin.Int? @JvmName("Person_weight") get() = this["weight"] as kotlin.Int? - """.trimIndent() - - code.value shouldBe expected - } - - @Test - fun `DataFrame generateCode - custom parameters`() { - val code = typed.generateCode(fields = false, extensionProperties = true) - - val packageName = "org.jetbrains.kotlinx.dataframe" - - @Language("kotlin") - val expected = - """ - @DataSchema - interface Person { } - - val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("Person_age") get() = this["age"] as $packageName.DataColumn - val $packageName.DataRow.age: kotlin.Int @JvmName("Person_age") get() = this["age"] as kotlin.Int - val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("Person_city") get() = this["city"] as $packageName.DataColumn - val $packageName.DataRow.city: kotlin.String? @JvmName("Person_city") get() = this["city"] as kotlin.String? - val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("Person_name") get() = this["name"] as $packageName.DataColumn - val $packageName.DataRow.name: kotlin.String @JvmName("Person_name") get() = this["name"] as kotlin.String - val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("Person_weight") get() = this["weight"] as $packageName.DataColumn - val $packageName.DataRow.weight: kotlin.Int? @JvmName("Person_weight") get() = this["weight"] as kotlin.Int? - """.trimIndent() - - code.value shouldBe expected - } - - @Test - fun `DataFrame generateCode - with marker name and default parameters`() { - val code = typed.generateCode("CustomMarker") - - val packageName = "org.jetbrains.kotlinx.dataframe" - - @Language("kotlin") - val expected = - """ - @DataSchema - interface CustomMarker { - val age: kotlin.Int - val city: kotlin.String? - val name: kotlin.String - val weight: kotlin.Int? - } - - val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("CustomMarker_age") get() = this["age"] as $packageName.DataColumn - val $packageName.DataRow.age: kotlin.Int @JvmName("CustomMarker_age") get() = this["age"] as kotlin.Int - val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("CustomMarker_city") get() = this["city"] as $packageName.DataColumn - val $packageName.DataRow.city: kotlin.String? @JvmName("CustomMarker_city") get() = this["city"] as kotlin.String? - val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("CustomMarker_name") get() = this["name"] as $packageName.DataColumn - val $packageName.DataRow.name: kotlin.String @JvmName("CustomMarker_name") get() = this["name"] as kotlin.String - val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("CustomMarker_weight") get() = this["weight"] as $packageName.DataColumn - val $packageName.DataRow.weight: kotlin.Int? @JvmName("CustomMarker_weight") get() = this["weight"] as kotlin.Int? - """.trimIndent() - - code.value shouldBe expected - } - - @Test - fun `DataFrame generateCode - with marker name and custom parameters`() { - val code = typed.generateCode( - markerName = "CustomMarker", - fields = false, - extensionProperties = false, - visibility = MarkerVisibility.INTERNAL, - ) - - @Language("kotlin") - val expected = - """ - @DataSchema - internal interface CustomMarker { } - """.trimIndent() - - code.value shouldBe expected - } - @Test fun `DataFrame generateInterfaces`() { val code = typed.generateInterfaces() @@ -595,62 +489,6 @@ class CodeGenerationTests : BaseTest() { code.value shouldBe expected } - @Test - fun `DataFrameSchema generateCode - with default parameters`() { - val schema = typed.schema() - val code = schema.generateCode("SchemaMarker") - - val packageName = "org.jetbrains.kotlinx.dataframe" - - @Language("kotlin") - val expected = - """ - @DataSchema - interface SchemaMarker { - val age: kotlin.Int - val city: kotlin.String? - val name: kotlin.String - val weight: kotlin.Int? - } - - val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("SchemaMarker_age") get() = this["age"] as $packageName.DataColumn - val $packageName.DataRow.age: kotlin.Int @JvmName("SchemaMarker_age") get() = this["age"] as kotlin.Int - val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("SchemaMarker_city") get() = this["city"] as $packageName.DataColumn - val $packageName.DataRow.city: kotlin.String? @JvmName("SchemaMarker_city") get() = this["city"] as kotlin.String? - val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("SchemaMarker_name") get() = this["name"] as $packageName.DataColumn - val $packageName.DataRow.name: kotlin.String @JvmName("SchemaMarker_name") get() = this["name"] as kotlin.String - val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("SchemaMarker_weight") get() = this["weight"] as $packageName.DataColumn - val $packageName.DataRow.weight: kotlin.Int? @JvmName("SchemaMarker_weight") get() = this["weight"] as kotlin.Int? - """.trimIndent() - - code.value shouldBe expected - } - - @Test - fun `DataFrameSchema generateCode - with custom parameters`() { - val schema = typed.schema() - val code = schema.generateCode( - markerName = "SchemaMarker", - fields = true, - extensionProperties = false, - visibility = MarkerVisibility.EXPLICIT_PUBLIC, - ) - - @Language("kotlin") - val expected = - """ - @DataSchema - public interface SchemaMarker { - public val age: kotlin.Int - public val city: kotlin.String? - public val name: kotlin.String - public val weight: kotlin.Int? - } - """.trimIndent() - - code.value shouldBe expected - } - @Test fun `DataFrameSchema generateInterfaces`() { val schema = typed.schema() From fcd76d0d3f3492dbd1fadc0316800a648e21f15b Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Tue, 22 Jul 2025 15:35:03 +0200 Subject: [PATCH 4/8] updated DataSchemaGenerationMethods.md on the website to reflect the changes --- .../kotlinx/dataframe/api/generateCode.kt | 2 +- .../concepts/DataSchemaGenerationMethods.md | 194 ++++++------------ 2 files changed, 67 insertions(+), 129 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt index d05c285477..b464a0d0ba 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt @@ -65,7 +65,7 @@ private interface Params { * @param extensionProperties Whether to generate [extension properties (column accessors)][ExtensionPropertiesApi] * in addition to data schema declarations (markers). * Useful if you don't use the compiler plugin, otherwise they are not needed; - * the compiler plugin and older Gradle/KSP plugin generate them automatically. + * the compiler plugin, notebooks, and older Gradle/KSP plugin generate them automatically. * Default is `false`. */ interface ExtensionProperties diff --git a/docs/StardustDocs/topics/concepts/DataSchemaGenerationMethods.md b/docs/StardustDocs/topics/concepts/DataSchemaGenerationMethods.md index 602c66db6e..d06819e6ab 100644 --- a/docs/StardustDocs/topics/concepts/DataSchemaGenerationMethods.md +++ b/docs/StardustDocs/topics/concepts/DataSchemaGenerationMethods.md @@ -14,83 +14,9 @@ Generate useful Kotlin definitions based on your DataFrame structure. -Special utility functions that generate code of useful Kotlin definitions (returned as a `String`) +Special utility functions that generate code of useful Kotlin definitions (returned as a `String`) based on the current `DataFrame` schema. - -## generateInterfaces - -```kotlin -inline fun DataFrame.generateInterfaces(): CodeString - -fun DataFrame.generateInterfaces(markerName: String): CodeString -``` - -Generates [`@DataSchema`](schemas.md) interfaces for this `DataFrame` -(including all nested `DataFrame` columns and column groups) as Kotlin interfaces. - -This is useful when working with the [compiler plugin](Compiler-Plugin.md) -in cases where the schema cannot be inferred automatically from the source. - -### Arguments {id="generateInterfaces-arguments"} -* `markerName`: `String` – The base name to use for generated interfaces. - If not specified, uses the `T` type argument of `DataFrame` simple name. - -### Returns {id="generateInterfaces-returns"} -* `CodeString` – A value class wrapper for `String`, containing - the generated Kotlin code of `@DataSchema` interfaces without extension properties. - -### Examples {id="generateInterfaces-examples"} - - - - -```kotlin -df -``` - - - - - - - -```kotlin -df.generateInterfaces() -``` - - - -Output: -```kotlin -@DataSchema(isOpen = false) -interface _DataFrameType11 { - val amount: kotlin.Double - val orderId: kotlin.Int -} - -@DataSchema -interface _DataFrameType1 { - val orders: List<_DataFrameType11> - val user: kotlin.String -} -``` - -By adding these interfaces to your project with the [compiler plugin](Compiler-Plugin.md) enabled, -you'll gain full support for the [extension properties API](extensionPropertiesApi.md) and type-safe operations. - -Use [`cast`](cast.md) to apply the generated schema to a `DataFrame`: - - - -```kotlin -df.cast<_DataFrameType1>().filter { orders.all { orderId >= 102 } } -``` - - - - - ## generateDataClasses ```kotlin @@ -103,29 +29,37 @@ inline fun DataFrame.generateDataClasses( ): CodeString ``` -Generates Kotlin data classes corresponding to the `DataFrame` schema +Generates Kotlin data classes corresponding to the `DataFrame` schema (including all nested `DataFrame` columns and column groups). Useful when you want to: - Work with the data as regular Kotlin data classes. +- Convert a dataframe to instantiated data classes with `df.toListOf()`. - Work with data classes serialization. - Extract structured types for further use in your application. ### Arguments {id="generateDataClasses-arguments"} + * `markerName`: `String?` — The base name to use for generated data classes. If `null`, uses the `T` type argument of `DataFrame` simple name. Default: `null`. -* `extensionProperties`: `Boolean` – Whether to generate extension properties in addition to `data class` declarations. +* `extensionProperties`: `Boolean` – Whether to generate extension properties in addition to `interface` declarations. + Useful if you don't use the [compiler plugin](Compiler-Plugin.md), otherwise they are not needed; + the [compiler plugin](Compiler-Plugin.md), [notebooks](schemasJupyter.md), + and older [Gradle/KSP plugin](schemasGradle.md) generate them automatically. Default: `false`. * `visibility`: `MarkerVisibility` – Visibility modifier for the generated declarations. Default: `MarkerVisibility.IMPLICIT_PUBLIC`. * `useFqNames`: `Boolean` – If `true`, fully qualified type names will be used in generated code. Default: `false`. -* `nameNormalizer`: `NameNormalizer` – Strategy for converting column names (with spaces, underscores, etc.) to valid Kotlin identifiers. +* `nameNormalizer`: `NameNormalizer` – Strategy for converting column names (with spaces, underscores, etc.) to + Kotlin-style identifiers. + Generated properties will still refer to columns by their actual name using the `@ColumnName` annotation. Default: `NameNormalizer.default`. ### Returns {id="generateDataClasses-returns"} + * `CodeString` – A value class wrapper for `String`, containing the generated Kotlin code of `data class` declarations and optionally extension properties. @@ -140,6 +74,7 @@ df.generateDataClasses("Customer") Output: + ```kotlin @DataSchema data class Customer1( @@ -164,90 +99,93 @@ val customers: List = df.cast().toList() -## generateCode +## generateInterfaces ```kotlin -inline fun DataFrame.generateCode( - fields: Boolean = true, - extensionProperties: Boolean = true, -): CodeString +inline fun DataFrame.generateInterfaces(): CodeString -fun DataFrame.generateCode( - markerName: String, - fields: Boolean = true, - extensionProperties: Boolean = true, - visibility: MarkerVisibility = MarkerVisibility.IMPLICIT_PUBLIC, -): CodeString +fun DataFrame.generateInterfaces(markerName: String): CodeString ``` -Generates a data schema interface as [`generateInterfaces()`](#generateinterfaces), -along with explicit [extension properties](extensionPropertiesApi.md). -Useful if you don't use the [compiler plugin](Compiler-Plugin.md). +Generates [`@DataSchema`](schemas.md) interfaces for this `DataFrame` +(including all nested `DataFrame` columns and column groups) as Kotlin interfaces. + +This is useful when working with the [compiler plugin](Compiler-Plugin.md) +in cases where the schema cannot be inferred automatically from the source. + +### Arguments {id="generateInterfaces-arguments"} + +* `markerName`: `String?` — The base name to use for generated interfaces. + If `null`, uses the `T` type argument of `DataFrame` simple name. + Default: `null`. +* `extensionProperties`: `Boolean` – Whether to generate extension properties in addition to `interface` declarations. + Useful if you don't use the [compiler plugin](Compiler-Plugin.md), otherwise they are not needed; + the [compiler plugin](Compiler-Plugin.md), [notebooks](schemasJupyter.md), + and older [Gradle/KSP plugin](schemasGradle.md) generate them automatically. + Default: `false`. +* `visibility`: `MarkerVisibility` – Visibility modifier for the generated declarations. + Default: `MarkerVisibility.IMPLICIT_PUBLIC`. +* `useFqNames`: `Boolean` – If `true`, fully qualified type names will be used in generated code. + Default: `false`. +* `nameNormalizer`: `NameNormalizer` – Strategy for converting column names (with spaces, underscores, etc.) to + Kotlin-style identifiers. + Generated properties will still refer to columns by their actual name using the `@ColumnName` annotation. + Default: `NameNormalizer.default`. + +### Returns {id="generateInterfaces-returns"} + +* `CodeString` – A value class wrapper for `String`, containing + the generated Kotlin code of `@DataSchema` interfaces without extension properties. -### Arguments {id="generateCode-arguments"} -* `markerName`: `String` – The base name to use for generated interfaces. -If not specified, uses the `T` type argument of `DataFrame` simple name. -* `fields`: `Boolean` – Whether to generate fields (`val ...`) inside interfaces. -Default: `true`. -* `extensionProperties`: `Boolean` – Whether to generate extension properties for the schema. -Default: `true`. -* `visibility`: `MarkerVisibility` – Visibility modifier for the generated declarations. -Default: `MarkerVisibility.IMPLICIT_PUBLIC`. +### Examples {id="generateInterfaces-examples"} -### Returns {id="generateCode-returns"} -* `CodeString` – A value class wrapper for `String`, containing -the generated Kotlin code of `@DataSchema` interfaces and/or extension properties. + + +```kotlin +df +``` + + -### Examples {id="generateCode-examples"} + - + ```kotlin -df.generateCode("Customer") +df.generateInterfaces() ``` Output: + ```kotlin @DataSchema(isOpen = false) -interface Customer1 { +interface _DataFrameType11 { val amount: kotlin.Double val orderId: kotlin.Int } -val org.jetbrains.kotlinx.dataframe.ColumnsContainer.amount: org.jetbrains.kotlinx.dataframe.DataColumn @JvmName("Customer1_amount") get() = this["amount"] as org.jetbrains.kotlinx.dataframe.DataColumn -val org.jetbrains.kotlinx.dataframe.DataRow.amount: kotlin.Double @JvmName("Customer1_amount") get() = this["amount"] as kotlin.Double -val org.jetbrains.kotlinx.dataframe.ColumnsContainer.orderId: org.jetbrains.kotlinx.dataframe.DataColumn @JvmName("Customer1_orderId") get() = this["orderId"] as org.jetbrains.kotlinx.dataframe.DataColumn -val org.jetbrains.kotlinx.dataframe.DataRow.orderId: kotlin.Int @JvmName("Customer1_orderId") get() = this["orderId"] as kotlin.Int - @DataSchema -interface Customer { - val orders: List +interface _DataFrameType1 { + val orders: List<_DataFrameType11> val user: kotlin.String } - -val org.jetbrains.kotlinx.dataframe.ColumnsContainer.orders: org.jetbrains.kotlinx.dataframe.DataColumn> @JvmName("Customer_orders") get() = this["orders"] as org.jetbrains.kotlinx.dataframe.DataColumn> -val org.jetbrains.kotlinx.dataframe.DataRow.orders: org.jetbrains.kotlinx.dataframe.DataFrame @JvmName("Customer_orders") get() = this["orders"] as org.jetbrains.kotlinx.dataframe.DataFrame -val org.jetbrains.kotlinx.dataframe.ColumnsContainer.user: org.jetbrains.kotlinx.dataframe.DataColumn @JvmName("Customer_user") get() = this["user"] as org.jetbrains.kotlinx.dataframe.DataColumn -val org.jetbrains.kotlinx.dataframe.DataRow.user: kotlin.String @JvmName("Customer_user") get() = this["user"] as kotlin.String ``` -By adding this generated code to your project, you can use the [extension properties API](extensionPropertiesApi.md) -for fully type-safe column access and transformations. +By adding these interfaces to your project with the [compiler plugin](Compiler-Plugin.md) enabled, +you'll gain full support for the [extension properties API](extensionPropertiesApi.md) and type-safe operations. Use [`cast`](cast.md) to apply the generated schema to a `DataFrame`: - + ```kotlin -df.cast() - .add("ordersTotal") { orders.sumOf { it.amount } } - .filter { user.startsWith("A") } - .rename { user }.into("customer") +df.cast<_DataFrameType1>().filter { orders.all { orderId >= 102 } } ``` - + + From 0c1a34888d6418e4c3250b9823954be164c10a06 Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Tue, 22 Jul 2025 15:44:47 +0200 Subject: [PATCH 5/8] fixup! deprecated generateCode in favor of generateInterfaces. Reworked generateCode.kt from feedback --- .../dataframe/util/deprecationMessages.kt | 18 +++++++++--------- .../notebook_test_generate_docs_1.html | 12 ++++++------ .../api/rename/notebook_test_rename_3.html | 6 +++--- .../api/rename/notebook_test_rename_4.html | 6 +++--- .../api/rename/notebook_test_rename_5.html | 6 +++--- .../notebook_test_quickstart_10.html | 6 +++--- .../notebook_test_quickstart_11.html | 12 ++++++------ .../notebook_test_quickstart_12.html | 6 +++--- .../notebook_test_quickstart_13.html | 6 +++--- .../notebook_test_quickstart_14.html | 6 +++--- .../quickstart/notebook_test_quickstart_3.html | 6 +++--- .../quickstart/notebook_test_quickstart_4.html | 6 +++--- .../quickstart/notebook_test_quickstart_5.html | 6 +++--- .../quickstart/notebook_test_quickstart_6.html | 6 +++--- .../quickstart/notebook_test_quickstart_7.html | 6 +++--- .../quickstart/notebook_test_quickstart_8.html | 6 +++--- 16 files changed, 60 insertions(+), 60 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt index 76e2c58222..c24f934432 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/util/deprecationMessages.kt @@ -113,15 +113,6 @@ internal const val TO_URL_REPLACE = "toUrl()" internal const val FILTER_BY = "This function is deprecated in favor of `filter { }`. $MESSAGE_1_0" internal const val FILTER_BY_REPLACE = "filter { column }" -internal const val GENERATE_CODE = - "This function has been deprecated in favor of the more explicit `generateInterfaces()`. The `fields` parameter has also been removed. Use `CodeGenerator` explicitly, if you need it. $MESSAGE_1_0" - -internal const val GENERATE_CODE_REPLACE1 = "this.generateInterfaces(extensionProperties = extensionProperties)" -internal const val GENERATE_CODE_REPLACE2 = - "this.generateInterfaces(markerName = markerName, extensionProperties = extensionProperties, visibility = visibility)" - -internal const val GENERATE_INTERFACES = "This function is just here for binary compatibility. $MESSAGE_1_0" - // endregion // region WARNING in 1.0, ERROR in 1.1 @@ -189,6 +180,15 @@ internal const val SINGLE_COL_REPLACE = "this.allCols().filter(condition).single internal const val SINGLE_PLAIN_REPLACE = "this.cols().filter(condition).single()" internal const val SINGLE_SET_REPLACE = "this.filter(condition).single()" +internal const val GENERATE_CODE = + "This function has been deprecated in favor of the more explicit `generateInterfaces()`. The `fields` parameter has also been removed. Use `CodeGenerator` explicitly, if you need it. $MESSAGE_1_1" + +internal const val GENERATE_CODE_REPLACE1 = "this.generateInterfaces(extensionProperties = extensionProperties)" +internal const val GENERATE_CODE_REPLACE2 = + "this.generateInterfaces(markerName = markerName, extensionProperties = extensionProperties, visibility = visibility)" + +internal const val GENERATE_INTERFACES = "This function is just here for binary compatibility. $MESSAGE_1_1" + // endregion // region keep across releases diff --git a/docs/StardustDocs/resources/api/generate_docs/notebook_test_generate_docs_1.html b/docs/StardustDocs/resources/api/generate_docs/notebook_test_generate_docs_1.html index 546f685b5f..8ca7a8ece0 100644 --- a/docs/StardustDocs/resources/api/generate_docs/notebook_test_generate_docs_1.html +++ b/docs/StardustDocs/resources/api/generate_docs/notebook_test_generate_docs_1.html @@ -177,7 +177,7 @@ -
+

@@ -458,23 +458,23 @@ /**/ /**/ /**/ -call_DataFrame(function() { DataFrame.renderTable(234881024) }); +call_DataFrame(function() { DataFrame.renderTable(838860800) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/api/rename/notebook_test_rename_3.html b/docs/StardustDocs/resources/api/rename/notebook_test_rename_3.html index ed310c0195..d9d5649f29 100644 --- a/docs/StardustDocs/resources/api/rename/notebook_test_rename_3.html +++ b/docs/StardustDocs/resources/api/rename/notebook_test_rename_3.html @@ -177,7 +177,7 @@ -
+

@@ -460,10 +460,10 @@ call_DataFrame(function() { DataFrame.addTable({ cols: [{ name: "ColumnA", children: [], rightAlign: true, values: ["1","2"] }, { name: "column_b", children: [], rightAlign: false, values: ["a","b"] }, { name: "COLUMN-C", children: [], rightAlign: false, values: ["true","false"] }, -], id: 234881027, rootId: 234881027, totalRows: 2 } ) }); +], id: 838860803, rootId: 838860803, totalRows: 2 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881027) }); +call_DataFrame(function() { DataFrame.renderTable(838860803) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/api/rename/notebook_test_rename_4.html b/docs/StardustDocs/resources/api/rename/notebook_test_rename_4.html index 4809525d19..06cd98bfdb 100644 --- a/docs/StardustDocs/resources/api/rename/notebook_test_rename_4.html +++ b/docs/StardustDocs/resources/api/rename/notebook_test_rename_4.html @@ -177,7 +177,7 @@ -
+

@@ -460,10 +460,10 @@ call_DataFrame(function() { DataFrame.addTable({ cols: [{ name: "columnA", children: [], rightAlign: true, values: ["1","2"] }, { name: "column_b", children: [], rightAlign: false, values: ["a","b"] }, { name: "columnC", children: [], rightAlign: false, values: ["true","false"] }, -], id: 234881028, rootId: 234881028, totalRows: 2 } ) }); +], id: 838860804, rootId: 838860804, totalRows: 2 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881028) }); +call_DataFrame(function() { DataFrame.renderTable(838860804) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/api/rename/notebook_test_rename_5.html b/docs/StardustDocs/resources/api/rename/notebook_test_rename_5.html index 7ef5f4995a..e7dea84bae 100644 --- a/docs/StardustDocs/resources/api/rename/notebook_test_rename_5.html +++ b/docs/StardustDocs/resources/api/rename/notebook_test_rename_5.html @@ -177,7 +177,7 @@ -
+

@@ -460,10 +460,10 @@ call_DataFrame(function() { DataFrame.addTable({ cols: [{ name: "columnA", children: [], rightAlign: true, values: ["1","2"] }, { name: "columnB", children: [], rightAlign: false, values: ["a","b"] }, { name: "columnC", children: [], rightAlign: false, values: ["true","false"] }, -], id: 234881029, rootId: 234881029, totalRows: 2 } ) }); +], id: 838860805, rootId: 838860805, totalRows: 2 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881029) }); +call_DataFrame(function() { DataFrame.renderTable(838860805) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_10.html b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_10.html index e5e5adc85d..bb0c7bc1d7 100644 --- a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_10.html +++ b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_10.html @@ -177,7 +177,7 @@ -
+

... showing only top 20 of 24 rows

@@ -461,10 +461,10 @@ { name: "starsCount", children: [], rightAlign: true, values: ["6120","1241","12926","1066","39402","1737","5688","1074","1181","1072","1110","1058","1815","1017","7101","2424","2836","2628","6059","1133"] }, { name: "\">topics", children: [], rightAlign: false, values: ["[ideavim, intellij, intellij-pl..., ...]","[domain-specific-language, dsl]","[code-editor, ide, intellij, i..., i...]","[intellij-idea, intellij-plugin, scala]","[compiler, gradle-plugin, intel..., ...]","[]","[dao, kotlin, orm, sql]","[kotlin]","[gitignore, ignore-files, intellij, ...]","[]","[]","[gradle, gradle-intellij-plugin, ...]","[sprite, svg, svg-sprite, svg-s..., ...]","[hacktoberfest, jetbrains, plugin, ...]","[c, compiler, kotlin, llvm, objective-c]","[create-react-app, jetbrains-ui, ...]","[components, jetbrains-ui, react]","[]","[coding-font, font, ligatures, ...]","[intellij, intellij-idea, intel..., ...]"] }, { name: "isIntellij", children: [], rightAlign: false, values: ["true","false","true","true","false","true","false","false","true","false","false","true","false","false","false","false","false","false","false","true"] }, -], id: 234881036, rootId: 234881036, totalRows: 24 } ) }); +], id: 838860812, rootId: 838860812, totalRows: 24 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881036) }); +call_DataFrame(function() { DataFrame.renderTable(838860812) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_11.html b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_11.html index 47dffb7b7d..d131348803 100644 --- a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_11.html +++ b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_11.html @@ -177,7 +177,7 @@ -
+

@@ -458,8 +458,8 @@ /**/ /**/ /**/ -call_DataFrame(function() { DataFrame.renderTable(234881037) }); +call_DataFrame(function() { DataFrame.renderTable(838860813) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_12.html b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_12.html index ee32fc9bb6..505bd23373 100644 --- a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_12.html +++ b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_12.html @@ -177,7 +177,7 @@ -
+

@@ -459,10 +459,10 @@ /**/ -call_DataFrame(function() { DataFrame.renderTable(234881040) }); +call_DataFrame(function() { DataFrame.renderTable(838860816) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_13.html b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_13.html index 3b6b3ef24a..96577a3995 100644 --- a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_13.html +++ b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_13.html @@ -177,7 +177,7 @@ -
+

@@ -460,10 +460,10 @@ call_DataFrame(function() { DataFrame.addTable({ cols: [{ name: "isIntellij", children: [], rightAlign: false, values: ["true","false"] }, { name: "sumStars", children: [], rightAlign: true, values: ["25221","85392"] }, { name: "maxStars", children: [], rightAlign: true, values: ["12926","39402"] }, -], id: 234881041, rootId: 234881041, totalRows: 2 } ) }); +], id: 838860817, rootId: 838860817, totalRows: 2 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881041) }); +call_DataFrame(function() { DataFrame.renderTable(838860817) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_14.html b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_14.html index 7764d69b5a..e3064b19df 100644 --- a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_14.html +++ b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_14.html @@ -177,7 +177,7 @@ -
+

@@ -461,10 +461,10 @@ { name: "starsCount", children: [], rightAlign: true, values: ["39402","12926","7101","6805","6120","6059","5688","2836","2628","2424"] }, { name: "\">topics", children: [], rightAlign: false, values: ["[compiler, gradle-plugin, intel..., ...]","[code-editor, ide, intellij, i..., i...]","[c, compiler, kotlin, llvm, objective-c]","[android, awt, compose, declara..., ...]","[ideavim, intellij, intellij-pl..., ...]","[coding-font, font, ligatures, ...]","[dao, kotlin, orm, sql]","[components, jetbrains-ui, react]","[]","[create-react-app, jetbrains-ui, ...]"] }, { name: "isIntellij", children: [], rightAlign: false, values: ["false","true","false","false","true","false","false","false","false","false"] }, -], id: 234881042, rootId: 234881042, totalRows: 10 } ) }); +], id: 838860818, rootId: 838860818, totalRows: 10 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881042) }); +call_DataFrame(function() { DataFrame.renderTable(838860818) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_3.html b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_3.html index cfef33fbf8..f7c38dab3a 100644 --- a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_3.html +++ b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_3.html @@ -177,7 +177,7 @@ -
+

... showing only top 20 of 562 rows

@@ -462,10 +462,10 @@ { name: "stargazers_count", children: [], rightAlign: true, values: ["23","115","290","6120","5","8","47","11","41","534","6","218","1241","12926","39","118","1066","125","27","39402"] }, { name: "topics", children: [], rightAlign: false, values: ["[]","[jetbrains, jetbrains-youtrack, youtr...","[]","[ideavim, intellij, intellij-platform...","[]","[]","[]","[]","[nuget, nuget-feed, teamcity, teamcit...","[]","[]","[]","[domain-specific-language, dsl]","[code-editor, ide, intellij, intellij...","[c-sharp, teamcity, teamcity-service-...","[]","[intellij-idea, intellij-plugin, scala]","[]","[]","[compiler, gradle-plugin, intellij-pl..."] }, { name: "watchers", children: [], rightAlign: true, values: ["23","115","290","6120","5","8","47","11","41","534","6","218","1241","12926","39","118","1066","125","27","39402"] }, -], id: 234881030, rootId: 234881030, totalRows: 562 } ) }); +], id: 838860806, rootId: 838860806, totalRows: 562 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881030) }); +call_DataFrame(function() { DataFrame.renderTable(838860806) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_4.html b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_4.html index f54b98d227..a93f87ac5b 100644 --- a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_4.html +++ b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_4.html @@ -177,7 +177,7 @@ -
+

@@ -471,10 +471,10 @@ { name: "?\">median", children: [], rightAlign: false, values: ["JetBrains/lightbeam","null","8","[]","8"] }, { name: "?\">p75", children: [], rightAlign: false, values: ["JetBrains/teamcity-bazel-plugin","null","48","[awt, swing]","48"] }, { name: "?\">max", children: [], rightAlign: false, values: ["JetBrains/ztools","null","39402","[youtrack, youtrack-workflow]","39402"] }, -], id: 234881031, rootId: 234881031, totalRows: 5 } ) }); +], id: 838860807, rootId: 838860807, totalRows: 5 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881031) }); +call_DataFrame(function() { DataFrame.renderTable(838860807) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_5.html b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_5.html index 1f360953bc..3c4d788827 100644 --- a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_5.html +++ b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_5.html @@ -177,7 +177,7 @@ -
+

... showing only top 20 of 562 rows

@@ -460,10 +460,10 @@ call_DataFrame(function() { DataFrame.addTable({ cols: [{ name: "full_name", children: [], rightAlign: false, values: ["JetBrains/JPS","JetBrains/YouTrackSharp","JetBrains/colorSchemeTool","JetBrains/ideavim","JetBrains/youtrack-vcs-hooks","JetBrains/youtrack-rest-ruby-library","JetBrains/emacs4ij","JetBrains/codereview4intellij","JetBrains/teamcity-nuget-support","JetBrains/Grammar-Kit","JetBrains/intellij-starteam-plugin","JetBrains/la-clojure","JetBrains/MPS","JetBrains/intellij-community","JetBrains/TeamCity.ServiceMessages","JetBrains/youtrack-rest-python-library","JetBrains/intellij-scala","JetBrains/teamcity-messages","JetBrains/teamcity-cpp","JetBrains/kotlin"] }, { name: "stargazers_count", children: [], rightAlign: true, values: ["23","115","290","6120","5","8","47","11","41","534","6","218","1241","12926","39","118","1066","125","27","39402"] }, { name: "topics", children: [], rightAlign: false, values: ["[]","[jetbrains, jetbrains-youtrack, youtr...","[]","[ideavim, intellij, intellij-platform...","[]","[]","[]","[]","[nuget, nuget-feed, teamcity, teamcit...","[]","[]","[]","[domain-specific-language, dsl]","[code-editor, ide, intellij, intellij...","[c-sharp, teamcity, teamcity-service-...","[]","[intellij-idea, intellij-plugin, scala]","[]","[]","[compiler, gradle-plugin, intellij-pl..."] }, -], id: 234881032, rootId: 234881032, totalRows: 562 } ) }); +], id: 838860808, rootId: 838860808, totalRows: 562 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881032) }); +call_DataFrame(function() { DataFrame.renderTable(838860808) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_6.html b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_6.html index 604d7690d7..cb66c45753 100644 --- a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_6.html +++ b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_6.html @@ -177,7 +177,7 @@ -
+

... showing only top 20 of 24 rows

@@ -460,10 +460,10 @@ call_DataFrame(function() { DataFrame.addTable({ cols: [{ name: "full_name", children: [], rightAlign: false, values: ["JetBrains/ideavim","JetBrains/MPS","JetBrains/intellij-community","JetBrains/intellij-scala","JetBrains/kotlin","JetBrains/intellij-plugins","JetBrains/Exposed","JetBrains/kotlin-web-site","JetBrains/idea-gitignore","JetBrains/swot","JetBrains/phpstorm-stubs","JetBrains/gradle-intellij-plugin","JetBrains/svg-sprite-loader","JetBrains/resharper-unity","JetBrains/kotlin-native","JetBrains/create-react-kotlin-app","JetBrains/ring-ui","JetBrains/kotlinconf-app","JetBrains/JetBrainsMono","JetBrains/intellij-platform-plugin-te..."] }, { name: "stargazers_count", children: [], rightAlign: true, values: ["6120","1241","12926","1066","39402","1737","5688","1074","1181","1072","1110","1058","1815","1017","7101","2424","2836","2628","6059","1133"] }, { name: "topics", children: [], rightAlign: false, values: ["[ideavim, intellij, intellij-platform...","[domain-specific-language, dsl]","[code-editor, ide, intellij, intellij...","[intellij-idea, intellij-plugin, scala]","[compiler, gradle-plugin, intellij-pl...","[]","[dao, kotlin, orm, sql]","[kotlin]","[gitignore, ignore-files, intellij, i...","[]","[]","[gradle, gradle-intellij-plugin, grad...","[sprite, svg, svg-sprite, svg-stack, ...","[hacktoberfest, jetbrains, plugin, re...","[c, compiler, kotlin, llvm, objective-c]","[create-react-app, jetbrains-ui, kotl...","[components, jetbrains-ui, react]","[]","[coding-font, font, ligatures, monosp...","[intellij, intellij-idea, intellij-id..."] }, -], id: 234881033, rootId: 234881033, totalRows: 24 } ) }); +], id: 838860809, rootId: 838860809, totalRows: 24 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881033) }); +call_DataFrame(function() { DataFrame.renderTable(838860809) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_7.html b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_7.html index 20ec730ba5..96f04f2dad 100644 --- a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_7.html +++ b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_7.html @@ -177,7 +177,7 @@ -
+

... showing only top 20 of 24 rows

@@ -460,10 +460,10 @@ call_DataFrame(function() { DataFrame.addTable({ cols: [{ name: "name", children: [], rightAlign: false, values: ["JetBrains/ideavim","JetBrains/MPS","JetBrains/intellij-community","JetBrains/intellij-scala","JetBrains/kotlin","JetBrains/intellij-plugins","JetBrains/Exposed","JetBrains/kotlin-web-site","JetBrains/idea-gitignore","JetBrains/swot","JetBrains/phpstorm-stubs","JetBrains/gradle-intellij-plugin","JetBrains/svg-sprite-loader","JetBrains/resharper-unity","JetBrains/kotlin-native","JetBrains/create-react-kotlin-app","JetBrains/ring-ui","JetBrains/kotlinconf-app","JetBrains/JetBrainsMono","JetBrains/intellij-platform-plugin-te..."] }, { name: "starsCount", children: [], rightAlign: true, values: ["6120","1241","12926","1066","39402","1737","5688","1074","1181","1072","1110","1058","1815","1017","7101","2424","2836","2628","6059","1133"] }, { name: "topics", children: [], rightAlign: false, values: ["[ideavim, intellij, intellij-platform...","[domain-specific-language, dsl]","[code-editor, ide, intellij, intellij...","[intellij-idea, intellij-plugin, scala]","[compiler, gradle-plugin, intellij-pl...","[]","[dao, kotlin, orm, sql]","[kotlin]","[gitignore, ignore-files, intellij, i...","[]","[]","[gradle, gradle-intellij-plugin, grad...","[sprite, svg, svg-sprite, svg-stack, ...","[hacktoberfest, jetbrains, plugin, re...","[c, compiler, kotlin, llvm, objective-c]","[create-react-app, jetbrains-ui, kotl...","[components, jetbrains-ui, react]","[]","[coding-font, font, ligatures, monosp...","[intellij, intellij-idea, intellij-id..."] }, -], id: 234881034, rootId: 234881034, totalRows: 24 } ) }); +], id: 838860810, rootId: 838860810, totalRows: 24 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881034) }); +call_DataFrame(function() { DataFrame.renderTable(838860810) }); function sendHeight() { const table = document.querySelector('table.dataframe'); diff --git a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_8.html b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_8.html index 2fe990b816..d325a157cc 100644 --- a/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_8.html +++ b/docs/StardustDocs/resources/guides/quickstart/notebook_test_quickstart_8.html @@ -177,7 +177,7 @@ -
+

... showing only top 20 of 24 rows

@@ -460,10 +460,10 @@ call_DataFrame(function() { DataFrame.addTable({ cols: [{ name: "name", children: [], rightAlign: false, values: ["ideavim","MPS","intellij-community","intellij-scala","kotlin","intellij-plugins","Exposed","kotlin-web-site","idea-gitignore","swot","phpstorm-stubs","gradle-intellij-plugin","svg-sprite-loader","resharper-unity","kotlin-native","create-react-kotlin-app","ring-ui","kotlinconf-app","JetBrainsMono","intellij-platform-plugin-template"] }, { name: "starsCount", children: [], rightAlign: true, values: ["6120","1241","12926","1066","39402","1737","5688","1074","1181","1072","1110","1058","1815","1017","7101","2424","2836","2628","6059","1133"] }, { name: "\">topics", children: [], rightAlign: false, values: ["[ideavim, intellij, intellij-pl..., ...]","[domain-specific-language, dsl]","[code-editor, ide, intellij, i..., i...]","[intellij-idea, intellij-plugin, scala]","[compiler, gradle-plugin, intel..., ...]","[]","[dao, kotlin, orm, sql]","[kotlin]","[gitignore, ignore-files, intellij, ...]","[]","[]","[gradle, gradle-intellij-plugin, ...]","[sprite, svg, svg-sprite, svg-s..., ...]","[hacktoberfest, jetbrains, plugin, ...]","[c, compiler, kotlin, llvm, objective-c]","[create-react-app, jetbrains-ui, ...]","[components, jetbrains-ui, react]","[]","[coding-font, font, ligatures, ...]","[intellij, intellij-idea, intel..., ...]"] }, -], id: 234881035, rootId: 234881035, totalRows: 24 } ) }); +], id: 838860811, rootId: 838860811, totalRows: 24 } ) }); /*-->*/ -call_DataFrame(function() { DataFrame.renderTable(234881035) }); +call_DataFrame(function() { DataFrame.renderTable(838860811) }); function sendHeight() { const table = document.querySelector('table.dataframe'); From d3de91c14d7746a491f94e19d06557ef0240bc73 Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Tue, 22 Jul 2025 16:17:53 +0200 Subject: [PATCH 6/8] simplified generateCode.kt tests --- .../kotlinx/dataframe/api/generateCode.kt | 2 +- .../dataframe/codeGen/CodeGenerationTests.kt | 239 +++++++----------- 2 files changed, 94 insertions(+), 147 deletions(-) diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt index b464a0d0ba..a9b5380fa1 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/generateCode.kt @@ -293,7 +293,7 @@ internal fun DataFrameSchema.generateCodeImpl( name = markerName, fields = true, extensionProperties = extensionProperties, - isOpen = false, + isOpen = !asDataClass, visibility = visibility, asDataClass = asDataClass, fieldNameNormalizer = nameNormalizer, diff --git a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt index b9398089eb..7688f0e12f 100644 --- a/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt +++ b/core/src/test/kotlin/org/jetbrains/kotlinx/dataframe/codeGen/CodeGenerationTests.kt @@ -1,12 +1,12 @@ package org.jetbrains.kotlinx.dataframe.codeGen import io.kotest.matchers.shouldBe -import org.intellij.lang.annotations.Language import org.jetbrains.kotlinx.dataframe.AnyRow import org.jetbrains.kotlinx.dataframe.ColumnsScope import org.jetbrains.kotlinx.dataframe.DataColumn import org.jetbrains.kotlinx.dataframe.DataRow import org.jetbrains.kotlinx.dataframe.api.dataFrameOf +import org.jetbrains.kotlinx.dataframe.api.default import org.jetbrains.kotlinx.dataframe.api.dropNulls import org.jetbrains.kotlinx.dataframe.api.generateDataClasses import org.jetbrains.kotlinx.dataframe.api.generateInterfaces @@ -372,26 +372,20 @@ class CodeGenerationTests : BaseTest() { @Test fun `check method generateDataClasses`() { - val code1 = typed.groupBy { name }.toDataFrame().generateDataClasses() - val code2 = typed.groupBy { name }.toDataFrame().schema().generateDataClasses("Person") + val df = typed.groupBy { name }.toDataFrame() + val code1 = df.generateDataClasses() + val code2 = df.schema().generateDataClasses("Person") - @Language("kotlin") - val expected = - """ - @DataSchema - data class Person1( - val age: Int, - val city: String?, - val name: String, - val weight: Int? - ) - - @DataSchema - data class Person( - val group: List, - val name: String - ) - """.trimIndent().toCodeString() + val expected = CodeGenerator.create(useFqNames = false).generate( + schema = df.schema(), + name = "Person", + fields = true, + extensionProperties = false, + isOpen = false, + asDataClass = true, + visibility = MarkerVisibility.IMPLICIT_PUBLIC, + fieldNameNormalizer = NameNormalizer.default, + ).code.declarations.toCodeString() code1 shouldBe expected code2 shouldBe expected @@ -401,57 +395,36 @@ class CodeGenerationTests : BaseTest() { fun `DataFrame generateInterfaces`() { val code = typed.generateInterfaces() - @Language("kotlin") - val expected = - """ - @DataSchema - interface Person { - val age: kotlin.Int - val city: kotlin.String? - val name: kotlin.String - val weight: kotlin.Int? - } - """.trimIndent() + val expected = CodeGenerator.create(useFqNames = false).generate( + schema = df.schema(), + name = "Person", + fields = true, + extensionProperties = false, + isOpen = true, + asDataClass = false, + visibility = MarkerVisibility.IMPLICIT_PUBLIC, + fieldNameNormalizer = NameNormalizer.default, + ).code.declarations.toCodeString() - code.value shouldBe expected + code shouldBe expected } @Test fun `DataFrame generateInterfaces - with marker name`() { val code = typed.generateInterfaces("CustomInterface") - @Language("kotlin") - val expected = - """ - @DataSchema - interface CustomInterface { - val age: kotlin.Int - val city: kotlin.String? - val name: kotlin.String - val weight: kotlin.Int? - } - """.trimIndent() - - code.value shouldBe expected - } - - @Test - fun `DataFrame generateDataClasses - with default parameters`() { - val code = typed.generateDataClasses() - - @Language("kotlin") - val expected = - """ - @DataSchema - data class Person( - val age: Int, - val city: String?, - val name: String, - val weight: Int? - ) - """.trimIndent() + val expected = CodeGenerator.create(useFqNames = false).generate( + schema = df.schema(), + name = "CustomInterface", + fields = true, + extensionProperties = false, + isOpen = true, + asDataClass = false, + visibility = MarkerVisibility.IMPLICIT_PUBLIC, + fieldNameNormalizer = NameNormalizer.default, + ).code.declarations.toCodeString() - code.value shouldBe expected + code shouldBe expected } @Test @@ -463,30 +436,18 @@ class CodeGenerationTests : BaseTest() { useFqNames = true, ) - val packageName = "org.jetbrains.kotlinx.dataframe" - - @Language("kotlin") - val expected = - """ - @DataSchema - internal data class CustomDataClass( - val age: kotlin.Int, - val city: kotlin.String?, - val name: kotlin.String, - val weight: kotlin.Int? - ) - - internal val $packageName.ColumnsContainer.age: $packageName.DataColumn @JvmName("CustomDataClass_age") get() = this["age"] as $packageName.DataColumn - internal val $packageName.DataRow.age: kotlin.Int @JvmName("CustomDataClass_age") get() = this["age"] as kotlin.Int - internal val $packageName.ColumnsContainer.city: $packageName.DataColumn @JvmName("CustomDataClass_city") get() = this["city"] as $packageName.DataColumn - internal val $packageName.DataRow.city: kotlin.String? @JvmName("CustomDataClass_city") get() = this["city"] as kotlin.String? - internal val $packageName.ColumnsContainer.name: $packageName.DataColumn @JvmName("CustomDataClass_name") get() = this["name"] as $packageName.DataColumn - internal val $packageName.DataRow.name: kotlin.String @JvmName("CustomDataClass_name") get() = this["name"] as kotlin.String - internal val $packageName.ColumnsContainer.weight: $packageName.DataColumn @JvmName("CustomDataClass_weight") get() = this["weight"] as $packageName.DataColumn - internal val $packageName.DataRow.weight: kotlin.Int? @JvmName("CustomDataClass_weight") get() = this["weight"] as kotlin.Int? - """.trimIndent() + val expected = CodeGenerator.create(useFqNames = true).generate( + schema = df.schema(), + name = "CustomDataClass", + fields = true, + extensionProperties = true, + isOpen = true, + asDataClass = true, + visibility = MarkerVisibility.INTERNAL, + fieldNameNormalizer = NameNormalizer.default, + ).code.declarations.toCodeString() - code.value shouldBe expected + code shouldBe expected } @Test @@ -494,19 +455,18 @@ class CodeGenerationTests : BaseTest() { val schema = typed.schema() val code = schema.generateInterfaces("SchemaInterface") - @Language("kotlin") - val expected = - """ - @DataSchema - interface SchemaInterface { - val age: kotlin.Int - val city: kotlin.String? - val name: kotlin.String - val weight: kotlin.Int? - } - """.trimIndent() + val expected = CodeGenerator.create(useFqNames = false).generate( + schema = schema, + name = "SchemaInterface", + fields = true, + extensionProperties = false, + isOpen = true, + asDataClass = false, + visibility = MarkerVisibility.IMPLICIT_PUBLIC, + fieldNameNormalizer = NameNormalizer.default, + ).code.declarations.toCodeString() - code.value shouldBe expected + code shouldBe expected } @Test @@ -514,19 +474,18 @@ class CodeGenerationTests : BaseTest() { val schema = typed.schema() val code = schema.generateDataClasses("SchemaDataClass") - @Language("kotlin") - val expected = - """ - @DataSchema - data class SchemaDataClass( - val age: Int, - val city: String?, - val name: String, - val weight: Int? - ) - """.trimIndent() + val expected = CodeGenerator.create(useFqNames = false).generate( + schema = schema, + name = "SchemaDataClass", + fields = true, + extensionProperties = false, + isOpen = false, + asDataClass = true, + visibility = MarkerVisibility.IMPLICIT_PUBLIC, + fieldNameNormalizer = NameNormalizer.default, + ).code.declarations.toCodeString() - code.value shouldBe expected + code shouldBe expected } @Test @@ -539,52 +498,40 @@ class CodeGenerationTests : BaseTest() { useFqNames = false, ) - @Language("kotlin") - val expected = - """ - @DataSchema - public data class SchemaDataClass( - public val age: Int, - public val city: String?, - public val name: String, - public val weight: Int? - ) - - public val ColumnsScope.age: DataColumn @JvmName("SchemaDataClass_age") get() = this["age"] as DataColumn - public val DataRow.age: Int @JvmName("SchemaDataClass_age") get() = this["age"] as Int - public val ColumnsScope.city: DataColumn @JvmName("SchemaDataClass_city") get() = this["city"] as DataColumn - public val DataRow.city: String? @JvmName("SchemaDataClass_city") get() = this["city"] as String? - public val ColumnsScope.name: DataColumn @JvmName("SchemaDataClass_name") get() = this["name"] as DataColumn - public val DataRow.name: String @JvmName("SchemaDataClass_name") get() = this["name"] as String - public val ColumnsScope.weight: DataColumn @JvmName("SchemaDataClass_weight") get() = this["weight"] as DataColumn - public val DataRow.weight: Int? @JvmName("SchemaDataClass_weight") get() = this["weight"] as Int? - """.trimIndent() + val expected = CodeGenerator.create(useFqNames = false).generate( + schema = schema, + name = "SchemaDataClass", + fields = true, + extensionProperties = true, + isOpen = false, + asDataClass = true, + visibility = MarkerVisibility.EXPLICIT_PUBLIC, + fieldNameNormalizer = NameNormalizer.default, + ).code.declarations.toCodeString() - code.value shouldBe expected + code shouldBe expected } @Test fun `DataFrame generateDataClasses - with name normalizer`() { val dfWithSpecialNames = dataFrameOf("my_column", "another column", "third-column")(1, "test", 3.14) + val nameNormalizer = NameNormalizer { it.toCamelCaseByDelimiters() + "1" } val code = dfWithSpecialNames.generateDataClasses( - nameNormalizer = NameNormalizer { it.toCamelCaseByDelimiters() + "1" }, + nameNormalizer = nameNormalizer, ) - @Language("kotlin") - val expected = - """ - @DataSchema - data class DataEntry( - @ColumnName("another column") - val anotherColumn1: String, - @ColumnName("my_column") - val myColumn1: Int, - @ColumnName("third-column") - val thirdColumn1: Double - ) - """.trimIndent() + val expected = CodeGenerator.create(useFqNames = false).generate( + schema = dfWithSpecialNames.schema(), + name = "DataEntry", + fields = true, + extensionProperties = false, + isOpen = false, + asDataClass = true, + visibility = MarkerVisibility.IMPLICIT_PUBLIC, + fieldNameNormalizer = nameNormalizer, + ).code.declarations.toCodeString() - code.value shouldBe expected + code shouldBe expected } // endregion From 99d9c01e14a4701e1da63c4079f7a1a9b5e3c5d2 Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Tue, 22 Jul 2025 16:19:58 +0200 Subject: [PATCH 7/8] apiDump --- core/api/core.api | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/core/api/core.api b/core/api/core.api index ad594f3d90..f58af098e9 100644 --- a/core/api/core.api +++ b/core/api/core.api @@ -2401,12 +2401,16 @@ public final class org/jetbrains/kotlinx/dataframe/api/GatherKt { public final class org/jetbrains/kotlinx/dataframe/api/GenerateCodeKt { public static final fun generateCode (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Ljava/lang/String;ZZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;)Ljava/lang/String; public static synthetic fun generateCode$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Ljava/lang/String;ZZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;ILjava/lang/Object;)Ljava/lang/String; - public static final fun generateCodeForSchema (Lorg/jetbrains/kotlinx/dataframe/schema/DataFrameSchema;Ljava/lang/String;ZZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;)Ljava/lang/String; - public static synthetic fun generateCodeForSchema$default (Lorg/jetbrains/kotlinx/dataframe/schema/DataFrameSchema;Ljava/lang/String;ZZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;ILjava/lang/Object;)Ljava/lang/String; + public static final fun generateCodeImpl (Lorg/jetbrains/kotlinx/dataframe/schema/DataFrameSchema;Ljava/lang/String;ZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;ZLorg/jetbrains/kotlinx/dataframe/codeGen/NameNormalizer;Z)Ljava/lang/String; + public static final fun generateDataClasses (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Ljava/lang/String;ZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;ZLorg/jetbrains/kotlinx/dataframe/codeGen/NameNormalizer;)Ljava/lang/String; + public static synthetic fun generateDataClasses$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Ljava/lang/String;ZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;ZLorg/jetbrains/kotlinx/dataframe/codeGen/NameNormalizer;ILjava/lang/Object;)Ljava/lang/String; public static final fun generateDataClassesForSchema (Lorg/jetbrains/kotlinx/dataframe/schema/DataFrameSchema;Ljava/lang/String;ZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;ZLorg/jetbrains/kotlinx/dataframe/codeGen/NameNormalizer;)Ljava/lang/String; public static synthetic fun generateDataClassesForSchema$default (Lorg/jetbrains/kotlinx/dataframe/schema/DataFrameSchema;Ljava/lang/String;ZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;ZLorg/jetbrains/kotlinx/dataframe/codeGen/NameNormalizer;ILjava/lang/Object;)Ljava/lang/String; - public static final fun generateInterfaces (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Ljava/lang/String;)Ljava/lang/String; - public static final fun generateInterfacesForSchema (Lorg/jetbrains/kotlinx/dataframe/schema/DataFrameSchema;Ljava/lang/String;)Ljava/lang/String; + public static final synthetic fun generateInterfaces (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Ljava/lang/String;)Ljava/lang/String; + public static final fun generateInterfaces (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Ljava/lang/String;ZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;ZLorg/jetbrains/kotlinx/dataframe/codeGen/NameNormalizer;)Ljava/lang/String; + public static synthetic fun generateInterfaces$default (Lorg/jetbrains/kotlinx/dataframe/DataFrame;Ljava/lang/String;ZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;ZLorg/jetbrains/kotlinx/dataframe/codeGen/NameNormalizer;ILjava/lang/Object;)Ljava/lang/String; + public static final fun generateInterfacesForSchema (Lorg/jetbrains/kotlinx/dataframe/schema/DataFrameSchema;Ljava/lang/String;ZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;ZLorg/jetbrains/kotlinx/dataframe/codeGen/NameNormalizer;)Ljava/lang/String; + public static synthetic fun generateInterfacesForSchema$default (Lorg/jetbrains/kotlinx/dataframe/schema/DataFrameSchema;Ljava/lang/String;ZLorg/jetbrains/kotlinx/dataframe/codeGen/MarkerVisibility;ZLorg/jetbrains/kotlinx/dataframe/codeGen/NameNormalizer;ILjava/lang/Object;)Ljava/lang/String; public static final fun getDefault (Lorg/jetbrains/kotlinx/dataframe/codeGen/NameNormalizer$Companion;)Lorg/jetbrains/kotlinx/dataframe/codeGen/NameNormalizer; public static final fun toCodeString (Ljava/lang/String;)Ljava/lang/String; } From ec1aab79b931711e2504246e3b1ec7fc7768d88e Mon Sep 17 00:00:00 2001 From: Jolan Rensen Date: Tue, 22 Jul 2025 16:54:01 +0200 Subject: [PATCH 8/8] adding extension properties links in DataSchemaGenerationMethods.md --- .../topics/concepts/DataSchemaGenerationMethods.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/StardustDocs/topics/concepts/DataSchemaGenerationMethods.md b/docs/StardustDocs/topics/concepts/DataSchemaGenerationMethods.md index d06819e6ab..e36f233b7d 100644 --- a/docs/StardustDocs/topics/concepts/DataSchemaGenerationMethods.md +++ b/docs/StardustDocs/topics/concepts/DataSchemaGenerationMethods.md @@ -44,7 +44,8 @@ Useful when you want to: * `markerName`: `String?` — The base name to use for generated data classes. If `null`, uses the `T` type argument of `DataFrame` simple name. Default: `null`. -* `extensionProperties`: `Boolean` – Whether to generate extension properties in addition to `interface` declarations. +* `extensionProperties`: `Boolean` – Whether to generate [extension properties](extensionPropertiesApi.md) + in addition to `interface` declarations. Useful if you don't use the [compiler plugin](Compiler-Plugin.md), otherwise they are not needed; the [compiler plugin](Compiler-Plugin.md), [notebooks](schemasJupyter.md), and older [Gradle/KSP plugin](schemasGradle.md) generate them automatically. @@ -61,7 +62,7 @@ Useful when you want to: ### Returns {id="generateDataClasses-returns"} * `CodeString` – A value class wrapper for `String`, containing - the generated Kotlin code of `data class` declarations and optionally extension properties. + the generated Kotlin code of `data class` declarations and optionally [extension properties](extensionPropertiesApi.md). ### Examples {id="generateDataClasses-examples"} @@ -118,7 +119,8 @@ in cases where the schema cannot be inferred automatically from the source. * `markerName`: `String?` — The base name to use for generated interfaces. If `null`, uses the `T` type argument of `DataFrame` simple name. Default: `null`. -* `extensionProperties`: `Boolean` – Whether to generate extension properties in addition to `interface` declarations. +* `extensionProperties`: `Boolean` – Whether to generate [extension properties](extensionPropertiesApi.md) + in addition to `interface` declarations. Useful if you don't use the [compiler plugin](Compiler-Plugin.md), otherwise they are not needed; the [compiler plugin](Compiler-Plugin.md), [notebooks](schemasJupyter.md), and older [Gradle/KSP plugin](schemasGradle.md) generate them automatically. @@ -135,7 +137,7 @@ in cases where the schema cannot be inferred automatically from the source. ### Returns {id="generateInterfaces-returns"} * `CodeString` – A value class wrapper for `String`, containing - the generated Kotlin code of `@DataSchema` interfaces without extension properties. + the generated Kotlin code of `@DataSchema` interfaces without [extension properties](extensionPropertiesApi.md). ### Examples {id="generateInterfaces-examples"}