blob: 6dd43eaf6eed68dc84897b7ca6896fdac20fac8c [file] [log] [blame]
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)