| package org.jetbrains.dokka |
| |
| import org.jetbrains.dokka.Formats.classNodeNameWithOuterClass |
| import org.jetbrains.dokka.LanguageService.RenderMode |
| |
| /** |
| * Implements [LanguageService] and provides rendering of symbols in Kotlin language |
| */ |
| class KotlinLanguageService : CommonLanguageService() { |
| override fun showModifierInSummary(node: DocumentationNode): Boolean { |
| return node.name !in fullOnlyModifiers |
| } |
| |
| private val fullOnlyModifiers = |
| setOf("public", "protected", "private", "inline", "noinline", "crossinline", "reified") |
| |
| override fun render(node: DocumentationNode, renderMode: RenderMode): ContentNode { |
| return content { |
| when (node.kind) { |
| NodeKind.Package -> if (renderMode == RenderMode.FULL) renderPackage(node) |
| in NodeKind.classLike -> renderClass(node, renderMode) |
| |
| NodeKind.EnumItem -> renderClass(node, renderMode) |
| NodeKind.ExternalClass -> if (renderMode == RenderMode.FULL) identifier(node.name) |
| |
| NodeKind.Parameter -> renderParameter(node, renderMode) |
| NodeKind.TypeParameter -> renderTypeParameter(node, renderMode) |
| NodeKind.Type, |
| NodeKind.UpperBound -> renderType(node, renderMode) |
| |
| NodeKind.Modifier -> renderModifier(this, node, renderMode) |
| NodeKind.Constructor, |
| NodeKind.Function, |
| NodeKind.CompanionObjectFunction -> renderFunction(node, renderMode) |
| NodeKind.Property, |
| NodeKind.CompanionObjectProperty -> renderProperty(node, renderMode) |
| else -> identifier(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(NodeKind.TypeParameter).any() } ?: return null |
| return content { |
| val typeParameter = functionWithTypeParameter.details(NodeKind.TypeParameter).first() |
| if (functionWithTypeParameter.kind == NodeKind.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 != NodeKind.Function && kind != NodeKind.Property) return null |
| val receiver = details(NodeKind.Receiver).singleOrNull() ?: return null |
| return receiver.detail(NodeKind.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.renderFunctionalTypeParameterName(node: DocumentationNode, renderMode: RenderMode) { |
| node.references(RefKind.HiddenAnnotation).map { it.to } |
| .find { it.name == "ParameterName" }?.let { |
| val parameterNameValue = it.detail(NodeKind.Parameter).detail(NodeKind.Value) |
| identifier(parameterNameValue.name.removeSurrounding("\""), IdentifierKind.ParameterName) |
| symbol(":") |
| nbsp() |
| } |
| } |
| |
| private fun ContentBlock.renderFunctionalType(node: DocumentationNode, renderMode: RenderMode) { |
| var typeArguments = node.details(NodeKind.Type) |
| |
| if (node.name.startsWith("Suspend")) { |
| keyword("suspend ") |
| } |
| |
| // lambda |
| val isExtension = node.annotations.any { it.name == "ExtensionFunctionType" } |
| if (isExtension) { |
| renderType(typeArguments.first(), renderMode) |
| symbol(".") |
| typeArguments = typeArguments.drop(1) |
| } |
| symbol("(") |
| renderList(typeArguments.take(typeArguments.size - 1), noWrap = true) { |
| renderFunctionalTypeParameterName(it, renderMode) |
| renderType(it, renderMode) |
| } |
| symbol(")") |
| nbsp() |
| symbol("->") |
| nbsp() |
| renderType(typeArguments.last(), renderMode) |
| |
| } |
| |
| private fun DocumentationNode.isFunctionalType(): Boolean { |
| val typeArguments = details(NodeKind.Type) |
| val functionalTypeName = "Function${typeArguments.count() - 1}" |
| val suspendFunctionalTypeName = "Suspend$functionalTypeName" |
| return name == functionalTypeName || name == suspendFunctionalTypeName |
| } |
| |
| private fun ContentBlock.renderType(node: DocumentationNode, renderMode: RenderMode) { |
| if (node.name == "dynamic") { |
| keyword("dynamic") |
| return |
| } |
| if (node.isFunctionalType()) { |
| renderFunctionalType(node, renderMode) |
| return |
| } |
| if (renderMode == RenderMode.FULL) { |
| renderAnnotationsForNode(node) |
| } |
| renderModifiersForNode(node, renderMode, true) |
| renderLinked(this, node) { |
| identifier(it.typeDeclarationClass?.classNodeNameWithOuterClass() ?: it.name, IdentifierKind.TypeName) |
| } |
| val typeArguments = node.details(NodeKind.Type) |
| if (typeArguments.isNotEmpty()) { |
| symbol("<") |
| renderList(typeArguments, noWrap = true) { |
| renderType(it, renderMode) |
| } |
| symbol(">") |
| } |
| val nullabilityModifier = node.details(NodeKind.NullabilityModifier).singleOrNull() |
| if (nullabilityModifier != null) { |
| symbol(nullabilityModifier.name) |
| } |
| } |
| |
| override fun renderModifier( |
| block: ContentBlock, |
| node: DocumentationNode, |
| renderMode: RenderMode, |
| nowrap: Boolean |
| ) { |
| when (node.name) { |
| "final", "public", "var" -> { |
| } |
| else -> { |
| if (node.name !in fullOnlyModifiers || renderMode == RenderMode.FULL) { |
| super.renderModifier(block, node, renderMode, nowrap) |
| } |
| } |
| } |
| } |
| |
| private fun ContentBlock.renderTypeParameter(node: DocumentationNode, renderMode: RenderMode) { |
| renderModifiersForNode(node, renderMode, true) |
| |
| identifier(node.name) |
| |
| val constraints = node.details(NodeKind.UpperBound) |
| if (constraints.size == 1) { |
| 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, node.detailOrNull(NodeKind.Signature)?.name) |
| symbol(":") |
| nbsp() |
| val parameterType = node.detail(NodeKind.Type) |
| renderType(parameterType, renderMode) |
| val valueNode = node.details(NodeKind.Value).firstOrNull() |
| if (valueNode != null) { |
| nbsp() |
| symbol("=") |
| nbsp() |
| text(valueNode.name) |
| } |
| } |
| |
| private fun ContentBlock.renderTypeParametersForNode(node: DocumentationNode, renderMode: RenderMode) { |
| val typeParameters = node.details(NodeKind.TypeParameter) |
| if (typeParameters.any()) { |
| symbol("<") |
| renderList(typeParameters) { |
| renderTypeParameter(it, renderMode) |
| } |
| symbol(">") |
| } |
| } |
| |
| private fun ContentBlock.renderExtraTypeParameterConstraints(node: DocumentationNode, renderMode: RenderMode) { |
| val parametersWithMultipleConstraints = |
| node.details(NodeKind.TypeParameter).filter { it.details(NodeKind.UpperBound).size > 1 } |
| val parametersWithConstraints = parametersWithMultipleConstraints |
| .flatMap { parameter -> |
| parameter.details(NodeKind.UpperBound).map { constraint -> parameter to constraint } |
| } |
| if (parametersWithMultipleConstraints.isNotEmpty()) { |
| keyword(" where ") |
| renderList(parametersWithConstraints) { |
| identifier(it.first.name) |
| nbsp() |
| symbol(":") |
| nbsp() |
| renderType(it.second, renderMode) |
| } |
| } |
| } |
| |
| private fun ContentBlock.renderSupertypesForNode(node: DocumentationNode, renderMode: RenderMode) { |
| val supertypes = node.details(NodeKind.Supertype).filterNot { it.qualifiedNameFromType() in ignoredSupertypes } |
| if (supertypes.any()) { |
| nbsp() |
| symbol(":") |
| nbsp() |
| renderList(supertypes) { |
| indentedSoftLineBreak() |
| renderType(it, renderMode) |
| } |
| } |
| } |
| |
| 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(NodeKind.Parameter) |
| if (!parameters.isEmpty()) { |
| symbol("(") |
| renderList(parameters) { |
| text(it.detail(NodeKind.Value).name) |
| } |
| symbol(")") |
| } |
| text(" ") |
| } |
| |
| private fun ContentBlock.renderClass(node: DocumentationNode, renderMode: RenderMode) { |
| if (renderMode == RenderMode.FULL) { |
| renderAnnotationsForNode(node) |
| } |
| renderModifiersForNode(node, renderMode) |
| when (node.kind) { |
| NodeKind.Class, |
| NodeKind.AnnotationClass, |
| NodeKind.Exception, |
| NodeKind.Enum -> keyword("class ") |
| NodeKind.Interface -> keyword("interface ") |
| NodeKind.EnumItem -> keyword("enum val ") |
| NodeKind.Object -> keyword("object ") |
| NodeKind.TypeAlias -> keyword("typealias ") |
| else -> throw IllegalArgumentException("Node $node is not a class-like object") |
| } |
| |
| identifierOrDeprecated(node) |
| renderTypeParametersForNode(node, renderMode) |
| renderSupertypesForNode(node, renderMode) |
| renderExtraTypeParameterConstraints(node, renderMode) |
| |
| if (node.kind == NodeKind.TypeAlias) { |
| nbsp() |
| symbol("=") |
| nbsp() |
| renderType(node.detail(NodeKind.TypeAliasUnderlyingType), renderMode) |
| } |
| } |
| |
| private fun ContentBlock.renderFunction( |
| node: DocumentationNode, |
| renderMode: RenderMode, |
| signatureMapper: SignatureMapper? = null |
| ) { |
| if (renderMode == RenderMode.FULL) { |
| renderAnnotationsForNode(node) |
| } |
| renderModifiersForNode(node, renderMode) |
| when (node.kind) { |
| NodeKind.Constructor -> identifier(node.owner!!.name) |
| NodeKind.Function, |
| NodeKind.CompanionObjectFunction -> keyword("fun ") |
| else -> throw IllegalArgumentException("Node $node is not a function-like object") |
| } |
| renderTypeParametersForNode(node, renderMode) |
| if (node.details(NodeKind.TypeParameter).any()) { |
| text(" ") |
| } |
| |
| renderReceiver(node, renderMode, signatureMapper) |
| |
| if (node.kind != NodeKind.Constructor) |
| identifierOrDeprecated(node) |
| |
| symbol("(") |
| val parameters = node.details(NodeKind.Parameter) |
| renderList(parameters) { |
| indentedSoftLineBreak() |
| renderParameter(it, renderMode) |
| } |
| if (needReturnType(node)) { |
| if (parameters.isNotEmpty()) { |
| softLineBreak() |
| } |
| symbol(")") |
| symbol(": ") |
| renderType(node.detail(NodeKind.Type), renderMode) |
| } else { |
| symbol(")") |
| } |
| renderExtraTypeParameterConstraints(node, renderMode) |
| } |
| |
| private fun ContentBlock.renderReceiver( |
| node: DocumentationNode, |
| renderMode: RenderMode, |
| signatureMapper: SignatureMapper? |
| ) { |
| val receiver = node.details(NodeKind.Receiver).singleOrNull() |
| if (receiver != null) { |
| if (signatureMapper != null) { |
| signatureMapper.renderReceiver(receiver, this) |
| } else { |
| val type = receiver.detail(NodeKind.Type) |
| |
| if (type.isFunctionalType()) { |
| symbol("(") |
| renderFunctionalType(type, renderMode) |
| symbol(")") |
| } else { |
| renderType(type, renderMode) |
| } |
| } |
| symbol(".") |
| } |
| } |
| |
| private fun needReturnType(node: DocumentationNode) = when (node.kind) { |
| NodeKind.Constructor -> false |
| else -> !node.isUnitReturnType() |
| } |
| |
| fun DocumentationNode.isUnitReturnType(): Boolean = |
| detail(NodeKind.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) { |
| NodeKind.Property, |
| NodeKind.CompanionObjectProperty -> keyword("${node.getPropertyKeyword()} ") |
| else -> throw IllegalArgumentException("Node $node is not a property") |
| } |
| renderTypeParametersForNode(node, renderMode) |
| if (node.details(NodeKind.TypeParameter).any()) { |
| text(" ") |
| } |
| |
| renderReceiver(node, renderMode, signatureMapper) |
| |
| identifierOrDeprecated(node) |
| symbol(": ") |
| renderType(node.detail(NodeKind.Type), renderMode) |
| renderExtraTypeParameterConstraints(node, renderMode) |
| } |
| |
| fun DocumentationNode.getPropertyKeyword() = |
| if (details(NodeKind.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(): String { |
| return details.firstOrNull { it.kind == NodeKind.QualifiedName }?.name |
| ?: (links.firstOrNull() ?: hiddenLinks.firstOrNull())?.qualifiedName() |
| ?: name |
| } |
| |
| |
| val DocumentationNode.typeDeclarationClass |
| get() = (links.firstOrNull { it.kind in NodeKind.classLike } ?: externalType) |