From 61788ca4a9e1234228a59882e9c608229c59837a Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Sat, 17 Jan 2026 19:03:11 +0400 Subject: [PATCH 01/13] kdoc guidelines --- .../jetbrains/kotlinx/dataframe/api/move.kt | 1 + docs/KDocs-Guidelines.md | 231 ++++++++++++++++++ 2 files changed, 232 insertions(+) create mode 100644 docs/KDocs-Guidelines.md diff --git a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt index cd4aed1adb..9ab1426f18 100644 --- a/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt +++ b/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/move.kt @@ -47,6 +47,7 @@ import kotlin.reflect.KProperty * destination of the selected columns using methods such as [to][MoveClause.to], [toStart][MoveClause.toStart], * [toEnd][MoveClause.toEnd], [into][MoveClause.into], [intoIndexed][MoveClause.intoIndexed], [toTop][MoveClause.toTop], * [after][MoveClause.after] or [under][MoveClause.under], that return a new [DataFrame] with updated columns structure. + * * Check out [Grammar]. * * @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention] diff --git a/docs/KDocs-Guidelines.md b/docs/KDocs-Guidelines.md new file mode 100644 index 0000000000..0fc442c18f --- /dev/null +++ b/docs/KDocs-Guidelines.md @@ -0,0 +1,231 @@ +# Documentation Guidelines + +This document outlines the guidelines for writing KDocs in the Kotlin DataFrame project. + +## The most important advice + +Please never write this all from scratch! +Find existing KDocs for the similar operation and reuse it. +However, take its specific into account. + +And don't be afraid to deviate from the rules or add something new – +the most important thing is to help users to understand the library better! + +## KoDEx + +We use [KoDEx](https://github.com/Jolanrensen/KoDEx) KDocs preprocessor. +It adds several useful utilities for writing KDocs. Please read about +the [KoDEx notation](https://github.com/Jolanrensen/KoDEx/wiki/Notation) before working with Kotlin DataFrame KDocs. + +Install the [KoDEx plugin for IDEA](https://plugins.jetbrains.com/plugin/27473---kodex---kotlin-documentation-extensions) +for correct KDocs display inside the IntelliJ IDEA. + +### Reuse Common Parts + +One of the best utilities of KoDEx is the ability to reuse common parts of KDocs. +This can be done by using the +[`@include` tag](https://github.com/Jolanrensen/KoDEx/wiki/Notation#include-including-content-from-other-kdocs), +which allows you to include a KDoc of another class. + +There are a lot of interfaces in the project that are only used for including their KDocs +to other KDocs. Such interfaces are marked with `@ExcludeFromSources` +and not included in the sources after the compilation (make sure you are not referencing them directly, +i.e., only use it inside the `@include`). For example, +[`ColumnPathCreation` interface](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ColumnPathCreation.ktc) +has a KDoc which describes column path creation behavior. The whole file is excluded from sources, +but the KDoc is included in other KDocs. +z +Also, you can use +[`@set` and `@get` tags](https://github.com/Jolanrensen/KoDEx/wiki/Notation#set-and-get---setting-and-getting-variables) +along with `@include` to change variables value in common parts. This is especially useful for +writing examples of methods with similar usage but with different names. + +## What should be documented? + +**All public API should be documented!** Our goal is 100% public functions, classes and variable KDocs coverage. +Some part of public API is not intended for end users, but can be used in Compiler Plugin or potential +KDF extension libraries. These methods should have a small KDocs as well. + +For internal API, please add at least a small note. + +## Kotlin DataFrame Operations KDoc Structure + +Operation KDocs size and structure completely depend on the operation complexity. + +The best way to write a new KDoc for an operation is to define its kind and +use the existing KDoc of an operation of the same kind as a template. + +There are four kinds; here's a list of them: + +1. Simple, Stdlib-like operations that don't have arguments or have simple types (primitives, classes) +as arguments and return simple value, `DataFrame`, `DataRow` or `DataColumn`. + * For example, +[`first` without arguments](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt#L106). + * KDocs for such operations can be short, especially if it's trivial enough. +2. Operations with [`DataRow` API](https://kotlin.github.io/dataframe/datarow.html). + * For example, [`first` with predicate](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt#L139). + * Remember to describe a mechanism of `DataRow` API usage in the KDoc - it's not obvious to the user. +3. Operations with the [Columns Selection DSL](https://kotlin.github.io/dataframe/columnselectors.html) that return a single and return simple value, `DataFrame`, `DataRow` or `DataColumn`. + * For example, [`remove`](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/remove.kt). + * Remember to describe a mechanism of Columns Selection DSL. + * Add several examples with different columns selection options. +4. Complex operations with a multiple methods chain. + * For example, [`convert`](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convert.kt). + * Such operations consist of at least two methods and special resulting classes as the intermediate steps. + All of them should be well documented and have cross references to each other. + * Usually some os the methods have the [Columns Selection DSL](https://kotlin.github.io/dataframe/columnselectors.html); + these methods should be documented by the rules above. + * For a better understanding of the complex operation, we write an [operation grammar](#grammar) using a + [special notation](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt). + Add a reference to the operation Grammar in each related class and method KDoc. + +### General Template + +The generalized template for all operations: + +```kotlin +/** + * (First line - brief and concise description of the operation) + * + * (Body - second, third, and so on lines - detailed description of the operation, + * related mechanisms, etc.; optional) + * + * (Documentation website link) + * + * (See also section; optional) + * + * (Columns selection information - for operations with columns selection only) + * + * (Examples section) + * + * (Parameters and return section) + */ +``` + +Below there are some rules for each section: + +#### First line + +The first line should be short but at the same time give a clear understanding of the operation. + +Should start with a verb. Usually "returns" or "creates" for operations that return a simple value, +`DataFrame`, `DataColumn` or `DataRow` (excluding methods which are non-final part of complex operations). + +#### Body + +For the non-trivial operations, write a detailed description of the operation, +describe method behavior and resulting value. + +For complex operations, write that this is only the first step of the operation, +and it should be continued with other methods. lso add a note about the [operation grammar](#grammar) in this case. +For example (from the `insert` KDoc): + +``` + * This function does not immediately insert the new column but instead specify a column to insert and + * returns an [InsertClause], + * which serves as an intermediate step. + * The [InsertClause] object provides methods to insert a new column using: + * - [under][InsertClause.under] - inserts a new column under the specified column group. + * - [after][InsertClause.after] - inserts a new column after the specified column. + * - [at][InsertClause.at]- inserts a new column at the specified position. + * + * Each method returns a new [DataFrame] with the inserted column. + * + * Check out [Grammar]. +``` + +If the method uses columns selection, add a note about nested columns and column groups: + +``` +@include [SelectingColumns.ColumnGroupsAndNestedColumnsMention] +``` + +#### See also section + +Add a reference to all related methods. Those can be methods with the similar or opposite behavior. + +Example from `DataFrame.first` KDoc: + +``` + * See also [firstOrNull][DataFrame.firstOrNull], + * [last][DataFrame.last], + * [take][DataFrame.take], + * [takeWhile][DataFrame.takeWhile], + * [takeLast][DataFrame.takeLast]. +``` + +* If there is a reverse operation, add a specific note about it. +* If this operation is a shortcut or a special case of another one, +add a note about it. +* If you think a user can confuse the operation with another one, +write it down exactly like that. + +For example, from `group` KDoc: + + +``` + * Reverse operation: [ungroup]. + * + * It is a special case of [move] operation. + * + * Don't confuse this with [groupBy], + * which groups the dataframe by the values in the selected columns! +``` + +#### Documentation website link + +Add a link to the corresponding operation in the +[documentation website](https://kotlin.github.io/dataframe). + +Please add it as an interface KDoc inside +[DocumentationUrls](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt) +and then use add it using `@include`: + +``` + * For more information: {@include [DocumentationUrls.Move]} +``` + +#### Columns selection information + +For any method with columns selection, add a section with information about the columns selection. + +Usually, just `@include` [custom SelectingOptions](#selecting-options). + +#### Examples section + +Write meaningful, easy-to-undertand examples, with detailed comments. + +For complex operations, write a **complete** example with all steps. + +For methods with Columns Selection DSL, add several examples with different CS DSL selection methods. + +Start the section with + +``` +### Examples +``` + +#### Parameters and return section + +Describe parameters and return of the method using `@param` and `@return` tags. +Remember about type parameters. + +Wrap parameter names into `[]` for better readability. + +## Helper KDoc Interfaces Structure + +Sometimes, you do not need helper interfaces with KoDEx temples at all - +for simple operations, it's enough to write a short KDoc. + +However, if you want to reuse some common parts of KDocs +(for example, for different overloads of the same method or very similar methods), +it's better to use some helper interfaces. Here's a standard structure for them: + +```kotlin + +``` + +### Grammar + + +### Selecting options From d2b8e0362a1fb194e166368a9c4e472c9c5356df Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Mon, 19 Jan 2026 17:32:16 +0400 Subject: [PATCH 02/13] kdoc guidelines helper structure --- docs/KDocs-Guidelines.md | 66 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/docs/KDocs-Guidelines.md b/docs/KDocs-Guidelines.md index 0fc442c18f..f016924fda 100644 --- a/docs/KDocs-Guidelines.md +++ b/docs/KDocs-Guidelines.md @@ -117,7 +117,7 @@ For the non-trivial operations, write a detailed description of the operation, describe method behavior and resulting value. For complex operations, write that this is only the first step of the operation, -and it should be continued with other methods. lso add a note about the [operation grammar](#grammar) in this case. +and it should be continued with other methods. Also add a note about the [operation grammar](#grammar) in this case. For example (from the `insert` KDoc): ``` @@ -219,13 +219,67 @@ for simple operations, it's enough to write a short KDoc. However, if you want to reuse some common parts of KDocs (for example, for different overloads of the same method or very similar methods), -it's better to use some helper interfaces. Here's a standard structure for them: +it's better to use some helper interfaces. -```kotlin +Complex operations -``` +Here's a standard structure for them: -### Grammar +```kotlin +// Main KDoc helper interface + +/** + * (First line) + * + * (Body) + * + * (Documentation website link) + * + * (See also section) + */ +internal interface ~OperationName~Docs { + + // `SelectingColumns` helper KDoc with this operation in examples + // - for operations with columns selection + /** + * {@comment Version of [SelectingColumns] with correctly filled in examples} + * @include [SelectingColumns] {@include [Set~OperationName~OperationArg]} + */ + interface ~OperationName~Options + + // Operation Grammar for complex operations + /** + * ## ~OperationName~ Operation Grammar + * ... + */ + interface Grammar +} + +// Set operation in [SelectingColumns] examples (in [SelectingColumns.Dsl] and so on) +/** {@set [SelectingColumns.OPERATION] [~operationName~][~operation~]} */ +@ExcludeFromSources +private interface Set~OperationName~OperationArg + +// Common KDoc part for different overloads of the same method +/** + * {@include [~OperationName~Docs]} + * ### This ~OperationName~ Overload + */ +@ExcludeFromSources +private interface Common~OperationName~Docs +/** + * (Include common docs) + * @include [Common~OperationName~Docs] + * + * (Columns selection information) + * @include [SelectingColumns.Dsl] {@include [Set~OperationName~OperationArg]} + * + * (Examples section) + * + * (Parameters and return section) + */ +public fun DataFrame.operation(columns: ColumnsSelector) +``` -### Selecting options +For more information, see [KoDEx Conventions in DataFrame](../KDOC_PREPROCESSING.md#kodex-conventions-in-dataframe). From 6752779b50538b24fa2028ef0ddc377e850c0eb1 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Mon, 19 Jan 2026 18:37:36 +0400 Subject: [PATCH 03/13] kdoc guidelines fixes --- docs/KDocs-Guidelines.md | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/docs/KDocs-Guidelines.md b/docs/KDocs-Guidelines.md index f016924fda..2ad7b31b42 100644 --- a/docs/KDocs-Guidelines.md +++ b/docs/KDocs-Guidelines.md @@ -116,7 +116,7 @@ Should start with a verb. Usually "returns" or "creates" for operations that ret For the non-trivial operations, write a detailed description of the operation, describe method behavior and resulting value. -For complex operations, write that this is only the first step of the operation, +For complex operations, in the KDoc of the initial method write that this is only the first step of the operation, and it should be continued with other methods. Also add a note about the [operation grammar](#grammar) in this case. For example (from the `insert` KDoc): @@ -134,6 +134,9 @@ For example (from the `insert` KDoc): * Check out [Grammar]. ``` +The next methods in the chain may be finalizing or intermediate steps - write about it explicitly. +Remember to add a link to the initial method and [operation grammar](#grammar) in all of them. + If the method uses columns selection, add a note about nested columns and column groups: ``` @@ -214,6 +217,8 @@ Wrap parameter names into `[]` for better readability. ## Helper KDoc Interfaces Structure +For more information, see [KoDEx Conventions in DataFrame](../KDOC_PREPROCESSING.md#kodex-conventions-in-dataframe). + Sometimes, you do not need helper interfaces with KoDEx temples at all - for simple operations, it's enough to write a short KDoc. @@ -247,7 +252,7 @@ internal interface ~OperationName~Docs { */ interface ~OperationName~Options - // Operation Grammar for complex operations + // Operation Grammar - for the initial method of the complex operations /** * ## ~OperationName~ Operation Grammar * ... @@ -281,5 +286,3 @@ private interface Common~OperationName~Docs */ public fun DataFrame.operation(columns: ColumnsSelector) ``` - -For more information, see [KoDEx Conventions in DataFrame](../KDOC_PREPROCESSING.md#kodex-conventions-in-dataframe). From 0669c0d3a2891e528980892c1e78e3706b6b5a46 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Mon, 19 Jan 2026 21:52:42 +0400 Subject: [PATCH 04/13] update KDocs guidelines for clarity and improved references --- docs/KDocs-Guidelines.md | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/docs/KDocs-Guidelines.md b/docs/KDocs-Guidelines.md index 2ad7b31b42..258debef13 100644 --- a/docs/KDocs-Guidelines.md +++ b/docs/KDocs-Guidelines.md @@ -4,8 +4,8 @@ This document outlines the guidelines for writing KDocs in the Kotlin DataFrame ## The most important advice -Please never write this all from scratch! -Find existing KDocs for the similar operation and reuse it. +Please never write KDocs from scratch without a necessity! +Find existing KDocs for the similar operation or other entity and reuse it. However, take its specific into account. And don't be afraid to deviate from the rules or add something new – @@ -14,8 +14,10 @@ the most important thing is to help users to understand the library better! ## KoDEx We use [KoDEx](https://github.com/Jolanrensen/KoDEx) KDocs preprocessor. -It adds several useful utilities for writing KDocs. Please read about -the [KoDEx notation](https://github.com/Jolanrensen/KoDEx/wiki/Notation) before working with Kotlin DataFrame KDocs. +It adds several useful utilities for writing KDocs. + +Please read about +the [KDocs preprocessing using KoDEx](../KDOC_PREPROCESSING.md) before working with Kotlin DataFrame KDocs. Install the [KoDEx plugin for IDEA](https://plugins.jetbrains.com/plugin/27473---kodex---kotlin-documentation-extensions) for correct KDocs display inside the IntelliJ IDEA. @@ -24,8 +26,9 @@ for correct KDocs display inside the IntelliJ IDEA. One of the best utilities of KoDEx is the ability to reuse common parts of KDocs. This can be done by using the -[`@include` tag](https://github.com/Jolanrensen/KoDEx/wiki/Notation#include-including-content-from-other-kdocs), -which allows you to include a KDoc of another class. +[`@include` tag](https://github.com/Jolanrensen/KoDEx/wiki/Notation#include-including-content-from-other-kdocs), +which allows you to include a KDoc for another documentable element. +This could be a class, an interface, a typealias, and so on. There are a lot of interfaces in the project that are only used for including their KDocs to other KDocs. Such interfaces are marked with `@ExcludeFromSources` @@ -33,8 +36,7 @@ and not included in the sources after the compilation (make sure you are not ref i.e., only use it inside the `@include`). For example, [`ColumnPathCreation` interface](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ColumnPathCreation.ktc) has a KDoc which describes column path creation behavior. The whole file is excluded from sources, -but the KDoc is included in other KDocs. -z +but the KDoc is included in other KDocs. Also, you can use [`@set` and `@get` tags](https://github.com/Jolanrensen/KoDEx/wiki/Notation#set-and-get---setting-and-getting-variables) along with `@include` to change variables value in common parts. This is especially useful for @@ -42,7 +44,7 @@ writing examples of methods with similar usage but with different names. ## What should be documented? -**All public API should be documented!** Our goal is 100% public functions, classes and variable KDocs coverage. +**All public API should be documented!** Our goal is 100% public functions, classes, and variable KDocs coverage. Some part of public API is not intended for end users, but can be used in Compiler Plugin or potential KDF extension libraries. These methods should have a small KDocs as well. @@ -247,10 +249,10 @@ internal interface ~OperationName~Docs { // `SelectingColumns` helper KDoc with this operation in examples // - for operations with columns selection /** - * {@comment Version of [SelectingColumns] with correctly filled in examples} + * @comment Version of [SelectingColumns] with correctly filled in examples * @include [SelectingColumns] {@include [Set~OperationName~OperationArg]} */ - interface ~OperationName~Options + typealias ~OperationName~Options = Nothing // Operation Grammar - for the initial method of the complex operations /** @@ -261,13 +263,13 @@ internal interface ~OperationName~Docs { } // Set operation in [SelectingColumns] examples (in [SelectingColumns.Dsl] and so on) -/** {@set [SelectingColumns.OPERATION] [~operationName~][~operation~]} */ +/** @set [SelectingColumns.OPERATION] [~operationName~][~operation~] */ @ExcludeFromSources -private interface Set~OperationName~OperationArg +private typealias Set~OperationName~OperationArg = Nothing // Common KDoc part for different overloads of the same method /** - * {@include [~OperationName~Docs]} + * @include [~OperationName~Docs] * ### This ~OperationName~ Overload */ @ExcludeFromSources From 779106c645eeb04a704fa94651b5117e2b082ad8 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Mon, 19 Jan 2026 22:33:30 +0400 Subject: [PATCH 05/13] KDocs guideline add TOC --- docs/KDocs-Guidelines.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/KDocs-Guidelines.md b/docs/KDocs-Guidelines.md index 258debef13..6ee57b826f 100644 --- a/docs/KDocs-Guidelines.md +++ b/docs/KDocs-Guidelines.md @@ -2,6 +2,18 @@ This document outlines the guidelines for writing KDocs in the Kotlin DataFrame project. + + +* [The most important advice](#the-most-important-advice) +* [KoDEx](#kodex) + *[Reuse Common Parts](#reuse-common-parts) +* [What should be documented?](#what-should-be-documented) +* [Kotlin DataFrame Operations KDoc Structure](#kotlin-dataframe-operations-kdoc-structure) + + [General Template](#general-template) +* [Helper KDoc Interfaces Structure](#helper-kdoc-interfaces-structure) + + + ## The most important advice Please never write KDocs from scratch without a necessity! From 43c4ee7a82c63221d194991d36341c0cfdbb4a02 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Fri, 27 Feb 2026 18:50:32 +0400 Subject: [PATCH 06/13] update KDoc / kodex guidelines --- KDOC_GUIDELINES.md | 695 +++++++++++++++++++++++++++++++++++ KDOC_PREPROCESSING.md | 713 ------------------------------------ KODEX_KDOC_PREPROCESSING.md | 369 +++++++++++++++++++ docs/KDocs-Guidelines.md | 302 --------------- docs/imgs/dslgrammar.png | Bin 31064 -> 46663 bytes 5 files changed, 1064 insertions(+), 1015 deletions(-) create mode 100644 KDOC_GUIDELINES.md delete mode 100644 KDOC_PREPROCESSING.md create mode 100644 KODEX_KDOC_PREPROCESSING.md delete mode 100644 docs/KDocs-Guidelines.md diff --git a/KDOC_GUIDELINES.md b/KDOC_GUIDELINES.md new file mode 100644 index 0000000000..06e0f528c5 --- /dev/null +++ b/KDOC_GUIDELINES.md @@ -0,0 +1,695 @@ +# KDocs Guidelines + +This document outlines the guidelines for writing KDocs in the Kotlin DataFrame project. + + +* [KDocs Guidelines](#kdocs-guidelines) + * [The most important advice](#the-most-important-advice) + * [What should be documented?](#what-should-be-documented) + * [KoDEx & KDoc-helpers](#kodex--kdoc-helpers) + * [KDoc-snippets: Reuse Common Parts](#kdoc-snippets-reuse-common-parts) + * [KDoc-topics: Reference to Topics](#kdoc-topics-reference-to-topics) + * [Common KDoc-helpers](#common-kdoc-helpers) + * [URLs](#urls) + * [Utils](#utils) + * [Kotlin DataFrame Operations KDoc Structure](#kotlin-dataframe-operations-kdoc-structure) + * [General Template](#general-template) + * [First line](#first-line) + * [Body](#body) + * [See also section](#see-also-section) + * [Documentation website link](#documentation-website-link-) + * [Columns selection information](#columns-selection-information) + * [Examples section](#examples-section) + * [Parameters and return section](#parameters-and-return-section) + * [KDoc-helpers Structure](#kdoc-helpers-structure) + * [`@set`/`@get` references](#setget-references) + * [Grammar](#grammar) + * [Symbols](#symbols) + * [Advanced KDocs](#advanced-kdocs) + * [Clickable Examples](#clickable-examples) + * [Advanced DSL Grammar Templating (Columns Selection DSL)](#advanced-dsl-grammar-templating-columns-selection-dsl) + + +## The most important advice + +Please never write KDocs from scratch without a necessity! +Find existing KDocs for the similar operation or other entity and reuse it. +However, take its specific into account. + +And don't be afraid to deviate from the rules or add something new – +the most important thing is to help users to understand the library better! + +## What should be documented? + +**All public API should be documented!** Our goal is 100% public functions, classes, and variable KDocs coverage. +Some parts of public API are not intended for end users but can be used in Compiler Plugin or potential +KDF extension libraries. These methods should have a small KDocs as well. + +For internal API, please add at least a small note. + +## KoDEx & KDoc-helpers + +We use [KoDEx](https://github.com/Jolanrensen/KoDEx) KDocs preprocessor. +It adds several useful utilities for writing KDocs. + +Please read about +the [KDocs preprocessing using KoDEx](KDOC_PREPROCESSING.md) before working with Kotlin DataFrame KDocs. + +Install the [KoDEx plugin for IDEA](https://plugins.jetbrains.com/plugin/27473---kodex---kotlin-documentation-extensions) +for correct KDocs display inside the IntelliJ IDEA. + +### KDoc-snippets: Reuse Common Parts + +One of the best utilities of KoDEx is the ability to reuse common parts of KDocs. +This can be done by using the +[`@include` tag](https://github.com/Jolanrensen/KoDEx/wiki/Notation#include-including-content-from-other-kdocs), +which allows you to include a KDoc for another documentable element. +This could be a class, an interface, a typealias, and so on. + +There are a lot of empty interfaces and typealiases in the project that are only used for including their KDocs +to other KDocs. They are called *KDoc-snippets*; they are marked with `@ExcludeFromSources` +and not included in the sources after the compilation (make sure you are not referencing them directly, +i.e., only use it inside the `@include`). + +```kotlin +/** + * (Snippet that will be included in other KDocs) + */ +@ExcludeFromSources +internal typealias ~SnippetDescription~Snippet = Nothing + +/** + * (KDoc part;) + * + * (Include snippet; it will be completely pasted here) + * @include [~SnippetDescription~Snippet] + * + * (KDoc part;) + */ +public fun someFunction() +``` + +For example, +[`ColumnPathCreation` interface](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ColumnPathCreation.ktc) +has a KDoc which describes column path creation behavior. +The whole file is excluded from sources, +but the KDoc is included in other KDocs. + +Also, you can use +[`@set` and `@get` tags](https://github.com/Jolanrensen/KoDEx/wiki/Notation#set-and-get---setting-and-getting-variables) +along with `@include` to change variables value in common parts. This is especially useful for +writing examples of methods with similar usage but with different names. + +More about `@set` and `@get` connventions [here](#setget-references). + +```kotlin +/** + * (Snippet body) + * + * ### Example + * `df.`{@get [OPERATION]}` { name and age }` + */ +@ExcludeFromSources +internal interface ~SnippetDescription~Snippet { + /* + * The key for a @set that will define the operation name for the snippet example. + */ + @ExcludeFromSources + interface OPERATION +} + +/** + * (KDoc part;) + * + * (Include snippet; set `[select]` reference as an `OPERATION` value) + * {@include [~SnippetDescription~Snippet] {@set [~SnippetDescription~Snippet.OPERATION] [select][select]}} + * + * (KDoc part;) + */ +public fun someFunction() +``` + +Naming convention for KDoc-snippet — the name must fully reflect its content and have `Snippet` at the end. + +### KDoc-topics: Reference to Topics + +In addition to KDoc snippets, there are also *KDoc-topics* interfaces and typealiases. +Their KDocs are not included in other KDocs. +Instead, they act as reference anchors, +allowing their KDocs to be referenced as standalone topics from other KDocs. +They are not marked with `@ExcludeFromSources` and included in the sources. + +For convenience, there are *link snippets* for each KDoc-topic, +which contain a link to it, and then can be added to other KDoc it via `@include`. + +```kotlin +/** + * ## (Topic name) + * + * (Topic body) + */ +internal typealias ~TopicName~ = Nothing + +/** [Topic name][~TopicName~] */ +@ExcludeFromSources +internal typealias ~TopicName~Link = Nothing + +/** + * (KDoc part;) + * + * (Include link snippet;) + * For more information, see {@include [~TopicName~Link]}. + * + * (KDoc part;) + */ +public fun someFunction() +``` + +Naming convention for KDoc-topic — the name must fully reflect its content at the end. +In the future, we want to have a nice topic names with backtics +(like `` `Access API` `` instead of `AccessApis`), but +[it's not possible yet due to KoDEx bug](https://github.com/Jolanrensen/KoDEx/issues/97). + +> Both KDoc-snippets and KDoc-topics are called *KDoc-helpers*. + +All KDoc-helpers must be `internal` or `private`! + +Some of KDoc-helpers can be used both as -snippets and -topics. + +### Common KDoc-helpers + +Some KDoc-snippets and KDoc-topics are used in multiple places in the library. +It's often useful to define them in one place and include them in multiple other places or +to just link to them. + +Common KDoc-helpers in +the [documentation folder](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation) +and include things like: + +- [Access APIs](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/AccessApi.kt) + - To be linked to + - String API, Column Accessors API etc. +- [Selecting Columns](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/SelectingColumns.kt) + - To be included in `select`, `update` etc. like `{@include [SelectingColumns.ColumnNames.WithExample]}` (with + args). + - Or to be linked to with `{@include [SelectingColumnsLink]}`. + - By name, by column accessor, by DSL etc. +- [Selecting Rows](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/SelectingRows.kt) + - To be included like `{@include [SelectingRows.RowValueCondition.WithExample]}` in `Update.where`, `filter`, etc. + - Explains the concept and provides examples (with args) +- [`ExpressionsGivenColumn`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenColumn.kt) / [`-DataFrame`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenDataFrame.kt) / [`-Row`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenRow.kt) / [`-RowAndColumn`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenRowAndColumn.kt) + - To be included or linked to in functions like `perRowCol`, `asFrame`, etc. + - Explains the concepts of `ColumnExpression`, `DataFrameExpression`, `RowExpression`, etc. +- [`NA`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/NA.kt) / [`NaN`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/NaN.kt) + - To be linked to for more information on the concepts +- [DslGrammar](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt) + - To be linked to from each DSL grammar by the link interface +- Check the folder to see if there are more and feel free to add them if needed :) + +#### URLs + +When linking to external URLs, it's recommended to use +[DocumentationUrls](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt) and +[Issues](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/Issues.kt). + +It's a central place where we can store URLs that can be used in multiple places in the library. Plus, it makes +it easier to update the documentation whenever (part of) a URL changes. + +#### Utils + +The [`utils.kt` file](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/utils.kt) contains all sorts of helper interfaces for the documentation. +For instance `{@include [LineBreak]}` can insert a line break in the KDoc and the family of `Indent` +documentation interfaces can provide you with different non-breaking-space-based indents. + +If you need a new utility, feel free to add it to this file. + +## Kotlin DataFrame Operations KDoc Structure + +Operation KDocs size and structure completely depend on the operation complexity. + +The best way to write a new KDoc for an operation is to define its kind and +use the existing KDoc of an operation of the same kind as a template. + +There are four kinds; here's a list of them: + +1. Simple, Stdlib-like operations that don't have arguments or have simple types (primitives, classes) +as arguments and return simple value, `DataFrame`, `DataRow` or `DataColumn`. + * For example, +[`first` without arguments](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt#L106). + * KDocs for such operations can be short, especially if it's trivial enough. +2. Operations with [`DataRow` API](https://kotlin.github.io/dataframe/datarow.html). + * For example, [`first` with predicate](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt#L139). + * Remember to describe a mechanism of `DataRow` API usage in the KDoc - it's not obvious to the user. +3. Operations with the [Columns Selection DSL](https://kotlin.github.io/dataframe/columnselectors.html) that return a single and return simple value, `DataFrame`, `DataRow` or `DataColumn`. + * For example, [`remove`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/remove.kt). + * Remember to describe a mechanism of Columns Selection DSL. + * Add several examples with different columns selection options. +4. Complex operations with a multiple methods chain. + * For example, [`convert`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convert.kt). + * Such operations consist of at least two methods and special resulting classes as the intermediate steps. + All of them should be well-documented and have cross-references to each other. + * Usually complex operation methods have the + [Columns Selection DSL](https://kotlin.github.io/dataframe/columnselectors.html); + these methods should be documented by the rules above. + * For a better understanding of the complex operation, we write an [operation grammar](#grammar) using a + [special notation](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt). + Add a reference to the operation Grammar in each related class and method KDoc. + +### General Template + +The generalized template for all operations: + +```kotlin +/** + * (First line - brief and concise description of the operation) + * + * (Body - second, third, and so on lines - detailed description of the operation, + * related mechanisms, etc.; optional) + * + * (Documentation website link) + * + * (See also section; optional) + * + * (Columns selection information - for operations with columns selection only) + * + * (Examples section) + * + * (Parameters and return section) + */ +``` + +Below there are some rules for each section: + +#### First line + +The first line should be short but at the same time give a clear understanding of the operation. + +Should start with a verb. Usually "returns" or "creates" for operations that return a simple value, +`DataFrame`, `DataColumn` or `DataRow` (excluding methods which are non-final part of complex operations). + +#### Body + +For the non-trivial operations, write a detailed description of the operation, +describe method behavior and resulting value. + +Describe method-specific mechanisms, especially if it has non-trivial lambda as an argument. +For example, for methods with `RowFilter` predicate, add + +```kotlin +{@include [SelectingRows.RowFilterSnippet]} +``` + +For complex operations, in the KDoc of the initial method write that this is only the first step of the operation, +and it should be continued with other methods. Also add a note about the [operation grammar](#grammar) in this case. +For example (from the `insert` KDoc): + +``` + * This function does not immediately insert the new column but instead specify a column to insert and + * returns an [InsertClause], + * which serves as an intermediate step. + * The [InsertClause] object provides methods to insert a new column using: + * - [under][InsertClause.under] - inserts a new column under the specified column group. + * - [after][InsertClause.after] - inserts a new column after the specified column. + * - [at][InsertClause.at]- inserts a new column at the specified position. + * + * Each method returns a new [DataFrame] with the inserted column. + * + * Check out [Grammar]. +``` + +The next methods in the chain may be finalizing or intermediate steps - write about it explicitly. +Remember to add a link to the initial method and [operation grammar](#grammar) in all of them. + +If the method uses columns selection, add a note about nested columns and column groups: + +``` +@include [SelectingColumns.ColumnGroupsAndNestedColumnsMention] +``` + +and add a link to the +[Selecting Columns topic](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/SelectingColumns.kt) +with custom operation (usually with the customized examples, see [below](#kdoc-helpers-structure). + +``` +See [Selecting Columns][InsertSelectingOptions]. +``` + +#### See also section + +Add a reference to all related methods. Those can be methods with the similar or opposite behavior. + +Example from `DataFrame.first` KDoc: + +``` + * See also [firstOrNull][DataFrame.firstOrNull], + * [last][DataFrame.last], + * [take][DataFrame.take], + * [takeWhile][DataFrame.takeWhile], + * [takeLast][DataFrame.takeLast]. +``` + +* If there is a reverse operation, add a specific note about it. +* If this operation is a shortcut or a special case of another one, +add a note about it. +* If you think a user can confuse the operation with another one, +write it down exactly like that. + +For example, from `group` KDoc: + + +``` + * Reverse operation: [ungroup]. + * + * It is a special case of [move] operation. + * + * Don't confuse this with [groupBy], + * which groups the dataframe by the values in the selected columns! +``` + +#### Documentation website link + +Add a link to the corresponding operation in the +[documentation website](https://kotlin.github.io/dataframe). + +Please add it as an interface KDoc inside +[DocumentationUrls](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt) +and then use add it using `@include`: + +``` + * For more information: {@include [DocumentationUrls.Move]} +``` + +#### Columns selection information + +For any method with columns selection, add a section with information about the columns selection. + +Usually, just `@include` [custom SelectingOptions](#selecting-options). + +#### Examples section + +Write meaningful, easy-to-undertand examples, with detailed comments. + +For complex operations, write a **complete** example with all steps. + +For methods with Columns Selection DSL, add several examples with different CS DSL selection methods. + +Start the section with + +``` +### Examples +``` + +#### Parameters and return section + +Describe parameters and return of the method using `@param` and `@return` tags. +Remember about type parameters. + +Wrap parameter names into `[]` for better readability. + +## KDoc-helpers Structure + +Sometimes, you do not need KDoc-helpers at all — +for simple operations, it's enough to write a short KDoc. + +However, if you want to reuse some common parts of KDocs +(for example, for different overloads of the same method or very similar methods), +it's better to use some KDoc-helpers. + +Here's a standard structure for operation KDoc-helpers: + +```kotlin +// Main KDoc helper interface + +/** + * (First line) + * + * (Body) + * + * (Documentation website link) + * + * (See also section) + */ +internal interface ~OperationName~Docs { + + // `SelectingColumns` helper KDoc with this operation in examples + // - for operations with columns selection + /** + * @comment Version of [SelectingColumns] with correctly filled in examples + * @include [SelectingColumns] {@include [Set~OperationName~OperationArg]} + */ + typealias ~OperationName~Options = Nothing + + // Operation Grammar - for the initial method of the complex operations + /** + * ## ~OperationName~ Operation Grammar + * ... + */ + interface Grammar +} + +// Set operation in [SelectingColumns] examples (in [SelectingColumns.Dsl] and so on) +/** @set [SelectingColumns.OPERATION] [~operationName~][~operation~] */ +@ExcludeFromSources +private typealias Set~OperationName~OperationArg = Nothing + +// Common KDoc part for different overloads of the same method +/** + * @include [~OperationName~Docs] + * ### This ~OperationName~ Overload + */ +@ExcludeFromSources +private interface Common~OperationName~Docs + +/** + * (Include common docs) + * @include [Common~OperationName~Docs] + * + * (Columns selection information) + * @include [SelectingColumns.Dsl] {@include [Set~OperationName~OperationArg]} + * + * (Examples section) + * + * (Parameters and return section) + */ +public fun DataFrame.operation(columns: ColumnsSelector) +``` + +### `@set`/`@get` references + +Sometimes, when you create a template KDoc for different operations, +it's highly useful to have one or more KDoc variables. + +```kt +/** + * Hello from {@get [OPERATION]}! + */ +internal interface CommonDoc { + + // Use UPPER_CASE for references to the set/get arguments + @ExcludeFromSources + interface OPERATION +} +``` + +When using `@set` and `@get` / `$`, it's a good practice to use a reference as the key name. +This makes the KDoc more refactor-safe, and it makes it easier to understand which arguments +need to be provided for a certain template. + +A good example of this concept can be found in the +[`AllColumnsSelectionDsl.CommonAllSubsetDocs` documentation interface](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/all.kt). +This interface provides a template for all overloads of `allBefore`, +`allAfter`, `allFrom`, and `allUpTo` in a single place. + +Nested in the documentation interface, there are several other interfaces that define the expected arguments +of the template. +These interfaces are named `TITLE`, `FUNCTION`, etc. and commonly have no KDocs itself, +just a simple comment explaining what the argument is for. + +Other documentation interfaces like `AllAfterDocs` or functions then include `CommonAllSubsetDocs` and set +all the arguments accordingly. + +It's recommended to name write their name in `UPPER_CASE` +and have them nested in the documentation interface. + +### Grammar + +Grammar is a special notation for describing the complex operation. + +![dslgrammar.png](docs/imgs/dslgrammar.png) + +Any family of functions or operations can show off their notation in a DSL grammar. +This is done by creating a KDoc-topic like +[`InsertDocs.Grammar`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/insert.kt) and linking to it +from each function. + +Each grammar doc must come with a `{@include [DslGrammarLink]}`, which is a link to provide the user with the details +of how the [DSL grammar notation](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt) +works. +An explanation is provided for each symbol used in the grammar. + +I'll copy it here for reference: + +The notation we use is _roughly_ based on [EBNF](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form) +with some slight deviations to improve readability in the context of Kotlin. +The grammars are also almost always decorated with highlighted code snippets allowing you to click around and explore! + +#### Symbols + +- '**`bold text`**' : literal Kotlin notation, e.g. '**`myFunction`**', '**`{ }`**', '**`[ ]`**', etc. +- '`normal text`' : Definitions or types existing either just in the grammar or in the library itself. +- '`:`' : Separates a definition from its type, e.g. '`name: String`'. +- '`|`', '`/`' : Separates multiple possibilities, often clarified with `()` brackets or spaces, e.g. '**`a`**` ( `**`b` + **` | `**`c`**` )`'. +- '`[ ... ]`' : Indicates that the contents are optional, e.g. '`[ `**`a`**` ]`'. Careful to not confuse this with * + *bold** Kotlin brackets **`[]`**. + - NOTE: sometimes **`function`**` [`**`{ }`**`]` notation is used to indicate that the function has an optional + lambda. This function will still require **`()`** brackets to work without lambda. +- '**`,`**` ..`' : Indicates that the contents can be repeated with multiple arguments of the same type(s), e.g. '`[ `* + *`a,`**` .. ]`'. +- '`( ... )`' : Indicates grouping, e.g. '`( `**`a`**` | `**`b`**` )` **`c`**'. + +No other symbols of [EBNF](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form) are used. + +Note that the grammar is not always 100% accurate to keep the readability acceptable. +Always use your common sense reading it, and if you're unsure, try out the function yourself or check +the source code :). + +## Advanced KDocs + +### Clickable Examples + +Examples inside ` ```kt ``` ` code blocks are not clickable unfortunately, as they are not resolved +as actual code +([KT-55073](https://youtrack.jetbrains.com/issue/KT-55073/Improve-KDoc-experience), +[KTIJ-23232](https://youtrack.jetbrains.com/issue/KTIJ-23232/KDoc-autocompletion-and-basic-highlighting-of-code-samples)). + +To work around this, we can do it manually by adding `` ` `` tags and references to functions. +For instance, writing + +```kt +/** + * For example: + * + * `df.`[`select`][DataFrame.select]` { `[`allExcept`][ColumnsSelectionDsl.allExcept]`("a") }` + */ +``` + +will render it correctly, like: + +![example.png](docs/imgs/example.png) + +But keep these things in mind: + +- `[]` references don't work inside `` ` `` tags, so make sure you write them outside code scope. +- Make sure all empty spaces are inside `` ` `` code spans. If they aren't, they will render weirdly. +- According to the [spec](https://github.github.com/gfm/#code-spans), if a string inside a `` ` `` code span `` ` `` + begins and ends with a space but does not consist entirely of whitespace, a single space is removed from the front + and the back. So be careful writing things like `` ` { ` `` and add extra spaces if needed. +- In IntelliJ, references inside `[]` are automatically formatted as `` when rendered to HTML at the moment. + This may change in the future, + so if you want to be sure it looks like code, you can write it like: `` [`function`][ref.to.function] `` +- Having multiple `[]` references and code spans in the same line breaks rendering in + IntelliJ ([KT-55073](https://youtrack.jetbrains.com/issue/KT-55073/Improve-KDoc-experience#focus=Comments-27-6854785.0-0)). + This can be avoided by providing aliases to each reference. +- Both `**` and `__` can be used to make something __bold__ in Markdown. So if you ever need to `@include` something + bold next to something else bold and you want to avoid getting `**a****b**` (which doesn't render correctly), + alternate, + like `**a**__b__`. +- Add one extra newline if you want to put something on a new line. Otherwise, they'll render on the same line. +- Use ` ` (or `{@include [Indent]}`) to add non-breaking-space-based indents in you code samples. + + +### Advanced DSL Grammar Templating (Columns Selection DSL) + +One place where KoDEx really shines is in the templating of DSL grammars. +This has been executed for providing DSL grammars to each function family of the Columns Selection DSL +(and a single large grammar for the DSL itself and the website). +It could be repeated in other places if it makes sense there. +I'll provide a brief overview of how this is structured for this specific case. + +The template is defined +at [DslGrammarTemplateColumnsSelectionDsl.DslGrammarTemplate](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammarTemplateColumnsSelectionDsl.kt). + +Filled in, it looks something like: + +![firstdslgrammar.png](docs/imgs/firstdslgrammar.png) + +As you can see, it consists of three parts: `Definitions`, `What can be called directly in the Columns Selection DSL`, +`What can be called on a ColumnSet`, and `What can be called on a Column Group (reference)`. + +The definition part is filled in like: + +```kt +/** + * {@set [DslGrammarTemplate.DEFINITIONS] + * {@include [DslGrammarTemplate.ColumnSetDef]} + * {@include [LineBreak]} + * {@include [DslGrammarTemplate.ColumnGroupDef]} + * {@include [LineBreak]} + * {@include [DslGrammarTemplate.ConditionDef]} + * ... + * } + */ +``` + +Inside, it should contain all definitions used in the current grammar. +All definitions are defined at `DslGrammarTemplate.XDef` and they contain their formal name and type. +They need to be broken up by line breaks. + +All other parts are filled in like: + +```kt +/** + * {@set [DslGrammarTemplate.PLAIN_DSL_FUNCTIONS] + * {@include [PlainDslName]}` [ `**`{ `**{@include [DslGrammarTemplate.ConditionRef]}**` \}`**` ]` + * ... + * } + * + * {@set [DslGrammarTemplate.COLUMN_SET_FUNCTIONS] + * {@include [Indent]}{@include [ColumnSetName]}` [ `**`{ `**{@include [DslGrammarTemplate.ConditionRef]}**` \}`**` ]` + * ... + * } + * ... + */ +interface Grammar { + + /** [**`first`**][ColumnsSelectionDsl.first] */ + interface PlainDslName + + /** __`.`__[**`first`**][ColumnsSelectionDsl.first] */ + interface ColumnSetName + + /** __`.`__[**`firstCol`**][ColumnsSelectionDsl.firstCol] */ + interface ColumnGroupName +} +``` + +When a reference to a certain definition is used, we take `DslGrammarTemplate.XRef`. +Clicking on them takes users to the respective +`XDef` and thus provides them with the formal name and type of the definition. + +You may also notice that the `PlainDslName`, `ColumnSetName`, and `ColumnGroupName` interfaces are defined separately. +This is to make sure they can be reused in the large Columns Selection DSL grammar and on the website. + +You don't always need all three parts in the grammar; not all functions can be used in each context. +For instance, for the function `none()`, the column set- and column group parts can be dropped. +This can be done in this template by overwriting the respective `DslGrammarTemplate.XPart` with nothing, like here: + +

+ nonegrammar1.png +        + nonegrammar2.png +

+ +Finally, to wrap up the part about this specific template, I'd like to show you the end result. +This is a part of the grammar for the `ColumnsSelectionDsl` itself and how it renders in the KDoc on the user side: + +

+ csdsl1.png +        + csdsl2.png +

+ +A fully interactive, single-source-of-truth grammar for the Columns Selection DSL! + + diff --git a/KDOC_PREPROCESSING.md b/KDOC_PREPROCESSING.md deleted file mode 100644 index c4f145f8ab..0000000000 --- a/KDOC_PREPROCESSING.md +++ /dev/null @@ -1,713 +0,0 @@ -# KDoc Preprocessing with KoDEx - -You might have spotted some notations like `{@include [Something]}` in the `/** KDocs */` of DataFrame's source code. -These are special notations for [KoDEx](https://github.com/Jolanrensen/KoDEx) -that we use to generate parts of the KDoc documentation. - -Kotlin libraries like DataFrame use KDoc to document their code and especially their public API. This allows users -to understand how to use the library and what to expect from it. However, writing KDoc can be a tedious task, especially -when you have to repeat the same information in multiple places. KoDEx allows us to write the -information only once and then include it in multiple places. - -This document explains how to use KoDEx in the DataFrame project. - - - -* [KDoc Preprocessing](#kdoc-preprocessing-with-kodex) - * [How the Processing Works](#how-the-processing-works) - * [Previewing the Processed KDocs in IntelliJ IDEA](#previewing-the-processed-kdocs-in-intellij-idea) - * [Notation](#notation) - * [`@include`: Including content from other KDocs](#include-including-content-from-other-kdocs) - * [ - `@includeFile`: Including all content from a relative file](#includefile-including-all-content-from-a-relative-file) - * [`@set` and `@get` / `$`: Setting and getting variables](#set-and-get---setting-and-getting-variables) - * [`@comment`: Commenting out KDoc content](#comment-commenting-out-kdoc-content) - * [`@sample` and - `@sampleNoComments`: Including code samples](#sample-and-samplenocomments-including-code-samples) - * [`@exportAsHtmlStart` and - `@exportAsHtmlEnd`: Exporting content as HTML](#exportashtmlstart-and-exportashtmlend-exporting-content-as-html) - * [`\`: Escape Character](#-escape-character) - * [ - `@ExcludeFromSources` Annotation: Excluding code content from sources](#excludefromsources-annotation-excluding-code-content-from-sources) - * [KoDEx Conventions in DataFrame](#kodex-conventions-in-dataframe) - * [Common Concepts and Definitions](#common-concepts-and-definitions) - * [Link Interfaces](#link-interfaces) - * [Arg Interfaces](#arg-interfaces) - * [URLs](#urls) - * [Utils](#utils) - * [Documenting an Operation](#documenting-an-operation) - * [Clickable Examples](#clickable-examples) - * [DSL Grammars](#dsl-grammars) - * [Symbols](#symbols) - * [Advanced DSL Grammar Templating (Columns Selection DSL)](#advanced-dsl-grammar-templating-columns-selection-dsl) - * [KDoc -> WriterSide](#kdoc---writerside) - - - -## How the Processing Works - -Unlike Java, Kotlin library authors -[don't have the ability to share a jar file with documentation](https://github.com/Kotlin/dokka/issues/2787). They have -to share documentation along with their `sources.jar` file which users can attach in their IDE to see the docs. -DataFrame thus uses KoDEx in Gradle to copy and modify the source code, processing the KDoc notations, -and publishing the modified files as the `sources.jar` file. - -This can be seen in action in the `core:processKDocsMain` and `core:changeJarTask` Gradle tasks in the -[core/build.gradle.kts file](core/build.gradle.kts). When you run any `publish` task in the `core` module, the -`processKDocsMain` task is executed first, which processes the KDocs in the source files and writes them to the -`generated-sources` folder. The `changeJarTask` task then makes sure that any `Jar` task in the `core` module uses the -`generated-sources` folder as the source directory instead of the normal `src` folder. -It's possible to optionally skip this step, for example, when you publish the library locally during development, -by providing the `-PskipKodex` project property: `./gradlew publishToMavenLocal -PskipKodex` - -`core:processKDocsMain` can also be run separately if you just want to see the result of the KDoc processing by KoDEx. - -To make sure the generated sources can be seen and reviewed on GitHub, -since [PR #731](https://github.com/Kotlin/dataframe/pull/731), -there's been a [GitHub action](.github/workflows/generated-sources.yml) that runs the `core:processKDocsMain` task and -shows the results in the PR checks. After a PR is -merged, [another action](.github/workflows/generated-sources-master.yml) -runs on the master branch and commits the generated sources automatically. -This way, the generated sources are always up to date with the latest changes in the code. -This means you don't have to run and commit the generated sources yourself, though it's -still okay if you do. - -The processing by KoDEx is done in multiple "waves" across the source files. -Each "wave" processes different notations and depends on the results of previous waves. -DataFrame uses -the [recommended order](https://github.com/Jolanrensen/KoDEx/tree/main?tab=readme-ov-file#recommended-order-of-default-processors) -of processors, which is as follows: - -- `INCLUDE_DOC_PROCESSOR`: The `@include` processor -- `INCLUDE_FILE_DOC_PROCESSOR`: The `@includeFile` processor -- `ARG_DOC_PROCESSOR`: The `@set` and `@get` / `$` processor. This runs `@set` first and then `@get` / `$`. -- `COMMENT_DOC_PROCESSOR`: The `@comment` processor -- `SAMPLE_DOC_PROCESSOR`: The `@sample` and `@sampleNoComments` processor -- `EXPORT_AS_HTML_DOC_PROCESSOR`: The `@exportAsHtmlStart` and `@exportAsHtmlEnd` tags for `@ExportAsHtml` -- `REMOVE_ESCAPE_CHARS_PROCESSOR`: The processor that removes escape characters - -See the [Notation](#notation) section for more information on each of these processors. - -## Previewing the Processed KDocs in IntelliJ IDEA - -KoDEx comes with an -[IntelliJ IDEA plugin](https://plugins.jetbrains.com/plugin/26250) -that allows you to preview the processed KDocs without having to run the Gradle task. -It also provides highlighting for the KDoc notations and more. - -![image](https://github.com/Jolanrensen/KoDEx/assets/17594275/7f051063-38c7-4e8b-aeb8-fa6cf14a2566) - -As described in the README of KoDEx, the plugin may not 100% match the results of the Gradle task. This is -because it uses IntelliJ to resolve references instead of Dokka. However, it should give you a good idea of what the -processed KDocs will look like, and, most importantly, it's really fast. - -You can install the plugin from [the marketplace](https://plugins.jetbrains.com/plugin/26250), -by building the project yourself, -or by downloading the latest release from the -[releases page](https://github.com/Jolanrensen/KoDEx/releases). -Simply look for the latest release which has the zip file attached. -If it's outdated or doesn't work on your version of IntelliJ, don't hesitate to -ping [@Jolanrensen](https://github.com/Jolanrensen) -on GitHub. This also applies if you have any issues with the IntelliJ or Gradle plugin, of course :). - -## Notation - -KoDEx uses special notations in KDocs to indicate that a certain (tag) processor should be applied -in that place. -These notations follow the Javadoc/KDoc `@tag content`/`{@tag content}` tag conventions. - -Tags without `{}` are allowed, but only at the beginning of a line, like you're used to with -`@param`, `@return`, `@throws`, etc. If you want to use them in the middle of a line, or inside ` ``` ` blocks, -you should use `{}`. - -Tag processors have access to any number of arguments they need, which are separated by spaces, like: - -```kt -/** - * @tag arg1 arg2 arg3 extra text - * or {@tag arg1 arg2 arg3} - */ -``` - -though, most only need one or two arguments. -It's up to the tag processor what to do with excessive arguments, but most tag processors will leave them in place. - -### `@include`: Including content from other KDocs - -

- include1.png -        - include2.png -

- -The most used tag across the library is `@include [Reference]`. -This tag includes all the content of the supplied reference's KDoc in the current KDoc. -The reference can be a class, function, property, or any other documented referable entity -(type aliases are an exception, as Dokka does not support them). -The reference can be a fully qualified name or a relative name; imports and aliases are taken into account. - -You cannot include something from another library at the moment. - -Writing something after the include tag, like - -```kt -/** - * @include [Reference] some text - */ -``` - -is allowed and will remain in place. Like: - -```kt -/** - * This is from the reference. some text - */ -``` - -Referring to a function with the same name as the current element is allowed and will be resolved correctly -(although, the IntelliJ plugin will not resolve it correctly). -KoDEx assumes you don't want a circular reference, as that does not work for obvious reasons. - -Finally, if you include some KDoc that contains a `[reference]`, KoDEx will replace that reference -with its fully qualified path. This is important because we cannot assume that the target file has access to -the same imports as the source file. The original name will be left in place as alias, like -`[reference][path.to.reference]`. -This is also done for references used as key in `@set` and `@get` / `$` tags. - -### `@includeFile`: Including all content from a relative file - -This tag is not used in the DataFrame project at the moment. It's used like: - -```kt -/** - * @includeFile (path/to/file.kt) - */ -``` - -and, as expected, it pastes the content of the file at the location of the tag. - -Both the relative- and absolute paths are supported. - -### `@set` and `@get` / `$`: Setting and getting variables - -

- arg1.png -        - arg2.png -

- -Combined with `@include`, these tags are the most powerful ones available. -They allow you to create templates and fill them in with different values at the location they're included. - -`@set` is used to set a variable, and `@get` / `$` is used to get the value of a variable -(with an optional default value). - -What's important to note is that this processor is run **after** the `@include` processor and the variables -that are created with `@set` are only available in the current KDoc. - -To form an idea of how they are processed, it's best to think of waves of processing again. - -All `@set` tags are processed before any `@get` / `$` tags. -So there's no `{@set A {@get B}}` cycle, as that would not work. - -For example, given the KDoc from the picture above: - -```kt -/** - * @include [Doc] - * @set NAME Function A - */ -``` - -After running the `@include` processor, the intermediate state of the KDoc will be: - -```kt -/** - * This is {@get NAME default} and it does something cool - * @set NAME Function A - */ -``` - -Then, all `@set` statements are processed: - -```kt -/** - * This is {@get NAME default} and it does something cool - */ -``` - -`NAME` is `"Function A"` now. - -Then all `@get` statements are processed: - -```kt -/** - * This is Function A and it does something cool - */ -``` - -You can put as many `@set` and `@get` / `$` tags in a KDoc as you want, just make sure to pick unique -key names :). -I'd always recommend using a `[Reference]` as key name. -It's a good practice to keep the key names unique and refactor-safe. - -Finally, you need to make sure you take the order of tags processing into account. As stated by -the [README](https://github.com/Jolanrensen/KoDEx/tree/main?tab=readme-ov-file#preprocessors), -tags are processed in the following order: - -* Inline tags - * depth-first - * top-to-bottom - * left-to-right -* Block tags - * top-to-bottom - -This means that you can overwrite a variable by a block tag that was set by an inline tag even if the -inline tag is written below the block tag! - -For example: - -```kt -/** - * $NAME - * @set NAME a - * {@set NAME b} - */ -``` - -Here, `NAME` is first set to `"b"` and the ` {@set NAME b}` part is erased from the doc. -Then `NAME` is set to `"a"` and that line disappears too. -`$NAME` is rewritten to `{@get NAME}` and then it's replaced by retrieving the value of `NAME`, -which makes the final doc look like: - -```kt -/** - * a - * - */ -``` - -### `@comment`: Commenting out KDoc content - -

- comment1.png -        - comment2.png -

- -Just like being able to use `//` in code to comment out lines, you can use `@comment` to comment out KDoc content. -This is useful for documenting something about the preprocessing processes that should not be visible in the -published `sources.jar`. - -Anything inside a `@comment` tag block or inline tag `{}` will be removed from the KDoc when the processor is run. - -### `@sample` and `@sampleNoComments`: Including code samples - -

- sample1.png -        - sample2.png -

- -While this processor is not used in the DataFrame project at the moment, it can be seen as an extension -to the normal `@sample` tag. While the 'normal' `@sample [Reference]` tag shows the code from the target reference as -is, -`@sample` and `@sampleNoComments` actually copy over the code to inside a ` ```kt ``` ` (or `java`) code block in the -KDoc. - -Just like [korro](https://github.com/devcrocod/korro), if `// SampleStart` or `// SampleEnd` are present in the code, -only the code between these markers will be included in the KDoc. - -`@sampleNoComments` is the same as `@sample`, but it will remove all KDocs from the code before pasting it here. - -### `@exportAsHtmlStart` and `@exportAsHtmlEnd`: Exporting content as HTML - -See [KDoc -> WriterSide](#kdoc---writerside). - -### `\`: Escape Character - -The final wave of processing is the removal of escape characters. -This is done by the `REMOVE_ESCAPE_CHARS_PROCESSOR`. - -The escape character `\` is used to escape the special characters `@`, `{`, `}`, `[`, `]`, `$`, and `\` itself. -Escaped characters are ignored by processors and are left in place. - -This means that `/** {\@get TEST} */` will become `/** {@get TEST} */` after preprocessing instead of actually -fetching the value of `TEST`. -Similarly, `/** [Reference\] */` will not be replaced by the fully qualified path of `Reference` after it is -`@include`'d somewhere else. -This can come in handy when building difficult templates containing a lot of `[]` characters that should not be -treated as references. - -### `@ExcludeFromSources` Annotation: Excluding code content from sources - -

- excludeFromSources.png -        - excludeFromSources.png -

- -The `@ExcludeFromSources` annotation is used to exclude a class, function, or property from the `sources.jar` file. -This is useful to clean up the sources and delete interfaces or classes that are only used as KDoc 'source'. - -The annotation is not a KDoc tag but a normal Kotlin annotation detected by KoDEx. - -Since [v0.3.9](https://github.com/Jolanrensen/KoDEx/releases/tag/v0.3.9) it's also possible to -exclude a whole file from the `sources.jar` by adding the annotation to the top of the file, -like `@file:ExcludeFromSources`. - -## KoDEx Conventions in DataFrame - -### Common Concepts and Definitions - -Some definitions are used in multiple places in the library. -It's often useful to define them in one place and include them in multiple other places or -to just link to them so users can read more explanation while clicking through KDocs. - -Common definitions and concepts are placed in -the [documentation folder](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation) -and include things like: - -- [Access APIs](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/AccessApi.kt) - - To be linked to - - String API, Column Accessors API etc. -- [Selecting Columns](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/SelectingColumns.kt) - - To be included in `select`, `update` etc. like `{@include [SelectingColumns.ColumnNames.WithExample]}` (with - args). - - Or to be linked to with `{@include [SelectingColumnsLink]}`. - - By name, by column accessor, by DSL etc. -- [Selecting Rows](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/SelectingRows.kt) - - To be included like `{@include [SelectingRows.RowValueCondition.WithExample]}` in `Update.where`, `filter`, etc. - - Explains the concept and provides examples (with args) -- [`ExpressionsGivenColumn`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenColumn.kt) / [`-DataFrame`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenDataFrame.kt) / [`-Row`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenRow.kt) / [`-RowAndColumn`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenRowAndColumn.kt) - - To be included or linked to in functions like `perRowCol`, `asFrame`, etc. - - Explains the concepts of `ColumnExpression`, `DataFrameExpression`, `RowExpression`, etc. -- [`NA`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/NA.kt) / [`NaN`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/NaN.kt) - - To be linked to for more information on the concepts -- [DslGrammar](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt) - - To be linked to from each DSL grammar by the link interface -- Check the folder to see if there are more and feel free to add them if needed :) - -### Link Interfaces - -As can be seen, interfaces that can be "linked" to, like [`AccessApi`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/AccessApi.kt), are often -accompanied by a `-Link` interface, like - -```kt -/** [Access API][AccessApi] */ -internal interface AccessApiLink -``` - -This allows other docs to simply `{@include [AccessApiLink]}` if they want to refer to -Access APIs, and it provides a single place of truth for if we ever want to rename this concept. - -In general, docs accompanied by a `-Link` interface are meant to be linked to, while docs without -a `-Link` interface are meant to be included in other docs -(and are often accompanied by [`@ExcludeFromSources`](#excludefromsources-annotation-excluding-code-content-from-sources)). -We can deviate from this convention if it makes sense, of course. - -### Arg Interfaces - -```kt -/** - * ## Common Doc - * Hello from $[NameArg]! - */ -interface CommonDoc { - - // The name to be greeted from - interface NameArg - - // alternative recommended notation - interface NAME -} -``` - -When using `@set` and `@get` / `$`, it's a good practice to use a reference as the key name. -This makes the KDoc more refactor-safe, and it makes it easier to understand which arguments -need to be provided for a certain template. - -A good example of this concept can be found in the -[`AllColumnsSelectionDsl.CommonAllSubsetDocs` documentation interface](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/all.kt). -This interface provides a template for all overloads of `allBefore`, -`allAfter`, `allFrom`, and `allUpTo` in a single place. - -Nested in the documentation interface, there are several other interfaces that define the expected arguments -of the template. -These interfaces are named `TitleArg`/`TITLE`, `FunctionArg`/`FUNCTION`, etc. and commonly have no KDocs itself, -just a simple comment explaining what the argument is for. - -Other documentation interfaces like `AllAfterDocs` or functions then include `CommonAllSubsetDocs` and set -all the arguments accordingly. - -It's recommended to name argument interfaces `-Arg`, or to write their name in `ALL_CAPS` (if the linter is shushed) -and have them nested in the documentation interface, though, -this has not always been done in the past. - -### URLs - -When linking to external URLs, it's recommended to use -[DocumentationUrls](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt) and -[Issues](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/Issues.kt). - -It's a central place where we can store URLs that can be used in multiple places in the library. Plus, it makes -it easier to update the documentation whenever (part of) a URL changes. - -### Utils - -The [`utils.kt` file](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/utils.kt) contains all sorts of helper interfaces for the documentation. -For instance `{@include [LineBreak]}` can insert a line break in the KDoc and the family of `Indent` -documentation interfaces can provide you with different non-breaking-space-based indents. - -If you need a new utility, feel free to add it to this file. - -### Documenting an Operation - -When documentation operations such as `select`, `update`, `filter`, etc., it's often useful to work with a central -template. -This template has a title like: `## The Select Operation`, explains its purpose and links to relevant concepts -(with examples). The template can then be included (optionally via multiple other templates and with/without args) -on each overload of the operation. - -It should also link to a DSL grammar if that's available for that operation, plus, if there's -a page on the website relevant to it, it should provide a way to get to that page. - -Let's take the [`select` operation](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/select.kt) as an example: - -It's a relatively simple operation with four overloads which essentially result in the same: a new DataFrame with a subset -of the original columns. - -So, to start off, we make a central documentation interface "Select" and describe what `select` does: -"Returns a new \[DataFrame\] with only the columns selected by \[columns\]." - -Just like `update`, `groupBy`, etc., `select` asks the user to select a subset of columns. -Selecting columns, like selecting rows, is a generic concept -for which there are -some [helpful templates](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/SelectingColumns.kt) ready. - -- For each overload there's a basic template with an optional example: - - Adding `@include [SelectingColumns.KProperties.WithExample] {@set [SelectingColumns.OPERATION] [select][select]}` - to an overload, for instance, generates: - - ![kprops1.png](docs/imgs/kprops1.png) - - As you can see, the example generated has the right, clickable function name! - Of course, we could write the example ourselves if the template doesn't suffice. -- There's a generic explanation for all the ways columns can be selected: - - ![selectingColumns.png](docs/imgs/selectingColumns.png) - - This is a bit large, so it's best if we just link to it. Also, you'll see the examples have - the generic `operation` name. So let's create our own interface `SelectSelectingOptions` we can let users link to and - `{@set [SelectingColumns.OPERATION] [select][select]}`. - Actually, we can even put this setting the operation arg in a central place, since we reuse it a lot. - - All in all, we get: - - ![selectop.png](docs/imgs/selectop.png) - -After using these templates (and a tiny bit of tweaking), we get a fully -and [extensively documented operation](core/generated-sources/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/select.kt) :) - -![selectop2.png](docs/imgs/selectop2.png) - -### Clickable Examples - -Examples inside ` ```kt ``` ` code blocks are not clickable unfortunately, as they are not resolved -as actual code -([KT-55073](https://youtrack.jetbrains.com/issue/KT-55073/Improve-KDoc-experience), -[KTIJ-23232](https://youtrack.jetbrains.com/issue/KTIJ-23232/KDoc-autocompletion-and-basic-highlighting-of-code-samples)). - -To work around this, we can do it manually by adding `` ` `` tags and references to functions. -For instance, writing - -```kt -/** - * For example: - * - * `df.`[`select`][DataFrame.select]` { `[`allExcept`][ColumnsSelectionDsl.allExcept]`("a") }` - */ -``` - -will render it correctly, like: - -![example.png](docs/imgs/example.png) - -But keep these things in mind: - -- `[]` references don't work inside `` ` `` tags, so make sure you write them outside code scope. -- Make sure all empty spaces are inside `` ` `` code spans. If they aren't, they will render weirdly. -- According to the [spec](https://github.github.com/gfm/#code-spans), if a string inside a `` ` `` code span `` ` `` - begins and ends with a space but does not consist entirely of whitespace, a single space is removed from the front - and the back. So be careful writing things like `` ` { ` `` and add extra spaces if needed. -- In IntelliJ, references inside `[]` are automatically formatted as `` when rendered to HTML at the moment. - This may change in the future, - so if you want to be sure it looks like code, you can write it like: `` [`function`][ref.to.function] `` -- Having multiple `[]` references and code spans in the same line breaks rendering in - IntelliJ ([KT-55073](https://youtrack.jetbrains.com/issue/KT-55073/Improve-KDoc-experience#focus=Comments-27-6854785.0-0)). - This can be avoided by providing aliases to each reference. -- Both `**` and `__` can be used to make something __bold__ in Markdown. So if you ever need to `@include` something - bold next to something else bold and you want to avoid getting `**a****b**` (which doesn't render correctly), - alternate, - like `**a**__b__`. -- Add one extra newline if you want to put something on a new line. Otherwise, they'll render on the same line. -- Use ` ` (or `{@include [Indent]}`) to add non-breaking-space-based indents in you code samples. - -### DSL Grammars - -![dslgrammar.png](docs/imgs/dslgrammar.png) - -Any family of functions or operations can show off their notation in a DSL grammar. -This is done by creating a documentation interface like -[`Update.Grammar`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/update.kt) and linking to it -from each function. - -Each grammar doc must come with a `{@include [DslGrammarLink]}`, which is a link to provide the user with the details -of how the [DSL grammar notation](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt) -works. -An explanation is provided for each symbol used in the grammar. - -I'll copy it here for reference: - -The notation we use is _roughly_ based on [EBNF](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form) -with some slight deviations to improve readability in the context of Kotlin. -The grammars are also almost always decorated with highlighted code snippets allowing you to click around and explore! - -#### Symbols - -- '**`bold text`**' : literal Kotlin notation, e.g. '**`myFunction`**', '**`{ }`**', '**`[ ]`**', etc. -- '`normal text`' : Definitions or types existing either just in the grammar or in the library itself. -- '`:`' : Separates a definition from its type, e.g. '`name: String`'. -- '`|`', '`/`' : Separates multiple possibilities, often clarified with `()` brackets or spaces, e.g. '**`a`**` ( `**`b` - **` | `**`c`**` )`'. -- '`[ ... ]`' : Indicates that the contents are optional, e.g. '`[ `**`a`**` ]`'. Careful to not confuse this with * - *bold** Kotlin brackets **`[]`**. - - NOTE: sometimes **`function`**` [`**`{ }`**`]` notation is used to indicate that the function has an optional - lambda. This function will still require **`()`** brackets to work without lambda. -- '**`,`**` ..`' : Indicates that the contents can be repeated with multiple arguments of the same type(s), e.g. '`[ `* - *`a,`**` .. ]`'. -- '`( ... )`' : Indicates grouping, e.g. '`( `**`a`**` | `**`b`**` )` **`c`**'. - -No other symbols of [EBNF](https://en.wikipedia.org/wiki/Extended_Backus%E2%80%93Naur_form) are used. - -Note that the grammar is not always 100% accurate to keep the readability acceptable. -Always use your common sense reading it, and if you're unsure, try out the function yourself or check -the source code :). - -## Advanced DSL Grammar Templating (Columns Selection DSL) - -One place where KoDEx really shines is in the templating of DSL grammars. -This has been executed for providing DSL grammars to each function family of the Columns Selection DSL -(and a single large grammar for the DSL itself and the website). -It could be repeated in other places if it makes sense there. -I'll provide a brief overview of how this is structured for this specific case. - -The template is defined -at [DslGrammarTemplateColumnsSelectionDsl.DslGrammarTemplate](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammarTemplateColumnsSelectionDsl.kt). - -Filled in, it looks something like: - -![firstdslgrammar.png](docs/imgs/firstdslgrammar.png) - -As you can see, it consists of three parts: `Definitions`, `What can be called directly in the Columns Selection DSL`, -`What can be called on a ColumnSet`, and `What can be called on a Column Group (reference)`. - -The definition part is filled in like: - -```kt -/** - * {@set [DslGrammarTemplate.DEFINITIONS] - * {@include [DslGrammarTemplate.ColumnSetDef]} - * {@include [LineBreak]} - * {@include [DslGrammarTemplate.ColumnGroupDef]} - * {@include [LineBreak]} - * {@include [DslGrammarTemplate.ConditionDef]} - * ... - * } - */ -``` - -Inside, it should contain all definitions used in the current grammar. -All definitions are defined at `DslGrammarTemplate.XDef` and they contain their formal name and type. -They need to be broken up by line breaks. - -All other parts are filled in like: - -```kt -/** - * {@set [DslGrammarTemplate.PLAIN_DSL_FUNCTIONS] - * {@include [PlainDslName]}` [ `**`{ `**{@include [DslGrammarTemplate.ConditionRef]}**` \}`**` ]` - * ... - * } - * - * {@set [DslGrammarTemplate.COLUMN_SET_FUNCTIONS] - * {@include [Indent]}{@include [ColumnSetName]}` [ `**`{ `**{@include [DslGrammarTemplate.ConditionRef]}**` \}`**` ]` - * ... - * } - * ... - */ -interface Grammar { - - /** [**`first`**][ColumnsSelectionDsl.first] */ - interface PlainDslName - - /** __`.`__[**`first`**][ColumnsSelectionDsl.first] */ - interface ColumnSetName - - /** __`.`__[**`firstCol`**][ColumnsSelectionDsl.firstCol] */ - interface ColumnGroupName -} -``` - -When a reference to a certain definition is used, we take `DslGrammarTemplate.XRef`. -Clicking on them takes users to the respective -`XDef` and thus provides them with the formal name and type of the definition. - -You may also notice that the `PlainDslName`, `ColumnSetName`, and `ColumnGroupName` interfaces are defined separately. -This is to make sure they can be reused in the large Columns Selection DSL grammar and on the website. - -You don't always need all three parts in the grammar; not all functions can be used in each context. -For instance, for the function `none()`, the column set- and column group parts can be dropped. -This can be done in this template by overwriting the respective `DslGrammarTemplate.XPart` with nothing, like here: - -

- nonegrammar1.png -        - nonegrammar2.png -

- -Finally, to wrap up the part about this specific template, I'd like to show you the end result. -This is a part of the grammar for the `ColumnsSelectionDsl` itself and how it renders in the KDoc on the user side: - -

- csdsl1.png -        - csdsl2.png -

- -A fully interactive, single-source-of-truth grammar for the Columns Selection DSL! - -## KDoc -> WriterSide - -There's a special annotation, `@ExportAsHtml`, that allows you to export the content of the KDoc of the annotated -function, interface, or class as HTML. -The Markdown of the KDoc is rendered to HTML using [JetBrains/markdown](https://github.com/JetBrains/markdown) and, in -the case of DataFrame, put in [./docs/StardustDocs/resources/snippets/kdocs](docs/StardustDocs/resources/snippets/kdocs). -From there, the HTML can be included in any WriterSide page as an iFrame. -This can be done using our custom `` tag. - -An example of the result can be found in the -[DataFrame documentation](https://kotlin.github.io/dataframe/columnselectors.html#full-dsl-grammar). - -The annotation supports two parameters: `theme`, and `stripReferences`, which both are `true` by default. -When the `theme` argument is `true`, some CSS is added to the HTML output to make it look good in combination with -WriterSide. If the `stripReferences` is `true`, all `[]` references are stripped, -like `[name][fully.qualified.name]` -> `name`. This makes the output a lot more readable since -the references won't be clickable in the HTML output anyway. - -Optionally, the tags `@exportAsHtmlStart` and `@exportAsHtmlEnd` can be used to mark the start and end of the content -to be exported as HTML. -This is useful when you only want to export a part of the KDoc. - -`@ExportAsHtml` can also safely be used in combination with `@ExcludeFromSources`. diff --git a/KODEX_KDOC_PREPROCESSING.md b/KODEX_KDOC_PREPROCESSING.md new file mode 100644 index 0000000000..8d1096ca42 --- /dev/null +++ b/KODEX_KDOC_PREPROCESSING.md @@ -0,0 +1,369 @@ +# KDoc Preprocessing with KoDEx + +You might have spotted some notations like `{@include [Something]}` in the `/** KDocs */` of DataFrame's source code. +These are special notations for [KoDEx](https://github.com/Jolanrensen/KoDEx) +that we use to generate parts of the KDoc documentation. + +Kotlin libraries like DataFrame use KDoc to document their code and especially their public API. This allows users +to understand how to use the library and what to expect from it. However, writing KDoc can be a tedious task, especially +when you have to repeat the same information in multiple places. KoDEx allows us to write the +information only once and then include it in multiple places. + +This document explains how to use KoDEx in the DataFrame project. + + +* [KDoc Preprocessing with KoDEx](#kdoc-preprocessing-with-kodex) + * [How the Processing Works](#how-the-processing-works) + * [Previewing the Processed KDocs in IntelliJ IDEA](#previewing-the-processed-kdocs-in-intellij-idea) + * [Notation](#notation) + * [`@include`: Including content from other KDocs](#include-including-content-from-other-kdocs) + * [`@includeFile`: Including all content from a relative file](#includefile-including-all-content-from-a-relative-file) + * [`@set` and `@get` / `$`: Setting and getting variables](#set-and-get---setting-and-getting-variables) + * [`@comment`: Commenting out KDoc content](#comment-commenting-out-kdoc-content) + * [`@sample` and `@sampleNoComments`: Including code samples](#sample-and-samplenocomments-including-code-samples) + * [`@exportAsHtmlStart` and `@exportAsHtmlEnd`: Exporting content as HTML](#exportashtmlstart-and-exportashtmlend-exporting-content-as-html) + * [`\`: Escape Character](#-escape-character) + * [`@ExcludeFromSources` Annotation: Excluding code content from sources](#excludefromsources-annotation-excluding-code-content-from-sources) + * [KoDEx Conventions in DataFrame](#kodex-conventions-in-dataframe) + * [KDoc -> WriterSide](#kdoc---writerside) + + +## How the Processing Works + +Unlike Java, Kotlin library authors +[don't have the ability to share a jar file with documentation](https://github.com/Kotlin/dokka/issues/2787). They have +to share documentation along with their `sources.jar` file which users can attach in their IDE to see the docs. +DataFrame thus uses KoDEx in Gradle to copy and modify the source code, processing the KDoc notations, +and publishing the modified files as the `sources.jar` file. + +This can be seen in action in the `core:processKDocsMain` and `core:changeJarTask` Gradle tasks in the +[core/build.gradle.kts file](core/build.gradle.kts). When you run any `publish` task in the `core` module, the +`processKDocsMain` task is executed first, which processes the KDocs in the source files and writes them to the +`generated-sources` folder. The `changeJarTask` task then makes sure that any `Jar` task in the `core` module uses the +`generated-sources` folder as the source directory instead of the normal `src` folder. +It's possible to optionally skip this step, for example, when you publish the library locally during development, +by providing the `-PskipKodex` project property: `./gradlew publishToMavenLocal -PskipKodex` + +`core:processKDocsMain` can also be run separately if you just want to see the result of the KDoc processing by KoDEx. + +To make sure the generated sources can be seen and reviewed on GitHub, +since [PR #731](https://github.com/Kotlin/dataframe/pull/731), +there's been a [GitHub action](.github/workflows/generated-sources.yml) that runs the `core:processKDocsMain` task and +shows the results in the PR checks. After a PR is +merged, [another action](.github/workflows/generated-sources-master.yml) +runs on the master branch and commits the generated sources automatically. +This way, the generated sources are always up to date with the latest changes in the code. +This means you don't have to run and commit the generated sources yourself, though it's +still okay if you do. + +The processing by KoDEx is done in multiple "waves" across the source files. +Each "wave" processes different notations and depends on the results of previous waves. +DataFrame uses +the [recommended order](https://github.com/Jolanrensen/KoDEx/tree/main?tab=readme-ov-file#recommended-order-of-default-processors) +of processors, which is as follows: + +- `INCLUDE_DOC_PROCESSOR`: The `@include` processor +- `INCLUDE_FILE_DOC_PROCESSOR`: The `@includeFile` processor +- `ARG_DOC_PROCESSOR`: The `@set` and `@get` / `$` processor. This runs `@set` first and then `@get` / `$`. +- `COMMENT_DOC_PROCESSOR`: The `@comment` processor +- `SAMPLE_DOC_PROCESSOR`: The `@sample` and `@sampleNoComments` processor +- `EXPORT_AS_HTML_DOC_PROCESSOR`: The `@exportAsHtmlStart` and `@exportAsHtmlEnd` tags for `@ExportAsHtml` +- `REMOVE_ESCAPE_CHARS_PROCESSOR`: The processor that removes escape characters + +See the [Notation](#notation) section for more information on each of these processors. + +## Previewing the Processed KDocs in IntelliJ IDEA + +KoDEx comes with an +[IntelliJ IDEA plugin](https://plugins.jetbrains.com/plugin/26250) +that allows you to preview the processed KDocs without having to run the Gradle task. +It also provides highlighting for the KDoc notations and more. + +![image](https://github.com/Jolanrensen/KoDEx/assets/17594275/7f051063-38c7-4e8b-aeb8-fa6cf14a2566) + +As described in the README of KoDEx, the plugin may not 100% match the results of the Gradle task. This is +because it uses IntelliJ to resolve references instead of Dokka. However, it should give you a good idea of what the +processed KDocs will look like, and, most importantly, it's really fast. + +You can install the plugin from [the marketplace](https://plugins.jetbrains.com/plugin/26250), +by building the project yourself, +or by downloading the latest release from the +[releases page](https://github.com/Jolanrensen/KoDEx/releases). +Simply look for the latest release which has the zip file attached. +If it's outdated or doesn't work on your version of IntelliJ, don't hesitate to +ping [@Jolanrensen](https://github.com/Jolanrensen) +on GitHub. This also applies if you have any issues with the IntelliJ or Gradle plugin, of course :). + +## Notation + +KoDEx uses special notations in KDocs to indicate that a certain (tag) processor should be applied +in that place. +These notations follow the Javadoc/KDoc `@tag content`/`{@tag content}` tag conventions. + +Tags without `{}` are allowed, but only at the beginning of a line, like you're used to with +`@param`, `@return`, `@throws`, etc. If you want to use them in the middle of a line, or inside ` ``` ` blocks, +you should use `{}`. + +Tag processors have access to any number of arguments they need, which are separated by spaces, like: + +```kt +/** + * @tag arg1 arg2 arg3 extra text + * or {@tag arg1 arg2 arg3} + */ +``` + +though, most only need one or two arguments. +It's up to the tag processor what to do with excessive arguments, but most tag processors will leave them in place. + +### `@include`: Including content from other KDocs + +

+ include1.png +        + include2.png +

+ +The most used tag across the library is `@include [Reference]`. +This tag includes all the content of the supplied reference's KDoc in the current KDoc. +The reference can be a class, function, property, or any other documented referable entity +(type aliases are an exception, as Dokka does not support them). +The reference can be a fully qualified name or a relative name; imports and aliases are taken into account. + +You cannot include something from another library at the moment. + +Writing something after the include tag, like + +```kt +/** + * @include [Reference] some text + */ +``` + +is allowed and will remain in place. Like: + +```kt +/** + * This is from the reference. some text + */ +``` + +Referring to a function with the same name as the current element is allowed and will be resolved correctly +(although, the IntelliJ plugin will not resolve it correctly). +KoDEx assumes you don't want a circular reference, as that does not work for obvious reasons. + +Finally, if you include some KDoc that contains a `[reference]`, KoDEx will replace that reference +with its fully qualified path. This is important because we cannot assume that the target file has access to +the same imports as the source file. The original name will be left in place as alias, like +`[reference][path.to.reference]`. +This is also done for references used as key in `@set` and `@get` / `$` tags. + +### `@includeFile`: Including all content from a relative file + +This tag is not used in the DataFrame project at the moment. It's used like: + +```kt +/** + * @includeFile (path/to/file.kt) + */ +``` + +and, as expected, it pastes the content of the file at the location of the tag. + +Both the relative- and absolute paths are supported. + +### `@set` and `@get` / `$`: Setting and getting variables + +

+ arg1.png +        + arg2.png +

+ +Combined with `@include`, these tags are the most powerful ones available. +They allow you to create templates and fill them in with different values at the location they're included. + +`@set` is used to set a variable, and `@get` / `$` is used to get the value of a variable +(with an optional default value). + +What's important to note is that this processor is run **after** the `@include` processor and the variables +that are created with `@set` are only available in the current KDoc. + +To form an idea of how they are processed, it's best to think of waves of processing again. + +All `@set` tags are processed before any `@get` / `$` tags. +So there's no `{@set A {@get B}}` cycle, as that would not work. + +For example, given the KDoc from the picture above: + +```kt +/** + * @include [Doc] + * @set NAME Function A + */ +``` + +After running the `@include` processor, the intermediate state of the KDoc will be: + +```kt +/** + * This is {@get NAME default} and it does something cool + * @set NAME Function A + */ +``` + +Then, all `@set` statements are processed: + +```kt +/** + * This is {@get NAME default} and it does something cool + */ +``` + +`NAME` is `"Function A"` now. + +Then all `@get` statements are processed: + +```kt +/** + * This is Function A and it does something cool + */ +``` + +You can put as many `@set` and `@get` / `$` tags in a KDoc as you want, just make sure to pick unique +key names :). +I'd always recommend using a `[Reference]` as key name. +It's a good practice to keep the key names unique and refactor-safe. + +Finally, you need to make sure you take the order of tags processing into account. As stated by +the [README](https://github.com/Jolanrensen/KoDEx/tree/main?tab=readme-ov-file#preprocessors), +tags are processed in the following order: + +* Inline tags + * depth-first + * top-to-bottom + * left-to-right +* Block tags + * top-to-bottom + +This means that you can overwrite a variable by a block tag that was set by an inline tag even if the +inline tag is written below the block tag! + +For example: + +```kt +/** + * $NAME + * @set NAME a + * {@set NAME b} + */ +``` + +Here, `NAME` is first set to `"b"` and the ` {@set NAME b}` part is erased from the doc. +Then `NAME` is set to `"a"` and that line disappears too. +`$NAME` is rewritten to `{@get NAME}` and then it's replaced by retrieving the value of `NAME`, +which makes the final doc look like: + +```kt +/** + * a + * + */ +``` + +### `@comment`: Commenting out KDoc content + +

+ comment1.png +        + comment2.png +

+ +Just like being able to use `//` in code to comment out lines, you can use `@comment` to comment out KDoc content. +This is useful for documenting something about the preprocessing processes that should not be visible in the +published `sources.jar`. + +Anything inside a `@comment` tag block or inline tag `{}` will be removed from the KDoc when the processor is run. + +### `@sample` and `@sampleNoComments`: Including code samples + +

+ sample1.png +        + sample2.png +

+ +While this processor is not used in the DataFrame project at the moment, it can be seen as an extension +to the normal `@sample` tag. While the 'normal' `@sample [Reference]` tag shows the code from the target reference as +is, +`@sample` and `@sampleNoComments` actually copy over the code to inside a ` ```kt ``` ` (or `java`) code block in the +KDoc. + +Just like [korro](https://github.com/devcrocod/korro), if `// SampleStart` or `// SampleEnd` are present in the code, +only the code between these markers will be included in the KDoc. + +`@sampleNoComments` is the same as `@sample`, but it will remove all KDocs from the code before pasting it here. + +### `@exportAsHtmlStart` and `@exportAsHtmlEnd`: Exporting content as HTML + +See [KDoc -> WriterSide](#kdoc---writerside). + +### `\`: Escape Character + +The final wave of processing is the removal of escape characters. +This is done by the `REMOVE_ESCAPE_CHARS_PROCESSOR`. + +The escape character `\` is used to escape the special characters `@`, `{`, `}`, `[`, `]`, `$`, and `\` itself. +Escaped characters are ignored by processors and are left in place. + +This means that `/** {\@get TEST} */` will become `/** {@get TEST} */` after preprocessing instead of actually +fetching the value of `TEST`. +Similarly, `/** [Reference\] */` will not be replaced by the fully qualified path of `Reference` after it is +`@include`'d somewhere else. +This can come in handy when building difficult templates containing a lot of `[]` characters that should not be +treated as references. + +### `@ExcludeFromSources` Annotation: Excluding code content from sources + +

+ excludeFromSources.png +        + excludeFromSources.png +

+ +The `@ExcludeFromSources` annotation is used to exclude a class, function, or property from the `sources.jar` file. +This is useful to clean up the sources and delete interfaces or classes that are only used as KDoc 'source'. + +The annotation is not a KDoc tag but a normal Kotlin annotation detected by KoDEx. + +Since [v0.3.9](https://github.com/Jolanrensen/KoDEx/releases/tag/v0.3.9) it's also possible to +exclude a whole file from the `sources.jar` by adding the annotation to the top of the file, +like `@file:ExcludeFromSources`. + +## KoDEx Conventions in DataFrame + +See [KDoc Guidelines](KDOC_GUIDELINES.md). + +## KDoc -> WriterSide + +There's a special annotation, `@ExportAsHtml`, that allows you to export the content of the KDoc of the annotated +function, interface, or class as HTML. +The Markdown of the KDoc is rendered to HTML using [JetBrains/markdown](https://github.com/JetBrains/markdown) and, in +the case of DataFrame, put in [./docs/StardustDocs/resources/snippets/kdocs](docs/StardustDocs/resources/snippets/kdocs). +From there, the HTML can be included in any WriterSide page as an iFrame. +This can be done using our custom `` tag. + +An example of the result can be found in the +[DataFrame documentation](https://kotlin.github.io/dataframe/columnselectors.html#full-dsl-grammar). + +The annotation supports two parameters: `theme`, and `stripReferences`, which both are `true` by default. +When the `theme` argument is `true`, some CSS is added to the HTML output to make it look good in combination with +WriterSide. If the `stripReferences` is `true`, all `[]` references are stripped, +like `[name][fully.qualified.name]` -> `name`. This makes the output a lot more readable since +the references won't be clickable in the HTML output anyway. + +Optionally, the tags `@exportAsHtmlStart` and `@exportAsHtmlEnd` can be used to mark the start and end of the content +to be exported as HTML. +This is useful when you only want to export a part of the KDoc. + +`@ExportAsHtml` can also safely be used in combination with `@ExcludeFromSources`. diff --git a/docs/KDocs-Guidelines.md b/docs/KDocs-Guidelines.md deleted file mode 100644 index 6ee57b826f..0000000000 --- a/docs/KDocs-Guidelines.md +++ /dev/null @@ -1,302 +0,0 @@ -# Documentation Guidelines - -This document outlines the guidelines for writing KDocs in the Kotlin DataFrame project. - - - -* [The most important advice](#the-most-important-advice) -* [KoDEx](#kodex) - *[Reuse Common Parts](#reuse-common-parts) -* [What should be documented?](#what-should-be-documented) -* [Kotlin DataFrame Operations KDoc Structure](#kotlin-dataframe-operations-kdoc-structure) - + [General Template](#general-template) -* [Helper KDoc Interfaces Structure](#helper-kdoc-interfaces-structure) - - - -## The most important advice - -Please never write KDocs from scratch without a necessity! -Find existing KDocs for the similar operation or other entity and reuse it. -However, take its specific into account. - -And don't be afraid to deviate from the rules or add something new – -the most important thing is to help users to understand the library better! - -## KoDEx - -We use [KoDEx](https://github.com/Jolanrensen/KoDEx) KDocs preprocessor. -It adds several useful utilities for writing KDocs. - -Please read about -the [KDocs preprocessing using KoDEx](../KDOC_PREPROCESSING.md) before working with Kotlin DataFrame KDocs. - -Install the [KoDEx plugin for IDEA](https://plugins.jetbrains.com/plugin/27473---kodex---kotlin-documentation-extensions) -for correct KDocs display inside the IntelliJ IDEA. - -### Reuse Common Parts - -One of the best utilities of KoDEx is the ability to reuse common parts of KDocs. -This can be done by using the -[`@include` tag](https://github.com/Jolanrensen/KoDEx/wiki/Notation#include-including-content-from-other-kdocs), -which allows you to include a KDoc for another documentable element. -This could be a class, an interface, a typealias, and so on. - -There are a lot of interfaces in the project that are only used for including their KDocs -to other KDocs. Such interfaces are marked with `@ExcludeFromSources` -and not included in the sources after the compilation (make sure you are not referencing them directly, -i.e., only use it inside the `@include`). For example, -[`ColumnPathCreation` interface](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ColumnPathCreation.ktc) -has a KDoc which describes column path creation behavior. The whole file is excluded from sources, -but the KDoc is included in other KDocs. -Also, you can use -[`@set` and `@get` tags](https://github.com/Jolanrensen/KoDEx/wiki/Notation#set-and-get---setting-and-getting-variables) -along with `@include` to change variables value in common parts. This is especially useful for -writing examples of methods with similar usage but with different names. - -## What should be documented? - -**All public API should be documented!** Our goal is 100% public functions, classes, and variable KDocs coverage. -Some part of public API is not intended for end users, but can be used in Compiler Plugin or potential -KDF extension libraries. These methods should have a small KDocs as well. - -For internal API, please add at least a small note. - -## Kotlin DataFrame Operations KDoc Structure - -Operation KDocs size and structure completely depend on the operation complexity. - -The best way to write a new KDoc for an operation is to define its kind and -use the existing KDoc of an operation of the same kind as a template. - -There are four kinds; here's a list of them: - -1. Simple, Stdlib-like operations that don't have arguments or have simple types (primitives, classes) -as arguments and return simple value, `DataFrame`, `DataRow` or `DataColumn`. - * For example, -[`first` without arguments](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt#L106). - * KDocs for such operations can be short, especially if it's trivial enough. -2. Operations with [`DataRow` API](https://kotlin.github.io/dataframe/datarow.html). - * For example, [`first` with predicate](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt#L139). - * Remember to describe a mechanism of `DataRow` API usage in the KDoc - it's not obvious to the user. -3. Operations with the [Columns Selection DSL](https://kotlin.github.io/dataframe/columnselectors.html) that return a single and return simple value, `DataFrame`, `DataRow` or `DataColumn`. - * For example, [`remove`](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/remove.kt). - * Remember to describe a mechanism of Columns Selection DSL. - * Add several examples with different columns selection options. -4. Complex operations with a multiple methods chain. - * For example, [`convert`](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convert.kt). - * Such operations consist of at least two methods and special resulting classes as the intermediate steps. - All of them should be well documented and have cross references to each other. - * Usually some os the methods have the [Columns Selection DSL](https://kotlin.github.io/dataframe/columnselectors.html); - these methods should be documented by the rules above. - * For a better understanding of the complex operation, we write an [operation grammar](#grammar) using a - [special notation](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt). - Add a reference to the operation Grammar in each related class and method KDoc. - -### General Template - -The generalized template for all operations: - -```kotlin -/** - * (First line - brief and concise description of the operation) - * - * (Body - second, third, and so on lines - detailed description of the operation, - * related mechanisms, etc.; optional) - * - * (Documentation website link) - * - * (See also section; optional) - * - * (Columns selection information - for operations with columns selection only) - * - * (Examples section) - * - * (Parameters and return section) - */ -``` - -Below there are some rules for each section: - -#### First line - -The first line should be short but at the same time give a clear understanding of the operation. - -Should start with a verb. Usually "returns" or "creates" for operations that return a simple value, -`DataFrame`, `DataColumn` or `DataRow` (excluding methods which are non-final part of complex operations). - -#### Body - -For the non-trivial operations, write a detailed description of the operation, -describe method behavior and resulting value. - -For complex operations, in the KDoc of the initial method write that this is only the first step of the operation, -and it should be continued with other methods. Also add a note about the [operation grammar](#grammar) in this case. -For example (from the `insert` KDoc): - -``` - * This function does not immediately insert the new column but instead specify a column to insert and - * returns an [InsertClause], - * which serves as an intermediate step. - * The [InsertClause] object provides methods to insert a new column using: - * - [under][InsertClause.under] - inserts a new column under the specified column group. - * - [after][InsertClause.after] - inserts a new column after the specified column. - * - [at][InsertClause.at]- inserts a new column at the specified position. - * - * Each method returns a new [DataFrame] with the inserted column. - * - * Check out [Grammar]. -``` - -The next methods in the chain may be finalizing or intermediate steps - write about it explicitly. -Remember to add a link to the initial method and [operation grammar](#grammar) in all of them. - -If the method uses columns selection, add a note about nested columns and column groups: - -``` -@include [SelectingColumns.ColumnGroupsAndNestedColumnsMention] -``` - -#### See also section - -Add a reference to all related methods. Those can be methods with the similar or opposite behavior. - -Example from `DataFrame.first` KDoc: - -``` - * See also [firstOrNull][DataFrame.firstOrNull], - * [last][DataFrame.last], - * [take][DataFrame.take], - * [takeWhile][DataFrame.takeWhile], - * [takeLast][DataFrame.takeLast]. -``` - -* If there is a reverse operation, add a specific note about it. -* If this operation is a shortcut or a special case of another one, -add a note about it. -* If you think a user can confuse the operation with another one, -write it down exactly like that. - -For example, from `group` KDoc: - - -``` - * Reverse operation: [ungroup]. - * - * It is a special case of [move] operation. - * - * Don't confuse this with [groupBy], - * which groups the dataframe by the values in the selected columns! -``` - -#### Documentation website link - -Add a link to the corresponding operation in the -[documentation website](https://kotlin.github.io/dataframe). - -Please add it as an interface KDoc inside -[DocumentationUrls](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt) -and then use add it using `@include`: - -``` - * For more information: {@include [DocumentationUrls.Move]} -``` - -#### Columns selection information - -For any method with columns selection, add a section with information about the columns selection. - -Usually, just `@include` [custom SelectingOptions](#selecting-options). - -#### Examples section - -Write meaningful, easy-to-undertand examples, with detailed comments. - -For complex operations, write a **complete** example with all steps. - -For methods with Columns Selection DSL, add several examples with different CS DSL selection methods. - -Start the section with - -``` -### Examples -``` - -#### Parameters and return section - -Describe parameters and return of the method using `@param` and `@return` tags. -Remember about type parameters. - -Wrap parameter names into `[]` for better readability. - -## Helper KDoc Interfaces Structure - -For more information, see [KoDEx Conventions in DataFrame](../KDOC_PREPROCESSING.md#kodex-conventions-in-dataframe). - -Sometimes, you do not need helper interfaces with KoDEx temples at all - -for simple operations, it's enough to write a short KDoc. - -However, if you want to reuse some common parts of KDocs -(for example, for different overloads of the same method or very similar methods), -it's better to use some helper interfaces. - -Complex operations - -Here's a standard structure for them: - -```kotlin -// Main KDoc helper interface - -/** - * (First line) - * - * (Body) - * - * (Documentation website link) - * - * (See also section) - */ -internal interface ~OperationName~Docs { - - // `SelectingColumns` helper KDoc with this operation in examples - // - for operations with columns selection - /** - * @comment Version of [SelectingColumns] with correctly filled in examples - * @include [SelectingColumns] {@include [Set~OperationName~OperationArg]} - */ - typealias ~OperationName~Options = Nothing - - // Operation Grammar - for the initial method of the complex operations - /** - * ## ~OperationName~ Operation Grammar - * ... - */ - interface Grammar -} - -// Set operation in [SelectingColumns] examples (in [SelectingColumns.Dsl] and so on) -/** @set [SelectingColumns.OPERATION] [~operationName~][~operation~] */ -@ExcludeFromSources -private typealias Set~OperationName~OperationArg = Nothing - -// Common KDoc part for different overloads of the same method -/** - * @include [~OperationName~Docs] - * ### This ~OperationName~ Overload - */ -@ExcludeFromSources -private interface Common~OperationName~Docs - -/** - * (Include common docs) - * @include [Common~OperationName~Docs] - * - * (Columns selection information) - * @include [SelectingColumns.Dsl] {@include [Set~OperationName~OperationArg]} - * - * (Examples section) - * - * (Parameters and return section) - */ -public fun DataFrame.operation(columns: ColumnsSelector) -``` diff --git a/docs/imgs/dslgrammar.png b/docs/imgs/dslgrammar.png index 5cb12221f82a93eaf925cd490f2427b34edbe132..b10c83ab838aced67f9a61bb6e5e155fdd8b0325 100644 GIT binary patch literal 46663 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%0D-pPOc=KvRzDKGm2#;3T> zz`EZUb%EU0nqb?fooE=19rbH$cjMxAhsmlXf5!>BhE@IjF6?!ic)Nb8nE_GmKjsbU zg@(WR0OyC7!1E{`$K4r@Z5F)6sPs(gsbbvC|tnMk11^N>rvH;e>e4yW%puOn}!1o+BZlie5i3pLAG!VfR@4q1Q?qsOMCFjRS zRUP8*m(KKW31$TX>k#-=S#l_Wu6v-FFaB6V8~<`5{&o233(2vDx1YRjJ<+z9-7Vf` z7JRR;$E~ewtK->PqIk358QslKTzE)PmD%>M1=98ET0ja)_-~~VC=V<+hlZff;)o?m z@8IGTJCl)tT!{tJJS>+?ad<*WubORt6zcP?wec?-^bcr*~>w$t-1OhFi|rieiQi8Yzlq_?V#13{Ev8fp&Mn z_o=FlLDeWBoM@EQ<`?F0+e8Cwku-$QQh0VnOb~7yj7x!SmCb>IQ+{+DR~BgEqdE?_Yx&e$j9*HiY3> z(j7)9oD)iU4FY;#XGg`&WA<6=tP?tE=Pj(xQ=h`@J1OuxinO0N2Dum$#nJ~V*9nYT zPsYmLoFOQhf0f74NaB7ZrT2I3G8?}qfZ(@P_DqNrOkdNk8+zlq^=j?kel$`Kk=(HsINxHwJwW3<)(&f*Tt9e|IHnG;DIyB-{R`O0n zuNn#?=(4Q#_-xF4L76zC%@NvC5u$Y6c!YA2N76;JQ*K#g%k_~}V(8c1aYnb=wqSWXkD z;hN~OOC;2e!~bzm`$ugq=@fx%cJjL;gQK-*L!EY1$Vg*eq&DdTXTxjd$`qgj_KHXJ zDk1ZrqPHA0e7+P+{Y*7wZs|nVp+WmVRwhojH|V{Ih>zzG;kHWzkQa=)*A@DgBDzgR z*H#Hjl@$M@n47ERaEqw`Jqc_oT5CX>KgRUsz{_~-`q`MHm~k6?%?WsgxDz;vE`p@7 zq$SQGDPaQXrs;8gFxR)y36B=g{~srqjhW?C1G(!T?~?`Y&mNB?youk4S)-8qv5lk- z!J=v8MSO)8H%Qm=Lp{#VEQqPuRmmslZrOo?@du)!!}y+dYe>AVnPZ!fqv}5<((wcM_2Pi?WwEf z*|x!uo$IP1-wFjG@8e=WkW15YX406)1))Bj#NC%;jrPt=1seSTG45K;H_(&a(qqY$pFH?aC)dBJyOI5t0B{y15eJe4 zn#nXDgjiBy_&8Hr(zKNY{rSxXsT4E?D9_VwXaw7GCI^qTi2FRWUl0cfzA6*K2as_9 zsP7(w&RdyE-80+Za$xQI2BYGC{8?!^C$|HN?~fcmtzqeA5q_(0~zB3SEUom49RAui6EAcaf! za3B5}zhvrL=+AT*GSRlEYR7~lk)>TL&_<7ZWI5ONTkaIpnY_{u1W3&!XgNM7;xO|(@jGK^ zBicgBYgVDYNG86*EW6ggcTO+QoMt#wwK^N_YRo=HWa1=!N4VvjbP965A6Q1%H$Z5T zs0o!hnn%kCZ6nIPji4BLXnQlXn-VVg+IDvsvx7&y;U`{;k+GmVJXF|iaQ`0L)Ye%z z4(!{DI?shDKX$(|r-6F+TuQ6=)4EIDqBlYimmNWN_GdPQ-wFcU7e;T_y8jT#XE$|u zsI0;$@qauA0WBLui67E?$kMBAp8P!a=6?xJkj2Vt!OvKhSKz!3hrO3pKs=B2SO_{;xxN7GvEAx=af71nx`w;47AEvz6 zl|cXftH5T!_@41B>)j#S^F<*>CM})*uP&O>mP%_!Bzug7`tF?}{%NcelVPsAp-<*8 zpA&0XC?xRoHpY_0umT%((Kl^PjPq4%_{{bc7U-S#mv|69cg43_+|~v$B|hf(-Ak14 zbY;#?3fXtdXps2oqSMsS>66zNKS$A2uX+xb#Y zN3ztymOb9o+8{@s$nXN88=}f%rDVb_VEv`;pX{+@l*~>SmD_=2O^OLFa-YmZI zdVraK!?CsuI(Lqv8Bt^8x3`4W>nmSpkcA)@J6b-}{U=!Hy$3XZLA%#o75}ayxCs<^Pp)*_i@h63F z0a%j&KaR#dFlk1`E-eoO4fGNkx_7~ksln>v!_%<{((43|)lZNG$hSmh4WSt0y?(yd zyWEydT0Nw3EqCVfN@8}diMdp5&&^K-1Yqfv{%}Mji;Y4G$2?FQb>4)5e1pY6NHrte zU_;(WJgv3Y-Xvw}hES+vmG4uJ93;RQC%K*nf-xiHw9Rr}t)@kEHhX||;p|I@D* zWC8&QJMixwSsYtCDVG~9)c)hlfxp#SIfp%vgDf2=9+qs4 za;{GoNtHOt1sNv4mFPLGiAt2JP(hU~@4O=gD>r@5G^jRWd=S5fp7wTLub_77%=jiw%k9;(~-ldZSs# z_3HBdUw-+-fQ+}7YU$XAo$FbGk(m?J96Di0Vu__u) zA^`cmHo*a?8Rp&Ws{$!B+u5ox?1KJ(P{Z761l!W}H#^zvt6@%8?N|9lt$tHWnAPlz zk+b{Rm8R4hUMY_zEHMSft6Usa5ityv486Kv@f~f|G_i#~&momm_yn8tAdhB~to2VF zW>~qg_fi#~<{!;_HNN=vT2iVaD`J=ao&o1@0%(tthomUl70$=}VytxL4P zv6-}vB(nOXjxQYZ--+Ua45pN;_v7kFmxxEBWXz7yQSmQ{Z6qry;rH~gc7Y7Hc8~m? zlbzO?zlwQyZ*FHJ_6OHP1NiU++qEZk zGNJu~BR%Qc0nmf7+dWGS!rz2j&;PEybXS*Y6xnQzcwWwnc92_L)2{uq1+KHmKxX;m z$^acoMvP)2`3R`yBFhh?r~6ioihlgIZ$o3z{R26k&5H7KBF!^-!LOmAc8j;JD0DbgyPb(fbJ_l4mbBMr zSKgs`>bsuTV|ML1bj+%VenBf*(d%ZF&_r>Tn4a8@RXh|Q(~+G4Z*+a@iR`yFMxHm0 zvjrf8*@(=@`HiWP?I@(p;c0MPPRrxinL8_2*t9RohRWdA{VCtW%Wflw4~N)0n3jyZ z-zUNrh$W2=CjbY>?`VRKx8Z>crAOe#kGGOg-0WGdxg5;Iw{Y3YNyu0$%k zycS91am3c5#auDD*)NMgsj1Th4bl)RVu)81Rb=Ja{9?Yp)LWIuX4^?;Mgvy^Gk0yp z&UfkqU!-lO>1e^ayxD_AIZ>~6xo7FPCHlvzfNE*ama!|@)?#?JrOI>m+_0DXg;-7J zmGcMMy{F4JtueTF=hTO3cd3VZb$D0~w7bw-F{lr|UR~m;Z&q)Z_3yJHh+QUl_p>aq z^xdU=vdvg2H}O)p3vtr$@MG?@LOE3VvgH->mX#j~G48(INlv@=ThpSYE)=?60iV5n zq*$AU+jK<(lHbH^H8cq~4j@HYWA>J3zAPh|X;MTOG|IsVLiBABB^kS=1R zu7zwwl_EUchk!R#~7RN(lPWtejNFN*}h#4uYK8!-H!4!nt&&;!lZFhOS+)SuUY%{K=t`mss4(FW%pSBUi-6myf zL#DD)ldXA7N7V777lZ@Rr4Nt5r)}Pd1v_lU-`uWcm{wbb4=o$bC5ZZU91s>a=aA>;WStH@8HnyC|$H(dUg z%e!q#8SR?qPu>|;eza1}keFz2)Y$*sO~%5~g!laue6MBb0J8mSsyi0{#f0AC*54s^ za>|XPB2%k@5Drsy>)Lz7xe;zFWfB(Llx+CwJsjt;y^FXF{}z7wxkZi>pVHpWE&{`* z{wCP~G{|@QD(*F!JF=+PV%HYww(nrbb(?^BUE9u?jx@roFA8+36+g>CK-U-v$#jFL z$tvnE4%>3XW{!3?*rJD$`}Wkmmi$HC<0h10;*hTaPAG44M=a0=b^z`nmi9X?uHj)t z*jCpp@7&ffrL29$xNA<+1p3fJn3eG%^T>}zl%~dyBa@Or-t%nlU#X*8MN#WvxH5c5 z#3}WF;qk}S3S<=(QKsMIY+5^8B0p~B%IV@HTBySKh}t;PW=FCsReCKd%-DR|#49`& zGe29GEov6(|1tE+akd!=C<#uC-xXiMMHgE5vVSq=_lmFxwp}+U)4^IHAaan5`&f;q zx8vz6pc$dh=IyBjio~f!=$-&B$3MSbC&6_5YxX%2cg7sdPSxUNC(3TK-8t4!UcW zLXOJ3szDd9kSSg5NTLh^p*LEa3$WIx&PMo^y_lGNH_h*ACi$8ADBLJ9brFrvvVp@_ zQ5O>Z;SG;@d;1g1Ns=;mbkj}Olyvne1dP9TcYJ-d#x}a9>mZtr5*6VwcoH7_{W0_O z!Ibf8MR}mCfnA(wLaA>l*u3k!H&C>iT6+UDDxxZxNj&DXX+804V5*;{q*{Uvt=sCy z%3K%{%XL~sbH|`e0}BgSlYCL4dxqsT;LG^sjR~6SGcM_B_oe%F+)+{QMd+XdT-VUU z6)7YN64?%Pq7G@jEOjQdj=O@??$d74r-Po1Kl5h9U^;es+Uv&GA)`*tWPrBy@eG_Q zpUN&2^h07Qm`a{Ksgw-@+)6~JkyH>e@cwc$!1g6=(btz4^1F1AP!{me5K!kdT=B9> z6q|)9b@ZkjH3fOg%iOYxi^vRfu0`vQSiexcTv3*dB5>HBU-!32KP{c}+0S$dKSgjA zHC^!}Uw^PO^49jK<~ogBqv!M#HOh3-*3rN93M4Z+UpJCaR<5CQlZT6)n4;6LZX``E z9kg#X?b)4CSE#}qZ(^n@efj#Xr}AZ$7p`^bBF5X~U`IlyS31-gQF$|W@^7L)@moDz z;Ql~A(Q3`bfyk_rzzT-<6Gv|yv~_6AySZ&Wo&G`$JKE-{^p2=0r?_^4&S&1@FV1^L z=TX-k{yyP$=Nlj=PNG|&?R7ZXBGzxcd|;HBA-lhFv@c|EJG%gJ^BNT3vL~&O zo(_h^4%eWd0yg1fuhXgVwxsc)kljF(P3SQuUy?y+K23sc3&rMk=iY~j-siIfBonaj z|6Fd^&qEanWZsW#;{3424wpu08F^6pY^@rZEQNaL>2Cp#enqS{x*R3F;|{r(o{UO1 zxiwdpbEji<4u3BX From 6c1d7d434de5023507a5cf1646cf79788f1fa323 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Fri, 27 Feb 2026 18:54:25 +0400 Subject: [PATCH 07/13] update KDocs guidelines: improve references and restructure sections --- KDOC_GUIDELINES.md | 82 ++++++++++++++++++++++++---------------------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/KDOC_GUIDELINES.md b/KDOC_GUIDELINES.md index 06e0f528c5..a05e4528e7 100644 --- a/KDOC_GUIDELINES.md +++ b/KDOC_GUIDELINES.md @@ -328,7 +328,7 @@ If the method uses columns selection, add a note about nested columns and column and add a link to the [Selecting Columns topic](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/SelectingColumns.kt) -with custom operation (usually with the customized examples, see [below](#kdoc-helpers-structure). +usually with the customized examples, see [below](#kdoc-helpers-structure). ``` See [Selecting Columns][InsertSelectingOptions]. @@ -383,7 +383,8 @@ and then use add it using `@include`: For any method with columns selection, add a section with information about the columns selection. -Usually, just `@include` [custom SelectingOptions](#selecting-options). +Usually, just `@include` +[custom `SelectingColumns` snippet](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/SelectingColumns.kt). #### Examples section @@ -474,43 +475,6 @@ private interface Common~OperationName~Docs public fun DataFrame.operation(columns: ColumnsSelector) ``` -### `@set`/`@get` references - -Sometimes, when you create a template KDoc for different operations, -it's highly useful to have one or more KDoc variables. - -```kt -/** - * Hello from {@get [OPERATION]}! - */ -internal interface CommonDoc { - - // Use UPPER_CASE for references to the set/get arguments - @ExcludeFromSources - interface OPERATION -} -``` - -When using `@set` and `@get` / `$`, it's a good practice to use a reference as the key name. -This makes the KDoc more refactor-safe, and it makes it easier to understand which arguments -need to be provided for a certain template. - -A good example of this concept can be found in the -[`AllColumnsSelectionDsl.CommonAllSubsetDocs` documentation interface](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/all.kt). -This interface provides a template for all overloads of `allBefore`, -`allAfter`, `allFrom`, and `allUpTo` in a single place. - -Nested in the documentation interface, there are several other interfaces that define the expected arguments -of the template. -These interfaces are named `TITLE`, `FUNCTION`, etc. and commonly have no KDocs itself, -just a simple comment explaining what the argument is for. - -Other documentation interfaces like `AllAfterDocs` or functions then include `CommonAllSubsetDocs` and set -all the arguments accordingly. - -It's recommended to name write their name in `UPPER_CASE` -and have them nested in the documentation interface. - ### Grammar Grammar is a special notation for describing the complex operation. @@ -523,7 +487,7 @@ This is done by creating a KDoc-topic like from each function. Each grammar doc must come with a `{@include [DslGrammarLink]}`, which is a link to provide the user with the details -of how the [DSL grammar notation](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt) +of how the [DSL grammar notation](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt) works. An explanation is provided for each symbol used in the grammar. @@ -554,6 +518,44 @@ Note that the grammar is not always 100% accurate to keep the readability accept Always use your common sense reading it, and if you're unsure, try out the function yourself or check the source code :). + +### `@set`/`@get` references + +Sometimes, when you create a template KDoc for different operations, +it's highly useful to have one or more KDoc variables. + +```kt +/** + * Hello from {@get [OPERATION]}! + */ +internal interface CommonDoc { + + // Use UPPER_CASE for references to the set/get arguments + @ExcludeFromSources + interface OPERATION +} +``` + +When using `@set` and `@get` / `$`, it's a good practice to use a reference as the key name. +This makes the KDoc more refactor-safe, and it makes it easier to understand which arguments +need to be provided for a certain template. + +A good example of this concept can be found in the +[`AllColumnsSelectionDsl.CommonAllSubsetDocs` documentation interface](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/all.kt). +This interface provides a template for all overloads of `allBefore`, +`allAfter`, `allFrom`, and `allUpTo` in a single place. + +Nested in the documentation interface, there are several other interfaces that define the expected arguments +of the template. +These interfaces are named `TITLE`, `FUNCTION`, etc. and commonly have no KDocs itself, +just a simple comment explaining what the argument is for. + +Other documentation interfaces like `AllAfterDocs` or functions then include `CommonAllSubsetDocs` and set +all the arguments accordingly. + +It's recommended to name write their name in `UPPER_CASE` +and have them nested in the documentation interface. + ## Advanced KDocs ### Clickable Examples From 30a928575006c91195853e29d530f0a00082a1e3 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Fri, 27 Feb 2026 18:54:46 +0400 Subject: [PATCH 08/13] restructure KDocs guidelines TOC: adjust `@set`/`@get` references position --- KDOC_GUIDELINES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KDOC_GUIDELINES.md b/KDOC_GUIDELINES.md index a05e4528e7..fc369538e2 100644 --- a/KDOC_GUIDELINES.md +++ b/KDOC_GUIDELINES.md @@ -22,9 +22,9 @@ This document outlines the guidelines for writing KDocs in the Kotlin DataFrame * [Examples section](#examples-section) * [Parameters and return section](#parameters-and-return-section) * [KDoc-helpers Structure](#kdoc-helpers-structure) - * [`@set`/`@get` references](#setget-references) * [Grammar](#grammar) * [Symbols](#symbols) + * [`@set`/`@get` references](#setget-references) * [Advanced KDocs](#advanced-kdocs) * [Clickable Examples](#clickable-examples) * [Advanced DSL Grammar Templating (Columns Selection DSL)](#advanced-dsl-grammar-templating-columns-selection-dsl) From 9ced4aadfc007f97c9e9f11fd8c478c394dffd75 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Fri, 27 Feb 2026 18:56:06 +0400 Subject: [PATCH 09/13] update KDocs guidelines TOC --- KDOC_GUIDELINES.md | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/KDOC_GUIDELINES.md b/KDOC_GUIDELINES.md index fc369538e2..da34edd514 100644 --- a/KDOC_GUIDELINES.md +++ b/KDOC_GUIDELINES.md @@ -10,20 +10,10 @@ This document outlines the guidelines for writing KDocs in the Kotlin DataFrame * [KDoc-snippets: Reuse Common Parts](#kdoc-snippets-reuse-common-parts) * [KDoc-topics: Reference to Topics](#kdoc-topics-reference-to-topics) * [Common KDoc-helpers](#common-kdoc-helpers) - * [URLs](#urls) - * [Utils](#utils) * [Kotlin DataFrame Operations KDoc Structure](#kotlin-dataframe-operations-kdoc-structure) * [General Template](#general-template) - * [First line](#first-line) - * [Body](#body) - * [See also section](#see-also-section) - * [Documentation website link](#documentation-website-link-) - * [Columns selection information](#columns-selection-information) - * [Examples section](#examples-section) - * [Parameters and return section](#parameters-and-return-section) * [KDoc-helpers Structure](#kdoc-helpers-structure) * [Grammar](#grammar) - * [Symbols](#symbols) * [`@set`/`@get` references](#setget-references) * [Advanced KDocs](#advanced-kdocs) * [Clickable Examples](#clickable-examples) @@ -204,6 +194,7 @@ and include things like: - To be linked to for more information on the concepts - [DslGrammar](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt) - To be linked to from each DSL grammar by the link interface +- snippets - Check the folder to see if there are more and feel free to add them if needed :) #### URLs From cef0a63bbd185edfb88aef086fb28712d2a98400 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Mon, 2 Mar 2026 17:22:45 +0400 Subject: [PATCH 10/13] update KDocs guidelines: add section on compiling KDocs --- KDOC_GUIDELINES.md | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/KDOC_GUIDELINES.md b/KDOC_GUIDELINES.md index da34edd514..9685db9c03 100644 --- a/KDOC_GUIDELINES.md +++ b/KDOC_GUIDELINES.md @@ -7,13 +7,24 @@ This document outlines the guidelines for writing KDocs in the Kotlin DataFrame * [The most important advice](#the-most-important-advice) * [What should be documented?](#what-should-be-documented) * [KoDEx & KDoc-helpers](#kodex--kdoc-helpers) + * [Compiling KDocs](#compiling-kdocs) * [KDoc-snippets: Reuse Common Parts](#kdoc-snippets-reuse-common-parts) * [KDoc-topics: Reference to Topics](#kdoc-topics-reference-to-topics) * [Common KDoc-helpers](#common-kdoc-helpers) + * [URLs](#urls) + * [Utils](#utils) * [Kotlin DataFrame Operations KDoc Structure](#kotlin-dataframe-operations-kdoc-structure) * [General Template](#general-template) + * [First line](#first-line) + * [Body](#body) + * [See also section](#see-also-section) + * [Documentation website link](#documentation-website-link-) + * [Columns selection information](#columns-selection-information) + * [Examples section](#examples-section) + * [Parameters and return section](#parameters-and-return-section) * [KDoc-helpers Structure](#kdoc-helpers-structure) * [Grammar](#grammar) + * [Symbols](#symbols) * [`@set`/`@get` references](#setget-references) * [Advanced KDocs](#advanced-kdocs) * [Clickable Examples](#clickable-examples) @@ -48,6 +59,16 @@ the [KDocs preprocessing using KoDEx](KDOC_PREPROCESSING.md) before working with Install the [KoDEx plugin for IDEA](https://plugins.jetbrains.com/plugin/27473---kodex---kotlin-documentation-extensions) for correct KDocs display inside the IntelliJ IDEA. +### Compiling KDocs + +KoDEx KDocs can be compiled using the dedicated Gradle task `processKDocsMain`: +``` +Gradle > Tasks > kdocs > processKDocsMain +``` + +After building the sources, the resulting artifact will contain standard KDocs +with all KoDEx utilities properly compiled and resolved. + ### KDoc-snippets: Reuse Common Parts One of the best utilities of KoDEx is the ability to reuse common parts of KDocs. From 0befd58e553566b695dc76d3942f8227d457aa0b Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Fri, 6 Mar 2026 20:11:01 +0400 Subject: [PATCH 11/13] update KDocs guidelines: rename KDOC_PREPROCESSING.md to KODEX_KDOC_PREPROCESSING.md, update references and add section on type aliases --- .github/README_GH_ACTIONS.md | 2 +- CONTRIBUTING.md | 4 +-- KDOC_GUIDELINES.md | 68 +++++++++++++++++++----------------- KDOC_PREPROCESSING.md | 0 KODEX_KDOC_PREPROCESSING.md | 26 +++++++++++++- core/README.md | 2 +- 6 files changed, 64 insertions(+), 38 deletions(-) delete mode 100644 KDOC_PREPROCESSING.md diff --git a/.github/README_GH_ACTIONS.md b/.github/README_GH_ACTIONS.md index ae927fd4ca..3e8851be10 100644 --- a/.github/README_GH_ACTIONS.md +++ b/.github/README_GH_ACTIONS.md @@ -20,7 +20,7 @@ fails if any unknown Gradle Wrapper JAR files are found. Anytime the source code changes on [master](https://github.com/Kotlin/dataframe/tree/master), this [GitHub Action](./workflows/generated-sources-master.yml) makes sure -[`processKDocsMain`](../KDOC_PREPROCESSING.md), +[`processKDocsMain`](../KODEX_KDOC_PREPROCESSING.md), and `korro` are run. If there have been any changes in either [core/generated-sources](../core/generated-sources) or [docs/StardustDocs/resources/snippets](../docs/StardustDocs/resources/snippets), these are auto-committed to the branch, to keep it up to date. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d428b4c927..9ef66dab62 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -96,7 +96,7 @@ has the best support for Kotlin, compiler plugins, Gradle, and [Kotlin Notebook] * We recommend using the [Ktlint plugin](https://plugins.jetbrains.com/plugin/15057-ktlint) for [IntelliJ IDEA](https://www.jetbrains.com/idea/download/). It is able to read the `.editorconfig` file and apply the same formatting rules as [Ktlint](https://pinterest.github.io/ktlint/latest/) in the CI. -* Check out the [KDoc Preprocessor guide](KDOC_PREPROCESSING.md) to understand how to work with +* Check out the [KDoc Preprocessor guide](KODEX_KDOC_PREPROCESSING.md) to understand how to work with [KoDEx](https://github.com/Jolanrensen/KoDEx). ## Building @@ -108,7 +108,7 @@ This library is built with Gradle. things up during development. * Make sure to pass the extra parameter `-Pkotlin.dataframe.debug=true` to enable debug mode. This flag will make sure some extra checks are run, which are important but too heavy for production. -* The parameter `-PskipKodex` allows you to skip [kdoc processing](KDOC_PREPROCESSING.md), +* The parameter `-PskipKodex` allows you to skip [kdoc processing](KODEX_KDOC_PREPROCESSING.md), making local publishing faster: `./gradlew publishToMavenLocal -PskipKodex`. This, however, publishes the library with "broken" KDocs, so it's only meant for faster iterations during development. diff --git a/KDOC_GUIDELINES.md b/KDOC_GUIDELINES.md index 9685db9c03..ded24fbdfb 100644 --- a/KDOC_GUIDELINES.md +++ b/KDOC_GUIDELINES.md @@ -54,7 +54,7 @@ We use [KoDEx](https://github.com/Jolanrensen/KoDEx) KDocs preprocessor. It adds several useful utilities for writing KDocs. Please read about -the [KDocs preprocessing using KoDEx](KDOC_PREPROCESSING.md) before working with Kotlin DataFrame KDocs. +the [KDocs preprocessing using KoDEx](KODEX_KDOC_PREPROCESSING.md) before working with Kotlin DataFrame KDocs. Install the [KoDEx plugin for IDEA](https://plugins.jetbrains.com/plugin/27473---kodex---kotlin-documentation-extensions) for correct KDocs display inside the IntelliJ IDEA. @@ -101,7 +101,7 @@ public fun someFunction() ``` For example, -[`ColumnPathCreation` interface](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ColumnPathCreation.ktc) +[`ColumnPathCreationSnippet` typealias](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/snippets.kt) has a KDoc which describes column path creation behavior. The whole file is excluded from sources, but the KDoc is included in other KDocs. @@ -126,7 +126,7 @@ internal interface ~SnippetDescription~Snippet { * The key for a @set that will define the operation name for the snippet example. */ @ExcludeFromSources - interface OPERATION + typealias OPERATION = Nothing } /** @@ -197,26 +197,24 @@ Common KDoc-helpers in the [documentation folder](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation) and include things like: -- [Access APIs](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/AccessApi.kt) - - To be linked to - - String API, Column Accessors API etc. +- [Access APIs](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/AccessApis.kt) + topics and snippets about String Names and Extension Properties API. + To be linked and included in KDocs of methods that use column accessing. - [Selecting Columns](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/SelectingColumns.kt) - - To be included in `select`, `update` etc. like `{@include [SelectingColumns.ColumnNames.WithExample]}` (with - args). - - Or to be linked to with `{@include [SelectingColumnsLink]}`. - - By name, by column accessor, by DSL etc. + topics and snippets about different columns selection options. + To be linked and included in KDocs of methods with columns selection. - [Selecting Rows](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/SelectingRows.kt) - - To be included like `{@include [SelectingRows.RowValueCondition.WithExample]}` in `Update.where`, `filter`, etc. - - Explains the concept and provides examples (with args) + snippets about rows selection for operations with row selection or filtering (like `filter`). - [`ExpressionsGivenColumn`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenColumn.kt) / [`-DataFrame`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenDataFrame.kt) / [`-Row`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenRow.kt) / [`-RowAndColumn`](core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/ExpressionsGivenRowAndColumn.kt) - - To be included or linked to in functions like `perRowCol`, `asFrame`, etc. - - Explains the concepts of `ColumnExpression`, `DataFrameExpression`, `RowExpression`, etc. + topics and snippets to be included or linked to in functions like `perRowCol`, `asFrame`, etc. + Explains the concepts of `ColumnExpression`, `DataFrameExpression`, `RowExpression`, etc. - [`NA`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/NA.kt) / [`NaN`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/NaN.kt) - - To be linked to for more information on the concepts + topics to be linked to for more information on the concepts - [DslGrammar](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt) - - To be linked to from each DSL grammar by the link interface -- snippets -- Check the folder to see if there are more and feel free to add them if needed :) + topic to be linked to from each DSL grammar by the link typealias +- [various snippets and topics](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/snippets.kt) + with common mechanisms description. +- And many others; Check the folder to see if there are more and feel free to add them if needed :) #### URLs @@ -227,11 +225,14 @@ When linking to external URLs, it's recommended to use It's a central place where we can store URLs that can be used in multiple places in the library. Plus, it makes it easier to update the documentation whenever (part of) a URL changes. +For [Kotlin DataFrame GitHub issues and PRs](https://github.com/Kotlin/dataframe/issues), +you can just write its number like `#1234` in the KDoc. + #### Utils -The [`utils.kt` file](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/utils.kt) contains all sorts of helper interfaces for the documentation. +The [`utils.kt` file](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/utils.kt) contains all sorts of KDoc-helpers for the documentation. For instance `{@include [LineBreak]}` can insert a line break in the KDoc and the family of `Indent` -documentation interfaces can provide you with different non-breaking-space-based indents. +documentation snippets can provide you with different non-breaking-space-based indents. If you need a new utility, feel free to add it to this file. @@ -247,10 +248,10 @@ There are four kinds; here's a list of them: 1. Simple, Stdlib-like operations that don't have arguments or have simple types (primitives, classes) as arguments and return simple value, `DataFrame`, `DataRow` or `DataColumn`. * For example, -[`first` without arguments](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt#L106). +[`first` without arguments](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt). * KDocs for such operations can be short, especially if it's trivial enough. 2. Operations with [`DataRow` API](https://kotlin.github.io/dataframe/datarow.html). - * For example, [`first` with predicate](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt#L139). + * For example, [`first` with predicate](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt). * Remember to describe a mechanism of `DataRow` API usage in the KDoc - it's not obvious to the user. 3. Operations with the [Columns Selection DSL](https://kotlin.github.io/dataframe/columnselectors.html) that return a single and return simple value, `DataFrame`, `DataRow` or `DataColumn`. * For example, [`remove`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/remove.kt). @@ -264,7 +265,7 @@ as arguments and return simple value, `DataFrame`, `DataRow` or `DataColumn`. [Columns Selection DSL](https://kotlin.github.io/dataframe/columnselectors.html); these methods should be documented by the rules above. * For a better understanding of the complex operation, we write an [operation grammar](#grammar) using a - [special notation](https://github.com/Kotlin/dataframe/blob/master/core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt). + [special notation](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DslGrammar.kt). Add a reference to the operation Grammar in each related class and method KDoc. ### General Template @@ -383,7 +384,7 @@ For example, from `group` KDoc: Add a link to the corresponding operation in the [documentation website](https://kotlin.github.io/dataframe). -Please add it as an interface KDoc inside +Please add it as a KDoc-snippet inside [DocumentationUrls](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/DocumentationUrls.kt) and then use add it using `@include`: @@ -457,7 +458,7 @@ internal interface ~OperationName~Docs { * ## ~OperationName~ Operation Grammar * ... */ - interface Grammar + typealias Grammar = Nothing } // Set operation in [SelectingColumns] examples (in [SelectingColumns.Dsl] and so on) @@ -544,7 +545,7 @@ internal interface CommonDoc { // Use UPPER_CASE for references to the set/get arguments @ExcludeFromSources - interface OPERATION + typealias OPERATION = Nothing } ``` @@ -557,12 +558,12 @@ A good example of this concept can be found in the This interface provides a template for all overloads of `allBefore`, `allAfter`, `allFrom`, and `allUpTo` in a single place. -Nested in the documentation interface, there are several other interfaces that define the expected arguments +Nested in the documentation interface, there are several other KDoc-helpers that define the expected arguments of the template. -These interfaces are named `TITLE`, `FUNCTION`, etc. and commonly have no KDocs itself, +These KDoc-helpes are named `TITLE`, `FUNCTION`, etc. and commonly have no KDocs itself, just a simple comment explaining what the argument is for. -Other documentation interfaces like `AllAfterDocs` or functions then include `CommonAllSubsetDocs` and set +Other KDoc-helpers like `AllAfterDocs` or functions then include `CommonAllSubsetDocs` and set all the arguments accordingly. It's recommended to name write their name in `UPPER_CASE` @@ -668,13 +669,13 @@ All other parts are filled in like: interface Grammar { /** [**`first`**][ColumnsSelectionDsl.first] */ - interface PlainDslName + typealias PlainDslName = Nothing /** __`.`__[**`first`**][ColumnsSelectionDsl.first] */ - interface ColumnSetName + typealias ColumnSetName = Nothing /** __`.`__[**`firstCol`**][ColumnsSelectionDsl.firstCol] */ - interface ColumnGroupName + typealias ColumnGroupName = Nothing } ``` @@ -682,7 +683,8 @@ When a reference to a certain definition is used, we take `DslGrammarTemplate.XR Clicking on them takes users to the respective `XDef` and thus provides them with the formal name and type of the definition. -You may also notice that the `PlainDslName`, `ColumnSetName`, and `ColumnGroupName` interfaces are defined separately. +You may also notice that the `PlainDslName`, `ColumnSetName`, and `ColumnGroupName` +KDoc-helpers are defined separately. This is to make sure they can be reused in the large Columns Selection DSL grammar and on the website. You don't always need all three parts in the grammar; not all functions can be used in each context. diff --git a/KDOC_PREPROCESSING.md b/KDOC_PREPROCESSING.md deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/KODEX_KDOC_PREPROCESSING.md b/KODEX_KDOC_PREPROCESSING.md index 8d1096ca42..443fedc8d5 100644 --- a/KODEX_KDOC_PREPROCESSING.md +++ b/KODEX_KDOC_PREPROCESSING.md @@ -340,6 +340,30 @@ Since [v0.3.9](https://github.com/Jolanrensen/KoDEx/releases/tag/v0.3.9) it's al exclude a whole file from the `sources.jar` by adding the annotation to the top of the file, like `@file:ExcludeFromSources`. +### Using (nested) Type Aliases Instead of Interfaces + +([Nested](https://kotlinlang.org/docs/type-aliases.html#nested-type-aliases)) +[Type aliases](https://kotlinlang.org/docs/type-aliases.html) +can be used to save byte size in the published library.jar file. +This is useful when you have a lot of documentation interfaces without a body that are only used to host KDoc. + +For example: + +```kt +/** [Common doc][CommonDoc] */ +typealias CommonDocLink = Nothing + +/** + * ## {@include [CommonDocLink]} + * Hello from $[NAME]! + */ +interface CommonDoc { + + // name argument + typealias NAME = Nothing +} +``` + ## KoDEx Conventions in DataFrame See [KDoc Guidelines](KDOC_GUIDELINES.md). @@ -347,7 +371,7 @@ See [KDoc Guidelines](KDOC_GUIDELINES.md). ## KDoc -> WriterSide There's a special annotation, `@ExportAsHtml`, that allows you to export the content of the KDoc of the annotated -function, interface, or class as HTML. +function, interface, typealias, or class as HTML. The Markdown of the KDoc is rendered to HTML using [JetBrains/markdown](https://github.com/JetBrains/markdown) and, in the case of DataFrame, put in [./docs/StardustDocs/resources/snippets/kdocs](docs/StardustDocs/resources/snippets/kdocs). From there, the HTML can be included in any WriterSide page as an iFrame. diff --git a/core/README.md b/core/README.md index 34da5e4944..744af1ce2f 100644 --- a/core/README.md +++ b/core/README.md @@ -16,7 +16,7 @@ The code you're working on needs to be edited in [src](src), but the KDocs are p [KoDEx](https://github.com/Jolanrensen/kodex) when the project is published (or the task is run manually). The generated sources with adjusted KDocs will be overwritten in [generated-sources](generated-sources). -See the [KDoc Preprocessing Guide](../KDOC_PREPROCESSING.md) for more information. +See the [KDoc Preprocessing Guide](../KODEX_KDOC_PREPROCESSING.md) for more information. KDocs can also be exported to HTML, for them to be reused on the website. Elements annotated with `@ExportAsHtml` will have their generated content be copied over to From 8dc3f24063e92271d908f2aece4e21cbabea06ec Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Mon, 9 Mar 2026 00:46:21 +0400 Subject: [PATCH 12/13] update KDocs guidelines: replace interface with typealias for Common~OperationName~Docs --- KDOC_GUIDELINES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KDOC_GUIDELINES.md b/KDOC_GUIDELINES.md index ded24fbdfb..cba97eec00 100644 --- a/KDOC_GUIDELINES.md +++ b/KDOC_GUIDELINES.md @@ -472,7 +472,7 @@ private typealias Set~OperationName~OperationArg = Nothing * ### This ~OperationName~ Overload */ @ExcludeFromSources -private interface Common~OperationName~Docs +private typealias Common~OperationName~Docs = Nothing /** * (Include common docs) From bd798b859c752ce7a73b560e4bec3bf9a5348079 Mon Sep 17 00:00:00 2001 From: "andrei.kislitsyn" Date: Mon, 9 Mar 2026 17:02:26 +0400 Subject: [PATCH 13/13] update KDOC_GUIDELINES.md: fix typos, improve clarity, and refine formatting rules --- KDOC_GUIDELINES.md | 70 +++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 29 deletions(-) diff --git a/KDOC_GUIDELINES.md b/KDOC_GUIDELINES.md index cba97eec00..7eaad7ac9f 100644 --- a/KDOC_GUIDELINES.md +++ b/KDOC_GUIDELINES.md @@ -35,29 +35,33 @@ This document outlines the guidelines for writing KDocs in the Kotlin DataFrame Please never write KDocs from scratch without a necessity! Find existing KDocs for the similar operation or other entity and reuse it. -However, take its specific into account. +However, take its specifics into account. And don't be afraid to deviate from the rules or add something new – the most important thing is to help users to understand the library better! ## What should be documented? -**All public API should be documented!** Our goal is 100% public functions, classes, and variable KDocs coverage. +**All public APIs should be documented!** Our goal is 100% public functions, classes, and variable KDocs coverage. Some parts of public API are not intended for end users but can be used in Compiler Plugin or potential -KDF extension libraries. These methods should have a small KDocs as well. +KDF extension libraries. These methods should have a small KDoc as well. -For internal API, please add at least a small note. +Deprecated methods, properties, classes, and constructors are not required to have KDocs. + +For the internal API, please add at least a small note. ## KoDEx & KDoc-helpers -We use [KoDEx](https://github.com/Jolanrensen/KoDEx) KDocs preprocessor. +We use [KoDEx](https://github.com/Jolanrensen/KoDEx), a KDocs preprocessor. It adds several useful utilities for writing KDocs. Please read about the [KDocs preprocessing using KoDEx](KODEX_KDOC_PREPROCESSING.md) before working with Kotlin DataFrame KDocs. Install the [KoDEx plugin for IDEA](https://plugins.jetbrains.com/plugin/27473---kodex---kotlin-documentation-extensions) -for correct KDocs display inside the IntelliJ IDEA. +for a preview of the rendered KDocs inside IntelliJ IDEA. + +> Note that this preview may deviate from the actual Gradle results. ### Compiling KDocs @@ -77,10 +81,11 @@ This can be done by using the which allows you to include a KDoc for another documentable element. This could be a class, an interface, a typealias, and so on. -There are a lot of empty interfaces and typealiases in the project that are only used for including their KDocs -to other KDocs. They are called *KDoc-snippets*; they are marked with `@ExcludeFromSources` +There are a lot of empty interfaces and `Nothing` typealiases in the project +that are only used by having their KDocs included in other KDocs. +They are called *KDoc-snippets*; they are marked with `@ExcludeFromSources` and not included in the sources after the compilation (make sure you are not referencing them directly, -i.e., only use it inside the `@include`). +i.e., only use it inside the `@include` or other KoDEx tags). ```kotlin /** @@ -100,7 +105,7 @@ internal typealias ~SnippetDescription~Snippet = Nothing public fun someFunction() ``` -For example, +For example, the [`ColumnPathCreationSnippet` typealias](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/snippets.kt) has a KDoc which describes column path creation behavior. The whole file is excluded from sources, @@ -108,7 +113,7 @@ but the KDoc is included in other KDocs. Also, you can use [`@set` and `@get` tags](https://github.com/Jolanrensen/KoDEx/wiki/Notation#set-and-get---setting-and-getting-variables) -along with `@include` to change variables value in common parts. This is especially useful for +along with `@include` to change variable values in common parts. This is especially useful for writing examples of methods with similar usage but with different names. More about `@set` and `@get` connventions [here](#setget-references). @@ -118,7 +123,7 @@ More about `@set` and `@get` connventions [here](#setget-references). * (Snippet body) * * ### Example - * `df.`{@get [OPERATION]}` { name and age }` + * `df.`{@get [OPERATION]}` { name and age }` */ @ExcludeFromSources internal interface ~SnippetDescription~Snippet { @@ -140,7 +145,9 @@ internal interface ~SnippetDescription~Snippet { public fun someFunction() ``` -Naming convention for KDoc-snippet — the name must fully reflect its content and have `Snippet` at the end. +Naming convention for KDoc-snippet — the name must fully reflect its content; +For a text snippet, also add `Snippet` at the end of its name. +For utility snippets, like `Indent` or `LineBreak`, it is not necessary. ### KDoc-topics: Reference to Topics @@ -223,7 +230,7 @@ When linking to external URLs, it's recommended to use [Issues](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/documentation/Issues.kt). It's a central place where we can store URLs that can be used in multiple places in the library. Plus, it makes -it easier to update the documentation whenever (part of) a URL changes. +it easier to update the documentation whenever (a part of) a URL changes. For [Kotlin DataFrame GitHub issues and PRs](https://github.com/Kotlin/dataframe/issues), you can just write its number like `#1234` in the KDoc. @@ -238,12 +245,12 @@ If you need a new utility, feel free to add it to this file. ## Kotlin DataFrame Operations KDoc Structure -Operation KDocs size and structure completely depend on the operation complexity. +Operation KDocs size and structure completely depend on the operation's complexity. The best way to write a new KDoc for an operation is to define its kind and use the existing KDoc of an operation of the same kind as a template. -There are four kinds; here's a list of them: +There are four kinds: 1. Simple, Stdlib-like operations that don't have arguments or have simple types (primitives, classes) as arguments and return simple value, `DataFrame`, `DataRow` or `DataColumn`. @@ -252,16 +259,16 @@ as arguments and return simple value, `DataFrame`, `DataRow` or `DataColumn`. * KDocs for such operations can be short, especially if it's trivial enough. 2. Operations with [`DataRow` API](https://kotlin.github.io/dataframe/datarow.html). * For example, [`first` with predicate](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/first.kt). - * Remember to describe a mechanism of `DataRow` API usage in the KDoc - it's not obvious to the user. -3. Operations with the [Columns Selection DSL](https://kotlin.github.io/dataframe/columnselectors.html) that return a single and return simple value, `DataFrame`, `DataRow` or `DataColumn`. + * Remember to describe the mechanism of `DataRow` API usage in the KDoc - it's not obvious to the user. +3. Operations with the [Columns Selection DSL](https://kotlin.github.io/dataframe/columnselectors.html) that return a single and simple value, `DataFrame`, `DataRow` or `DataColumn`. * For example, [`remove`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/remove.kt). - * Remember to describe a mechanism of Columns Selection DSL. + * Remember to describe the mechanism of Columns Selection DSL. * Add several examples with different columns selection options. -4. Complex operations with a multiple methods chain. +4. Complex operations with a multiple-methods chain. * For example, [`convert`](./core/src/main/kotlin/org/jetbrains/kotlinx/dataframe/api/convert.kt). * Such operations consist of at least two methods and special resulting classes as the intermediate steps. All of them should be well-documented and have cross-references to each other. - * Usually complex operation methods have the + * Usually, complex operation methods have the [Columns Selection DSL](https://kotlin.github.io/dataframe/columnselectors.html); these methods should be documented by the rules above. * For a better understanding of the complex operation, we write an [operation grammar](#grammar) using a @@ -302,11 +309,11 @@ Should start with a verb. Usually "returns" or "creates" for operations that ret #### Body -For the non-trivial operations, write a detailed description of the operation, -describe method behavior and resulting value. +For non-trivial operations, write a detailed description of the operation, +describe method behavior and the resulting value. -Describe method-specific mechanisms, especially if it has non-trivial lambda as an argument. -For example, for methods with `RowFilter` predicate, add +Describe method-specific mechanisms, especially if it has a non-trivial lambda as an argument. +For example, for methods with the `RowFilter` predicate, add ```kotlin {@include [SelectingRows.RowFilterSnippet]} @@ -333,7 +340,7 @@ For example (from the `insert` KDoc): The next methods in the chain may be finalizing or intermediate steps - write about it explicitly. Remember to add a link to the initial method and [operation grammar](#grammar) in all of them. -If the method uses columns selection, add a note about nested columns and column groups: +If the method uses column(s) selection, add a note about nested columns and column groups: ``` @include [SelectingColumns.ColumnGroupsAndNestedColumnsMention] @@ -401,7 +408,7 @@ Usually, just `@include` #### Examples section -Write meaningful, easy-to-undertand examples, with detailed comments. +Write meaningful, easy-to-understand examples, with detailed comments. For complex operations, write a **complete** example with all steps. @@ -416,7 +423,7 @@ Start the section with #### Parameters and return section Describe parameters and return of the method using `@param` and `@return` tags. -Remember about type parameters. +Remember type parameters. Wrap parameter names into `[]` for better readability. @@ -456,6 +463,9 @@ internal interface ~OperationName~Docs { // Operation Grammar - for the initial method of the complex operations /** * ## ~OperationName~ Operation Grammar + * {@include [LineBreak]} + * {@include [DslGrammarLink]} + * {@include [LineBreak]} * ... */ typealias Grammar = Nothing @@ -490,7 +500,9 @@ public fun DataFrame.operation(columns: ColumnsSelector) ### Grammar -Grammar is a special notation for describing the complex operation. +DSL Grammar (helpers usually are called simply `Grammar` and place inside the +helper interface) is a special notation for describing +the complex operation. ![dslgrammar.png](docs/imgs/dslgrammar.png)