diff options
author | Simon Ogorodnik <Simon.Ogorodnik@jetbrains.com> | 2017-05-03 13:45:30 +0300 |
---|---|---|
committer | Simon Ogorodnik <Simon.Ogorodnik@jetbrains.com> | 2017-05-11 19:52:40 +0300 |
commit | a86c859eba6154524f3b42461aad6b45f26e3650 (patch) | |
tree | 6772882331daf29c8d19e4a3ed77ef938d45b1ac /core/src/main/kotlin/Kotlin | |
parent | 022a6a6bc9a1d61f190715dec56c3bef31887388 (diff) | |
download | dokka-a86c859eba6154524f3b42461aad6b45f26e3650.tar.gz dokka-a86c859eba6154524f3b42461aad6b45f26e3650.tar.bz2 dokka-a86c859eba6154524f3b42461aad6b45f26e3650.zip |
Support linking of external documentation
Introduce PackageListService
#KT-16309 fixed
Diffstat (limited to 'core/src/main/kotlin/Kotlin')
3 files changed, 145 insertions, 37 deletions
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()) + } + } + + } +} + |