diff options
Diffstat (limited to 'core/src/main/kotlin/Kotlin/KotlinLanguageService.kt')
-rw-r--r-- | core/src/main/kotlin/Kotlin/KotlinLanguageService.kt | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt new file mode 100644 index 00000000..0d39f410 --- /dev/null +++ b/core/src/main/kotlin/Kotlin/KotlinLanguageService.kt @@ -0,0 +1,409 @@ +package org.jetbrains.dokka + +import org.jetbrains.dokka.LanguageService.RenderMode + +/** + * Implements [LanguageService] and provides rendering of symbols in Kotlin language + */ +class KotlinLanguageService : LanguageService { + private val fullOnlyModifiers = setOf("public", "protected", "private", "inline", "noinline", "crossinline", "reified") + + override fun render(node: DocumentationNode, renderMode: RenderMode): ContentNode { + return content { + when (node.kind) { + DocumentationNode.Kind.Package -> if (renderMode == RenderMode.FULL) renderPackage(node) + in DocumentationNode.Kind.classLike -> renderClass(node, renderMode) + + DocumentationNode.Kind.EnumItem, + DocumentationNode.Kind.ExternalClass -> if (renderMode == RenderMode.FULL) identifier(node.name) + + DocumentationNode.Kind.TypeParameter -> renderTypeParameter(node, renderMode) + DocumentationNode.Kind.Type, + DocumentationNode.Kind.UpperBound -> renderType(node, renderMode) + + DocumentationNode.Kind.Modifier -> renderModifier(node) + DocumentationNode.Kind.Constructor, + DocumentationNode.Kind.Function, + DocumentationNode.Kind.CompanionObjectFunction -> renderFunction(node, renderMode) + DocumentationNode.Kind.Property, + DocumentationNode.Kind.CompanionObjectProperty -> renderProperty(node, renderMode) + else -> identifier(node.name) + } + } + } + + override fun renderName(node: DocumentationNode): String { + return when (node.kind) { + DocumentationNode.Kind.Constructor -> node.owner!!.name + else -> node.name + } + } + + override fun summarizeSignatures(nodes: List<DocumentationNode>): 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() + if (functionWithTypeParameter.kind == DocumentationNode.Kind.Function) { + renderFunction(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name)) + } + else { + renderProperty(functionWithTypeParameter, RenderMode.SUMMARY, SummarizingMapper(receiverKind, typeParameter.name)) + } + } + } + + private fun List<DocumentationNode>.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 && kind != DocumentationNode.Kind.Property) return null + val receiver = details(DocumentationNode.Kind.Receiver).singleOrNull() ?: return null + return receiver.detail(DocumentationNode.Kind.Type).qualifiedNameFromType() + } + + 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<String>) { + 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) { + to.append(ContentIdentifier(kind.receiverName, IdentifierKind.SummarizedTypeName)) + to.text("<$typeParameterName>") + } + } + + private fun ContentBlock.renderPackage(node: DocumentationNode) { + keyword("package") + text(" ") + identifier(node.name) + } + + private fun ContentBlock.renderList(nodes: List<DocumentationNode>, separator: String = ", ", + noWrap: Boolean = false, renderItem: (DocumentationNode) -> Unit) { + if (nodes.none()) + return + renderItem(nodes.first()) + nodes.drop(1).forEach { + if (noWrap) { + symbol(separator.removeSuffix(" ")) + nbsp() + } else { + symbol(separator) + } + renderItem(it) + } + } + + private fun ContentBlock.renderLinked(node: DocumentationNode, body: ContentBlock.(DocumentationNode)->Unit) { + val to = node.links.firstOrNull() + if (to == null) + body(node) + else + link(to) { + body(node) + } + } + + private fun ContentBlock.renderType(node: DocumentationNode, renderMode: RenderMode) { + var typeArguments = node.details(DocumentationNode.Kind.Type) + if (node.name == "Function${typeArguments.count() - 1}") { + // lambda + val isExtension = node.annotations.any { it.name == "Extension" } + if (isExtension) { + renderType(typeArguments.first(), renderMode) + symbol(".") + typeArguments = typeArguments.drop(1) + } + symbol("(") + renderList(typeArguments.take(typeArguments.size - 1), noWrap = true) { + renderType(it, renderMode) + } + symbol(")") + nbsp() + symbol("->") + nbsp() + renderType(typeArguments.last(), renderMode) + return + } + if (renderMode == RenderMode.FULL) { + renderAnnotationsForNode(node) + } + renderModifiersForNode(node, renderMode, true) + renderLinked(node) { identifier(it.name, IdentifierKind.TypeName) } + if (typeArguments.any()) { + symbol("<") + renderList(typeArguments, noWrap = true) { + renderType(it, renderMode) + } + symbol(">") + } + val nullabilityModifier = node.details(DocumentationNode.Kind.NullabilityModifier).singleOrNull() + if (nullabilityModifier != null) { + symbol(nullabilityModifier.name) + } + } + + private fun ContentBlock.renderModifier(node: DocumentationNode, nowrap: Boolean = false) { + when (node.name) { + "final", "public", "var" -> {} + else -> { + keyword(node.name) + if (nowrap) { + nbsp() + } + else { + text(" ") + } + } + } + } + + private fun ContentBlock.renderTypeParameter(node: DocumentationNode, renderMode: RenderMode) { + renderModifiersForNode(node, renderMode, true) + + identifier(node.name) + + val constraints = node.details(DocumentationNode.Kind.UpperBound) + if (constraints.any()) { + nbsp() + symbol(":") + nbsp() + renderList(constraints, noWrap=true) { + renderType(it, renderMode) + } + } + } + private fun ContentBlock.renderParameter(node: DocumentationNode, renderMode: RenderMode) { + if (renderMode == RenderMode.FULL) { + renderAnnotationsForNode(node) + } + renderModifiersForNode(node, renderMode) + identifier(node.name, IdentifierKind.ParameterName) + symbol(":") + nbsp() + val parameterType = node.detail(DocumentationNode.Kind.Type) + renderType(parameterType, renderMode) + val valueNode = node.details(DocumentationNode.Kind.Value).firstOrNull() + if (valueNode != null) { + nbsp() + symbol("=") + nbsp() + text(valueNode.name) + } + } + + private fun ContentBlock.renderTypeParametersForNode(node: DocumentationNode, renderMode: RenderMode) { + val typeParameters = node.details(DocumentationNode.Kind.TypeParameter) + if (typeParameters.any()) { + symbol("<") + renderList(typeParameters) { + renderTypeParameter(it, renderMode) + } + symbol(">") + } + } + + private fun ContentBlock.renderSupertypesForNode(node: DocumentationNode, renderMode: RenderMode) { + val supertypes = node.details(DocumentationNode.Kind.Supertype) + if (supertypes.any()) { + nbsp() + symbol(":") + nbsp() + renderList(supertypes) { + indentedSoftLineBreak() + renderType(it, renderMode) + } + } + } + + private fun ContentBlock.renderModifiersForNode(node: DocumentationNode, + renderMode: RenderMode, + nowrap: Boolean = false) { + val modifiers = node.details(DocumentationNode.Kind.Modifier) + for (it in modifiers) { + if (node.kind == org.jetbrains.dokka.DocumentationNode.Kind.Interface && it.name == "abstract") + continue + if (renderMode == RenderMode.SUMMARY && it.name in fullOnlyModifiers) { + continue + } + renderModifier(it, nowrap) + } + } + + private fun ContentBlock.renderAnnotationsForNode(node: DocumentationNode) { + node.annotations.forEach { + renderAnnotation(it) + } + } + + private fun ContentBlock.renderAnnotation(node: DocumentationNode) { + identifier("@" + node.name, IdentifierKind.AnnotationName) + val parameters = node.details(DocumentationNode.Kind.Parameter) + if (!parameters.isEmpty()) { + symbol("(") + renderList(parameters) { + text(it.detail(DocumentationNode.Kind.Value).name) + } + symbol(")") + } + text(" ") + } + + private fun ContentBlock.renderClass(node: DocumentationNode, renderMode: RenderMode) { + if (renderMode == RenderMode.FULL) { + renderAnnotationsForNode(node) + } + renderModifiersForNode(node, renderMode) + when (node.kind) { + DocumentationNode.Kind.Class, + DocumentationNode.Kind.AnnotationClass, + DocumentationNode.Kind.Enum -> keyword("class ") + DocumentationNode.Kind.Interface -> keyword("interface ") + DocumentationNode.Kind.EnumItem -> keyword("enum val ") + DocumentationNode.Kind.Object -> keyword("object ") + else -> throw IllegalArgumentException("Node $node is not a class-like object") + } + + identifierOrDeprecated(node) + renderTypeParametersForNode(node, renderMode) + renderSupertypesForNode(node, renderMode) + } + + private fun ContentBlock.renderFunction(node: DocumentationNode, + renderMode: RenderMode, + signatureMapper: SignatureMapper? = null) { + if (renderMode == RenderMode.FULL) { + renderAnnotationsForNode(node) + } + renderModifiersForNode(node, renderMode) + when (node.kind) { + DocumentationNode.Kind.Constructor -> identifier(node.owner!!.name) + DocumentationNode.Kind.Function, + DocumentationNode.Kind.CompanionObjectFunction -> keyword("fun ") + else -> throw IllegalArgumentException("Node $node is not a function-like object") + } + renderTypeParametersForNode(node, renderMode) + if (node.details(DocumentationNode.Kind.TypeParameter).any()) { + text(" ") + } + + renderReceiver(node, renderMode, signatureMapper) + + if (node.kind != org.jetbrains.dokka.DocumentationNode.Kind.Constructor) + identifierOrDeprecated(node) + + symbol("(") + val parameters = node.details(DocumentationNode.Kind.Parameter) + renderList(parameters) { + indentedSoftLineBreak() + renderParameter(it, renderMode) + } + if (needReturnType(node)) { + if (parameters.isNotEmpty()) { + softLineBreak() + } + symbol(")") + symbol(": ") + renderType(node.detail(DocumentationNode.Kind.Type), renderMode) + } + else { + symbol(")") + } + } + + private fun ContentBlock.renderReceiver(node: DocumentationNode, renderMode: RenderMode, signatureMapper: SignatureMapper?) { + val receiver = node.details(DocumentationNode.Kind.Receiver).singleOrNull() + if (receiver != null) { + if (signatureMapper != null) { + signatureMapper.renderReceiver(receiver, this) + } else { + renderType(receiver.detail(DocumentationNode.Kind.Type), renderMode) + } + symbol(".") + } + } + + private fun needReturnType(node: DocumentationNode) = when(node.kind) { + DocumentationNode.Kind.Constructor -> false + else -> !node.isUnitReturnType() + } + + fun DocumentationNode.isUnitReturnType(): Boolean = + detail(DocumentationNode.Kind.Type).hiddenLinks.firstOrNull()?.qualifiedName() == "kotlin.Unit" + + private fun ContentBlock.renderProperty(node: DocumentationNode, + renderMode: RenderMode, + signatureMapper: SignatureMapper? = null) { + if (renderMode == RenderMode.FULL) { + renderAnnotationsForNode(node) + } + renderModifiersForNode(node, renderMode) + when (node.kind) { + DocumentationNode.Kind.Property, + DocumentationNode.Kind.CompanionObjectProperty -> keyword("${node.getPropertyKeyword()} ") + else -> throw IllegalArgumentException("Node $node is not a property") + } + renderTypeParametersForNode(node, renderMode) + if (node.details(DocumentationNode.Kind.TypeParameter).any()) { + text(" ") + } + + renderReceiver(node, renderMode, signatureMapper) + + identifierOrDeprecated(node) + symbol(": ") + renderType(node.detail(DocumentationNode.Kind.Type), renderMode) + } + + fun DocumentationNode.getPropertyKeyword() = + if (details(DocumentationNode.Kind.Modifier).any { it.name == "var" }) "var" else "val" + + fun ContentBlock.identifierOrDeprecated(node: DocumentationNode) { + if (node.deprecation != null) { + val strike = ContentStrikethrough() + strike.identifier(node.name) + append(strike) + } else { + identifier(node.name) + } + } +} + +fun DocumentationNode.qualifiedNameFromType() = (links.firstOrNull() ?: hiddenLinks.firstOrNull())?.qualifiedName() ?: name |