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 | |
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
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( |