Merge remote-tracking branch 'upstream/java-layout-html-format' into devsite-with-java-layout-html
diff --git a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
index 1a0763f..5c740eb 100644
--- a/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
+++ b/core/src/main/kotlin/Formats/JavaLayoutHtml/JavaLayoutHtmlPackageListService.kt
@@ -2,8 +2,13 @@
import org.jetbrains.dokka.DocumentationModule
import org.jetbrains.dokka.ExternalDocumentationLinkResolver.Companion.DOKKA_PARAM_PREFIX
+import org.jetbrains.dokka.InboundExternalLinkResolutionService
import org.jetbrains.dokka.NodeKind
import org.jetbrains.dokka.PackageListService
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameUnsafe
+import org.jetbrains.kotlin.resolve.descriptorUtil.isCompanionObject
+import org.jetbrains.kotlin.types.KotlinType
class JavaLayoutHtmlPackageListService : PackageListService {
@@ -19,10 +24,91 @@
return buildString {
appendParam("format", "java-layout-html")
+ appendParam("mode", "kotlin")
for (p in packages) {
appendln(p)
}
}
}
+}
+
+class JavaLayoutHtmlInboundLinkResolutionService(private val paramMap: Map<String, List<String>>) : InboundExternalLinkResolutionService {
+ private fun getContainerPath(symbol: DeclarationDescriptor): String? {
+ return when (symbol) {
+ is PackageFragmentDescriptor -> symbol.fqName.asString() + "/"
+ is ClassifierDescriptor -> getContainerPath(symbol.findPackage()) + symbol.nameWithOuter() + ".html"
+ else -> null
+ }
+ }
+
+ private fun DeclarationDescriptor.findPackage(): PackageFragmentDescriptor =
+ generateSequence(this) { it.containingDeclaration }.filterIsInstance<PackageFragmentDescriptor>().first()
+
+ private fun ClassifierDescriptor.nameWithOuter(): String =
+ generateSequence(this) { it.containingDeclaration as? ClassifierDescriptor }
+ .toList().asReversed().joinToString(".") { it.name.asString() }
+
+ private fun getPagePath(symbol: DeclarationDescriptor): String? {
+ return when (symbol) {
+ is PackageFragmentDescriptor -> getContainerPath(symbol) + "package-summary.html"
+ is ClassifierDescriptor -> getContainerPath(symbol) + "#"
+ is FunctionDescriptor, is PropertyDescriptor -> getContainerPath(symbol.containingDeclaration!!) + "#" + symbol.signatureForAnchorUrlEncoded()
+ else -> null
+ }
+ }
+
+ private fun DeclarationDescriptor.signatureForAnchor(): String? {
+
+ fun ReceiverParameterDescriptor.extractReceiverName(): String {
+ var receiverClass: DeclarationDescriptor = type.constructor.declarationDescriptor!!
+ if (receiverClass.isCompanionObject()) {
+ receiverClass = receiverClass.containingDeclaration!!
+ } else if (receiverClass is TypeParameterDescriptor) {
+ val upperBoundClass = receiverClass.upperBounds.singleOrNull()?.constructor?.declarationDescriptor
+ if (upperBoundClass != null) {
+ receiverClass = upperBoundClass
+ }
+ }
+
+ return receiverClass.name.asString()
+ }
+
+ fun KotlinType.qualifiedNameForSignature(): String {
+ val desc = constructor.declarationDescriptor
+ return desc?.fqNameUnsafe?.asString() ?: "<ERROR TYPE NAME>"
+ }
+
+ fun StringBuilder.appendReceiverAndCompanion(desc: CallableDescriptor) {
+ if (desc.containingDeclaration.isCompanionObject()) {
+ append("Companion.")
+ }
+ desc.extensionReceiverParameter?.let {
+ append("(")
+ append(it.extractReceiverName())
+ append(").")
+ }
+ }
+
+ return when(this) {
+ is FunctionDescriptor -> buildString {
+ appendReceiverAndCompanion(this@signatureForAnchor)
+ append(name.asString())
+ valueParameters.joinTo(this, prefix = "(", postfix = ")") {
+ it.type.qualifiedNameForSignature()
+ }
+ }
+ is PropertyDescriptor -> buildString {
+ appendReceiverAndCompanion(this@signatureForAnchor)
+ append(name.asString())
+ append(":")
+ append(returnType?.qualifiedNameForSignature())
+ }
+ else -> null
+ }
+ }
+
+ private fun DeclarationDescriptor.signatureForAnchorUrlEncoded(): String? = signatureForAnchor()?.urlEncoded()
+
+ override fun getPath(symbol: DeclarationDescriptor) = getPagePath(symbol)
}
\ No newline at end of file
diff --git a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
index 330e76a..0ac4854 100644
--- a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
+++ b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
@@ -4,6 +4,10 @@
import com.google.inject.Singleton
import com.intellij.psi.PsiMethod
import com.intellij.util.io.*
+import org.jetbrains.dokka.Formats.FileGeneratorBasedFormatDescriptor
+import org.jetbrains.dokka.Formats.FormatDescriptor
+import org.jetbrains.dokka.Utilities.ServiceLocator
+import org.jetbrains.dokka.Utilities.lookup
import org.jetbrains.kotlin.descriptors.*
import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor
import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
@@ -20,6 +24,7 @@
import java.net.URLConnection
import java.nio.file.Path
import java.security.MessageDigest
+import kotlin.reflect.full.findAnnotation
fun ByteArray.toHexString() = this.joinToString(separator = "") { "%02x".format(it) }
@@ -128,13 +133,22 @@
.map { (key, value) -> key to value }
.toMap()
- val resolver = if (format == "javadoc") {
- InboundExternalLinkResolutionService.Javadoc()
- } else {
- val linkExtension = paramsMap["linkExtension"]?.singleOrNull() ?:
- throw RuntimeException("Failed to parse package list from $packageListUrl")
- InboundExternalLinkResolutionService.Dokka(linkExtension)
- }
+
+ val defaultResolverDesc = services["dokka-default"]!!
+ val resolverDesc = services[format]
+ ?: defaultResolverDesc.takeIf { format in formatsWithDefaultResolver }
+ ?: defaultResolverDesc.also {
+ logger.warn("Couldn't find InboundExternalLinkResolutionService(format = `$format`) for $link, using Dokka default")
+ }
+
+
+ val resolverClass = javaClass.classLoader.loadClass(resolverDesc.className).kotlin
+
+ val constructors = resolverClass.constructors
+
+ val constructor = constructors.singleOrNull()
+ ?: constructors.first { it.findAnnotation<Inject>() != null }
+ val resolver = constructor.call(paramsMap) as InboundExternalLinkResolutionService
val rootInfo = ExternalDocumentationRoot(link.url, resolver, locations)
@@ -169,6 +183,15 @@
companion object {
const val DOKKA_PARAM_PREFIX = "\$dokka."
+ val services = ServiceLocator.allServices("inbound-link-resolver").associateBy { it.name }
+ private val formatsWithDefaultResolver =
+ ServiceLocator
+ .allServices("format")
+ .filter {
+ val desc = ServiceLocator.lookup<FormatDescriptor>(it) as? FileGeneratorBasedFormatDescriptor
+ desc?.generatorServiceClass == FileGenerator::class
+ }.map { it.name }
+ .toSet()
}
}
@@ -176,7 +199,7 @@
interface InboundExternalLinkResolutionService {
fun getPath(symbol: DeclarationDescriptor): String?
- class Javadoc : InboundExternalLinkResolutionService {
+ class Javadoc(paramsMap: Map<String, List<String>>) : InboundExternalLinkResolutionService {
override fun getPath(symbol: DeclarationDescriptor): String? {
if (symbol is JavaClassDescriptor) {
return DescriptorUtils.getFqName(symbol).asString().replace(".", "/") + ".html"
@@ -200,7 +223,9 @@
}
}
- class Dokka(val extension: String) : InboundExternalLinkResolutionService {
+ class Dokka(val paramsMap: Map<String, List<String>>) : InboundExternalLinkResolutionService {
+ val extension = paramsMap["linkExtension"]?.singleOrNull() ?: error("linkExtension not provided for Dokka resolver")
+
override fun getPath(symbol: DeclarationDescriptor): String? {
val leafElement = when (symbol) {
is CallableDescriptor, is TypeAliasDescriptor -> true
diff --git a/core/src/main/kotlin/Utilities/ServiceLocator.kt b/core/src/main/kotlin/Utilities/ServiceLocator.kt
index 71bfd21..83c4c65 100644
--- a/core/src/main/kotlin/Utilities/ServiceLocator.kt
+++ b/core/src/main/kotlin/Utilities/ServiceLocator.kt
@@ -14,12 +14,21 @@
object ServiceLocator {
fun <T : Any> lookup(clazz: Class<T>, category: String, implementationName: String): T {
val descriptor = lookupDescriptor(category, implementationName)
+ return lookup(clazz, descriptor)
+ }
+
+ fun <T : Any> lookup(
+ clazz: Class<T>,
+ descriptor: ServiceDescriptor
+ ): T {
val loadedClass = javaClass.classLoader.loadClass(descriptor.className)
val constructor = loadedClass.constructors
- .filter { it.parameterTypes.isEmpty() }
- .firstOrNull() ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor")
+ .filter { it.parameterTypes.isEmpty() }
+ .firstOrNull()
+ ?: throw ServiceLookupException("Class ${descriptor.className} has no corresponding constructor")
- val implementationRawType: Any = if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor)
+ val implementationRawType: Any =
+ if (constructor.parameterTypes.isEmpty()) constructor.newInstance() else constructor.newInstance(constructor)
if (!clazz.isInstance(implementationRawType)) {
throw ServiceLookupException("Class ${descriptor.className} is not a subtype of ${clazz.name}")
@@ -79,6 +88,7 @@
}
inline fun <reified T : Any> ServiceLocator.lookup(category: String, implementationName: String): T = lookup(T::class.java, category, implementationName)
+inline fun <reified T : Any> ServiceLocator.lookup(desc: ServiceDescriptor): T = lookup(T::class.java, desc)
private val ZipEntry.fileName: String
get() = name.substringAfterLast("/", name)
diff --git a/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties b/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties
new file mode 100644
index 0000000..c484a92
--- /dev/null
+++ b/core/src/main/resources/dokka/inbound-link-resolver/dokka-default.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.InboundExternalLinkResolutionService$Dokka
+description=Uses Dokka Default resolver
\ No newline at end of file
diff --git a/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties b/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties
new file mode 100644
index 0000000..3b61eab
--- /dev/null
+++ b/core/src/main/resources/dokka/inbound-link-resolver/java-layout-html.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.Formats.JavaLayoutHtmlInboundLinkResolutionService
+description=Resolver for JavaLayoutHtml
\ No newline at end of file
diff --git a/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties b/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties
new file mode 100644
index 0000000..0d5d7d1
--- /dev/null
+++ b/core/src/main/resources/dokka/inbound-link-resolver/javadoc.properties
@@ -0,0 +1,2 @@
+class=org.jetbrains.dokka.InboundExternalLinkResolutionService$Javadoc
+description=Uses Javadoc Default resolver
\ No newline at end of file
diff --git a/core/src/test/kotlin/format/JavaLayoutHtmlFormatTest.kt b/core/src/test/kotlin/format/JavaLayoutHtmlFormatTest.kt
index 8e7563c..c3b5ee9 100644
--- a/core/src/test/kotlin/format/JavaLayoutHtmlFormatTest.kt
+++ b/core/src/test/kotlin/format/JavaLayoutHtmlFormatTest.kt
@@ -1,8 +1,10 @@
package org.jetbrains.dokka.tests
+import org.jetbrains.dokka.*
import org.jetbrains.dokka.Formats.JavaLayoutHtmlFormatDescriptor
-import org.jetbrains.dokka.NodeKind
import org.junit.Test
+import java.io.File
+import java.net.URL
class JavaLayoutHtmlFormatTest : JavaLayoutHtmlFormatTestCase() {
override val formatDescriptor = JavaLayoutHtmlFormatDescriptor()
@@ -55,4 +57,49 @@
fun constJava() {
verifyNode("ConstJava.java", noStdlibLink = true)
}
+
+ @Test
+ fun inboundLinksInKotlinMode() {
+ val root = "./testdata/format/java-layout-html"
+
+ val options = DocumentationOptions(
+ "",
+ "java-layout-html",
+ sourceLinks = listOf(),
+ generateIndexPages = false,
+ noStdlibLink = true,
+ apiVersion = null,
+ languageVersion = null,
+ perPackageOptions = listOf(PackageOptionsImpl("foo", suppress = true)),
+ externalDocumentationLinks =
+ listOf(
+ DokkaConfiguration.ExternalDocumentationLink.Builder(
+ URL("file:///"),
+ File(root, "inboundLinksTestPackageList").toURI().toURL()
+ ).build()
+ )
+ )
+
+
+ val sourcePath = "$root/inboundLinksInKotlinMode.kt"
+ val documentation = DocumentationModule("test")
+
+ appendDocumentation(
+ documentation,
+ contentRootFromPath(sourcePath),
+ contentRootFromPath("$root/inboundLinksInKotlinMode.Dep.kt"),
+ withJdk = false,
+ withKotlinRuntime = false,
+ options = options
+ )
+ documentation.prepareForGeneration(options)
+
+ verifyModelOutput(documentation, ".html", sourcePath) { model, output ->
+ buildPagesAndReadInto(
+ model,
+ model.members.single { it.name == "bar" }.members,
+ output
+ )
+ }
+ }
}
\ No newline at end of file
diff --git a/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.Dep.kt b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.Dep.kt
new file mode 100644
index 0000000..610ebb2
--- /dev/null
+++ b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.Dep.kt
@@ -0,0 +1,23 @@
+package foo
+
+
+fun foobar() {
+
+}
+
+
+val v = 22
+
+class G {
+
+ fun oo() = ""
+
+ val og = 11
+
+ companion object {
+
+ fun dg() = "22"
+
+ val dv = 12
+ }
+}
\ No newline at end of file
diff --git a/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.html b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.html
new file mode 100644
index 0000000..7025fc7
--- /dev/null
+++ b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.html
@@ -0,0 +1,80 @@
+<!-- File: /test/bar/X.html# -->
+<html>
+ <head>
+ <meta charset="UTF-8">
+ </head>
+ <body>
+ <h1>X</h1>
+ <pre><span class="keyword">class </span><span class="identifier">X</span></pre>
+ <table>
+ <tr>
+ <td><a href="#">bar.X</a></td>
+ </tr>
+ </table>
+ <p>See <a href="file:/foo/#foobar%28%29">foo.foobar</a>
+See <a href="file:/foo/#v%3Akotlin.Int">foo.v</a>
+See <a href="file:/foo/G.html#">foo.G</a>
+See <a href="file:/foo/G.html#oo%28%29">foo.G.oo</a>
+See <a href="file:/foo/G.html#og%3Akotlin.Int">foo.G.og</a>
+See <a href="file:/foo/G.Companion.html#Companion.dg%28%29">foo.G.Companion.dg</a>
+See <a href="file:/foo/G.Companion.html#Companion.dv%3Akotlin.Int">foo.G.Companion.dv</a></p>
+ <h2>Summary</h2>
+ <table>
+ <thead>
+ <tr>
+ <td>
+ <h3>Constructors</h3>
+ </td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td>
+ <div><code><a href="#%3Cinit%3E%28%29"><init></a>()</code></div>
+ <p>See <a href="file:/foo/#foobar%28%29">foo.foobar</a>
+See <a href="file:/foo/#v%3Akotlin.Int">foo.v</a>
+See <a href="file:/foo/G.html#">foo.G</a>
+See <a href="file:/foo/G.html#oo%28%29">foo.G.oo</a>
+See <a href="file:/foo/G.html#og%3Akotlin.Int">foo.G.og</a>
+See <a href="file:/foo/G.Companion.html#Companion.dg%28%29">foo.G.Companion.dg</a>
+See <a href="file:/foo/G.Companion.html#Companion.dv%3Akotlin.Int">foo.G.Companion.dv</a></p>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <table>
+ <thead>
+ <tr>
+ <td>
+ <h3>Functions</h3>
+ </td>
+ </tr>
+ </thead>
+ <tbody>
+ <tr>
+ <td><span class="identifier">String</span></td>
+ <td>
+ <div><code><a href="file:/foo/G.html#"><span class="identifier">G</span></a>.<a href="#%28foo.G%29.ext%28%29">ext</a>()</code></div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
+ <h2>Constructors</h2>
+ <div id="<init>()">
+ <h3><init></h3>
+ <pre><span class="identifier">X</span><span class="symbol">(</span><span class="symbol">)</span></pre>
+ <p>See <a href="file:/foo/#foobar%28%29">foo.foobar</a>
+See <a href="file:/foo/#v%3Akotlin.Int">foo.v</a>
+See <a href="file:/foo/G.html#">foo.G</a>
+See <a href="file:/foo/G.html#oo%28%29">foo.G.oo</a>
+See <a href="file:/foo/G.html#og%3Akotlin.Int">foo.G.og</a>
+See <a href="file:/foo/G.Companion.html#Companion.dg%28%29">foo.G.Companion.dg</a>
+See <a href="file:/foo/G.Companion.html#Companion.dv%3Akotlin.Int">foo.G.Companion.dv</a></p>
+ </div>
+ <h2>Functions</h2>
+ <div id="(foo.G).ext()">
+ <h3>ext</h3>
+ <pre><span class="keyword">fun </span><a href="file:/foo/G.html#"><span class="identifier">G</span></a><span class="symbol">.</span><span class="identifier">ext</span><span class="symbol">(</span><span class="symbol">)</span><span class="symbol">: </span><span class="identifier">String</span></pre>
+ </div>
+ </body>
+</html>
diff --git a/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.kt b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.kt
new file mode 100644
index 0000000..6420d78
--- /dev/null
+++ b/core/testdata/format/java-layout-html/inboundLinksInKotlinMode.kt
@@ -0,0 +1,16 @@
+package bar
+
+/**
+ * See [foo.foobar]
+ * See [foo.v]
+ * See [foo.G]
+ * See [foo.G.oo]
+ * See [foo.G.og]
+ * See [foo.G.Companion.dg]
+ * See [foo.G.Companion.dv]
+ */
+class X {
+
+ fun (foo.G).ext() = this.oo()
+}
+
diff --git a/core/testdata/format/java-layout-html/inboundLinksTestPackageList b/core/testdata/format/java-layout-html/inboundLinksTestPackageList
new file mode 100644
index 0000000..64d25c3
--- /dev/null
+++ b/core/testdata/format/java-layout-html/inboundLinksTestPackageList
@@ -0,0 +1,3 @@
+$dokka.format:java-layout-html
+$dokka.mode:kotlin
+foo
diff --git a/core/testdata/format/java-layout-html/topLevel.package-summary.html b/core/testdata/format/java-layout-html/topLevel.package-summary.html
index 791c371..03665c6 100644
--- a/core/testdata/format/java-layout-html/topLevel.package-summary.html
+++ b/core/testdata/format/java-layout-html/topLevel.package-summary.html
@@ -14,6 +14,17 @@
</tr>
</tbody>
</table>
+ <h2>Top-level constants summary</h2>
+ <table>
+ <tbody>
+ <tr>
+ <td><span class="keyword">const</span> <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></td>
+ <td>
+ <div><code><a href="#topLevelConst%3Akotlin.String">topLevelConst</a></code></div>
+ </td>
+ </tr>
+ </tbody>
+ </table>
<h2>Top-level functions summary</h2>
<table>
<tbody>
@@ -41,12 +52,6 @@
</td>
</tr>
<tr>
- <td><span class="keyword">const</span> <a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></td>
- <td>
- <div><code><a href="#topLevelConst%3Akotlin.String">topLevelConst</a></code></div>
- </td>
- </tr>
- <tr>
<td><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></td>
<td>
<div><code><a href="#topLevelGetVal%3Akotlin.String">topLevelGetVal</a></code></div>
@@ -60,6 +65,12 @@
</tr>
</tbody>
</table>
+ <h2>Top-level constants</h2>
+ <div id="topLevelConst:kotlin.String">
+ <h3>topLevelConst</h3>
+ <pre><span class="keyword">const</span> <span class="keyword">val </span><span class="identifier">topLevelConst</span><span class="symbol">: </span><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></pre>
+ <pre>Value: <code>""</code></pre>
+ </div>
<h2>Top-level functions</h2>
<div id="topLevelFun()">
<h3>topLevelFun</h3>
@@ -74,10 +85,6 @@
<h3>topLevelVal</h3>
<pre><span class="keyword">val </span><span class="identifier">topLevelVal</span><span class="symbol">: </span><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></pre>
</div>
- <div id="topLevelConst:kotlin.String">
- <h3>topLevelConst</h3>
- <pre><span class="keyword">const</span> <span class="keyword">val </span><span class="identifier">topLevelConst</span><span class="symbol">: </span><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></pre>
- </div>
<div id="topLevelGetVal:kotlin.String">
<h3>topLevelGetVal</h3>
<pre><span class="keyword">val </span><span class="identifier">topLevelGetVal</span><span class="symbol">: </span><a href="https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-string/index.html"><span class="identifier">String</span></a></pre>