diff options
author | Paweł Marks <pmarks@virtuslab.com> | 2020-07-17 16:36:09 +0200 |
---|---|---|
committer | Paweł Marks <pmarks@virtuslab.com> | 2020-07-17 16:36:09 +0200 |
commit | 6996b1135f61c7d2cb60b0652c6a2691dda31990 (patch) | |
tree | d568096c25e31c28d14d518a63458b5a7526b896 /plugins/developer_guide.md | |
parent | de56cab76f556e5b4af0b8c8cb08d8b482b86d0a (diff) | |
parent | 1c3530dcbb50c347f80bef694829dbefe89eca77 (diff) | |
download | dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.tar.gz dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.tar.bz2 dokka-6996b1135f61c7d2cb60b0652c6a2691dda31990.zip |
Merge branch 'dev-0.11.0'
Diffstat (limited to 'plugins/developer_guide.md')
-rw-r--r-- | plugins/developer_guide.md | 413 |
1 files changed, 413 insertions, 0 deletions
diff --git a/plugins/developer_guide.md b/plugins/developer_guide.md new file mode 100644 index 00000000..b7b184f8 --- /dev/null +++ b/plugins/developer_guide.md @@ -0,0 +1,413 @@ +# 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(definition: ExtendingDSL.() -> Extension<T, *, *>)` function,that returns a delegate `ExtensionProvider` with supplied definition. + +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 specifying 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 +* `override( Extension<T, *, *> )` to override other extension. Overridden extension won't be loaded and overridding one will inherit ordering from it. + +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 only one is not overridden. + +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 treated as a fallback, so it can be overridden by a custom 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 only one is not overridden. + +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 treated as a fallback, so it can be overridden by a custom 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 only one is not overridden. + +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 only one is not overridden. + +The extension is required to implement `PageCreator` interface: + +```kotlin +interface PageCreator { + operator fun invoke(): RootPageNode +} +``` + +By default, `MultimodulePageCreator` is created. This extension is treated as a fallback, so it can be replaced by a custom 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` | +| `signatureProvider` | provides representation of methods signatures | `SignatureProvider` | `DefaultDocumentableToPageTransformer` | true | `KotlinSignatureProvider` | +| `locationProviderFactory` | provides `LocationProvider` instance that returns paths for requested elements | `LocationProviderFactory` | `DefaultRenderer` `HtmlRenderer` `PackageListService` | true | `DefaultLocationProviderFactory` 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`| +| `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` | + + |