aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Ogorodnik <Simon.Ogorodnik@jetbrains.com>2017-05-03 13:45:30 +0300
committerSimon Ogorodnik <Simon.Ogorodnik@jetbrains.com>2017-05-11 19:52:40 +0300
commita86c859eba6154524f3b42461aad6b45f26e3650 (patch)
tree6772882331daf29c8d19e4a3ed77ef938d45b1ac
parent022a6a6bc9a1d61f190715dec56c3bef31887388 (diff)
downloaddokka-a86c859eba6154524f3b42461aad6b45f26e3650.tar.gz
dokka-a86c859eba6154524f3b42461aad6b45f26e3650.tar.bz2
dokka-a86c859eba6154524f3b42461aad6b45f26e3650.zip
Support linking of external documentation
Introduce PackageListService #KT-16309 fixed
-rw-r--r--core/src/main/kotlin/Formats/FormatDescriptor.kt1
-rw-r--r--core/src/main/kotlin/Formats/FormatService.kt4
-rw-r--r--core/src/main/kotlin/Formats/PackageListService.kt63
-rw-r--r--core/src/main/kotlin/Formats/StandardFormats.kt2
-rw-r--r--core/src/main/kotlin/Formats/StructuredFormatService.kt14
-rw-r--r--core/src/main/kotlin/Generation/DokkaGenerator.kt2
-rw-r--r--core/src/main/kotlin/Generation/FileGenerator.kt17
-rw-r--r--core/src/main/kotlin/Generation/Generator.kt2
-rw-r--r--core/src/main/kotlin/Generation/configurationImpl.kt11
-rw-r--r--core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt32
-rw-r--r--core/src/main/kotlin/Kotlin/DocumentationBuilder.kt21
-rw-r--r--core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt129
-rw-r--r--core/src/main/kotlin/Model/DocumentationNode.kt1
-rw-r--r--core/src/main/kotlin/Utilities/DokkaModules.kt2
-rw-r--r--core/src/main/kotlin/javadoc/dokka-adapters.kt9
-rw-r--r--core/src/test/kotlin/model/ClassTest.kt8
-rw-r--r--core/src/test/kotlin/model/FunctionTest.kt28
-rw-r--r--integration/src/main/kotlin/org/jetbrains/dokka/configuration.kt7
-rw-r--r--runners/cli/src/main/kotlin/cli/main.kt28
19 files changed, 316 insertions, 65 deletions
diff --git a/core/src/main/kotlin/Formats/FormatDescriptor.kt b/core/src/main/kotlin/Formats/FormatDescriptor.kt
index 7cc50acb..fc925f40 100644
--- a/core/src/main/kotlin/Formats/FormatDescriptor.kt
+++ b/core/src/main/kotlin/Formats/FormatDescriptor.kt
@@ -11,4 +11,5 @@ interface FormatDescriptor {
val packageDocumentationBuilderClass: KClass<out PackageDocumentationBuilder>
val javaDocumentationBuilderClass: KClass<out JavaDocumentationBuilder>
val sampleProcessingService: KClass<out SampleProcessingService>
+ val packageListServiceClass: KClass<out PackageListService>?
}
diff --git a/core/src/main/kotlin/Formats/FormatService.kt b/core/src/main/kotlin/Formats/FormatService.kt
index e281f2fc..63f25008 100644
--- a/core/src/main/kotlin/Formats/FormatService.kt
+++ b/core/src/main/kotlin/Formats/FormatService.kt
@@ -11,6 +11,10 @@ interface FormatService {
/** Returns extension for output files */
val extension: String
+ /** extension which will be used for internal and external linking */
+ val linkExtension: String
+ get() = extension
+
fun createOutputBuilder(to: StringBuilder, location: Location): FormattedOutputBuilder
fun enumerateSupportFiles(callback: (resource: String, targetPath: String) -> Unit) {
diff --git a/core/src/main/kotlin/Formats/PackageListService.kt b/core/src/main/kotlin/Formats/PackageListService.kt
new file mode 100644
index 00000000..360dbb46
--- /dev/null
+++ b/core/src/main/kotlin/Formats/PackageListService.kt
@@ -0,0 +1,63 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+
+
+interface PackageListService {
+ fun formatPackageList(module: DocumentationModule): String
+}
+
+class DefaultPackageListService @Inject constructor(locationService: FileLocationService,
+ val formatService: FormatService) : PackageListService {
+
+ val locationService: FileLocationService = locationService.withExtension(formatService.linkExtension)
+
+ override fun formatPackageList(module: DocumentationModule): String {
+ val packages = mutableSetOf<String>()
+ val nonStandardLocations = mutableMapOf<String, String>()
+
+ fun visit(node: DocumentationNode, relocated: Boolean = false) {
+ val nodeKind = node.kind
+
+ when (nodeKind) {
+ NodeKind.Package -> {
+ packages.add(node.qualifiedName())
+ node.members.forEach { visit(it) }
+ }
+ NodeKind.Signature -> {
+ if (relocated)
+ nonStandardLocations[node.name] = locationService.relativePathToLocation(module, node.owner!!)
+ }
+ NodeKind.ExternalClass -> {
+ node.members.forEach { visit(it, relocated = true) }
+ }
+ NodeKind.GroupNode -> {
+ //only children of top-level GN records interesting for us, since link to top-level ones should point to GN
+ node.members.forEach { it.members.forEach { visit(it, relocated = true) } }
+ //record signature of GN as signature of type alias and class merged to GN, so link to it should point to GN
+ node.detailOrNull(NodeKind.Signature)?.let { visit(it, relocated = true) }
+ }
+ else -> {
+ if (nodeKind in NodeKind.classLike || nodeKind in NodeKind.memberLike) {
+ node.details(NodeKind.Signature).forEach { visit(it, relocated) }
+ node.members.forEach { visit(it, relocated) }
+ }
+ }
+ }
+ }
+
+ module.members.forEach { visit(it) }
+
+ return buildString {
+ appendln("\$dokka.linkExtension:${formatService.linkExtension}")
+
+ nonStandardLocations.map { (signature, location) -> "\$dokka.location:$signature\u001f$location" }
+ .joinTo(this, separator = "\n", postfix = "\n")
+
+ packages.joinTo(this, separator = "\n", postfix = "\n")
+ }
+
+ }
+
+}
+
diff --git a/core/src/main/kotlin/Formats/StandardFormats.kt b/core/src/main/kotlin/Formats/StandardFormats.kt
index c67386af..fad65ff1 100644
--- a/core/src/main/kotlin/Formats/StandardFormats.kt
+++ b/core/src/main/kotlin/Formats/StandardFormats.kt
@@ -13,6 +13,7 @@ abstract class KotlinFormatDescriptorBase : FormatDescriptor {
override val generatorServiceClass = FileGenerator::class
override val outlineServiceClass: KClass<out OutlineFormatService>? = null
override val sampleProcessingService: KClass<out SampleProcessingService> = DefaultSampleProcessingService::class
+ override val packageListServiceClass: KClass<out PackageListService>? = DefaultPackageListService::class
}
class HtmlFormatDescriptor : KotlinFormatDescriptorBase() {
@@ -27,6 +28,7 @@ class HtmlAsJavaFormatDescriptor : FormatDescriptor {
override val packageDocumentationBuilderClass = KotlinAsJavaDocumentationBuilder::class
override val javaDocumentationBuilderClass = JavaPsiDocumentationBuilder::class
override val sampleProcessingService: KClass<out SampleProcessingService> = DefaultSampleProcessingService::class
+ override val packageListServiceClass: KClass<out PackageListService>? = DefaultPackageListService::class
}
class KotlinWebsiteFormatDescriptor : KotlinFormatDescriptorBase() {
diff --git a/core/src/main/kotlin/Formats/StructuredFormatService.kt b/core/src/main/kotlin/Formats/StructuredFormatService.kt
index b29cf492..a8b000b7 100644
--- a/core/src/main/kotlin/Formats/StructuredFormatService.kt
+++ b/core/src/main/kotlin/Formats/StructuredFormatService.kt
@@ -290,16 +290,16 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
for ((name, items) in breakdownByName) {
if (!noHeader)
appendHeader { appendText(name) }
- appendDocumentation(items)
+ appendDocumentation(items, singleNode != null)
}
}
}
- private fun appendDocumentation(overloads: Iterable<DocumentationNode>) {
+ private fun appendDocumentation(overloads: Iterable<DocumentationNode>, isSingleNode: Boolean) {
val breakdownBySummary = overloads.groupByTo(LinkedHashMap()) { node -> node.content }
if (breakdownBySummary.size == 1) {
- formatOverloadGroup(breakdownBySummary.values.single())
+ formatOverloadGroup(breakdownBySummary.values.single(), isSingleNode)
} else {
for ((_, items) in breakdownBySummary) {
@@ -311,12 +311,13 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
}
}
- private fun formatOverloadGroup(items: List<DocumentationNode>) {
+ private fun formatOverloadGroup(items: List<DocumentationNode>, isSingleNode: Boolean = false) {
for ((index, item) in items.withIndex()) {
if (index > 0) appendLine()
val rendered = languageService.render(item)
item.detailOrNull(NodeKind.Signature)?.let {
- appendAnchor(it.name)
+ if (item.kind !in NodeKind.classLike || !isSingleNode)
+ appendAnchor(it.name)
}
appendAsSignature(rendered) {
appendCode { appendContent(rendered) }
@@ -439,7 +440,6 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
inner class GroupNodePageBuilder(val node: DocumentationNode) : PageBuilder(listOf(node)) {
override fun build() {
-
val breakdownByLocation = node.path.filterNot { it.name.isEmpty() }.map { link(node, it) }
appendBreadcrumbs(breakdownByLocation)
@@ -658,6 +658,6 @@ abstract class StructuredOutputBuilder(val to: StringBuilder,
abstract class StructuredFormatService(locationService: LocationService,
val languageService: LanguageService,
override val extension: String,
- linkExtension: String = extension) : FormatService {
+ override final val linkExtension: String = extension) : FormatService {
val locationService: LocationService = locationService.withExtension(linkExtension)
}
diff --git a/core/src/main/kotlin/Generation/DokkaGenerator.kt b/core/src/main/kotlin/Generation/DokkaGenerator.kt
index 3350ab1d..c0b631e4 100644
--- a/core/src/main/kotlin/Generation/DokkaGenerator.kt
+++ b/core/src/main/kotlin/Generation/DokkaGenerator.kt
@@ -109,7 +109,7 @@ class DokkaGenerator(val logger: DokkaLogger,
}
}
-class DokkaMessageCollector(val logger: DokkaLogger): MessageCollector {
+class DokkaMessageCollector(val logger: DokkaLogger) : MessageCollector {
override fun clear() {
seenErrors = false
}
diff --git a/core/src/main/kotlin/Generation/FileGenerator.kt b/core/src/main/kotlin/Generation/FileGenerator.kt
index 86ebd6b7..e055c537 100644
--- a/core/src/main/kotlin/Generation/FileGenerator.kt
+++ b/core/src/main/kotlin/Generation/FileGenerator.kt
@@ -10,6 +10,8 @@ class FileGenerator @Inject constructor(val locationService: FileLocationService
@set:Inject(optional = true) var outlineService: OutlineFormatService? = null
@set:Inject(optional = true) lateinit var formatService: FormatService
+ @set:Inject(optional = true) lateinit var options: DocumentationOptions
+ @set:Inject(optional = true) var packageListService: PackageListService? = null
override fun buildPages(nodes: Iterable<DocumentationNode>) {
val specificLocationService = locationService.withExtension(formatService.extension)
@@ -50,6 +52,21 @@ class FileGenerator @Inject constructor(val locationService: FileLocationService
}
}
}
+
+ override fun buildPackageList(nodes: Iterable<DocumentationNode>) {
+ if (packageListService == null) return
+
+ for (module in nodes) {
+
+ val moduleRoot = locationService.location(module).file.parentFile
+ val packageListFile = File(moduleRoot, "package-list")
+
+ packageListFile.writeText("\$dokka.format:${options.outputFormat}\n" +
+ packageListService!!.formatPackageList(module as DocumentationModule))
+ }
+
+ }
+
}
private fun File.mkdirsOrFail() {
diff --git a/core/src/main/kotlin/Generation/Generator.kt b/core/src/main/kotlin/Generation/Generator.kt
index 83ddd04f..76a5f350 100644
--- a/core/src/main/kotlin/Generation/Generator.kt
+++ b/core/src/main/kotlin/Generation/Generator.kt
@@ -4,12 +4,14 @@ interface Generator {
fun buildPages(nodes: Iterable<DocumentationNode>)
fun buildOutlines(nodes: Iterable<DocumentationNode>)
fun buildSupportFiles()
+ fun buildPackageList(nodes: Iterable<DocumentationNode>)
}
fun Generator.buildAll(nodes: Iterable<DocumentationNode>) {
buildPages(nodes)
buildOutlines(nodes)
buildSupportFiles()
+ buildPackageList(nodes)
}
fun Generator.buildPage(node: DocumentationNode): Unit = buildPages(listOf(node))
diff --git a/core/src/main/kotlin/Generation/configurationImpl.kt b/core/src/main/kotlin/Generation/configurationImpl.kt
index bb2f6d12..425b016b 100644
--- a/core/src/main/kotlin/Generation/configurationImpl.kt
+++ b/core/src/main/kotlin/Generation/configurationImpl.kt
@@ -3,6 +3,7 @@ package org.jetbrains.dokka
import org.jetbrains.dokka.DokkaConfiguration.SourceLinkDefinition
import org.jetbrains.dokka.DokkaConfiguration.SourceRoot
import java.io.File
+import java.net.URL
data class SourceLinkDefinitionImpl(override val path: String,
@@ -34,6 +35,16 @@ data class PackageOptionsImpl(override val prefix: String,
override val reportUndocumented: Boolean = true,
override val skipDeprecated: Boolean = false) : DokkaConfiguration.PackageOptions
+data class ExternalDocumentationLinkImpl(override val url: URL,
+ override val packageListUrl: URL) : DokkaConfiguration.ExternalDocumentationLink {
+ constructor(root: URL) : this(root, URL(root, "package-list"))
+
+ constructor(root: String) : this(URL(root))
+
+ constructor(root: String, packageListUrl: String) : this(URL(root), URL(packageListUrl))
+
+}
+
data class DokkaConfigurationImpl(override val moduleName: String,
override val classpath: List<String>,
override val sourceRoots: List<SourceRootImpl>,
diff --git a/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt
index 71b636bf..2ff69b4c 100644
--- a/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt
+++ b/core/src/main/kotlin/Kotlin/DeclarationLinkResolver.kt
@@ -14,7 +14,9 @@ class DeclarationLinkResolver
@Inject constructor(val resolutionFacade: DokkaResolutionFacade,
val refGraph: NodeReferenceGraph,
val logger: DokkaLogger,
- val options: DocumentationOptions) {
+ val options: DocumentationOptions,
+ val externalDocumentationLinkResolver: ExternalDocumentationLinkResolver) {
+
fun resolveContentLink(fromDescriptor: DeclarationDescriptor, href: String): ContentBlock {
val symbol = try {
val symbols = resolveKDocLink(resolutionFacade.resolveSession.bindingContext,
@@ -27,9 +29,9 @@ class DeclarationLinkResolver
// don't include unresolved links in generated doc
// assume that if an href doesn't contain '/', it's not an attempt to reference an external file
if (symbol != null) {
- val jdkHref = buildJdkLink(symbol)
- if (jdkHref != null) {
- return ContentExternalLink(jdkHref)
+ val externalHref = externalDocumentationLinkResolver.buildExternalDocumentationLink(symbol)
+ if (externalHref != null) {
+ return ContentExternalLink(externalHref)
}
return ContentNodeLazyLink(href, { -> refGraph.lookupOrWarn(symbol.signature(), logger) })
}
@@ -51,26 +53,4 @@ class DeclarationLinkResolver
return symbol
}
- fun buildJdkLink(symbol: DeclarationDescriptor): String? {
- if (symbol is JavaClassDescriptor) {
- val fqName = DescriptorUtils.getFqName(symbol)
- if (fqName.startsWith(Name.identifier("java")) || fqName.startsWith(Name.identifier("javax"))) {
- return javadocRoot + fqName.asString().replace(".", "/") + ".html"
- }
- }
- else if (symbol is JavaMethodDescriptor) {
- val containingClass = symbol.containingDeclaration as? JavaClassDescriptor ?: return null
- val containingClassLink = buildJdkLink(containingClass)
- if (containingClassLink != null) {
- val psi = symbol.sourcePsi() as? PsiMethod
- if (psi != null) {
- val params = psi.parameterList.parameters.joinToString { it.type.canonicalText }
- return containingClassLink + "#" + symbol.name + "(" + params + ")"
- }
- }
- }
- return null
- }
-
- private val javadocRoot = "http://docs.oracle.com/javase/${options.jdkVersion}/docs/api/"
}
diff --git a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
index 4dae3a54..6a18bf26 100644
--- a/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
+++ b/core/src/main/kotlin/Kotlin/DocumentationBuilder.kt
@@ -3,8 +3,7 @@ package org.jetbrains.dokka
import com.google.inject.Inject
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.PsiJavaFile
-import org.jetbrains.dokka.DokkaConfiguration.PackageOptions
-import org.jetbrains.dokka.DokkaConfiguration.SourceLinkDefinition
+import org.jetbrains.dokka.DokkaConfiguration.*
import org.jetbrains.dokka.Kotlin.DescriptorDocumentationParser
import org.jetbrains.kotlin.builtins.KotlinBuiltIns
import org.jetbrains.kotlin.descriptors.*
@@ -39,13 +38,13 @@ class DocumentationOptions(val outputDir: String,
reportUndocumented: Boolean = true,
val skipEmptyPackages: Boolean = true,
skipDeprecated: Boolean = false,
- val jdkVersion: Int = 6,
+ jdkVersion: Int = 6,
val generateIndexPages: Boolean = true,
val sourceLinks: List<SourceLinkDefinition> = emptyList(),
val impliedPlatforms: List<String> = emptyList(),
// Sorted by pattern length
- perPackageOptions: List<PackageOptions> = emptyList()) {
-
+ perPackageOptions: List<PackageOptions> = emptyList(),
+ externalDocumentationLinks: List<ExternalDocumentationLink> = emptyList()) {
init {
if (perPackageOptions.any { it.prefix == "" })
throw IllegalArgumentException("Please do not register packageOptions with all match pattern, use global settings instead")
@@ -56,6 +55,8 @@ class DocumentationOptions(val outputDir: String,
fun effectivePackageOptions(pack: String): PackageOptions = perPackageOptions.firstOrNull { pack.startsWith(it.prefix + ".") } ?: rootPackageOptions
fun effectivePackageOptions(pack: FqName): PackageOptions = effectivePackageOptions(pack.asString())
+
+ val externalDocumentationLinks = listOf(ExternalDocumentationLinkImpl("http://docs.oracle.com/javase/$jdkVersion/docs/api/")) + externalDocumentationLinks
}
private fun isExtensionForExternalClass(extensionFunctionDescriptor: DeclarationDescriptor,
@@ -114,6 +115,7 @@ class DocumentationBuilder
fun <T> nodeForDescriptor(descriptor: T, kind: NodeKind): DocumentationNode where T : DeclarationDescriptor, T : Named {
val (doc, callback) = descriptorDocumentationParser.parseDocumentationAndDetails(descriptor, kind == NodeKind.Parameter)
val node = DocumentationNode(descriptor.name.asString(), doc, kind).withModifiers(descriptor)
+ node.appendSignature(descriptor)
callback(node)
return node
}
@@ -207,9 +209,9 @@ class DocumentationBuilder
node.appendTextNode("?", NodeKind.NullabilityModifier)
}
if (classifierDescriptor != null) {
- val jdkLink = linkResolver.buildJdkLink(classifierDescriptor)
- if (jdkLink != null) {
- node.append(DocumentationNode(jdkLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link)
+ val externalLink = linkResolver.externalDocumentationLinkResolver.buildExternalDocumentationLink(classifierDescriptor)
+ if (externalLink != null) {
+ node.append(DocumentationNode(externalLink, Content.Empty, NodeKind.ExternalLink), RefKind.Link)
} else {
link(node, classifierDescriptor,
if (classifierDescriptor.isBoringBuiltinClass()) RefKind.HiddenLink else RefKind.Link)
@@ -598,7 +600,6 @@ class DocumentationBuilder
node.appendAnnotations(this)
node.appendModifiers(this)
node.appendSourceLink(source)
- node.appendSignature(this)
node.appendDefaultPlatforms(this)
overriddenDescriptors.forEach {
@@ -628,7 +629,6 @@ class DocumentationBuilder
node.appendAnnotations(this)
node.appendModifiers(this)
node.appendSourceLink(source)
- node.appendSignature(this)
if (isVar) {
node.appendTextNode("var", NodeKind.Modifier)
}
@@ -680,7 +680,6 @@ class DocumentationBuilder
}
node.appendAnnotations(this)
node.appendModifiers(this)
- node.appendSignature(this)
if (varargElementType != null && node.details(NodeKind.Modifier).none { it.name == "vararg" }) {
node.appendTextNode("vararg", NodeKind.Modifier)
}
diff --git a/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
new file mode 100644
index 00000000..8113f95d
--- /dev/null
+++ b/core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt
@@ -0,0 +1,129 @@
+package org.jetbrains.dokka
+
+import com.google.inject.Inject
+import com.intellij.psi.PsiMethod
+import org.jetbrains.kotlin.descriptors.*
+import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor
+import org.jetbrains.kotlin.load.java.descriptors.JavaMethodDescriptor
+import org.jetbrains.kotlin.name.FqName
+import org.jetbrains.kotlin.resolve.DescriptorUtils
+import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe
+import org.jetbrains.kotlin.resolve.descriptorUtil.parents
+import java.net.URL
+
+
+class ExternalDocumentationLinkResolver @Inject constructor(
+ val options: DocumentationOptions
+) {
+
+ val packageFqNameToLocation = mutableMapOf<FqName, ExternalDocumentationRoot>()
+ val formats = mutableMapOf<String, InboundExternalLinkResolutionService>()
+
+ class ExternalDocumentationRoot(val rootUrl: URL, val resolver: InboundExternalLinkResolutionService, val locations: Map<String, String>)
+
+ fun loadPackageLists() {
+ options.externalDocumentationLinks.forEach { link ->
+ val (params, packages) =
+ link.packageListUrl
+ .openStream()
+ .bufferedReader()
+ .useLines { lines -> lines.partition { it.startsWith(DOKKA_PARAM_PREFIX) } }
+
+ val paramsMap = params.asSequence()
+ .map { it.removePrefix(DOKKA_PARAM_PREFIX).split(":", limit = 2) }
+ .groupBy({ (key, _) -> key }, { (_, value) -> value })
+
+ val format = paramsMap["format"]?.singleOrNull() ?: "javadoc"
+
+ val locations = paramsMap["location"].orEmpty()
+ .map { it.split("\u001f", limit = 2) }
+ .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 ${link.packageListUrl}")
+ InboundExternalLinkResolutionService.Dokka(linkExtension)
+ }
+
+ val rootInfo = ExternalDocumentationRoot(link.url, resolver, locations)
+
+ packages.map { FqName(it) }.forEach { packageFqNameToLocation[it] = rootInfo }
+ }
+ }
+
+ init {
+ loadPackageLists()
+ }
+
+ fun buildExternalDocumentationLink(symbol: DeclarationDescriptor): String? {
+ val packageFqName: FqName =
+ when (symbol) {
+ is DeclarationDescriptorNonRoot -> symbol.parents.firstOrNull { it is PackageFragmentDescriptor }?.fqNameSafe ?: return null
+ is PackageFragmentDescriptor -> symbol.fqName
+ else -> return null
+ }
+
+ val externalLocation = packageFqNameToLocation[packageFqName] ?: return null
+
+ val path = externalLocation.locations[symbol.signature()] ?:
+ externalLocation.resolver.getPath(symbol) ?: return null
+
+ return URL(externalLocation.rootUrl, path).toExternalForm()
+ }
+
+ companion object {
+ const val DOKKA_PARAM_PREFIX = "\$dokka."
+ }
+}
+
+
+interface InboundExternalLinkResolutionService {
+ fun getPath(symbol: DeclarationDescriptor): String?
+
+ class Javadoc : InboundExternalLinkResolutionService {
+ override fun getPath(symbol: DeclarationDescriptor): String? {
+ if (symbol is JavaClassDescriptor) {
+ return DescriptorUtils.getFqName(symbol).asString().replace(".", "/") + ".html"
+ } else if (symbol is JavaMethodDescriptor) {
+ val containingClass = symbol.containingDeclaration as? JavaClassDescriptor ?: return null
+ val containingClassLink = getPath(containingClass)
+ if (containingClassLink != null) {
+ val psi = symbol.sourcePsi() as? PsiMethod
+ if (psi != null) {
+ val params = psi.parameterList.parameters.joinToString { it.type.canonicalText }
+ return containingClassLink + "#" + symbol.name + "(" + params + ")"
+ }
+ }
+ }
+ // TODO Kotlin javadoc
+ return null
+ }
+ }
+
+ class Dokka(val extension: String) : InboundExternalLinkResolutionService {
+ override fun getPath(symbol: DeclarationDescriptor): String? {
+ val leafElement = when (symbol) {
+ is CallableDescriptor, is TypeAliasDescriptor -> true
+ else -> false
+ }
+ val path = getPathWithoutExtension(symbol)
+ if (leafElement) return "$path.$extension"
+ else return "$path/index.$extension"
+ }
+
+ fun getPathWithoutExtension(symbol: DeclarationDescriptor): String {
+ if (symbol.containingDeclaration == null)
+ return identifierToFilename(symbol.name.asString())
+ else if (symbol is PackageFragmentDescriptor) {
+ return symbol.fqName.asString()
+ } else {
+ return getPathWithoutExtension(symbol.containingDeclaration!!) + '/' + identifierToFilename(symbol.name.asString())
+ }
+ }
+
+ }
+}
+
diff --git a/core/src/main/kotlin/Model/DocumentationNode.kt b/core/src/main/kotlin/Model/DocumentationNode.kt
index c38a6a9f..da85cabf 100644
--- a/core/src/main/kotlin/Model/DocumentationNode.kt
+++ b/core/src/main/kotlin/Model/DocumentationNode.kt
@@ -62,6 +62,7 @@ enum class NodeKind {
companion object {
val classLike = setOf(Class, Interface, Enum, AnnotationClass, Exception, Object, TypeAlias)
+ val memberLike = setOf(Function, Property, Constructor, CompanionObjectFunction, CompanionObjectProperty, EnumItem)
}
}
diff --git a/core/src/main/kotlin/Utilities/DokkaModules.kt b/core/src/main/kotlin/Utilities/DokkaModules.kt
index e213c0fc..28c5dc45 100644
--- a/core/src/main/kotlin/Utilities/DokkaModules.kt
+++ b/core/src/main/kotlin/Utilities/DokkaModules.kt
@@ -79,6 +79,8 @@ class DokkaOutputModule(val options: DocumentationOptions,
binder.bind<Generator>().to(descriptor.generatorServiceClass.java)
+ descriptor.packageListServiceClass?.let { binder.bind<PackageListService>().to(it.java) }
+
binder.bind<DocumentationOptions>().toInstance(options)
binder.bind<DokkaLogger>().toInstance(logger)
binder.bind(StringListType).annotatedWith(Names.named(impliedPlatformsName)).toInstance(options.impliedPlatforms)
diff --git a/core/src/main/kotlin/javadoc/dokka-adapters.kt b/core/src/main/kotlin/javadoc/dokka-adapters.kt
index 920eca2d..9555aeb9 100644
--- a/core/src/main/kotlin/javadoc/dokka-adapters.kt
+++ b/core/src/main/kotlin/javadoc/dokka-adapters.kt
@@ -5,8 +5,10 @@ import com.sun.tools.doclets.formats.html.HtmlDoclet
import org.jetbrains.dokka.*
import org.jetbrains.dokka.Formats.FormatDescriptor
import org.jetbrains.dokka.Samples.DefaultSampleProcessingService
+import kotlin.reflect.KClass
+
+class JavadocGenerator @Inject constructor(val options: DocumentationOptions, val logger: DokkaLogger) : Generator {
-class JavadocGenerator @Inject constructor (val options: DocumentationOptions, val logger: DokkaLogger) : Generator {
override fun buildPages(nodes: Iterable<DocumentationNode>) {
val module = nodes.single() as DocumentationModule
@@ -20,6 +22,10 @@ class JavadocGenerator @Inject constructor (val options: DocumentationOptions, v
override fun buildSupportFiles() {
}
+
+ override fun buildPackageList(nodes: Iterable<DocumentationNode>) {
+ // handled by javadoc itself
+ }
}
class JavadocFormatDescriptor : FormatDescriptor {
@@ -29,4 +35,5 @@ class JavadocFormatDescriptor : FormatDescriptor {
override val packageDocumentationBuilderClass = KotlinAsJavaDocumentationBuilder::class
override val javaDocumentationBuilderClass = JavaPsiDocumentationBuilder::class
override val sampleProcessingService = DefaultSampleProcessingService::class
+ override val packageListServiceClass: KClass<out PackageListService>? = null
}
diff --git a/core/src/test/kotlin/model/ClassTest.kt b/core/src/test/kotlin/model/ClassTest.kt
index f8baf251..ea586041 100644
--- a/core/src/test/kotlin/model/ClassTest.kt
+++ b/core/src/test/kotlin/model/ClassTest.kt
@@ -45,9 +45,9 @@ class ClassTest {
assertEquals("<init>", name)
assertEquals(Content.Empty, content)
assertEquals(NodeKind.Constructor, kind)
- assertEquals(2, details.count())
+ assertEquals(3, details.count())
assertEquals("public", details.elementAt(0).name)
- with(details.elementAt(1)) {
+ with(details.elementAt(2)) {
assertEquals("name", name)
assertEquals(NodeKind.Parameter, kind)
assertEquals(Content.Empty, content)
@@ -75,7 +75,7 @@ class ClassTest {
assertEquals("<init>", name)
assertEquals(Content.Empty, content)
assertEquals(NodeKind.Constructor, kind)
- assertEquals(1, details.count())
+ assertEquals(2, details.count())
assertEquals("public", details.elementAt(0).name)
assertTrue(links.none())
assertTrue(members.none())
@@ -105,7 +105,7 @@ class ClassTest {
assertEquals("<init>", name)
assertEquals(Content.Empty, content)
assertEquals(NodeKind.Constructor, kind)
- assertEquals(1, details.count())
+ assertEquals(2, details.count())
assertEquals("public", details.elementAt(0).name)
assertTrue(members.none())
assertTrue(links.none())
diff --git a/core/src/test/kotlin/model/FunctionTest.kt b/core/src/test/kotlin/model/FunctionTest.kt
index ddd33941..065decef 100644
--- a/core/src/test/kotlin/model/FunctionTest.kt
+++ b/core/src/test/kotlin/model/FunctionTest.kt
@@ -32,7 +32,7 @@ class FunctionTest {
assertEquals("Function with receiver", content.summary.toTestString())
assertEquals("public", details.elementAt(0).name)
assertEquals("final", details.elementAt(1).name)
- with(details.elementAt(2)) {
+ with(details.elementAt(3)) {
assertEquals("<this>", name)
assertEquals(NodeKind.Receiver, kind)
assertEquals(Content.Empty, content)
@@ -40,7 +40,7 @@ class FunctionTest {
assertTrue(members.none())
assertTrue(links.none())
}
- assertEquals("Unit", details.elementAt(3).name)
+ assertEquals("Unit", details.elementAt(4).name)
assertTrue(members.none())
assertTrue(links.none())
}
@@ -61,7 +61,7 @@ class FunctionTest {
assertEquals("private", details.elementAt(0).name)
assertEquals("final", details.elementAt(1).name)
- with(details.elementAt(2)) {
+ with(details.elementAt(3)) {
assertEquals("T", name)
assertEquals(NodeKind.TypeParameter, kind)
assertEquals(Content.Empty, content)
@@ -69,7 +69,7 @@ class FunctionTest {
assertTrue(members.none())
assertTrue(links.none())
}
- assertEquals("Unit", details.elementAt(3).name)
+ assertEquals("Unit", details.elementAt(4).name)
assertTrue(members.none())
assertTrue(links.none())
@@ -85,7 +85,7 @@ class FunctionTest {
assertEquals("public", details.elementAt(0).name)
assertEquals("final", details.elementAt(1).name)
- with(details.elementAt(2)) {
+ with(details.elementAt(3)) {
assertEquals("T", name)
assertEquals(NodeKind.TypeParameter, kind)
assertEquals(Content.Empty, content)
@@ -100,14 +100,14 @@ class FunctionTest {
assertTrue(members.none())
assertTrue(links.none())
}
- with(details.elementAt(3)) {
+ with(details.elementAt(4)) {
assertEquals("R", name)
assertEquals(NodeKind.TypeParameter, kind)
assertEquals(Content.Empty, content)
assertTrue(members.none())
assertTrue(links.none())
}
- assertEquals("Unit", details.elementAt(4).name)
+ assertEquals("Unit", details.elementAt(5).name)
assertTrue(members.none())
assertTrue(links.none())
@@ -126,7 +126,7 @@ Documentation""", content.description.toTestString())
assertEquals("public", details.elementAt(0).name)
assertEquals("final", details.elementAt(1).name)
- with(details.elementAt(2)) {
+ with(details.elementAt(3)) {
assertEquals("x", name)
assertEquals(NodeKind.Parameter, kind)
assertEquals("parameter", content.summary.toTestString())
@@ -134,7 +134,7 @@ Documentation""", content.description.toTestString())
assertTrue(members.none())
assertTrue(links.none())
}
- assertEquals("Unit", details.elementAt(3).name)
+ assertEquals("Unit", details.elementAt(4).name)
assertTrue(members.none())
assertTrue(links.none())
}
@@ -167,8 +167,8 @@ Documentation""", content.description.toTestString())
@Test fun functionWithAnnotatedParam() {
verifyModel("testdata/functions/functionWithAnnotatedParam.kt") { model ->
- with(model.members.single().members.single { it.name == "function"} ) {
- with(details.elementAt(2)) {
+ with(model.members.single().members.single { it.name == "function" }) {
+ with(details(NodeKind.Parameter).first()) {
assertEquals(1, annotations.count())
with(annotations[0]) {
assertEquals("Fancy", name)
@@ -182,7 +182,7 @@ Documentation""", content.description.toTestString())
@Test fun functionWithNoinlineParam() {
verifyPackageMember("testdata/functions/functionWithNoinlineParam.kt") { func ->
- with(func.details.elementAt(2)) {
+ with(func.details(NodeKind.Parameter).first()) {
val modifiers = details(NodeKind.Modifier).map { it.name }
assertTrue("noinline" in modifiers)
}
@@ -191,7 +191,7 @@ Documentation""", content.description.toTestString())
@Test fun annotatedFunctionWithAnnotationParameters() {
verifyModel("testdata/functions/annotatedFunctionWithAnnotationParameters.kt") { model ->
- with(model.members.single().members.single { it.name == "f"}) {
+ with(model.members.single().members.single { it.name == "f" }) {
assertEquals(1, annotations.count())
with(annotations[0]) {
assertEquals("Fancy", name)
@@ -214,7 +214,7 @@ Documentation""", content.description.toTestString())
@Test fun functionWithDefaultParameter() {
verifyModel("testdata/functions/functionWithDefaultParameter.kt") { model ->
with(model.members.single().members.single()) {
- with(details.elementAt(2)) {
+ with(details.elementAt(3)) {
val value = details(NodeKind.Value)
assertEquals(1, value.count())
with(value[0]) {
diff --git a/integration/src/main/kotlin/org/jetbrains/dokka/configuration.kt b/integration/src/main/kotlin/org/jetbrains/dokka/configuration.kt
index 5d10f6d2..7f16b1a5 100644
--- a/integration/src/main/kotlin/org/jetbrains/dokka/configuration.kt
+++ b/integration/src/main/kotlin/org/jetbrains/dokka/configuration.kt
@@ -1,5 +1,7 @@
package org.jetbrains.dokka
+import java.net.URL
+
interface DokkaConfiguration {
val moduleName: String
@@ -37,6 +39,11 @@ interface DokkaConfiguration {
val reportUndocumented: Boolean
val skipDeprecated: Boolean
}
+
+ interface ExternalDocumentationLink {
+ val url: URL
+ val packageListUrl: URL
+ }
}
data class SerializeOnlyDokkaConfiguration(override val moduleName: String,
diff --git a/runners/cli/src/main/kotlin/cli/main.kt b/runners/cli/src/main/kotlin/cli/main.kt
index 0d1ff76c..f8a01c38 100644
--- a/runners/cli/src/main/kotlin/cli/main.kt
+++ b/runners/cli/src/main/kotlin/cli/main.kt
@@ -5,6 +5,8 @@ import org.jetbrains.kotlin.cli.common.arguments.ValueDescription
import org.jetbrains.kotlin.cli.common.parser.com.sampullara.cli.Args
import org.jetbrains.kotlin.cli.common.parser.com.sampullara.cli.Argument
import java.io.File
+import java.net.MalformedURLException
+import java.net.URL
import java.net.URLClassLoader
class DokkaArguments {
@@ -51,11 +53,33 @@ class DokkaArguments {
@set:Argument(value = "packageOptions", description = "List of package options in format \"prefix,-deprecated,-privateApi,+warnUndocumented;...\" ")
var packageOptions: String = ""
+
+ @set:Argument(value = "links", description = "")
+ var links: String = ""
}
object MainKt {
+ fun parseLinks(links: String): List<DokkaConfiguration.ExternalDocumentationLink> {
+ val (parsedLinks, parsedOfflineLinks) = links.split("^^")
+ .map { it.split("^").map { it.trim() }.filter { it.isNotBlank() } }
+ .filter { it.isNotEmpty() }
+ .partition { it.size == 1 }
+
+ return parsedLinks.map { (root) -> ExternalDocumentationLinkImpl(root) } +
+ parsedOfflineLinks.map { (root, packageList) ->
+ val rootUrl = URL(root)
+ val packageListUrl =
+ try {
+ URL(packageList)
+ } catch (ex: MalformedURLException) {
+ File(packageList).toURI().toURL()
+ }
+ ExternalDocumentationLinkImpl(rootUrl, packageListUrl)
+ }
+ }
+
@JvmStatic
fun entry(args: Array<String>) {
val arguments = DokkaArguments()
@@ -81,7 +105,9 @@ object MainKt {
skipDeprecated = arguments.nodeprecated,
sourceLinks = sourceLinks,
impliedPlatforms = arguments.impliedPlatforms.split(','),
- perPackageOptions = parsePerPackageOptions(arguments.packageOptions)
+ perPackageOptions = parsePerPackageOptions(arguments.packageOptions),
+ jdkVersion = arguments.jdkVersion,
+ externalDocumentationLinks = parseLinks(arguments.links)
)
val generator = DokkaGenerator(