aboutsummaryrefslogtreecommitdiff
path: root/plugins/developer_guide.md
blob: 57d5ff62e052640c3fb3b4d0771ed02e250f67ca (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
# Guide to Dokka Plugin development

## Configuration

Dokka requires configured kotlin plugin and dokka-core dependency. 

```kotlin
plugins {
    kotlin("jvm") version <kotlin_version>
}

dependencies {
    compileOnly("org.jetbrains.dokka:dokka-core:<dokka_version>>)
}

tasks.withType<KotlinCompile> {
    kotlinOptions.jvmTarget = "1.8"
}
```

## Building sample plugin

In order to load a plugin into dokka, your class must extend `DokkaPlugin` class. All instances are automatically loaded during dokka setup using `java.util.ServiceLoader`.

Dokka provides a set of entry points, for which user can create their own implementations. They must be delegated using `DokkaPlugin.extending(isFallback: Boolean = false, definition: ExtendingDSL.() -> Extension<T>)` function,that returns a delegate `ExtensionProvider` with supplied definition. 

The function receives optional `isFallback` parameter that determines whether the provided implementation can be overriden by other plugin.   

To create a definition, you can use one of two infix functions`with(T)` or `providing( (DokkaContext) -> T)` where `T` is the type of an extended endpoint. You can also use infix functions:
* `applyIf( () -> Boolean )` to add additional condition whether or not the extension should be used
* `order((OrderDsl.() -> Unit))` to determine if your extension should be used before or after another particular extension for the same endpoint

Following sample provides custom translator object as a `DokkaCore.sourceToDocumentableTranslator`

```kotlin
package org.jetbrains.dokka.sample

import org.jetbrains.dokka.plugability.DokkaPlugin

class SamplePlugin : DokkaPlugin() {
    extension by extending {
        DokkaCore.sourceToDocumentableTranslator with CustomSourceToDocumentableTranslator
    }
}

object CustomSourceToDocumentableTranslator: SourceToDocumentableTranslator {
    override fun invoke(sourceSet: SourceSetData, context: DokkaContext): DModule
}
```

### Registering extension point

You can register your own extension point using `extensionPoint` function declared in `DokkaPlugin` class

```kotlin
class SamplePlugin : DokkaPlugin() {
    val extensionPoint by extensionPoint<SampleExtensionPointInterface>()
}

interface SampleExtensionPointInterface
```

### Obtaining extension instance

All registered plugins are accessible with `DokkaContext.plugin` function. All plugins that extends `DokkaPlugin` can use `DokkaPlugin.plugin` function, that uses underlying `DokkaContext` instance. If you want to pass context to your extension, you can obtain it using aforementioned `providing` infix function.

With plugin instance obtained, you can browse extensions registered for this plugins' extension points using `querySingle` and `query` methods:

```kotlin
    context.plugin<DokkaBase>().query { htmlPreprocessors }
    context.plugin<DokkaBase>().querySingle { samplesTransformer }
```

You can also browse `DokkaContext` directly, using `single` and `get` methods:

```kotlin
class SamplePlugin : DokkaPlugin() {

    val extensionPoint by extensionPoint<SampleExtensionPointInterface>()
    val anotherExtensionPoint by extensionPoint<AnotherSampleExtensionPointInterface>()

    val extension by extending {
        extensionPoint with SampleExtension()
    }

    val anotherExtension by extending { 
        anotherExtensionPoint providing { context ->
            AnotherSampleExtension(context.single(extensionPoint))
        }
    }
}

interface SampleExtensionPointInterface
interface AnotherSampleExtensionPointInterface

class SampleExtension: SampleExtensionPointInterface
class AnotherSampleExtension(sampleExtension: SampleExtensionPointInterface): AnotherSampleExtensionPointInterface
```

## Dokka Data Model

There a four data models that dokka uses: Documentable Model, Documentation Model, Page Model and Content Model.

### Documentable Model

Documentable model represents parsed data, returned by compiler analysis. It retains basic order structure of parsed `Psi` or `Descriptor` models.

After creation, it is a collection of trees, each with `DModel` as a root. After the Merge step, all trees are folded into one. 

The main building block of this model is `Documentable` class, that is a base class for all more specific types that represents elements of parsed Kotlin and Java classes with pretty self-explanatory names: `DPackage`, `DFunction` and so on. `DClasslike` is a base for class-like elements, such as Classes, Enums, Interfaces and so on.

There are three non-documentable classes important for the model: `DRI`, `SourceSetDependent` and `ExtraProperty`.

* `DRI` (Dokka Resource Identifier) is an unique value that identifies specific `Documentable`. All references to other documentables different than direct ownership are described using DRIs. For example, `DFunction` with parameter of type `X` has only X's DRI, not the actual reference to X's Documentable object.
* `SourceSetDependent` is a map that handles multiplatform data, by connecting platform-specific data, declared with either `expect` or `actual` modifier, to a particular Source Set
* `ExtraProperty` is used to store any additional information that falls outside of regular model. It is highly recommended to use extras to provide any additional information when creating custom Dokka plugins. This element is a bit more complex, so you can read more about how to use it below.

#### `ExtraProperty` class usage

`ExtraProperty` classes are used both by Documentable and Content models. To declare a new extra, you need to implement `ExtraProperty` interface.

```kotlin
interface ExtraProperty<in C : Any> {
    interface Key<in C : Any, T : Any> {
        fun mergeStrategyFor(left: T, right: T): MergeStrategy<C> = MergeStrategy.Fail {
            throw NotImplementedError("Property merging for $this is not implemented")
        }
    }

    val key: Key<C, *>
}
```

It is advised to use following pattern when declaring new extras:

```kotlin
data class CustomExtra( [any values relevant to your extra ] ): ExtraProperty<Documentable> {
    companion object : CustomExtra.Key<Documentable, CustomExtra>
    override val key: CustomExtra.Key<Documentable, *> = CustomExtra
}
```
Merge strategy for extras is invoked only if merged objects have different values for same Extra. If you don't expect it to happen, you can omit implementing `mergeStrategyFor` function.

All extras for `ContentNode` and `Documentable` classes are stored in `PropertyContainer<C : Any>` class instances. The `C` generic class parameter limits the type of properties, that can be stored in the container -  it must match generic `C` class parameter from `ExtraProperty` interface. For example, if you would create `DFunction`-only `ExtraProperty`, it will be limited to be added only to `PropertyContainer<DFunction>`. 

In following example we will create `Documentable`-only property, store it in the container and then retrieve its value:

```kotlin
data class CustomExtra(val customExtraValue: String) : ExtraProperty<Documentable> {

    companion object: ExtraProperty.Key<Documentable, CustomExtra>

    override val key: ExtraProperty.Key<Documentable, *> = CustomExtra
}

val extra : PropertyContainer<DFunction> = PropertyContainer.withAll(
    CustomExtra("our value")
)

val customExtraValue : String? = extra[CustomProperty]?.customExtraValue
``` 

You can also use extras as markers, without storing any data in them:

```kotlin

object MarkerExtra : ExtraProperty<Any>, ExtraProperty.Key<Any, MarkerExtra> {
    override val key: ExtraProperty.Key<Any, *> = this
}

val extra : PropertyContainer<Any> = PropertyContainer.withAll(MarkerExtra)

val isMarked : Boolean = extra[MarkerExtra] != null

```

### Documentation Model

Documentation model is used along Documentable Model to store data obtained by parsing code commentaries.

There are three important classes here:

* `DocTag` describes a specific documentation syntax element, for example: header, footer, list, link, raw text, paragraph, etc.
* `TagWrapper` described a whole comment description or a specific comment tag, for example: @See, @Returns, @Author; and holds consisting `DocTag` elements 
* `DocumentationNode` acts as a container for `TagWrappers` for a specific `Documentable`

DocumentationNodes are references by a specific `Documentable`

### Page Model

Page Model represents the structure of future generated documentation pages and is independent of the final output format, which each node corresponding to exactly one output file. `Renderer` is processing each page separately.Subclasses of `PageNode` represents different kinds of rendered pages for Modules, Packages, Classes etc.

The Page Model is a tree structure, with `RootPageNode` as a root. 

### Content Model

Content Model describes how the actual page content is presented. It organizes it's structure into groups, tables, links, etc. Each node is identified by unique `DCI` (Dokka Content Identifier) and all references to other nodes different than direct ownership are described using DCIs.

`DCI` aggregates `DRI`s of all `Documentables` that make up specific `ContentNode`. 

Also, all `ExtraProperty` info from consisting `Documentable`s is propagated into Content Model and available for `Renderer`.

## Extension points

### Core extension points

We will discuss all base extension points along with the steps, that `DokkaGenerator` does to build a documentation.

#### Setting up Kotlin and Java analysis process and initializing plugins

The provided Maven / CLI / Gradle configuration is read.Then, all the `DokkaPlugin` classes are loaded and the extensions are created.
 
 No entry points here.

#### Creating documentation models

The documentation models are created.

This step uses `DokkaCore.sourceToDocumentableTranslator` entry point. All extensions registered using this entry point will be invoked. Each of them is required to implement `SourceToDocumentableTranslator` interface:

```kotlin
interface SourceToDocumentableTranslator {
    fun invoke(sourceSet: SourceSetData, context: DokkaContext): DModule
}
```
By default, two translators are created:
* `DefaultDescriptorToDocumentableTranslator` that handles Kotlin files
* `DefaultPsiToDocumentableTranslator` that handles Java files

After this step, all data from different source sets and languages are kept separately.

#### Pre-merge documentation transform

Here you can apply any transformation to model data before different source sets are merged.

This step uses `DokkaCore.preMergeDocumentableTransformer` entry point. All extensions registered using this entry point will be invoked. Each of them is required to implement `PreMergeDocumentableTransformer` interface:

```kotlin
interface PreMergeDocumentableTransformer {
    operator fun invoke(modules: List<DModule>, context: DokkaContext): List<DModule>
}
```
By default, three transformers are created:
* `DocumentableVisibilityFilter` that, depending on configuration, filters out all private members from declared packages
* `ActualTypealiasAdder` that handles Kotlin typealiases
* `ModuleAndPackageDocumentationTransformer` that creates documentation content for models and packages itself 

#### Merging

All `DModule` instances are merged into one.

This step uses `DokkaCore.documentableMerger` entry point. It is required to have exactly one extension registered for this entry point. Having more will trigger an error, unless all except one will have parameter `isFallback` set to true, in which case the non-fallback extension will be used.

The extension is required to implement `DocumentableMerger` interface:

```kotlin
interface DocumentableMerger {
    operator fun invoke(modules: Collection<DModule>, context: DokkaContext): DModule
}
```

By default, `DefaultDocumentableMerger` is created. This extension is a fallback, so it could be replaced by a custom non-fallback one.

#### Merged data transformation

You can apply any transformation to already merged data

This step uses `DokkaCore.documentableTransformer` entry point. All extensions registered using this entry point will be invoked. Each of them is required to implement `DocumentableTransformer` interface:

```kotlin
interface DocumentableTransformer {
    operator fun invoke(original: DModule, context: DokkaContext): DModule
}
```

By default, `InheritorsExtractorTransformer` is created, that extracts inherited classes data across source sets and creates inheritance map.

#### Creating page models

The documentable model is translated into page format, that aggregates all tha data that will be available for different pages of documentation.

This step uses `DokkaCore.documentableToPageTranslator` entry point. It is required to have exactly one extension registered for this entry point. Having more will trigger an error, unless all except one will have parameter `isFallback` set to true, in which case the non-fallback extension will be used.

The extension is required to implement `DocumentableToPageTranslator` interface:

```kotlin
interface DocumentableToPageTranslator {
    operator fun invoke(module: DModule): ModulePageNode
}
```

By default, `DefaultDocumentableToPageTranslator` is created.  This extension is a fallback, so it could be replaced by a custom non-fallback one.

#### Transforming page models

You can apply any transformations to paged data.

This step uses `DokkaCore.pageTransformer` entry point. All extensions registered using this entry point will be invoked. Each of them is required to implement `PageTransformer` interface:

```kotlin
interface PageTransformer {
    operator fun invoke(input: RootPageNode): RootPageNode
}
```
By default, two transformers are created:
* `PageMerger` merges some pages depending on `MergeStrategy`
* `DeprecatedStrikethroughTransformer` marks all deprecated members on every page

#### Rendering

All pages are rendered to desired format.

This step uses `DokkaCore.renderer` entry point. It is required to have exactly one extension registered for this entry point. Having more will trigger an error, unless all except one will have parameter `isFallback` set to true, in which case the non-fallback extension will be used.
                                    
The extension is required to implement  `Renderer` interface:

```kotlin
interface Renderer {
    fun render(root: RootPageNode)
}
```

By default, only `HtmlRenderer`, that extends basic `DefaultRenderer`, is created, but it will be registered only if configuration parameter `format` is set to `html`. Using any other value without providing valid renderer will cause dokka to fail. 

### Multimodule page generation endpoints

Multimodule page generation is a separate process, that declares two additional entry points:

#### Multimodule page creation

Generation of the page that points to all module for which we generates documentation.

This step uses `CoreExtensions.allModulePageCreator` entry point. It is required to have exactly one extension registered for this entry point. Having more will trigger an error, unless all except one will have parameter `isFallback` set to true, in which case the non-fallback extension will be used.

The extension is required to implement  `PageCreator` interface:

```kotlin
interface PageCreator {
    operator fun invoke(): RootPageNode
}
```

By default, `MultimodulePageCreator` is created.  This extension is a fallback, so it could be replaced by a custom non-fallback one. 

#### Multimodule page transformation

Additional transformation that we might apply for multimodule page.

This step uses `CoreExtensions.allModulePageTransformer` entry point. All extensions registered using this entry point will be invoked.  Each of them is required to implement common `PageTransformer` interface.

### Default extensions' extension points

Default core extension points already have an implementation for providing basic dokka functionality. All of them are declared in `DokkaBase` plugin. If you don't want this default extensions to load, all you need to do is not load dokka base and load your plugin instead.
 
 ```kotlin
val customPlugin by configurations.creating

dependencies {
    customPlugin("[custom plugin load signature]")
}
tasks {
    val dokka by getting(DokkaTask::class) {
        pluginsConfig = alternativeAndIndependentPlugins
        outputDirectory = dokkaOutputDir
        outputFormat = "html"
        [...]
    }
}
```
 
 You will then need to implement extensions for all core extension points. 

`DokkaBase` also register several new extension points, with which you can change default behaviour of `DokkaBase` extensions. In order to use them, you need to add `dokka-base` to you dependencies:

```kotlin
    compileOnly("org.jetbrains.dokka:dokka-base:<dokka_version>")
```

Then, you need to obtain `DokkaBase` instance using `plugin` function:

```kotlin
class SamplePlugin : DokkaPlugin() {

    val dokkaBase = plugin<DokkaBase>()

    val extension by extending {
        dokkaBase.pageMergerStrategy with SamplePageMergerStrategy order {
           before(dokkaBase.fallbackMerger)
       }
    }
}

object SamplePageMergerStrategy: PageMergerStrategy {
    override fun tryMerge(pages: List<PageNode>, path: List<String>): List<PageNode> {
        ...
    }

}
```

#### Following extension points are available with base plugin

| Entry point | Function | Required interface | Used by | Singular | Preregistered extensions
|---|:---|:---:|:---:|:---:|:---:|
| `pageMergerStrategy` |  determines what kind of pages should be merged | `PageMergerStrategy` | `PageMerger` | false | `FallbackPageMergerStrategy` `SameMethodNamePageMergerStrategy`   |
| `commentsToContentConverter` | transforms comment model into page content model | `CommentsToContentConverter` | `DefaultDocumentableToPageTransformer` `SignatureProvider` | true | `DocTagToContentConverter`(fallback) |
| `signatureProvider`  | provides representation of methods signatures | `SignatureProvider` | `DefaultDocumentableToPageTransformer` | true | `KotlinSignatureProvider`(fallback) |
| `locationProviderFactory` | provides `LocationProvider` instance that returns paths for requested elements | `LocationProviderFactory` | `DefaultRenderer` `HtmlRenderer` `PackageListService` | true | `DefaultLocationProviderFactory`(fallback) which returns `DefaultLocationProvider` |
| `externalLocationProviderFactory` | provides `ExternalLocationProvider` instance that returns paths for elements that are not part of generated documentation | `ExternalLocationProviderFactory` | `DefaultLocationProvider` | false | `JavadocExternalLocationProviderFactory` `DokkaExternalLocationProviderFactory` |
| `outputWriter` | writes rendered pages files | `OutputWriter` | `DefaultRenderer` `HtmlRenderer` | true | `FileWriter`(fallback)|
| `htmlPreprocessors` | transforms page content before HTML rendering | `PageTransformer`| `DefaultRenderer` `HtmlRenderer` | false | `RootCreator` `SourceLinksTransformer` `NavigationPageInstaller` `SearchPageInstaller` `ResourceInstaller` `StyleAndScriptsAppender` `PackageListCreator` |
| `samplesTransformer` | transforms content for code samples for HTML rendering | `SamplesTransformer` | `HtmlRenderer` | true | `DefaultSamplesTransformer` |