From 3faa3f2d1c7ca33ad8d98bc6c562e4fe6977225f Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Tue, 27 Oct 2015 17:12:31 +0100 Subject: summarize signatures for overloaded stdlib functions --- src/Formats/HtmlFormatService.kt | 2 +- src/Formats/KotlinWebsiteFormatService.kt | 1 + src/Formats/StructuredFormatService.kt | 51 ++++++++++++-------- src/Kotlin/DocumentationBuilder.kt | 9 ++-- src/Kotlin/KotlinLanguageService.kt | 80 ++++++++++++++++++++++++++++++- src/Languages/JavaLanguageService.kt | 2 + src/Languages/LanguageService.kt | 12 +++-- src/Model/Content.kt | 1 + src/Model/DocumentationNode.kt | 6 ++- src/Model/DocumentationReference.kt | 1 + 10 files changed, 135 insertions(+), 30 deletions(-) (limited to 'src') diff --git a/src/Formats/HtmlFormatService.kt b/src/Formats/HtmlFormatService.kt index 74b10255..8e38a32c 100644 --- a/src/Formats/HtmlFormatService.kt +++ b/src/Formats/HtmlFormatService.kt @@ -152,7 +152,7 @@ fun formatPageTitle(node: DocumentationNode): String { if (path.size == 1) { return path.first().name } - val qualifiedName = path.drop(1).map { it.name }.filter { it.length > 0 }.joinToString(".") + val qualifiedName = node.qualifiedName() if (qualifiedName.length == 0 && path.size == 2) { return path.first().name + " / root package" } diff --git a/src/Formats/KotlinWebsiteFormatService.kt b/src/Formats/KotlinWebsiteFormatService.kt index 3a9fc6cd..97419c58 100644 --- a/src/Formats/KotlinWebsiteFormatService.kt +++ b/src/Formats/KotlinWebsiteFormatService.kt @@ -90,6 +90,7 @@ public class KotlinWebsiteFormatService(locationService: LocationService, private fun identifierClassName(kind: IdentifierKind) = when(kind) { IdentifierKind.ParameterName -> "parameterName" + IdentifierKind.SummarizedTypeName -> "summarizedTypeName" else -> "identifier" } } diff --git a/src/Formats/StructuredFormatService.kt b/src/Formats/StructuredFormatService.kt index a9b058d1..405a8b10 100644 --- a/src/Formats/StructuredFormatService.kt +++ b/src/Formats/StructuredFormatService.kt @@ -221,7 +221,7 @@ public abstract class StructuredFormatService(locationService: LocationService, } } - private fun StructuredFormatService.appendSection(location: Location, caption: String, nodes: List, node: DocumentationNode, to: StringBuilder) { + private fun appendSection(location: Location, caption: String, nodes: List, node: DocumentationNode, to: StringBuilder) { if (nodes.any()) { appendHeader(to, caption, 3) @@ -238,24 +238,7 @@ public abstract class StructuredFormatService(locationService: LocationService, appendTableCell(to) { val breakdownBySummary = members.groupBy { formatText(location, it.summary) } for ((summary, items) in breakdownBySummary) { - val signatureTexts = items.map { signature -> - val signatureText = languageService.render(signature, RenderMode.SUMMARY) - if (signatureText is ContentBlock && signatureText.isEmpty()) { - "" - } else { - val signatureAsCode = ContentCode() - signatureAsCode.append(signatureText) - formatText(location, signatureAsCode) - } - } - signatureTexts.subList(0, signatureTexts.size -1).forEach { - appendAsSignature(to) { - appendLine(to, it) - } - } - appendAsSignature(to) { - to.append(signatureTexts.last()) - } + appendSummarySignatures(items, location, to) if (!summary.isEmpty()) { to.append(summary) } @@ -268,6 +251,36 @@ public abstract class StructuredFormatService(locationService: LocationService, } } + private fun appendSummarySignatures(items: List, location: Location, to: StringBuilder) { + val summarySignature = languageService.summarizeSignatures(items) + if (summarySignature != null) { + appendAsSignature(to) { + val signatureAsCode = ContentCode() + signatureAsCode.append(summarySignature) + to.append(formatText(location, signatureAsCode)) + } + return + } + val signatureTexts = items.map { signature -> + val signatureText = languageService.render(signature, RenderMode.SUMMARY) + if (signatureText is ContentBlock && signatureText.isEmpty()) { + "" + } else { + val signatureAsCode = ContentCode() + signatureAsCode.append(signatureText) + formatText(location, signatureAsCode) + } + } + signatureTexts.subList(0, signatureTexts.size - 1).forEach { + appendAsSignature(to) { + appendLine(to, it) + } + } + appendAsSignature(to) { + to.append(signatureTexts.last()) + } + } + override fun appendNodes(location: Location, to: StringBuilder, nodes: Iterable) { val breakdownByLocation = nodes.groupBy { node -> formatBreadcrumbs(node.path.filterNot { it.name.isEmpty() }.map { link(node, it) }) diff --git a/src/Kotlin/DocumentationBuilder.kt b/src/Kotlin/DocumentationBuilder.kt index a9ac8551..b30d59d7 100644 --- a/src/Kotlin/DocumentationBuilder.kt +++ b/src/Kotlin/DocumentationBuilder.kt @@ -229,8 +229,8 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } } - fun link(node: DocumentationNode, descriptor: DeclarationDescriptor) { - refGraph.link(node, descriptor.signature(), DocumentationReference.Kind.Link) + fun link(node: DocumentationNode, descriptor: DeclarationDescriptor, kind: DocumentationReference.Kind) { + refGraph.link(node, descriptor.signature(), kind) } fun link(fromDescriptor: DeclarationDescriptor?, toDescriptor: DeclarationDescriptor?, kind: DocumentationReference.Kind) { @@ -328,8 +328,9 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, if (jetType.isMarkedNullable) { node.appendTextNode("?", Kind.NullabilityModifier) } - if (classifierDescriptor != null && !classifierDescriptor.isBoringBuiltinClass()) { - link(node, classifierDescriptor) + if (classifierDescriptor != null) { + link(node, classifierDescriptor, + if (classifierDescriptor.isBoringBuiltinClass()) DocumentationReference.Kind.HiddenLink else DocumentationReference.Kind.Link) } append(node, DocumentationReference.Kind.Detail) diff --git a/src/Kotlin/KotlinLanguageService.kt b/src/Kotlin/KotlinLanguageService.kt index 12a0bcda..1e7050ef 100644 --- a/src/Kotlin/KotlinLanguageService.kt +++ b/src/Kotlin/KotlinLanguageService.kt @@ -43,6 +43,74 @@ class KotlinLanguageService : LanguageService { } } + override fun summarizeSignatures(nodes: List): ContentNode? { + if (nodes.size < 2) return null + val receiverKind = nodes.getReceiverKind() ?: return null + val functionWithTypeParameter = nodes.firstOrNull { it.details(DocumentationNode.Kind.TypeParameter).any() } ?: return null + return content { + val typeParameter = functionWithTypeParameter.details(DocumentationNode.Kind.TypeParameter).first() + renderFunction(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name)) + } + } + + private fun List.getReceiverKind(): ReceiverKind? { + val qNames = map { it.getReceiverQName() }.filterNotNull() + if (qNames.size != size) + return null + + return ReceiverKind.values.firstOrNull { kind -> qNames.all { it in kind.classes } } + } + + private fun DocumentationNode.getReceiverQName(): String? { + if (kind != DocumentationNode.Kind.Function) return null + val receiver = details(DocumentationNode.Kind.Receiver).singleOrNull() ?: return null + val receiverType = receiver.detail(DocumentationNode.Kind.Type) + return (receiverType.links.firstOrNull() ?: receiverType.hiddenLinks.firstOrNull())?.qualifiedName() + } + + companion object { + private val arrayClasses = setOf( + "kotlin.Array", + "kotlin.BooleanArray", + "kotlin.ByteArray", + "kotlin.CharArray", + "kotlin.ShortArray", + "kotlin.IntArray", + "kotlin.LongArray", + "kotlin.FloatArray", + "kotlin.DoubleArray" + ) + + private val arrayOrListClasses = setOf("kotlin.List") + arrayClasses + + private val iterableClasses = setOf( + "kotlin.Collection", + "kotlin.Sequence", + "kotlin.Iterable", + "kotlin.Map", + "kotlin.String", + "kotlin.CharSequence") + arrayOrListClasses + } + + private enum class ReceiverKind(val receiverName: String, val classes: Collection) { + ARRAY("any_array", arrayClasses), + ARRAY_OR_LIST("any_array_or_list", arrayOrListClasses), + ITERABLE("any_iterable", iterableClasses), + } + + interface SignatureMapper { + fun renderReceiver(receiver: DocumentationNode, to: ContentBlock) + } + + private class SummarizingMapper(val kind: ReceiverKind, val typeParameterName: String): SignatureMapper { + override fun renderReceiver(receiver: DocumentationNode, to: ContentBlock) { + val newReceiver = ContentEmphasis() + newReceiver.append(ContentIdentifier(kind.receiverName, IdentifierKind.SummarizedTypeName)) + to.append(newReceiver) + to.text("<$typeParameterName>") + } + } + private fun ContentBlock.renderPackage(node: DocumentationNode) { keyword("package") text(" ") @@ -238,7 +306,9 @@ class KotlinLanguageService : LanguageService { renderSupertypesForNode(node) } - private fun ContentBlock.renderFunction(node: DocumentationNode, renderMode: RenderMode) { + private fun ContentBlock.renderFunction(node: DocumentationNode, + renderMode: RenderMode, + signatureMapper: SignatureMapper? = null) { if (renderMode == RenderMode.FULL) { renderAnnotationsForNode(node) } @@ -253,9 +323,15 @@ class KotlinLanguageService : LanguageService { if (node.details(DocumentationNode.Kind.TypeParameter).any()) { text(" ") } + val receiver = node.details(DocumentationNode.Kind.Receiver).singleOrNull() if (receiver != null) { - renderType(receiver.detail(DocumentationNode.Kind.Type)) + if (signatureMapper != null) { + signatureMapper.renderReceiver(receiver, this) + } + else { + renderType(receiver.detail(DocumentationNode.Kind.Type)) + } symbol(".") } diff --git a/src/Languages/JavaLanguageService.kt b/src/Languages/JavaLanguageService.kt index bcd058b5..cf0f127b 100644 --- a/src/Languages/JavaLanguageService.kt +++ b/src/Languages/JavaLanguageService.kt @@ -34,6 +34,8 @@ public class JavaLanguageService : LanguageService { } } + override fun summarizeSignatures(nodes: List): ContentNode? = null + private fun renderPackage(node: DocumentationNode): String { return "package ${node.name}" } diff --git a/src/Languages/LanguageService.kt b/src/Languages/LanguageService.kt index c587335a..b0f4bbc9 100644 --- a/src/Languages/LanguageService.kt +++ b/src/Languages/LanguageService.kt @@ -12,12 +12,18 @@ interface LanguageService { } /** - * Renders a [node](DocumentationNode) as a class, function, property or other signature in a target language. - * $node: A [DocumentationNode] to render - * $returns: [ContentNode] which is a root for a rich content tree suitable for formatting with [FormatService] + * Renders a [node] as a class, function, property or other signature in a target language. + * @param node A [DocumentationNode] to render + * @return [ContentNode] which is a root for a rich content tree suitable for formatting with [FormatService] */ fun render(node: DocumentationNode, renderMode: RenderMode = RenderMode.FULL): ContentNode + /** + * Tries to summarize the signatures of the specified documentation nodes in a compact representation. + * Returns the representation if successful, or null if the signatures could not be summarized. + */ + fun summarizeSignatures(nodes: List): ContentNode? + /** * Renders [node] as a named representation in the target language * diff --git a/src/Model/Content.kt b/src/Model/Content.kt index 38a42afc..4dfd3606 100644 --- a/src/Model/Content.kt +++ b/src/Model/Content.kt @@ -24,6 +24,7 @@ enum class IdentifierKind { TypeName, ParameterName, AnnotationName, + SummarizedTypeName, Other } diff --git a/src/Model/DocumentationNode.kt b/src/Model/DocumentationNode.kt index 04285594..c082365f 100644 --- a/src/Model/DocumentationNode.kt +++ b/src/Model/DocumentationNode.kt @@ -1,6 +1,6 @@ package org.jetbrains.dokka -import java.util.LinkedHashSet +import java.util.* public open class DocumentationNode(val name: String, content: Content, @@ -27,6 +27,8 @@ public open class DocumentationNode(val name: String, get() = references(DocumentationReference.Kind.Override).map { it.to } public val links: List get() = references(DocumentationReference.Kind.Link).map { it.to } + public val hiddenLinks: List + get() = references(DocumentationReference.Kind.HiddenLink).map { it.to } public val annotations: List get() = references(DocumentationReference.Kind.Annotation).map { it.to } public val deprecation: DocumentationNode? @@ -147,3 +149,5 @@ fun DocumentationNode.appendTextNode(text: String, refKind: DocumentationReference.Kind = DocumentationReference.Kind.Detail) { append(DocumentationNode(text, Content.Empty, kind), refKind) } + +fun DocumentationNode.qualifiedName() = path.drop(1).map { it.name }.filter { it.length > 0 }.joinToString(".") diff --git a/src/Model/DocumentationReference.kt b/src/Model/DocumentationReference.kt index a61ac65f..bbdd026d 100644 --- a/src/Model/DocumentationReference.kt +++ b/src/Model/DocumentationReference.kt @@ -6,6 +6,7 @@ public data class DocumentationReference(val from: DocumentationNode, val to: Do Member, Detail, Link, + HiddenLink, Extension, Inheritor, Override, -- cgit