From dac6ba2a589aa1e8a9f4a9c7af32026be77776b3 Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Thu, 14 Nov 2019 15:18:56 +0100 Subject: First plugin draft --- core/src/main/kotlin/plugability/DokkaContext.kt | 27 ++++++++++++++++++++++++ core/src/main/kotlin/plugability/DokkaPlugin.kt | 6 ++++++ 2 files changed, 33 insertions(+) create mode 100644 core/src/main/kotlin/plugability/DokkaContext.kt create mode 100644 core/src/main/kotlin/plugability/DokkaPlugin.kt (limited to 'core/src/main/kotlin/plugability') diff --git a/core/src/main/kotlin/plugability/DokkaContext.kt b/core/src/main/kotlin/plugability/DokkaContext.kt new file mode 100644 index 00000000..7da4d9a7 --- /dev/null +++ b/core/src/main/kotlin/plugability/DokkaContext.kt @@ -0,0 +1,27 @@ +package org.jetbrains.dokka.plugability + +import java.io.File +import java.net.URLClassLoader +import java.util.* + + +class DokkaContext private constructor() { + private val plugins = mutableListOf() + + val pluginNames: List + get() = plugins.map { it.name } + + private fun install(plugin: DokkaPlugin) { + plugins += plugin + plugin.install(this) + } + + companion object { + fun from(pluginsClasspath: Iterable) = DokkaContext().apply { + pluginsClasspath.map { it.relativeTo(File(".").absoluteFile).toURI().toURL() } + .toTypedArray() + .let { ServiceLoader.load(DokkaPlugin::class.java, URLClassLoader(it, this.javaClass.classLoader)) } + .forEach { install(it) } + } + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/plugability/DokkaPlugin.kt b/core/src/main/kotlin/plugability/DokkaPlugin.kt new file mode 100644 index 00000000..2654bcee --- /dev/null +++ b/core/src/main/kotlin/plugability/DokkaPlugin.kt @@ -0,0 +1,6 @@ +package org.jetbrains.dokka.plugability + +interface DokkaPlugin { + val name: String + fun install(context: DokkaContext) +} \ No newline at end of file -- cgit From 007aacb9ae3b228d0cef45ff5beb066b1dcd4cdc Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Fri, 15 Nov 2019 10:23:35 +0100 Subject: Adds simplest classpath checking for plugins --- core/src/main/kotlin/plugability/DokkaContext.kt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'core/src/main/kotlin/plugability') diff --git a/core/src/main/kotlin/plugability/DokkaContext.kt b/core/src/main/kotlin/plugability/DokkaContext.kt index 7da4d9a7..b54d8ed0 100644 --- a/core/src/main/kotlin/plugability/DokkaContext.kt +++ b/core/src/main/kotlin/plugability/DokkaContext.kt @@ -20,8 +20,17 @@ class DokkaContext private constructor() { fun from(pluginsClasspath: Iterable) = DokkaContext().apply { pluginsClasspath.map { it.relativeTo(File(".").absoluteFile).toURI().toURL() } .toTypedArray() - .let { ServiceLoader.load(DokkaPlugin::class.java, URLClassLoader(it, this.javaClass.classLoader)) } + .let { URLClassLoader(it, this.javaClass.classLoader) } + .also { checkClasspath(it) } + .let { ServiceLoader.load(DokkaPlugin::class.java, it) } .forEach { install(it) } } } -} \ No newline at end of file + + private fun checkClasspath(classLoader: URLClassLoader) { + classLoader.findResource(javaClass.name.replace('.','/') + ".class")?.also { + throw AssertionError("Dokka API found on plugins classpath. This will lead to subtle bugs. " + + "Please fix your plugins dependencies or exclude dokka api artifact from plugin classpath") + } + } +} -- cgit From 8a057a4611684a6a4616e136d480c005997070cd Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Tue, 19 Nov 2019 16:54:47 +0100 Subject: Working extensions in plugins --- .idea/compiler.xml | 4 ++ core/src/main/kotlin/DokkaGenerator.kt | 36 ++++++--------- core/src/main/kotlin/plugability/DokkaContext.kt | 30 +++++++++--- core/src/main/kotlin/plugability/DokkaPlugin.kt | 58 ++++++++++++++++++++++-- core/src/main/kotlin/plugability/extensions.kt | 52 +++++++++++++++++++++ plugins/mathjax/build.gradle | 6 +++ runners/fatjar/build.gradle | 4 +- settings.gradle | 3 +- 8 files changed, 159 insertions(+), 34 deletions(-) create mode 100644 core/src/main/kotlin/plugability/extensions.kt create mode 100644 plugins/mathjax/build.gradle (limited to 'core/src/main/kotlin/plugability') diff --git a/.idea/compiler.xml b/.idea/compiler.xml index e8fe0465..34ea6446 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -44,7 +44,11 @@ + + + + diff --git a/core/src/main/kotlin/DokkaGenerator.kt b/core/src/main/kotlin/DokkaGenerator.kt index fb4bf98f..f02f1e87 100644 --- a/core/src/main/kotlin/DokkaGenerator.kt +++ b/core/src/main/kotlin/DokkaGenerator.kt @@ -26,14 +26,8 @@ class DokkaGenerator( logger.debug("Initializing plugins") val context = DokkaContext.from(configuration.pluginsClasspath) - context.pluginNames.also { names -> - logger.progress("Loaded plugins: $names") - names.groupingBy { it }.eachCount().filter { it.value > 1 }.forEach { - logger.warn("Duplicate plugin name: ${it.key}. It will make debugging much harder.") - } - } - - + logger.progress("Loaded plugins: ${context.pluginNames}") + logger.progress("Loaded: ${context.loadedListForDebug}") configuration.passesConfigurations.map { pass -> AnalysisEnvironment(DokkaMessageCollector(logger), pass.analysisPlatform).run { @@ -79,23 +73,23 @@ class DokkaGenerator( ).render(it) } } -} -private fun nierzigoj(niczym: String) {} + private fun nierzigoj(niczym: String) {} -private class DokkaMessageCollector(private val logger: DokkaLogger) : MessageCollector { - override fun clear() { - seenErrors = false - } + private class DokkaMessageCollector(private val logger: DokkaLogger) : MessageCollector { + override fun clear() { + seenErrors = false + } - private var seenErrors = false + private var seenErrors = false - override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) { - if (severity == CompilerMessageSeverity.ERROR) { - seenErrors = true + override fun report(severity: CompilerMessageSeverity, message: String, location: CompilerMessageLocation?) { + if (severity == CompilerMessageSeverity.ERROR) { + seenErrors = true + } + logger.error(MessageRenderer.PLAIN_FULL_PATHS.render(severity, message, location)) } - logger.error(MessageRenderer.PLAIN_FULL_PATHS.render(severity, message, location)) - } - override fun hasErrors() = seenErrors + override fun hasErrors() = seenErrors + } } \ No newline at end of file diff --git a/core/src/main/kotlin/plugability/DokkaContext.kt b/core/src/main/kotlin/plugability/DokkaContext.kt index b54d8ed0..d67f34b1 100644 --- a/core/src/main/kotlin/plugability/DokkaContext.kt +++ b/core/src/main/kotlin/plugability/DokkaContext.kt @@ -3,17 +3,27 @@ package org.jetbrains.dokka.plugability import java.io.File import java.net.URLClassLoader import java.util.* +import kotlin.reflect.KClass class DokkaContext private constructor() { - private val plugins = mutableListOf() + private val plugins = mutableMapOf, DokkaPlugin>() + + internal val extensions = mutableMapOf, MutableList>>() + + @PublishedApi + internal fun plugin(kclass: KClass<*>) = plugins[kclass] val pluginNames: List - get() = plugins.map { it.name } + get() = plugins.values.map { it::class.qualifiedName.toString() } + + val loadedListForDebug + get() = extensions.run { keys + values.flatten() }.toList() + .joinToString(prefix = "[\n", separator = ",\n", postfix = "\n]") { "\t$it" } private fun install(plugin: DokkaPlugin) { - plugins += plugin - plugin.install(this) + plugins[plugin::class] = plugin + plugin.internalInstall(this) } companion object { @@ -28,9 +38,15 @@ class DokkaContext private constructor() { } private fun checkClasspath(classLoader: URLClassLoader) { - classLoader.findResource(javaClass.name.replace('.','/') + ".class")?.also { - throw AssertionError("Dokka API found on plugins classpath. This will lead to subtle bugs. " + - "Please fix your plugins dependencies or exclude dokka api artifact from plugin classpath") + classLoader.findResource(javaClass.name.replace('.', '/') + ".class")?.also { + throw AssertionError( + "Dokka API found on plugins classpath. This will lead to subtle bugs. " + + "Please fix your plugins dependencies or exclude dokka api artifact from plugin classpath" + ) } } + + internal fun addExtension(it: Extension<*>) { + extensions.getOrPut(it.extensionPoint, ::mutableListOf) += it + } } diff --git a/core/src/main/kotlin/plugability/DokkaPlugin.kt b/core/src/main/kotlin/plugability/DokkaPlugin.kt index 2654bcee..60ca245c 100644 --- a/core/src/main/kotlin/plugability/DokkaPlugin.kt +++ b/core/src/main/kotlin/plugability/DokkaPlugin.kt @@ -1,6 +1,58 @@ package org.jetbrains.dokka.plugability -interface DokkaPlugin { - val name: String - fun install(context: DokkaContext) +import kotlin.properties.ReadOnlyProperty +import kotlin.reflect.KProperty +import kotlin.reflect.KProperty1 +import kotlin.reflect.KTypeProjection +import kotlin.reflect.full.createInstance +import kotlin.reflect.full.createType +import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.jvm.isAccessible +import kotlin.reflect.jvm.jvmErasure + +private typealias ExtensionDelegate = ReadOnlyProperty> + +abstract class DokkaPlugin { + private val extensionDelegates = mutableListOf>() + + @PublishedApi + internal var context: DokkaContext? = null + + protected open fun install(context: DokkaContext) {} + + protected inline fun plugin(): T = + context?.plugin(T::class) as? T ?: T::class.createInstance().also { it.context = this.context } + + protected fun extensionPoint() = + object : ReadOnlyProperty> { + override fun getValue(thisRef: DokkaPlugin, property: KProperty<*>) = ExtensionPoint( + thisRef::class.qualifiedName ?: throw AssertionError("Plugin must be named class"), + property.name + ) + } + + protected fun extending(definition: ExtendingDSL.() -> Extension) = ExtensionProvider(definition) + + protected class ExtensionProvider internal constructor( + private val definition: ExtendingDSL.() -> Extension + ) { + operator fun provideDelegate(thisRef: DokkaPlugin, property: KProperty<*>) = lazy { + ExtendingDSL( + thisRef::class.qualifiedName ?: throw AssertionError("Plugin must be named class"), + property.name + ).definition() + }.also { thisRef.extensionDelegates += property } + } + + internal fun internalInstall(ctx: DokkaContext) { + context = ctx + install(ctx) + + extensionDelegates.asSequence() + .filterIsInstance>>() // always true + .map { it.get(this) } + .forEach { ctx.addExtension(it) } + } + + } \ No newline at end of file diff --git a/core/src/main/kotlin/plugability/extensions.kt b/core/src/main/kotlin/plugability/extensions.kt new file mode 100644 index 00000000..c1573e1a --- /dev/null +++ b/core/src/main/kotlin/plugability/extensions.kt @@ -0,0 +1,52 @@ +package org.jetbrains.dokka.plugability + +data class ExtensionPoint internal constructor( + internal val pluginClass: String, + internal val pointName: String +) { + override fun toString() = "ExtensionPoint: $pluginClass/$pointName" +} + +class Extension internal constructor( + internal val extensionPoint: ExtensionPoint, + internal val pluginClass: String, + internal val extensionName: String, + internal val action: T, + internal val ordering: (OrderDsl.() -> Unit)? = null +) { + override fun toString() = "Extension: $pluginClass/$extensionName" + + override fun equals(other: Any?) = + if (other is Extension<*>) this.pluginClass == other.extensionName && this.extensionName == other.extensionName + else false + + override fun hashCode() = listOf(pluginClass, extensionName).hashCode() +} + +internal data class Ordering(val previous: Set>, val following: Set>) + +@DslMarker +annotation class ExtensionsDsl + +@ExtensionsDsl +class ExtendingDSL(private val pluginClass: String, private val extensionName: String) { + infix fun ExtensionPoint.with(action: T) = + Extension(this, this@ExtendingDSL.pluginClass, extensionName, action) + + infix fun Extension.order(block: OrderDsl.() -> Unit) = + Extension(extensionPoint, pluginClass, extensionName, action, block) +} + +@ExtensionsDsl +class OrderDsl { + private val previous = mutableSetOf>() + private val following = mutableSetOf>() + + fun after(vararg extensions: Extension<*>) { + previous += extensions + } + + fun before(vararg extensions: Extension<*>) { + following += extensions + } +} \ No newline at end of file diff --git a/plugins/mathjax/build.gradle b/plugins/mathjax/build.gradle new file mode 100644 index 00000000..a4a59c50 --- /dev/null +++ b/plugins/mathjax/build.gradle @@ -0,0 +1,6 @@ +import javax.tools.ToolProvider + + +dependencies { + compileOnly project(':core') +} \ No newline at end of file diff --git a/runners/fatjar/build.gradle b/runners/fatjar/build.gradle index 1da23841..cab64c0f 100644 --- a/runners/fatjar/build.gradle +++ b/runners/fatjar/build.gradle @@ -33,7 +33,7 @@ shadowJar { exclude 'src/**' - relocate('kotlin.reflect.full', 'kotlin.reflect') +// relocate('kotlin.reflect.full', 'kotlin.reflect') } task apiShadow(type: ShadowJar) { @@ -56,7 +56,7 @@ task apiShadow(type: ShadowJar) { exclude 'src/**' - relocate('kotlin.reflect.full', 'kotlin.reflect') +// relocate('kotlin.reflect.full', 'kotlin.reflect') } apply plugin: 'maven-publish' diff --git a/settings.gradle b/settings.gradle index c793bd79..0447a904 100644 --- a/settings.gradle +++ b/settings.gradle @@ -9,5 +9,6 @@ include 'core', 'runners:ant', 'runners:cli', // 'runners:maven-plugin', - 'runners:gradle-plugin' + 'runners:gradle-plugin', // 'plugins:javadoc8' + 'plugins:mathjax' -- cgit From 87c65d1bf22dc0dbc6da8d007fafc73382432812 Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Wed, 20 Nov 2019 08:09:59 +0100 Subject: Mathjax plugin implemented --- core/src/main/kotlin/CoreExtensions.kt | 8 +++ core/src/main/kotlin/DokkaGenerator.kt | 7 +- core/src/main/kotlin/pages/PageNodes.kt | 82 ++++++++++++++++++---- core/src/main/kotlin/plugability/DokkaContext.kt | 3 + core/src/main/kotlin/renderers/HtmlRenderer.kt | 8 +++ .../kotlin/transformers/PageNodeTransformer.kt | 8 +++ plugins/mathjax/build.gradle | 11 +++ plugins/mathjax/src/main/kotlin/MathjaxPlugin.kt | 35 +++++++++ .../org.jetbrains.dokka.plugability.DokkaPlugin | 1 + 9 files changed, 146 insertions(+), 17 deletions(-) create mode 100644 core/src/main/kotlin/CoreExtensions.kt create mode 100644 core/src/main/kotlin/transformers/PageNodeTransformer.kt create mode 100644 plugins/mathjax/src/main/kotlin/MathjaxPlugin.kt create mode 100644 plugins/mathjax/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin (limited to 'core/src/main/kotlin/plugability') diff --git a/core/src/main/kotlin/CoreExtensions.kt b/core/src/main/kotlin/CoreExtensions.kt new file mode 100644 index 00000000..32559d52 --- /dev/null +++ b/core/src/main/kotlin/CoreExtensions.kt @@ -0,0 +1,8 @@ +package org.jetbrains.dokka + +import org.jetbrains.dokka.plugability.ExtensionPoint +import org.jetbrains.dokka.transformers.PageNodeTransformer + +object CoreExtensions { + val pageTransformer = ExtensionPoint(this::class.qualifiedName!!, "pageTransformer") +} \ No newline at end of file diff --git a/core/src/main/kotlin/DokkaGenerator.kt b/core/src/main/kotlin/DokkaGenerator.kt index f02f1e87..129549b9 100644 --- a/core/src/main/kotlin/DokkaGenerator.kt +++ b/core/src/main/kotlin/DokkaGenerator.kt @@ -62,11 +62,10 @@ class DokkaGenerator( } }.let { val markdownConverter = MarkdownToContentConverter(logger) -// it.forEach { it.genericPretty().also(::nierzigoj) } DefaultDocumentationToPageTransformer(markdownConverter, logger).transform(DocumentationNodesMerger(it)) - } - .also { -// it.genericPretty().also(::nierzigoj) + }.let { + context[CoreExtensions.pageTransformer].fold(it) { pn, t -> t.action.invoke(pn, context) } + }.also { HtmlRenderer( FileWriter(configuration.outputDir, ""), DefaultLocationProvider(it, configuration, ".${configuration.format}") diff --git a/core/src/main/kotlin/pages/PageNodes.kt b/core/src/main/kotlin/pages/PageNodes.kt index 0d6072f7..810cd776 100644 --- a/core/src/main/kotlin/pages/PageNodes.kt +++ b/core/src/main/kotlin/pages/PageNodes.kt @@ -10,10 +10,18 @@ interface PageNode { val parent: PageNode? val dri: DRI val documentationNode: DocumentationNode<*>? + val embeddedResources: List val children: List + + fun modified( + name: String = this.name, + content: ContentNode = this.content, + embeddedResources: List = this.embeddedResources, + children: List = this.children + ): PageNode } -abstract class BasicPageNode(children: List): PageNode { +abstract class BasicPageNode(children: List) : PageNode { private lateinit var _parent: PageNode override val parent: PageNode? by lazy { _parent } @@ -22,8 +30,7 @@ abstract class BasicPageNode(children: List): PageNode { override fun equals(other: Any?): Boolean = if (other is PageNode) { dri == other.dri && name == other.name - } - else false + } else false override fun hashCode(): Int = (name + dri).hashCode() @@ -31,17 +38,26 @@ abstract class BasicPageNode(children: List): PageNode { init { children.forEach { if (it is BasicPageNode) it._parent = this } } - } class ModulePageNode( override val name: String, override val content: ContentNode, override val documentationNode: DocumentationNode<*>?, - children: List -): BasicPageNode(children) { + children: List, + override val embeddedResources: List = listOf() +) : BasicPageNode(children) { override val parent: Nothing? = null override val dri: DRI = DRI.topLevel + + override fun modified( + name: String, + content: ContentNode, + embeddedResources: List, + children: List + ): ModulePageNode = + if (name == this.name && content === this.content && embeddedResources === this.embeddedResources && children shallowEq this.children) this + else ModulePageNode(name, content, documentationNode, children, embeddedResources) } class PackagePageNode( @@ -49,30 +65,70 @@ class PackagePageNode( override val content: ContentNode, override val dri: DRI, override val documentationNode: DocumentationNode<*>?, - children: List -): BasicPageNode(children) + children: List, + override val embeddedResources: List = listOf() +) : BasicPageNode(children) { + + override fun modified( + name: String, + content: ContentNode, + embeddedResources: List, + children: List + ): PackagePageNode = + if (name == this.name && content === this.content && embeddedResources === this.embeddedResources && children shallowEq this.children) this + else PackagePageNode(name, content, dri, documentationNode, children, embeddedResources) +} class ClassPageNode( override val name: String, override val content: ContentNode, override val dri: DRI, override val documentationNode: DocumentationNode<*>?, - children: List -): BasicPageNode(children) + children: List, + override val embeddedResources: List = listOf() +) : BasicPageNode(children) { + + override fun modified( + name: String, + content: ContentNode, + embeddedResources: List, + children: List + ): ClassPageNode = + if (name == this.name && content === this.content && embeddedResources === this.embeddedResources && children shallowEq this.children) this + else ClassPageNode(name, content, dri, documentationNode, children, embeddedResources) +} class MemberPageNode( override val name: String, override val content: ContentNode, override val dri: DRI, override val documentationNode: DocumentationNode<*>?, - children: List = emptyList() -): BasicPageNode(children) + children: List = emptyList(), + override val embeddedResources: List = listOf() +) : BasicPageNode(children) { + + override fun modified( + name: String, + content: ContentNode, + embeddedResources: List, + children: List + ): MemberPageNode = + if (name == this.name && content === this.content && embeddedResources === this.embeddedResources && children shallowEq this.children) this + else MemberPageNode(name, content, dri, documentationNode, children, embeddedResources) +} data class PlatformData(val platformType: Platform, val targets: List) { override fun toString() = targets.toString() } -fun PageNode.dfs(predicate: (PageNode) -> Boolean): PageNode? = if (predicate(this)) { this } else { this.children.asSequence().mapNotNull { it.dfs(predicate) }.firstOrNull() } +fun PageNode.dfs(predicate: (PageNode) -> Boolean): PageNode? = if (predicate(this)) { + this +} else { + this.children.asSequence().mapNotNull { it.dfs(predicate) }.firstOrNull() +} + +private infix fun List.shallowEq(other: List) = + this === other || (this.size == other.size && (this zip other).all { (a, b) -> a === b }) // Navigation?? diff --git a/core/src/main/kotlin/plugability/DokkaContext.kt b/core/src/main/kotlin/plugability/DokkaContext.kt index d67f34b1..dcd3398e 100644 --- a/core/src/main/kotlin/plugability/DokkaContext.kt +++ b/core/src/main/kotlin/plugability/DokkaContext.kt @@ -11,6 +11,9 @@ class DokkaContext private constructor() { internal val extensions = mutableMapOf, MutableList>>() + @Suppress("UNCHECKED_CAST") + operator fun > get(point: E) = extensions[point] as List> + @PublishedApi internal fun plugin(kclass: KClass<*>) = plugins[kclass] diff --git a/core/src/main/kotlin/renderers/HtmlRenderer.kt b/core/src/main/kotlin/renderers/HtmlRenderer.kt index f750140a..3b778671 100644 --- a/core/src/main/kotlin/renderers/HtmlRenderer.kt +++ b/core/src/main/kotlin/renderers/HtmlRenderer.kt @@ -4,6 +4,9 @@ import org.jetbrains.dokka.htmlEscape import org.jetbrains.dokka.pages.* import org.jetbrains.dokka.resolvers.LocationProvider import java.io.File +import java.net.URL +import java.nio.file.Path +import java.nio.file.Paths open class HtmlRenderer(fileWriter: FileWriter, locationProvider: LocationProvider) : DefaultRenderer(fileWriter, locationProvider) { @@ -69,11 +72,16 @@ open class HtmlRenderer(fileWriter: FileWriter, locationProvider: LocationProvid ) } + protected open fun buildScripts(page: PageNode) = + page.embeddedResources.filter { URL(it).path.substringAfterLast('.') == "js" } + .joinToString(separator = "") { """""" + "\n" } + protected open fun buildStartHtml(page: PageNode) = """ | | |${page.name} | + |${buildScripts(page)} | | |""".trimMargin() diff --git a/core/src/main/kotlin/transformers/PageNodeTransformer.kt b/core/src/main/kotlin/transformers/PageNodeTransformer.kt new file mode 100644 index 00000000..b86e0b34 --- /dev/null +++ b/core/src/main/kotlin/transformers/PageNodeTransformer.kt @@ -0,0 +1,8 @@ +package org.jetbrains.dokka.transformers + +import org.jetbrains.dokka.pages.ModulePageNode +import org.jetbrains.dokka.plugability.DokkaContext + +interface PageNodeTransformer { + fun invoke(input: ModulePageNode, dokkaContext: DokkaContext): ModulePageNode +} \ No newline at end of file diff --git a/plugins/mathjax/build.gradle b/plugins/mathjax/build.gradle index a4a59c50..6578ff45 100644 --- a/plugins/mathjax/build.gradle +++ b/plugins/mathjax/build.gradle @@ -1,6 +1,17 @@ import javax.tools.ToolProvider +apply plugin: 'maven-publish' dependencies { compileOnly project(':core') +} + +publishing { + publications { + dokkaCore(MavenPublication) { publication -> + artifactId = 'mathjax-plugin' + + from components.java + } + } } \ No newline at end of file diff --git a/plugins/mathjax/src/main/kotlin/MathjaxPlugin.kt b/plugins/mathjax/src/main/kotlin/MathjaxPlugin.kt new file mode 100644 index 00000000..63512966 --- /dev/null +++ b/plugins/mathjax/src/main/kotlin/MathjaxPlugin.kt @@ -0,0 +1,35 @@ +package org.jetbrains.dokka.mathjax + +import org.jetbrains.dokka.CoreExtensions +import org.jetbrains.dokka.pages.ModulePageNode +import org.jetbrains.dokka.pages.PageNode +import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.DokkaPlugin +import org.jetbrains.dokka.transformers.PageNodeTransformer + +class MathjaxPlugin : DokkaPlugin() { + val transformer by extending { + CoreExtensions.pageTransformer with MathjaxTransformer + } +} + +private const val ANNOTATION = "@usesMathJax" +private const val LIB_PATH = "https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.6/MathJax.js?config=TeX-AMS_SVG&latest" + +object MathjaxTransformer : PageNodeTransformer { + override fun invoke(input: ModulePageNode, dokkaContext: DokkaContext) = input.modified( + children = input.children.map { transform(it) } + ) + + private fun transform(input: PageNode): PageNode = input.modified( + embeddedResources = input.embeddedResources + if (input.isNeedingMathjax) listOf(LIB_PATH) else emptyList(), + children = input.children.map { transform(it) } + ) + + + private val PageNode.isNeedingMathjax + get() = documentationNode?.descriptors + ?.flatMap { it.docTag?.children?.toList().orEmpty() } + .orEmpty() + .any { it.text == ANNOTATION } +} \ No newline at end of file diff --git a/plugins/mathjax/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin b/plugins/mathjax/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin new file mode 100644 index 00000000..9f9736f3 --- /dev/null +++ b/plugins/mathjax/src/main/resources/META-INF/services/org.jetbrains.dokka.plugability.DokkaPlugin @@ -0,0 +1 @@ +org.jetbrains.dokka.mathjax.MathjaxPlugin -- cgit From a945617725e8df084270aacd5af76da2b911111e Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Mon, 25 Nov 2019 15:34:47 +0100 Subject: Interface segregation in plugins api --- core/src/main/kotlin/DokkaGenerator.kt | 4 +- core/src/main/kotlin/plugability/DokkaContext.kt | 82 +++++++++++++++--------- core/src/main/kotlin/plugability/DokkaPlugin.kt | 14 +--- plugins/mathjax/src/main/kotlin/MathjaxPlugin.kt | 1 + 4 files changed, 55 insertions(+), 46 deletions(-) (limited to 'core/src/main/kotlin/plugability') diff --git a/core/src/main/kotlin/DokkaGenerator.kt b/core/src/main/kotlin/DokkaGenerator.kt index b29a0da2..05eb3054 100644 --- a/core/src/main/kotlin/DokkaGenerator.kt +++ b/core/src/main/kotlin/DokkaGenerator.kt @@ -25,9 +25,7 @@ class DokkaGenerator( fun generate(): Unit { logger.debug("Initializing plugins") - val context = DokkaContext.from(configuration.pluginsClasspath) - logger.progress("Loaded plugins: ${context.pluginNames}") - logger.progress("Loaded: ${context.loadedListForDebug}") + val context = DokkaContext.create(configuration.pluginsClasspath, logger) configuration.passesConfigurations.map { pass -> AnalysisEnvironment(DokkaMessageCollector(logger), pass.analysisPlatform).run { diff --git a/core/src/main/kotlin/plugability/DokkaContext.kt b/core/src/main/kotlin/plugability/DokkaContext.kt index dcd3398e..7727329c 100644 --- a/core/src/main/kotlin/plugability/DokkaContext.kt +++ b/core/src/main/kotlin/plugability/DokkaContext.kt @@ -1,55 +1,75 @@ package org.jetbrains.dokka.plugability +import org.jetbrains.dokka.DokkaLogger import java.io.File import java.net.URLClassLoader import java.util.* import kotlin.reflect.KClass +interface DokkaContext { + operator fun > get(point: E): List> -class DokkaContext private constructor() { + fun plugin(kclass: KClass): T? + + val logger: DokkaLogger + + companion object { + fun create(pluginsClasspath: Iterable, logger: DokkaLogger): DokkaContext = + DokkaContextConfigurationImpl(logger).apply { + pluginsClasspath.map { it.relativeTo(File(".").absoluteFile).toURI().toURL() } + .toTypedArray() + .let { URLClassLoader(it, this.javaClass.classLoader) } + .also { checkClasspath(it) } + .let { ServiceLoader.load(DokkaPlugin::class.java, it) } + .forEach { install(it) } + }.also { it.logInitialisationInfo() } + } +} + +interface DokkaContextConfiguration { + fun addExtension(extension: Extension<*>) +} + +private class DokkaContextConfigurationImpl( + override val logger: DokkaLogger +) : DokkaContext, DokkaContextConfiguration { private val plugins = mutableMapOf, DokkaPlugin>() internal val extensions = mutableMapOf, MutableList>>() @Suppress("UNCHECKED_CAST") - operator fun > get(point: E) = extensions[point] as List> - - @PublishedApi - internal fun plugin(kclass: KClass<*>) = plugins[kclass] - - val pluginNames: List - get() = plugins.values.map { it::class.qualifiedName.toString() } + override operator fun > get(point: E) = extensions[point] as List> - val loadedListForDebug - get() = extensions.run { keys + values.flatten() }.toList() - .joinToString(prefix = "[\n", separator = ",\n", postfix = "\n]") { "\t$it" } + @Suppress("UNCHECKED_CAST") + override fun plugin(kclass: KClass) = plugins[kclass] as T - private fun install(plugin: DokkaPlugin) { + fun install(plugin: DokkaPlugin) { plugins[plugin::class] = plugin + plugin.context = this plugin.internalInstall(this) } - companion object { - fun from(pluginsClasspath: Iterable) = DokkaContext().apply { - pluginsClasspath.map { it.relativeTo(File(".").absoluteFile).toURI().toURL() } - .toTypedArray() - .let { URLClassLoader(it, this.javaClass.classLoader) } - .also { checkClasspath(it) } - .let { ServiceLoader.load(DokkaPlugin::class.java, it) } - .forEach { install(it) } - } + override fun addExtension(extension: Extension<*>) { + extensions.getOrPut(extension.extensionPoint, ::mutableListOf) += extension } - private fun checkClasspath(classLoader: URLClassLoader) { - classLoader.findResource(javaClass.name.replace('.', '/') + ".class")?.also { - throw AssertionError( - "Dokka API found on plugins classpath. This will lead to subtle bugs. " + - "Please fix your plugins dependencies or exclude dokka api artifact from plugin classpath" - ) - } - } + fun logInitialisationInfo() { + val pluginNames: List = plugins.values.map { it::class.qualifiedName.toString() } + + val loadedListForDebug = extensions.run { keys + values.flatten() }.toList() + .joinToString(prefix = "[\n", separator = ",\n", postfix = "\n]") { "\t$it" } + + logger.progress("Loaded plugins: ${pluginNames}") + logger.progress("Loaded: ${loadedListForDebug}") - internal fun addExtension(it: Extension<*>) { - extensions.getOrPut(it.extensionPoint, ::mutableListOf) += it } } + +private fun checkClasspath(classLoader: URLClassLoader) { + classLoader.findResource(DokkaContext::class.java.name.replace('.', '/') + ".class")?.also { + throw AssertionError( + "Dokka API found on plugins classpath. This will lead to subtle bugs. " + + "Please fix your plugins dependencies or exclude dokka api artifact from plugin classpath" + ) + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/plugability/DokkaPlugin.kt b/core/src/main/kotlin/plugability/DokkaPlugin.kt index 60ca245c..663cf88e 100644 --- a/core/src/main/kotlin/plugability/DokkaPlugin.kt +++ b/core/src/main/kotlin/plugability/DokkaPlugin.kt @@ -3,12 +3,7 @@ package org.jetbrains.dokka.plugability import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 -import kotlin.reflect.KTypeProjection import kotlin.reflect.full.createInstance -import kotlin.reflect.full.createType -import kotlin.reflect.full.declaredMemberProperties -import kotlin.reflect.jvm.isAccessible -import kotlin.reflect.jvm.jvmErasure private typealias ExtensionDelegate = ReadOnlyProperty> @@ -18,10 +13,8 @@ abstract class DokkaPlugin { @PublishedApi internal var context: DokkaContext? = null - protected open fun install(context: DokkaContext) {} - protected inline fun plugin(): T = - context?.plugin(T::class) as? T ?: T::class.createInstance().also { it.context = this.context } + context?.plugin(T::class) ?: T::class.createInstance().also { it.context = this.context } protected fun extensionPoint() = object : ReadOnlyProperty> { @@ -44,10 +37,7 @@ abstract class DokkaPlugin { }.also { thisRef.extensionDelegates += property } } - internal fun internalInstall(ctx: DokkaContext) { - context = ctx - install(ctx) - + internal fun internalInstall(ctx: DokkaContextConfiguration) { extensionDelegates.asSequence() .filterIsInstance>>() // always true .map { it.get(this) } diff --git a/plugins/mathjax/src/main/kotlin/MathjaxPlugin.kt b/plugins/mathjax/src/main/kotlin/MathjaxPlugin.kt index 0decfb1c..b7b2d505 100644 --- a/plugins/mathjax/src/main/kotlin/MathjaxPlugin.kt +++ b/plugins/mathjax/src/main/kotlin/MathjaxPlugin.kt @@ -4,6 +4,7 @@ import org.jetbrains.dokka.CoreExtensions import org.jetbrains.dokka.pages.ModulePageNode import org.jetbrains.dokka.pages.PageNode import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.DokkaContextConfiguration import org.jetbrains.dokka.plugability.DokkaPlugin import org.jetbrains.dokka.transformers.PageNodeTransformer -- cgit From 893dd637e3727d455e82f72ff90e823c6e017f7a Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Mon, 25 Nov 2019 15:48:08 +0100 Subject: Cache for plugin stubs --- core/src/main/kotlin/plugability/DokkaContext.kt | 12 +++++++++--- core/src/main/kotlin/plugability/DokkaPlugin.kt | 11 +++++------ 2 files changed, 14 insertions(+), 9 deletions(-) (limited to 'core/src/main/kotlin/plugability') diff --git a/core/src/main/kotlin/plugability/DokkaContext.kt b/core/src/main/kotlin/plugability/DokkaContext.kt index 7727329c..a5bcd695 100644 --- a/core/src/main/kotlin/plugability/DokkaContext.kt +++ b/core/src/main/kotlin/plugability/DokkaContext.kt @@ -5,6 +5,7 @@ import java.io.File import java.net.URLClassLoader import java.util.* import kotlin.reflect.KClass +import kotlin.reflect.full.createInstance interface DokkaContext { operator fun > get(point: E): List> @@ -35,13 +36,18 @@ private class DokkaContextConfigurationImpl( ) : DokkaContext, DokkaContextConfiguration { private val plugins = mutableMapOf, DokkaPlugin>() + private val pluginStubs = mutableMapOf, DokkaPlugin>() + internal val extensions = mutableMapOf, MutableList>>() @Suppress("UNCHECKED_CAST") override operator fun > get(point: E) = extensions[point] as List> @Suppress("UNCHECKED_CAST") - override fun plugin(kclass: KClass) = plugins[kclass] as T + override fun plugin(kclass: KClass) = (plugins[kclass] ?: pluginStubFor(kclass)) as T + + private fun pluginStubFor(kclass: KClass): DokkaPlugin = + pluginStubs.getOrPut(kclass) { kclass.createInstance().also { it.context = this } } fun install(plugin: DokkaPlugin) { plugins[plugin::class] = plugin @@ -59,8 +65,8 @@ private class DokkaContextConfigurationImpl( val loadedListForDebug = extensions.run { keys + values.flatten() }.toList() .joinToString(prefix = "[\n", separator = ",\n", postfix = "\n]") { "\t$it" } - logger.progress("Loaded plugins: ${pluginNames}") - logger.progress("Loaded: ${loadedListForDebug}") + logger.progress("Loaded plugins: $pluginNames") + logger.progress("Loaded: $loadedListForDebug") } } diff --git a/core/src/main/kotlin/plugability/DokkaPlugin.kt b/core/src/main/kotlin/plugability/DokkaPlugin.kt index 663cf88e..7a968b8b 100644 --- a/core/src/main/kotlin/plugability/DokkaPlugin.kt +++ b/core/src/main/kotlin/plugability/DokkaPlugin.kt @@ -3,7 +3,6 @@ package org.jetbrains.dokka.plugability import kotlin.properties.ReadOnlyProperty import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 -import kotlin.reflect.full.createInstance private typealias ExtensionDelegate = ReadOnlyProperty> @@ -13,8 +12,8 @@ abstract class DokkaPlugin { @PublishedApi internal var context: DokkaContext? = null - protected inline fun plugin(): T = - context?.plugin(T::class) ?: T::class.createInstance().also { it.context = this.context } + protected inline fun plugin(): T = context?.plugin(T::class) + ?: throw IllegalStateException("Querying about plugins is only possible with dokka context initialised") protected fun extensionPoint() = object : ReadOnlyProperty> { @@ -24,9 +23,9 @@ abstract class DokkaPlugin { ) } - protected fun extending(definition: ExtendingDSL.() -> Extension) = ExtensionProvider(definition) + protected fun extending(definition: ExtendingDSL.() -> Extension) = ExtensionProvider(definition) - protected class ExtensionProvider internal constructor( + protected class ExtensionProvider internal constructor( private val definition: ExtendingDSL.() -> Extension ) { operator fun provideDelegate(thisRef: DokkaPlugin, property: KProperty<*>) = lazy { @@ -39,7 +38,7 @@ abstract class DokkaPlugin { internal fun internalInstall(ctx: DokkaContextConfiguration) { extensionDelegates.asSequence() - .filterIsInstance>>() // always true + .filterIsInstance>>() // should be always true .map { it.get(this) } .forEach { ctx.addExtension(it) } } -- cgit From 202845bf4c9e72cfcda578a548f32bc7cc04f64d Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Mon, 25 Nov 2019 17:45:23 +0100 Subject: Default context for dokka --- core/src/main/kotlin/plugability/DokkaContext.kt | 33 ++++++++++++++++++++---- 1 file changed, 28 insertions(+), 5 deletions(-) (limited to 'core/src/main/kotlin/plugability') diff --git a/core/src/main/kotlin/plugability/DokkaContext.kt b/core/src/main/kotlin/plugability/DokkaContext.kt index a5bcd695..b917ff99 100644 --- a/core/src/main/kotlin/plugability/DokkaContext.kt +++ b/core/src/main/kotlin/plugability/DokkaContext.kt @@ -8,7 +8,8 @@ import kotlin.reflect.KClass import kotlin.reflect.full.createInstance interface DokkaContext { - operator fun > get(point: E): List> + operator fun get(point: E, askDefault: AskDefault = AskDefault.WhenEmpty): List> + where T : Any, E : ExtensionPoint fun plugin(kclass: KClass): T? @@ -16,7 +17,7 @@ interface DokkaContext { companion object { fun create(pluginsClasspath: Iterable, logger: DokkaLogger): DokkaContext = - DokkaContextConfigurationImpl(logger).apply { + DokkaContextConfigurationImpl(logger, DefaultContext(logger)).apply { pluginsClasspath.map { it.relativeTo(File(".").absoluteFile).toURI().toURL() } .toTypedArray() .let { URLClassLoader(it, this.javaClass.classLoader) } @@ -32,7 +33,8 @@ interface DokkaContextConfiguration { } private class DokkaContextConfigurationImpl( - override val logger: DokkaLogger + override val logger: DokkaLogger, + private val defaultContext: DokkaContext? ) : DokkaContext, DokkaContextConfiguration { private val plugins = mutableMapOf, DokkaPlugin>() @@ -41,10 +43,17 @@ private class DokkaContextConfigurationImpl( internal val extensions = mutableMapOf, MutableList>>() @Suppress("UNCHECKED_CAST") - override operator fun > get(point: E) = extensions[point] as List> + override operator fun get(point: E, askDefault: AskDefault) where T : Any, E : ExtensionPoint = + when (askDefault) { + AskDefault.Never -> extensions[point].orEmpty() + AskDefault.Always -> extensions[point].orEmpty() + defaultContext?.get(point, askDefault).orEmpty() + AskDefault.WhenEmpty -> + extensions[point]?.takeIf { it.isNotEmpty() } ?: defaultContext?.get(point, askDefault).orEmpty() + } as List> @Suppress("UNCHECKED_CAST") - override fun plugin(kclass: KClass) = (plugins[kclass] ?: pluginStubFor(kclass)) as T + override fun plugin(kclass: KClass) = + (plugins[kclass] ?: defaultContext?.plugin(kclass) ?: pluginStubFor(kclass)) as T private fun pluginStubFor(kclass: KClass): DokkaPlugin = pluginStubs.getOrPut(kclass) { kclass.createInstance().also { it.context = this } } @@ -78,4 +87,18 @@ private fun checkClasspath(classLoader: URLClassLoader) { "Please fix your plugins dependencies or exclude dokka api artifact from plugin classpath" ) } +} + +class DefaultContext(override val logger: DokkaLogger) : DokkaContext { + override fun > get(point: E, askDefault: AskDefault): List> = + when (point) { + + else -> emptyList() + } + + override fun plugin(kclass: KClass): Nothing? = null +} + +enum class AskDefault { + Always, Never, WhenEmpty } \ No newline at end of file -- cgit From 49439594f86217d8a25e8df2580b8ef29d836230 Mon Sep 17 00:00:00 2001 From: Paweł Marks Date: Tue, 26 Nov 2019 13:44:27 +0100 Subject: Introduction of all important extension points and restructuring of DokkaGenerator --- .idea/compiler.xml | 2 + core/src/main/kotlin/CoreExtensions.kt | 35 +++- core/src/main/kotlin/DokkaDescriptorVisitor.kt | 191 ----------------- core/src/main/kotlin/DokkaGenerator.kt | 183 +++++++++++----- core/src/main/kotlin/Model/DocumentationNode.kt | 2 +- .../transformers/DocumentationNodeTransformer.kt | 8 - .../Model/transformers/DocumentationNodesMerger.kt | 90 -------- .../pages/DefaultMarkdownToContentConverter.kt | 229 +++++++++++++++++++++ .../kotlin/pages/MarkdownToContentConverter.kt | 219 +------------------- .../pages/transformers/PageNodeTransformer.kt | 7 - .../main/kotlin/plugability/DefaultExtensions.kt | 25 +++ core/src/main/kotlin/plugability/DokkaContext.kt | 61 ++++-- core/src/main/kotlin/plugability/extensions.kt | 2 +- core/src/main/kotlin/renderers/DefaultRenderer.kt | 7 +- core/src/main/kotlin/renderers/HtmlRenderer.kt | 7 +- core/src/main/kotlin/renderers/Renderer.kt | 1 + .../kotlin/resolvers/DefaultLocationProvider.kt | 11 +- .../DefaultDocumentationToPageTransformer.kt | 20 -- .../transformers/DocumentationToPageTransformer.kt | 11 - .../kotlin/transformers/PageNodeTransformer.kt | 8 - .../DefaultDescriptorToDocumentationTranslator.kt | 208 +++++++++++++++++++ .../DescriptorToDocumentationTranslator.kt | 15 ++ .../DefaultDocumentationNodeMerger.kt | 86 ++++++++ .../DefaultDocumentationToPageTranslator.kt | 24 +++ .../documentation/DocumentationNodeMerger.kt | 8 + .../documentation/DocumentationNodeTransformer.kt | 8 + .../documentation/DocumentationToPageTranslator.kt | 12 ++ .../transformers/pages/PageNodeTransformer.kt | 8 + plugins/mathjax/src/main/kotlin/MathjaxPlugin.kt | 3 +- plugins/xml/src/main/kotlin/XmlPlugin.kt | 10 +- 30 files changed, 861 insertions(+), 640 deletions(-) delete mode 100644 core/src/main/kotlin/DokkaDescriptorVisitor.kt delete mode 100644 core/src/main/kotlin/Model/transformers/DocumentationNodeTransformer.kt delete mode 100644 core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt create mode 100644 core/src/main/kotlin/pages/DefaultMarkdownToContentConverter.kt delete mode 100644 core/src/main/kotlin/pages/transformers/PageNodeTransformer.kt create mode 100644 core/src/main/kotlin/plugability/DefaultExtensions.kt delete mode 100644 core/src/main/kotlin/transformers/DefaultDocumentationToPageTransformer.kt delete mode 100644 core/src/main/kotlin/transformers/DocumentationToPageTransformer.kt delete mode 100644 core/src/main/kotlin/transformers/PageNodeTransformer.kt create mode 100644 core/src/main/kotlin/transformers/descriptors/DefaultDescriptorToDocumentationTranslator.kt create mode 100644 core/src/main/kotlin/transformers/descriptors/DescriptorToDocumentationTranslator.kt create mode 100644 core/src/main/kotlin/transformers/documentation/DefaultDocumentationNodeMerger.kt create mode 100644 core/src/main/kotlin/transformers/documentation/DefaultDocumentationToPageTranslator.kt create mode 100644 core/src/main/kotlin/transformers/documentation/DocumentationNodeMerger.kt create mode 100644 core/src/main/kotlin/transformers/documentation/DocumentationNodeTransformer.kt create mode 100644 core/src/main/kotlin/transformers/documentation/DocumentationToPageTranslator.kt create mode 100644 core/src/main/kotlin/transformers/pages/PageNodeTransformer.kt (limited to 'core/src/main/kotlin/plugability') diff --git a/.idea/compiler.xml b/.idea/compiler.xml index 34ea6446..e78fe4d3 100644 --- a/.idea/compiler.xml +++ b/.idea/compiler.xml @@ -50,6 +50,8 @@ + + diff --git a/core/src/main/kotlin/CoreExtensions.kt b/core/src/main/kotlin/CoreExtensions.kt index 6579cab2..f56cd854 100644 --- a/core/src/main/kotlin/CoreExtensions.kt +++ b/core/src/main/kotlin/CoreExtensions.kt @@ -1,14 +1,37 @@ package org.jetbrains.dokka -import org.jetbrains.dokka.Model.transformers.DocumentationNodeTransformer +import org.jetbrains.dokka.pages.MarkdownToContentConverter +import org.jetbrains.dokka.pages.PageNode +import org.jetbrains.dokka.plugability.DokkaContext import org.jetbrains.dokka.plugability.ExtensionPoint +import org.jetbrains.dokka.renderers.FileWriter import org.jetbrains.dokka.renderers.Renderer import org.jetbrains.dokka.resolvers.LocationProvider -import org.jetbrains.dokka.transformers.PageNodeTransformer +import org.jetbrains.dokka.transformers.descriptors.DescriptorToDocumentationTranslator +import org.jetbrains.dokka.transformers.documentation.DocumentationNodeMerger +import org.jetbrains.dokka.transformers.documentation.DocumentationNodeTransformer +import org.jetbrains.dokka.transformers.documentation.DocumentationToPageTranslator +import org.jetbrains.dokka.transformers.pages.PageNodeTransformer +import kotlin.reflect.KProperty + +/** + * Extension points declared by dokka core. + * Default values are stored in [org.jetbrains.dokka.plugability.DefaultExtensions] + */ object CoreExtensions { - val nodeTransformer = ExtensionPoint(this::class.qualifiedName!!, "nodeTransformer") - val pageTransformer = ExtensionPoint(this::class.qualifiedName!!, "pageTransformer") - val renderer = ExtensionPoint(this::class.qualifiedName!!, "renderer") - val locationProvider = ExtensionPoint(this::class.qualifiedName!!, "locationProvider") + val descriptorToDocumentationTranslator by coreExtension() + val documentationMerger by coreExtension() + val documentationTransformer by coreExtension() + val markdownToContentConverterFactory by coreExtension<(DokkaContext) -> MarkdownToContentConverter>() + val documentationToPageTranslator by coreExtension() + val pageTransformer by coreExtension() + val renderer by coreExtension<(FileWriter, LocationProvider, DokkaContext) -> Renderer>() + val locationProvider by coreExtension<(root: PageNode, DokkaConfiguration, DokkaContext) -> LocationProvider>() + val fileExtension by coreExtension() + + private fun coreExtension() = object { + operator fun provideDelegate(thisRef: CoreExtensions, property: KProperty<*>): Lazy> = + lazy { ExtensionPoint(thisRef::class.qualifiedName!!, property.name) } + } } \ No newline at end of file diff --git a/core/src/main/kotlin/DokkaDescriptorVisitor.kt b/core/src/main/kotlin/DokkaDescriptorVisitor.kt deleted file mode 100644 index 6c82f2c3..00000000 --- a/core/src/main/kotlin/DokkaDescriptorVisitor.kt +++ /dev/null @@ -1,191 +0,0 @@ -package org.jetbrains.dokka - -import org.jetbrains.dokka.Model.* -import org.jetbrains.dokka.Model.ClassKind -import org.jetbrains.dokka.Model.Function -import org.jetbrains.dokka.links.Callable -import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.links.withClass -import org.jetbrains.dokka.pages.PlatformData -import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.descriptors.impl.DeclarationDescriptorVisitorEmptyBodies -import org.jetbrains.kotlin.idea.kdoc.findKDoc -import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink -import org.jetbrains.kotlin.kdoc.psi.impl.KDocLink -import org.jetbrains.kotlin.kdoc.psi.impl.KDocName -import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe -import org.jetbrains.kotlin.resolve.descriptorUtil.getAllSuperclassesWithoutAny -import org.jetbrains.kotlin.resolve.descriptorUtil.getSuperInterfaces -import org.jetbrains.kotlin.resolve.scopes.DescriptorKindFilter -import org.jetbrains.kotlin.resolve.scopes.MemberScope -import org.jetbrains.kotlin.types.KotlinType - -class DokkaDescriptorVisitor( - private val platformData: PlatformData, - private val resolutionFacade: DokkaResolutionFacade -) : DeclarationDescriptorVisitorEmptyBodies() { - override fun visitDeclarationDescriptor(descriptor: DeclarationDescriptor, parent: DRI): Nothing { - throw IllegalStateException("${javaClass.simpleName} should never enter ${descriptor.javaClass.simpleName}") - } - - override fun visitPackageFragmentDescriptor( - descriptor: PackageFragmentDescriptor, - parent: DRI - ): Package { - val dri = DRI(packageName = descriptor.fqName.asString()) - val scope = descriptor.getMemberScope() - return Package( - dri, - scope.functions(dri), - scope.properties(dri), - scope.classes(dri) - ) - } - - override fun visitClassDescriptor(descriptor: ClassDescriptor, parent: DRI): Class { - val dri = parent.withClass(descriptor.name.asString()) - val scope = descriptor.getMemberScope(emptyList()) - val descriptorData = descriptor.takeUnless { it.isExpect }?.resolveClassDescriptionData() - return Class( - dri, - descriptor.name.asString(), - KotlinClassKindTypes.valueOf(descriptor.kind.toString()), - descriptor.constructors.map { visitConstructorDescriptor(it, dri) }, - scope.functions(dri), - scope.properties(dri), - scope.classes(dri), - descriptor.takeIf { it.isExpect }?.resolveClassDescriptionData(), - listOfNotNull(descriptorData), - getXMLDRIs(descriptor, descriptorData).toMutableSet() - ) - } - - override fun visitPropertyDescriptor(descriptor: PropertyDescriptor, parent: DRI): Property { - val dri = parent.copy(callable = Callable.from(descriptor)) - return Property( - dri, - descriptor.name.asString(), - descriptor.extensionReceiverParameter?.let { visitReceiverParameterDescriptor(it, dri) }, - descriptor.takeIf { it.isExpect }?.resolveDescriptorData(), - listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()) - ) - } - - override fun visitFunctionDescriptor(descriptor: FunctionDescriptor, parent: DRI): Function { - val dri = parent.copy(callable = Callable.from(descriptor)) - return Function( - dri, - descriptor.name.asString(), - descriptor.returnType?.let { KotlinTypeWrapper(it) }, - false, - descriptor.extensionReceiverParameter?.let { visitReceiverParameterDescriptor(it, dri) }, - descriptor.valueParameters.mapIndexed { index, desc -> parameter(index, desc, dri) }, - descriptor.takeIf { it.isExpect }?.resolveDescriptorData(), - listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()) - ) - } - - override fun visitConstructorDescriptor(descriptor: ConstructorDescriptor, parent: DRI): Function { - val dri = parent.copy(callable = Callable.from(descriptor)) - return Function( - dri, - "", - KotlinTypeWrapper(descriptor.returnType), - true, - null, - descriptor.valueParameters.mapIndexed { index, desc -> parameter(index, desc, dri) }, - descriptor.takeIf { it.isExpect }?.resolveDescriptorData(), - listOfNotNull(descriptor.takeUnless { it.isExpect }?.resolveDescriptorData()) - ) - } - - override fun visitReceiverParameterDescriptor( - descriptor: ReceiverParameterDescriptor, - parent: DRI - ) = Parameter( - parent.copy(target = 0), - null, - KotlinTypeWrapper(descriptor.type), - listOf(descriptor.resolveDescriptorData()) - ) - - private fun parameter(index: Int, descriptor: ValueParameterDescriptor, parent: DRI) = - Parameter( - parent.copy(target = index + 1), - descriptor.name.asString(), - KotlinTypeWrapper(descriptor.type), - listOf(descriptor.resolveDescriptorData()) - ) - - private fun MemberScope.functions(parent: DRI): List = - getContributedDescriptors(DescriptorKindFilter.FUNCTIONS) { true } - .filterIsInstance() - .map { visitFunctionDescriptor(it, parent) } - - private fun MemberScope.properties(parent: DRI): List = - getContributedDescriptors(DescriptorKindFilter.VALUES) { true } - .filterIsInstance() - .map { visitPropertyDescriptor(it, parent) } - - private fun MemberScope.classes(parent: DRI): List = - getContributedDescriptors(DescriptorKindFilter.CLASSIFIERS) { true } - .filterIsInstance() - .map { visitClassDescriptor(it, parent) } - - private fun DeclarationDescriptor.resolveDescriptorData(): PlatformInfo { - val doc = findKDoc() - val links = doc?.children?.filter { it is KDocLink }?.flatMap { link -> - val destination = link.children.first { it is KDocName }.text - resolveKDocLink( - resolutionFacade.resolveSession.bindingContext, - resolutionFacade, - this, - null, - destination.split('.') - ).map { Pair(destination, DRI.from(it)) } - }?.toMap() ?: emptyMap() - return BasePlatformInfo(doc, links, listOf(platformData)) - } - - private fun ClassDescriptor.resolveClassDescriptionData(): ClassPlatformInfo { - return ClassPlatformInfo(resolveDescriptorData(), - (getSuperInterfaces() + getAllSuperclassesWithoutAny()).map { DRI.from(it) }) - } - - private fun getXMLDRIs(descriptor: DeclarationDescriptor, platformInfo: PlatformInfo?) = - platformInfo?.docTag?.children - ?.filter { - it.text.contains("@attr") - }?.flatMap { ref -> - val matchResult = "@attr\\s+ref\\s+(.+)".toRegex().matchEntire(ref.text) - val toFind = matchResult?.groups?.last()?.value.orEmpty() - resolveKDocLink( - resolutionFacade.resolveSession.bindingContext, - resolutionFacade, - descriptor, - null, - toFind.split('.') - ).map { XMLMega("@attr ref", DRI.from(it)) } - }.orEmpty() -} - -data class XMLMega(val key: String, val dri: DRI) : Extra - -enum class KotlinClassKindTypes : ClassKind { - CLASS, - INTERFACE, - ENUM_CLASS, - ENUM_ENTRY, - ANNOTATION_CLASS, - OBJECT; -} - -class KotlinTypeWrapper(private val kotlinType: KotlinType) : TypeWrapper { - private val declarationDescriptor = kotlinType.constructor.declarationDescriptor - private val fqNameSafe = declarationDescriptor?.fqNameSafe - override val constructorFqName = fqNameSafe?.asString() - override val constructorNamePathSegments: List = - fqNameSafe?.pathSegments()?.map { it.asString() } ?: emptyList() - override val arguments: List by lazy { kotlinType.arguments.map { KotlinTypeWrapper(it.type) } } - override val dri: DRI? by lazy { declarationDescriptor?.let { DRI.from(it) } } -} \ No newline at end of file diff --git a/core/src/main/kotlin/DokkaGenerator.kt b/core/src/main/kotlin/DokkaGenerator.kt index 05eb3054..b6bf7a73 100644 --- a/core/src/main/kotlin/DokkaGenerator.kt +++ b/core/src/main/kotlin/DokkaGenerator.kt @@ -1,20 +1,24 @@ package org.jetbrains.dokka import org.jetbrains.dokka.Model.Module -import org.jetbrains.dokka.Model.transformers.DocumentationNodesMerger import org.jetbrains.dokka.Utilities.pretty import org.jetbrains.dokka.links.DRI -import org.jetbrains.dokka.pages.MarkdownToContentConverter +import org.jetbrains.dokka.pages.DefaultMarkdownToContentConverter import org.jetbrains.dokka.pages.PlatformData import org.jetbrains.dokka.plugability.DokkaContext +import org.jetbrains.dokka.plugability.single import org.jetbrains.dokka.renderers.FileWriter import org.jetbrains.dokka.renderers.HtmlRenderer import org.jetbrains.dokka.resolvers.DefaultLocationProvider -import org.jetbrains.dokka.transformers.DefaultDocumentationToPageTransformer +import org.jetbrains.dokka.resolvers.LocationProvider +import org.jetbrains.dokka.transformers.documentation.DefaultDocumentationToPageTranslator +import org.jetbrains.dokka.transformers.descriptors.DokkaDescriptorVisitor +import org.jetbrains.dokka.transformers.documentation.DefaultDocumentationNodeMerger import org.jetbrains.kotlin.cli.common.messages.CompilerMessageLocation import org.jetbrains.kotlin.cli.common.messages.CompilerMessageSeverity import org.jetbrains.kotlin.cli.common.messages.MessageCollector import org.jetbrains.kotlin.cli.common.messages.MessageRenderer +import org.jetbrains.kotlin.cli.jvm.compiler.KotlinCoreEnvironment import org.jetbrains.kotlin.utils.PathUtil import java.io.File @@ -22,56 +26,131 @@ class DokkaGenerator( private val configuration: DokkaConfiguration, private val logger: DokkaLogger ) { - fun generate(): Unit { + + fun generate() { + logger.debug("Setting up analysis environments") + val platforms: Map = configuration.passesConfigurations.map { + PlatformData(it.analysisPlatform, it.targets) to createEnvironmentAndFacade(it) + }.toMap() logger.debug("Initializing plugins") - val context = DokkaContext.create(configuration.pluginsClasspath, logger) - - configuration.passesConfigurations.map { pass -> - AnalysisEnvironment(DokkaMessageCollector(logger), pass.analysisPlatform).run { - if (analysisPlatform == Platform.jvm) { - addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre()) - } - for (element in pass.classpath) { - addClasspath(File(element)) - } - - addSources(pass.sourceRoots.map { it.path }) - - loadLanguageVersionSettings(pass.languageVersion, pass.apiVersion) - - val environment = createCoreEnvironment() - val (facade, _) = createResolutionFacade(environment) - - environment.getSourceFiles().asSequence() - .map { it.packageFqName } - .distinct() - .mapNotNull { facade.resolveSession.getPackageFragment(it) } - .map { - DokkaDescriptorVisitor( - PlatformData(pass.analysisPlatform, pass.targets), - facade - ).visitPackageFragmentDescriptor( - it, - DRI.topLevel - ) - } - .toList() - .let { Module(it) } - .let { DocumentationNodesMerger(it) } - .also { println("${pass.analysisPlatform}:\n${it.pretty()}\n\n") } + val context = DokkaContext.create(configuration.pluginsClasspath, logger, platforms) + + logger.debug("Creating documentation models") + val modulesFromPlatforms = platforms.map { (pdata, _) -> translateDescriptors(pdata, context) } + + logger.debug("Merging documentation models") + val documentationModel = context.single(CoreExtensions.documentationMerger) + .invoke(modulesFromPlatforms, context) + + logger.debug("Transforming documentation model") + val transformedDocumentation = context[CoreExtensions.documentationTransformer] + .fold(documentationModel) { acc, t -> t(acc, context) } + + logger.debug("Creating pages") + val pages = context.single(CoreExtensions.documentationToPageTranslator) + .invoke(transformedDocumentation, context) + + logger.debug("Transforming pages") + val transformedPages = context[CoreExtensions.pageTransformer] + .fold(pages) { acc, t -> t(acc, context) } + + logger.debug("Rendering") + val fileWriter = FileWriter(configuration.outputDir, "") + val locationProvider = context.single(CoreExtensions.locationProvider) + .invoke(transformedPages, configuration, context) + val renderer = context.single(CoreExtensions.renderer) + .invoke(fileWriter, locationProvider, context) + + renderer.render(transformedPages) + } + +// fun generate(int: Int) { +// +// logger.debug("Initializing plugins") +// val context = DokkaContext.create(configuration.pluginsClasspath, logger, platforms) +// +// configuration.passesConfigurations.map { pass -> +// AnalysisEnvironment(DokkaMessageCollector(logger), pass.analysisPlatform).run { +// if (analysisPlatform == Platform.jvm) { +// addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre()) +// } +// for (element in pass.classpath) { +// addClasspath(File(element)) +// } +// +// addSources(pass.sourceRoots.map { it.path }) +// +// loadLanguageVersionSettings(pass.languageVersion, pass.apiVersion) +// +// val environment = createCoreEnvironment() +// val (facade, _) = createResolutionFacade(environment) +// +// environment.getSourceFiles().asSequence() +// .map { it.packageFqName } +// .distinct() +// .mapNotNull { facade.resolveSession.getPackageFragment(it) } +// .map { +// DokkaDescriptorVisitor( +// PlatformData( +// pass.analysisPlatform, +// pass.targets +// ), facade +// ) +// .visitPackageFragmentDescriptor(it, DRI.topLevel) +// } +// .toList() +// .let { Module(it) } +// .let { DefaultDocumentationNodeMerger(it) } +// .also { println("${pass.analysisPlatform}:\n${it.pretty()}\n\n") } +// } +// }.let { +// val markdownConverter = DefaultMarkdownToContentConverter(logger) +// DefaultDocumentationToPageTranslator( +// markdownConverter, +// logger +// ).transform( +// DefaultDocumentationNodeMerger( +// it +// ) +// ) +// }.let { +// context[CoreExtensions.pageTransformer].fold(it) { pn, t -> t(pn, context) } +// }.also { +// HtmlRenderer( +// FileWriter(configuration.outputDir, ""), +// DefaultLocationProvider(it, configuration, ".${configuration.format}") +// ).render(it) +// } +// } + + private fun createEnvironmentAndFacade(pass: DokkaConfiguration.PassConfiguration): EnvironmentAndFacade = + AnalysisEnvironment(DokkaMessageCollector(logger), pass.analysisPlatform).run { + if (analysisPlatform == Platform.jvm) { + addClasspath(PathUtil.getJdkClassesRootsFromCurrentJre()) } - }.let { - val markdownConverter = MarkdownToContentConverter(logger) - DefaultDocumentationToPageTransformer(markdownConverter, logger).transform(DocumentationNodesMerger(it)) - }.let { - context[CoreExtensions.pageTransformer].fold(it) { pn, t -> t.action.invoke(pn, context) } - }.also { - HtmlRenderer( - FileWriter(configuration.outputDir, ""), - DefaultLocationProvider(it, configuration, ".${configuration.format}") - ).render(it) + pass.classpath.forEach { addClasspath(File(it)) } + + addSources(pass.sourceRoots.map { it.path }) + + loadLanguageVersionSettings(pass.languageVersion, pass.apiVersion) + + val environment = createCoreEnvironment() + val (facade, _) = createResolutionFacade(environment) + EnvironmentAndFacade(environment, facade) } + + private fun translateDescriptors(platformData: PlatformData, context: DokkaContext): Module { + val (environment, facade) = context.platforms.getValue(platformData) + + val packageFragments = environment.getSourceFiles().asSequence() + .map { it.packageFqName } + .distinct() + .mapNotNull { facade.resolveSession.getPackageFragment(it) } + .toList() + + return context.single(CoreExtensions.descriptorToDocumentationTranslator) + .invoke(packageFragments, platformData, context) } private class DokkaMessageCollector(private val logger: DokkaLogger) : MessageCollector { @@ -90,4 +169,10 @@ class DokkaGenerator( override fun hasErrors() = seenErrors } +} + +// It is not data class due to ill-defined equals +class EnvironmentAndFacade(val environment: KotlinCoreEnvironment, val facade: DokkaResolutionFacade) { + operator fun component1() = environment + operator fun component2() = facade } \ No newline at end of file diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt index 0adb37b5..b1d4be55 100644 --- a/core/src/main/kotlin/Model/DocumentationNode.kt +++ b/core/src/main/kotlin/Model/DocumentationNode.kt @@ -1,6 +1,6 @@ package org.jetbrains.dokka.Model -import org.jetbrains.dokka.KotlinTypeWrapper +import org.jetbrains.dokka.transformers.descriptors.KotlinTypeWrapper import org.jetbrains.dokka.links.DRI import org.jetbrains.dokka.pages.PlatformData import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag diff --git a/core/src/main/kotlin/Model/transformers/DocumentationNodeTransformer.kt b/core/src/main/kotlin/Model/transformers/DocumentationNodeTransformer.kt deleted file mode 100644 index 318d20b3..00000000 --- a/core/src/main/kotlin/Model/transformers/DocumentationNodeTransformer.kt +++ /dev/null @@ -1,8 +0,0 @@ -package org.jetbrains.dokka.Model.transformers - -import org.jetbrains.dokka.Model.Module - -interface DocumentationNodeTransformer { - operator fun invoke(original: Module): Module - operator fun invoke(modules: Collection): Module -} \ No newline at end of file diff --git a/core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt b/core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt deleted file mode 100644 index ae4f8d99..00000000 --- a/core/src/main/kotlin/Model/transformers/DocumentationNodesMerger.kt +++ /dev/null @@ -1,90 +0,0 @@ -package org.jetbrains.dokka.Model.transformers - -import org.jetbrains.dokka.Model.* -import org.jetbrains.dokka.Model.Function - -internal object DocumentationNodesMerger : DocumentationNodeTransformer { - override fun invoke(original: Module) = Module( - original.packages.map { mergePackageContent(it) } - ) - override fun invoke(modules: Collection): Module = - Module(merge(modules.flatMap { it.packages }, Package::mergeWith)) -} - -private fun mergePackageContent(original: Package) = Package( - original.dri, - merge(original.functions, Function::mergeWith), - merge(original.properties, Property::mergeWith), - merge(original.classes, Class::mergeWith) -) - -private fun merge(elements: List, reducer: (T, T) -> T): List = - elements.groupingBy { it.dri } - .reduce { _, left, right -> reducer(left, right)} - .values.toList() - -fun PlatformInfo.mergeWith(other: PlatformInfo?) = BasePlatformInfo( - docTag, - links, - (platformData + (other?.platformData ?: emptyList())).distinct() -) - -fun ClassPlatformInfo.mergeWith(other: ClassPlatformInfo?) = ClassPlatformInfo( - info.mergeWith(other?.info), - (inherited + (other?.inherited ?: emptyList())).distinct() -) - -fun List.mergeClassPlatformInfo() : List = - groupingBy { it.docTag.toString() + it.links + it.inherited}.reduce { - _, left, right -> left.mergeWith(right) - }.values.toList() - -fun List.merge() : List = - groupingBy { it.docTag.toString() + it.links }.reduce { - _, left, right -> left.mergeWith(right) - }.values.toList() - -fun Function.mergeWith(other: Function) = Function( - dri, - name, - returnType, - isConstructor, - if (receiver != null && other.receiver != null) receiver.mergeWith(other.receiver) else null, - merge(parameters + other.parameters, Parameter::mergeWith), - expected?.mergeWith(other.expected), - (actual + other.actual).merge() -) - -fun Property.mergeWith(other: Property) = Property( - dri, - name, - if (receiver != null && other.receiver != null) receiver.mergeWith(other.receiver) else null, - expected?.mergeWith(other.expected), - (actual + other.actual).merge() -) - -fun Class.mergeWith(other: Class) = Class( - dri, - name, - kind, - merge(constructors + other.constructors, Function::mergeWith), - merge(functions + other.functions, Function::mergeWith), - merge(properties + other.properties, Property::mergeWith), - merge(classes + other.classes, Class::mergeWith), - expected?.mergeWith(other.expected), - (actual + other.actual).mergeClassPlatformInfo() -) - -fun Parameter.mergeWith(other: Parameter) = Parameter( - dri, - name, - type, - (actual + other.actual).merge() -) - -fun Package.mergeWith(other: Package) = Package( - dri, - merge(functions + other.functions, Function::mergeWith), - merge(properties + other.properties, Property::mergeWith), - merge(classes + other.classes, Class::mergeWith) -) \ No newline at end of file diff --git a/core/src/main/kotlin/pages/DefaultMarkdownToContentConverter.kt b/core/src/main/kotlin/pages/DefaultMarkdownToContentConverter.kt new file mode 100644 index 00000000..72b5ead2 --- /dev/null +++ b/core/src/main/kotlin/pages/DefaultMarkdownToContentConverter.kt @@ -0,0 +1,229 @@ +package org.jetbrains.dokka.pages + +import org.intellij.markdown.MarkdownElementTypes +import org.intellij.markdown.MarkdownTokenTypes +import org.jetbrains.dokka.MarkdownNode +import org.jetbrains.dokka.links.DRI +import org.jetbrains.dokka.plugability.DokkaContext + +class DefaultMarkdownToContentConverter( + private val context: DokkaContext +) : MarkdownToContentConverter { + override fun buildContent( + node: MarkdownNode, + dci: DCI, + platforms: Set, + links: Map, + styles: Set