From a86c859eba6154524f3b42461aad6b45f26e3650 Mon Sep 17 00:00:00 2001 From: Simon Ogorodnik Date: Wed, 3 May 2017 13:45:30 +0300 Subject: Support linking of external documentation Introduce PackageListService #KT-16309 fixed --- .../Kotlin/ExternalDocumentationLinkResolver.kt | 129 +++++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt (limited to 'core/src/main/kotlin/Kotlin/ExternalDocumentationLinkResolver.kt') 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() + val formats = mutableMapOf() + + class ExternalDocumentationRoot(val rootUrl: URL, val resolver: InboundExternalLinkResolutionService, val locations: Map) + + 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()) + } + } + + } +} + -- cgit