diff options
Diffstat (limited to 'src/Kotlin')
-rw-r--r-- | src/Kotlin/ContentBuilder.kt | 62 | ||||
-rw-r--r-- | src/Kotlin/DocumentationBuilder.kt | 209 | ||||
-rw-r--r-- | src/Kotlin/KotlinLanguageService.kt | 248 |
3 files changed, 317 insertions, 202 deletions
diff --git a/src/Kotlin/ContentBuilder.kt b/src/Kotlin/ContentBuilder.kt index 9ce81cee..c981eb42 100644 --- a/src/Kotlin/ContentBuilder.kt +++ b/src/Kotlin/ContentBuilder.kt @@ -1,14 +1,19 @@ package org.jetbrains.dokka -import java.util.ArrayDeque -import org.jetbrains.kotlin.descriptors.* -import org.jetbrains.kotlin.resolve.* -import org.jetbrains.kotlin.resolve.scopes.* -import org.jetbrains.kotlin.name.* +import org.intellij.markdown.MarkdownElementTypes +import org.intellij.markdown.MarkdownTokenTypes +import org.intellij.markdown.html.entities.EntityConverter +import org.jetbrains.kotlin.descriptors.ClassDescriptor +import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.idea.kdoc.getResolutionScope -import org.intellij.markdown.* -import org.jetbrains.kotlin.psi.JetDeclarationWithBody -import org.jetbrains.kotlin.psi.JetBlockExpression +import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.name.Name +import org.jetbrains.kotlin.psi.KtBlockExpression +import org.jetbrains.kotlin.psi.KtDeclarationWithBody +import org.jetbrains.kotlin.resolve.DescriptorToSourceUtils +import org.jetbrains.kotlin.resolve.scopes.KtScope +import org.jetbrains.kotlin.resolve.scopes.utils.asJetScope +import java.util.* public fun buildContent(tree: MarkdownNode, linkResolver: (String) -> ContentBlock): MutableContent { val result = MutableContent() @@ -43,7 +48,8 @@ public fun buildContentTo(tree: MarkdownNode, target: ContentBlock, linkResolver MarkdownElementTypes.EMPH -> appendNodeWithChildren(ContentEmphasis()) MarkdownElementTypes.STRONG -> appendNodeWithChildren(ContentStrong()) MarkdownElementTypes.CODE_SPAN -> appendNodeWithChildren(ContentCode()) - MarkdownElementTypes.CODE_BLOCK -> appendNodeWithChildren(ContentBlockCode()) + MarkdownElementTypes.CODE_BLOCK, + MarkdownElementTypes.CODE_FENCE -> appendNodeWithChildren(ContentBlockCode()) MarkdownElementTypes.PARAGRAPH -> appendNodeWithChildren(ContentParagraph()) MarkdownElementTypes.INLINE_LINK -> { @@ -84,11 +90,24 @@ public fun buildContentTo(tree: MarkdownNode, target: ContentBlock, linkResolver parent.append(block) } - MarkdownTokenTypes.HTML_ENTITY -> { - parent.append(ContentEntity(node.text)) + MarkdownTokenTypes.TEXT -> { + fun createEntityOrText(text: String): ContentNode { + if (text == "&" || text == """ || text == "<" || text == ">") { + return ContentEntity(text) + } + if (text == "&") { + return ContentEntity("&") + } + val decodedText = EntityConverter.replaceEntities(text, true, true) + if (decodedText != text) { + return ContentEntity(text) + } + return ContentText(text) + } + + parent.append(createEntityOrText(node.text)) } - MarkdownTokenTypes.TEXT, MarkdownTokenTypes.COLON, MarkdownTokenTypes.DOUBLE_QUOTE, MarkdownTokenTypes.LT, @@ -96,7 +115,8 @@ public fun buildContentTo(tree: MarkdownNode, target: ContentBlock, linkResolver MarkdownTokenTypes.LPAREN, MarkdownTokenTypes.RPAREN, MarkdownTokenTypes.LBRACKET, - MarkdownTokenTypes.RBRACKET -> { + MarkdownTokenTypes.RBRACKET, + MarkdownTokenTypes.CODE_FENCE_CONTENT -> { parent.append(ContentText(node.text)) } else -> { @@ -120,8 +140,8 @@ fun DocumentationBuilder.functionBody(descriptor: DeclarationDescriptor, functio logger.warn("Missing function name in @sample in ${descriptor.signature()}") return ContentBlockCode().let() { it.append(ContentText("Missing function name in @sample")); it } } - val scope = getResolutionScope(resolutionFacade, descriptor) - val rootPackage = session.getModuleDescriptor().getPackage(FqName.ROOT) + val scope = getResolutionScope(resolutionFacade, descriptor).asJetScope() + val rootPackage = resolutionFacade.moduleDescriptor.getPackage(FqName.ROOT) val rootScope = rootPackage.memberScope val symbol = resolveInScope(functionName, scope) ?: resolveInScope(functionName, rootScope) if (symbol == null) { @@ -135,23 +155,23 @@ fun DocumentationBuilder.functionBody(descriptor: DeclarationDescriptor, functio } val text = when (psiElement) { - is JetDeclarationWithBody -> ContentBlockCode().let() { + is KtDeclarationWithBody -> ContentBlockCode().let() { val bodyExpression = psiElement.bodyExpression when (bodyExpression) { - is JetBlockExpression -> bodyExpression.text.removeSurrounding("{", "}") + is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}") else -> bodyExpression!!.text } } else -> psiElement.text } - val lines = text.trimEnd().split("\n".toRegex()).toTypedArray().filterNot { it.length() == 0 } + val lines = text.trimEnd().split("\n".toRegex()).toTypedArray().filterNot { it.length == 0 } val indent = lines.map { it.takeWhile { it.isWhitespace() }.count() }.min() ?: 0 - val finalText = lines.map { it.drop(indent) }.join("\n") + val finalText = lines.map { it.drop(indent) }.joinToString("\n") return ContentBlockCode("kotlin").let() { it.append(ContentText(finalText)); it } } -private fun DocumentationBuilder.resolveInScope(functionName: String, scope: JetScope): DeclarationDescriptor? { +private fun DocumentationBuilder.resolveInScope(functionName: String, scope: KtScope): DeclarationDescriptor? { var currentScope = scope val parts = functionName.split('.') @@ -169,7 +189,7 @@ private fun DocumentationBuilder.resolveInScope(functionName: String, scope: Jet currentScope = if (partSymbol is ClassDescriptor) partSymbol.defaultType.memberScope else - getResolutionScope(resolutionFacade, partSymbol) + getResolutionScope(resolutionFacade, partSymbol).asJetScope() symbol = partSymbol } diff --git a/src/Kotlin/DocumentationBuilder.kt b/src/Kotlin/DocumentationBuilder.kt index 6bfeb360..2d3105ac 100644 --- a/src/Kotlin/DocumentationBuilder.kt +++ b/src/Kotlin/DocumentationBuilder.kt @@ -8,23 +8,28 @@ import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotated import org.jetbrains.kotlin.descriptors.annotations.AnnotationDescriptor import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor -import org.jetbrains.kotlin.idea.caches.resolve.ResolutionFacade import org.jetbrains.kotlin.idea.kdoc.KDocFinder import org.jetbrains.kotlin.idea.kdoc.resolveKDocLink +import org.jetbrains.kotlin.idea.resolve.ResolutionFacade +import org.jetbrains.kotlin.incremental.components.NoLookupLocation import org.jetbrains.kotlin.kdoc.psi.impl.KDocSection import org.jetbrains.kotlin.kdoc.psi.impl.KDocTag -import org.jetbrains.kotlin.lexer.JetSingleValueToken +import org.jetbrains.kotlin.lexer.KtTokens import org.jetbrains.kotlin.name.FqName -import org.jetbrains.kotlin.psi.JetParameter +import org.jetbrains.kotlin.psi.KtModifierListOwner +import org.jetbrains.kotlin.psi.KtParameter import org.jetbrains.kotlin.resolve.DescriptorUtils +import org.jetbrains.kotlin.resolve.constants.CompileTimeConstant import org.jetbrains.kotlin.resolve.constants.ConstantValue +import org.jetbrains.kotlin.resolve.constants.TypedCompileTimeConstant +import org.jetbrains.kotlin.resolve.descriptorUtil.fqNameSafe +import org.jetbrains.kotlin.resolve.descriptorUtil.isDocumentedAnnotation import org.jetbrains.kotlin.resolve.lazy.ResolveSession import org.jetbrains.kotlin.resolve.source.PsiSourceElement import org.jetbrains.kotlin.resolve.source.getPsi import org.jetbrains.kotlin.types.ErrorUtils -import org.jetbrains.kotlin.types.JetType +import org.jetbrains.kotlin.types.KtType import org.jetbrains.kotlin.types.TypeProjection -import org.jetbrains.kotlin.types.expressions.OperatorConventions public data class DocumentationOptions(val includeNonPublic: Boolean = false, val reportUndocumented: Boolean = true, @@ -33,8 +38,8 @@ public data class DocumentationOptions(val includeNonPublic: Boolean = false, val sourceLinks: List<SourceLinkDefinition>) private fun isSamePackage(descriptor1: DeclarationDescriptor, descriptor2: DeclarationDescriptor): Boolean { - val package1 = DescriptorUtils.getParentOfType(descriptor1, javaClass<PackageFragmentDescriptor>()) - val package2 = DescriptorUtils.getParentOfType(descriptor2, javaClass<PackageFragmentDescriptor>()) + val package1 = DescriptorUtils.getParentOfType(descriptor1, PackageFragmentDescriptor::class.java) + val package2 = DescriptorUtils.getParentOfType(descriptor2, PackageFragmentDescriptor::class.java) return package1 != null && package2 != null && package1.fqName == package2.fqName } @@ -47,6 +52,10 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, val boringBuiltinClasses = setOf( "kotlin.Unit", "kotlin.Byte", "kotlin.Short", "kotlin.Int", "kotlin.Long", "kotlin.Char", "kotlin.Boolean", "kotlin.Float", "kotlin.Double", "kotlin.String", "kotlin.Array", "kotlin.Any") + val knownModifiers = setOf( + KtTokens.PUBLIC_KEYWORD, KtTokens.PROTECTED_KEYWORD, KtTokens.INTERNAL_KEYWORD, KtTokens.PRIVATE_KEYWORD, + KtTokens.OPEN_KEYWORD, KtTokens.FINAL_KEYWORD, KtTokens.ABSTRACT_KEYWORD, KtTokens.SEALED_KEYWORD, + KtTokens.OVERRIDE_KEYWORD) fun parseDocumentation(descriptor: DeclarationDescriptor): Content { val kdoc = KDocFinder.findKDoc(descriptor) ?: findStdlibKDoc(descriptor) @@ -101,9 +110,10 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, deepestDescriptor = deepestDescriptor.overriddenDescriptors.first() } if (DescriptorUtils.getFqName(deepestDescriptor.containingDeclaration).asString() == "kotlin.Any") { - val anyClassDescriptors = session.getTopLevelClassDescriptors(FqName.fromSegments(listOf("kotlin", "Any"))) + val anyClassDescriptors = session.getTopLevelClassDescriptors(FqName.fromSegments(listOf("kotlin", "Any")), + NoLookupLocation.UNSORTED) anyClassDescriptors.forEach { - val anyMethod = it.getMemberScope(listOf()).getFunctions(descriptor.name).single() + val anyMethod = it.getMemberScope(listOf()).getFunctions(descriptor.name, NoLookupLocation.UNSORTED).single() val kdoc = KDocFinder.findKDoc(anyMethod) if (kdoc != null) { return kdoc @@ -115,7 +125,7 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } fun DeclarationDescriptor.isDeprecated(): Boolean = annotations.any { - DescriptorUtils.getFqName(it.type.constructor.declarationDescriptor!!).asString() == "kotlin.deprecated" + DescriptorUtils.getFqName(it.type.constructor.declarationDescriptor!!).asString() == "kotlin.Deprecated" } || (this is ConstructorDescriptor && containingDeclaration.isDeprecated()) fun DeclarationDescriptor.signature(): String = when(this) { @@ -142,13 +152,13 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, if (extensionReceiver != null) { params.add(0, extensionReceiver.type) } - return "(" + params.map { it.signature() }.join() + ")" + return "(" + params.map { it.signature() }.joinToString() + ")" } - fun JetType.signature(): String { + fun KtType.signature(): String { val declarationDescriptor = constructor.declarationDescriptor ?: return "<null>" val typeName = DescriptorUtils.getFqName(declarationDescriptor).asString() - if (typeName == "Array" && arguments.size() == 1) { + if (typeName == "Array" && arguments.size == 1) { return "Array<" + arguments.first().type.signature() + ">" } return typeName @@ -203,12 +213,12 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, return symbol } - fun KDocSection.getTags(): Array<KDocTag> = PsiTreeUtil.getChildrenOfType(this, javaClass<KDocTag>()) ?: arrayOf() + fun KDocSection.getTags(): Array<KDocTag> = PsiTreeUtil.getChildrenOfType(this, KDocTag::class.java) ?: arrayOf() private fun MutableContent.addTagToSeeAlso(descriptor: DeclarationDescriptor, seeTag: KDocTag) { val subjectName = seeTag.getSubjectName() if (subjectName != null) { - val seeSection = findSectionByTag(ContentTags.SeeAlso) ?: addSection(ContentTags.SeeAlso, null) + val seeSection = findSectionByTag("See Also") ?: addSection("See Also", null) val link = resolveContentLink(descriptor, subjectName) link.append(ContentText(subjectName)) val para = ContentParagraph() @@ -217,8 +227,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) { @@ -231,7 +241,7 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, refGraph.register(descriptor.signature(), node) } - fun DocumentationNode<T>(descriptor: T, kind: Kind): DocumentationNode where T : DeclarationDescriptor, T : Named { + fun <T> DocumentationNode(descriptor: T, kind: Kind): DocumentationNode where T : DeclarationDescriptor, T : Named { val doc = parseDocumentation(descriptor) val node = DocumentationNode(descriptor.name.asString(), doc, kind).withModifiers(descriptor) return node @@ -255,7 +265,7 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, modality = Modality.FINAL } } - val modifier = modality.name().toLowerCase() + val modifier = modality.name.toLowerCase() appendTextNode(modifier, DocumentationNode.Kind.Modifier) } @@ -269,12 +279,14 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, for (superType in superTypes) { if (!ignoreSupertype(superType)) { appendType(superType, DocumentationNode.Kind.Supertype) - link(superType?.constructor?.declarationDescriptor, descriptor, DocumentationReference.Kind.Inheritor) + val superclass = superType?.constructor?.declarationDescriptor + link(superclass, descriptor, DocumentationReference.Kind.Inheritor) + link(descriptor, superclass, DocumentationReference.Kind.Superclass) } } } - private fun ignoreSupertype(superType: JetType): Boolean { + private fun ignoreSupertype(superType: KtType): Boolean { val superClass = superType.constructor.declarationDescriptor as? ClassDescriptor if (superClass != null) { val fqName = DescriptorUtils.getFqNameSafe(superClass).asString() @@ -284,10 +296,15 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } fun DocumentationNode.appendProjection(projection: TypeProjection, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type) { - appendType(projection.type, kind, projection.projectionKind.label) + if (projection.isStarProjection) { + appendTextNode("*", Kind.Type) + } + else { + appendType(projection.type, kind, projection.projectionKind.label) + } } - fun DocumentationNode.appendType(jetType: JetType?, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type, prefix: String = "") { + fun DocumentationNode.appendType(jetType: KtType?, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type, prefix: String = "") { if (jetType == null) return val classifierDescriptor = jetType.constructor.declarationDescriptor @@ -311,28 +328,53 @@ 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) - for (typeArgument in jetType.arguments) + node.appendAnnotations(jetType) + for (typeArgument in jetType.arguments) { node.appendProjection(typeArgument) + } } fun ClassifierDescriptor.isBoringBuiltinClass(): Boolean = DescriptorUtils.getFqName(this).asString() in boringBuiltinClasses fun DocumentationNode.appendAnnotations(annotated: Annotated) { - annotated.annotations.forEach { + annotated.annotations.filter { it.isDocumented() }.forEach { val annotationNode = it.build() if (annotationNode != null) { append(annotationNode, - if (annotationNode.name == "deprecated") DocumentationReference.Kind.Deprecation else DocumentationReference.Kind.Annotation) + if (annotationNode.isDeprecation()) DocumentationReference.Kind.Deprecation else DocumentationReference.Kind.Annotation) + } + } + } + + private fun AnnotationDescriptor.isDocumented(): Boolean { + if (source.getPsi() != null && mustBeDocumented()) return true + val annotationClassName = type.constructor.declarationDescriptor?.fqNameSafe?.asString() + return annotationClassName == "kotlin.Extension" + } + + fun AnnotationDescriptor.mustBeDocumented(): Boolean { + val annotationClass = type.constructor.declarationDescriptor as? Annotated ?: return false + return annotationClass.isDocumentedAnnotation() + } + + fun DocumentationNode.appendModifiers(descriptor: DeclarationDescriptor) { + val psi = (descriptor as DeclarationDescriptorWithSource).source.getPsi() as? KtModifierListOwner ?: return + KtTokens.MODIFIER_KEYWORDS_ARRAY.filter { it !in knownModifiers }.forEach { + if (psi.hasModifier(it)) { + appendTextNode(it.value, Kind.Modifier) } } } + fun DocumentationNode.isDeprecation() = name == "Deprecated" || name == "deprecated" + fun DocumentationNode.appendSourceLink(sourceElement: SourceElement) { appendSourceLink(sourceElement.getPsi(), options.sourceLinks) } @@ -364,8 +406,18 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } - fun DocumentationNode.appendChildren(descriptors: Iterable<DeclarationDescriptor>, kind: DocumentationReference.Kind) { - descriptors.forEach { descriptor -> appendChild(descriptor, kind) } + fun DocumentationNode.appendMembers(descriptors: Iterable<DeclarationDescriptor>) { + descriptors.forEach { descriptor -> + if (descriptor is CallableMemberDescriptor && descriptor.kind == CallableMemberDescriptor.Kind.FAKE_OVERRIDE) { + val baseDescriptor = descriptor.overriddenDescriptors.firstOrNull() + if (baseDescriptor != null) { + link(this, baseDescriptor, DocumentationReference.Kind.InheritedMember) + } + } + else { + appendChild(descriptor, DocumentationReference.Kind.Member) + } + } } fun DocumentationNode.appendInPageChildren(descriptors: Iterable<DeclarationDescriptor>, kind: DocumentationReference.Kind) { @@ -381,7 +433,7 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, val extensionClassDescriptor = descriptor.getExtensionClassDescriptor() if (extensionClassDescriptor != null && !isSamePackage(descriptor, extensionClassDescriptor) && !ErrorUtils.isError(extensionClassDescriptor)) { - val fqName = DescriptorUtils.getFqNameFromTopLevelClass(extensionClassDescriptor) + val fqName = DescriptorUtils.getFqNameSafe(extensionClassDescriptor) return externalClassNodes.getOrPut(fqName, { val newNode = DocumentationNode(fqName.asString(), Content.Empty, Kind.ExternalClass) append(newNode, DocumentationReference.Kind.Member) @@ -436,26 +488,23 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, else -> Kind.Class } val node = DocumentationNode(this, kind) - if (isInner) { - node.appendTextNode("inner", Kind.Modifier) - } node.appendSupertypes(this) if (getKind() != ClassKind.OBJECT && getKind() != ClassKind.ENUM_ENTRY) { node.appendInPageChildren(typeConstructor.parameters, DocumentationReference.Kind.Detail) val constructorsToDocument = if (getKind() == ClassKind.ENUM_CLASS) - constructors.filter { it.valueParameters.size() > 0 } + constructors.filter { it.valueParameters.size > 0 } else constructors - node.appendChildren(constructorsToDocument, DocumentationReference.Kind.Member) + node.appendMembers(constructorsToDocument) } val members = defaultType.memberScope.getAllDescriptors().filter { it != companionObjectDescriptor } - node.appendChildren(members, DocumentationReference.Kind.Member) + node.appendMembers(members) val companionObjectDescriptor = companionObjectDescriptor if (companionObjectDescriptor != null) { - node.appendChildren(companionObjectDescriptor.defaultType.memberScope.getAllDescriptors(), - DocumentationReference.Kind.Member) + node.appendMembers(companionObjectDescriptor.defaultType.memberScope.getAllDescriptors()) } node.appendAnnotations(this) + node.appendModifiers(this) node.appendSourceLink(source) register(this, node) return node @@ -498,8 +547,8 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, node.appendInPageChildren(valueParameters, DocumentationReference.Kind.Detail) node.appendType(returnType) node.appendAnnotations(this) + node.appendModifiers(this) node.appendSourceLink(source) - node.appendOperatorOverloadNote(this) overriddenDescriptors.forEach { addOverrideLink(it, this) @@ -520,64 +569,13 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } } - fun DocumentationNode.appendOperatorOverloadNote(descriptor: FunctionDescriptor) { - val operatorName = descriptor.getImplementedOperator() - if (operatorName != null) { - val content = Content() - content.append(ContentText("Implements ")) - content.strong { - text("operator ") - code { - text(operatorName) - } - } - val noteNode = DocumentationNode("", content, DocumentationNode.Kind.OverloadGroupNote) - append(noteNode, DocumentationReference.Kind.Detail) - } - } - - fun FunctionDescriptor.getImplementedOperator(): String? { - var arity = valueParameters.size() - if (containingDeclaration is ClassDescriptor) { - arity++ - } - if (extensionReceiverParameter != null) { - arity++ - } - - val token = if (arity == 2) { - OperatorConventions.BINARY_OPERATION_NAMES.inverse()[name] ?: - OperatorConventions.ASSIGNMENT_OPERATIONS.inverse()[name] ?: - OperatorConventions.BOOLEAN_OPERATIONS.inverse()[name] - } else if (arity == 1) { - OperatorConventions.UNARY_OPERATION_NAMES.inverse()[name] - } - else null - - if (token is JetSingleValueToken) { - return token.value - } - - val name = name.asString() - if (arity == 2 && name == "contains") { - return "in" - } - if (arity >= 2 && (name == "get" || name == "set")) { - return "[]" - } - if (arity == 2 && name == "equals" && valueParameters.size() == 1 && - KotlinBuiltIns.isNullableAny(valueParameters.first().type)) { - return "==" - } - return null - } - fun PropertyDescriptor.build(): DocumentationNode { val node = DocumentationNode(this, if (inCompanionObject()) Kind.CompanionObjectProperty else Kind.Property) node.appendInPageChildren(typeParameters, DocumentationReference.Kind.Detail) extensionReceiverParameter?.let { node.appendChild(it, DocumentationReference.Kind.Detail) } node.appendType(returnType) node.appendAnnotations(this) + node.appendModifiers(this) node.appendSourceLink(source) if (isVar) { node.appendTextNode("var", DocumentationNode.Kind.Modifier) @@ -617,15 +615,9 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, fun ValueParameterDescriptor.build(): DocumentationNode { val node = DocumentationNode(this, Kind.Parameter) - val varargType = varargElementType - if (varargType != null) { - node.appendTextNode("vararg", Kind.Annotation, DocumentationReference.Kind.Annotation) - node.appendType(varargType) - } else { - node.appendType(type) - } - if (hasDefaultValue()) { - val psi = source.getPsi() as? JetParameter + node.appendType(varargElementType ?: type) + if (declaresDefaultValue()) { + val psi = source.getPsi() as? KtParameter if (psi != null) { val defaultValueText = psi.defaultValue?.text if (defaultValueText != null) { @@ -634,6 +626,7 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } } node.appendAnnotations(this) + node.appendModifiers(this) register(this, node) return node } @@ -647,11 +640,14 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, if (prefix != "") { node.appendTextNode(prefix, Kind.Modifier) } + if (isReified) { + node.appendTextNode("reified", Kind.Modifier) + } - val builtIns = KotlinBuiltIns.getInstance() for (constraint in upperBounds) { - if (constraint == builtIns.defaultBound) + if (KotlinBuiltIns.isDefaultBound(constraint)) { continue + } node.appendType(constraint, Kind.UpperBound) } @@ -683,7 +679,7 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, return null } val node = DocumentationNode(annotationClass.name.asString(), Content.Empty, DocumentationNode.Kind.Annotation) - val arguments = allValueArguments.toList().sortBy { it.first.index } + val arguments = allValueArguments.toList().sortedBy { it.first.index } arguments.forEach { val valueNode = it.second.toDocumentationNode() if (valueNode != null) { @@ -695,6 +691,11 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, return node } + fun CompileTimeConstant<Any?>.build(): DocumentationNode? = when (this) { + is TypedCompileTimeConstant -> constantValue.toDocumentationNode() + else -> null + } + fun ConstantValue<*>.toDocumentationNode(): DocumentationNode? = value?.let { value -> when (value) { is String -> diff --git a/src/Kotlin/KotlinLanguageService.kt b/src/Kotlin/KotlinLanguageService.kt index 75675c6f..3c4b974f 100644 --- a/src/Kotlin/KotlinLanguageService.kt +++ b/src/Kotlin/KotlinLanguageService.kt @@ -6,7 +6,7 @@ import org.jetbrains.dokka.LanguageService.RenderMode * Implements [LanguageService] and provides rendering of symbols in Kotlin language */ class KotlinLanguageService : LanguageService { - private val visibilityModifiers = setOf("public", "protected", "private") + private val fullOnlyModifiers = setOf("public", "protected", "private", "inline", "noinline", "crossinline", "reified") override fun render(node: DocumentationNode, renderMode: RenderMode): ContentNode { return content { @@ -21,9 +21,9 @@ class KotlinLanguageService : LanguageService { DocumentationNode.Kind.EnumItem, DocumentationNode.Kind.ExternalClass -> if (renderMode == RenderMode.FULL) identifier(node.name) - DocumentationNode.Kind.TypeParameter -> renderTypeParameter(node) + DocumentationNode.Kind.TypeParameter -> renderTypeParameter(node, renderMode) DocumentationNode.Kind.Type, - DocumentationNode.Kind.UpperBound -> renderType(node) + DocumentationNode.Kind.UpperBound -> renderType(node, renderMode) DocumentationNode.Kind.Modifier -> renderModifier(node) DocumentationNode.Kind.Constructor, @@ -43,6 +43,77 @@ class KotlinLanguageService : LanguageService { } } + 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 + 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<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(" ") @@ -75,42 +146,36 @@ class KotlinLanguageService : LanguageService { } } - private fun ContentBlock.renderType(node: DocumentationNode) { - val typeArguments = node.details(DocumentationNode.Kind.Type) + private fun ContentBlock.renderType(node: DocumentationNode, renderMode: RenderMode) { + var typeArguments = node.details(DocumentationNode.Kind.Type) if (node.name == "Function${typeArguments.count() - 1}") { // lambda - symbol("(") - renderList(typeArguments.take(typeArguments.size() - 1), noWrap = true) { - renderType(it) + val isExtension = node.annotations.any { it.name == "Extension" } + if (isExtension) { + renderType(typeArguments.first(), renderMode) + symbol(".") + typeArguments = typeArguments.drop(1) } - symbol(")") - nbsp() - symbol("->") - nbsp() - renderType(typeArguments.last()) - return - } - if (node.name == "ExtensionFunction${typeArguments.count() - 2}") { - // extension lambda - renderType(typeArguments.first()) - symbol(".") symbol("(") - renderList(typeArguments.drop(1).take(typeArguments.size() - 2), noWrap = true) { - renderType(it) + renderList(typeArguments.take(typeArguments.size - 1), noWrap = true) { + renderType(it, renderMode) } symbol(")") nbsp() symbol("->") nbsp() - renderType(typeArguments.last()) + renderType(typeArguments.last(), renderMode) return } - renderSingleModifier(node) + 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) + renderType(it, renderMode) } symbol(">") } @@ -120,18 +185,23 @@ class KotlinLanguageService : LanguageService { } } - private fun ContentBlock.renderModifier(node: DocumentationNode) { + private fun ContentBlock.renderModifier(node: DocumentationNode, nowrap: Boolean = false) { when (node.name) { - "final", "internal", "var" -> {} + "final", "public", "var" -> {} else -> { keyword(node.name) - text(" ") + if (nowrap) { + nbsp() + } + else { + text(" ") + } } } } - private fun ContentBlock.renderTypeParameter(node: DocumentationNode) { - renderSingleModifier(node) + private fun ContentBlock.renderTypeParameter(node: DocumentationNode, renderMode: RenderMode) { + renderModifiersForNode(node, renderMode, true) identifier(node.name) @@ -141,26 +211,20 @@ class KotlinLanguageService : LanguageService { symbol(":") nbsp() renderList(constraints, noWrap=true) { - renderType(it) + renderType(it, renderMode) } } } - - private fun ContentBlock.renderSingleModifier(node: DocumentationNode) { - val modifier = node.details(DocumentationNode.Kind.Modifier).singleOrNull() - if (modifier != null) { - keyword(modifier.name) - nbsp() + private fun ContentBlock.renderParameter(node: DocumentationNode, renderMode: RenderMode) { + if (renderMode == RenderMode.FULL) { + renderAnnotationsForNode(node) } - } - - private fun ContentBlock.renderParameter(node: DocumentationNode) { - renderAnnotationsForNode(node) + renderModifiersForNode(node, renderMode) identifier(node.name, IdentifierKind.ParameterName) symbol(":") nbsp() val parameterType = node.detail(DocumentationNode.Kind.Type) - renderType(parameterType) + renderType(parameterType, renderMode) val valueNode = node.details(DocumentationNode.Kind.Value).firstOrNull() if (valueNode != null) { nbsp() @@ -170,38 +234,41 @@ class KotlinLanguageService : LanguageService { } } - private fun ContentBlock.renderTypeParametersForNode(node: DocumentationNode) { + private fun ContentBlock.renderTypeParametersForNode(node: DocumentationNode, renderMode: RenderMode) { val typeParameters = node.details(DocumentationNode.Kind.TypeParameter) if (typeParameters.any()) { symbol("<") renderList(typeParameters) { - renderTypeParameter(it) + renderTypeParameter(it, renderMode) } symbol(">") } } - private fun ContentBlock.renderSupertypesForNode(node: DocumentationNode) { + private fun ContentBlock.renderSupertypesForNode(node: DocumentationNode, renderMode: RenderMode) { val supertypes = node.details(DocumentationNode.Kind.Supertype) if (supertypes.any()) { nbsp() symbol(":") nbsp() renderList(supertypes) { - renderType(it) + indentedSoftLineBreak() + renderType(it, renderMode) } } } - private fun ContentBlock.renderModifiersForNode(node: DocumentationNode, renderMode: 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 visibilityModifiers) { + if (renderMode == RenderMode.SUMMARY && it.name in fullOnlyModifiers) { continue } - renderModifier(it) + renderModifier(it, nowrap) } } @@ -212,7 +279,7 @@ class KotlinLanguageService : LanguageService { } private fun ContentBlock.renderAnnotation(node: DocumentationNode) { - identifier(node.name, IdentifierKind.AnnotationName) + identifier("@" + node.name, IdentifierKind.AnnotationName) val parameters = node.details(DocumentationNode.Kind.Parameter) if (!parameters.isEmpty()) { symbol("(") @@ -225,82 +292,109 @@ class KotlinLanguageService : LanguageService { } private fun ContentBlock.renderClass(node: DocumentationNode, renderMode: RenderMode) { + if (renderMode == RenderMode.FULL) { + renderAnnotationsForNode(node) + } renderModifiersForNode(node, renderMode) - renderAnnotationsForNode(node) when (node.kind) { - DocumentationNode.Kind.Class -> keyword("class ") + DocumentationNode.Kind.Class, + DocumentationNode.Kind.AnnotationClass, + DocumentationNode.Kind.Enum -> keyword("class ") DocumentationNode.Kind.Interface -> keyword("interface ") - DocumentationNode.Kind.Enum -> keyword("enum class ") - DocumentationNode.Kind.AnnotationClass -> keyword("annotation class ") 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) - renderSupertypesForNode(node) + renderTypeParametersForNode(node, renderMode) + renderSupertypesForNode(node, renderMode) } - 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) + } renderModifiersForNode(node, renderMode) - renderAnnotationsForNode(node) 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) + renderTypeParametersForNode(node, renderMode) 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)) - symbol(".") - } + + renderReceiver(node, renderMode, signatureMapper) if (node.kind != org.jetbrains.dokka.DocumentationNode.Kind.Constructor) identifierOrDeprecated(node) symbol("(") - renderList(node.details(DocumentationNode.Kind.Parameter)) { - renderParameter(it) + val parameters = node.details(DocumentationNode.Kind.Parameter) + renderList(parameters) { + indentedSoftLineBreak() + renderParameter(it, renderMode) } - symbol(")") if (needReturnType(node)) { + if (parameters.isNotEmpty()) { + softLineBreak() + } + symbol(")") symbol(": ") - renderType(node.detail(DocumentationNode.Kind.Type)) + 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 -> true + else -> !node.isUnitReturnType() } - private fun ContentBlock.renderProperty(node: DocumentationNode, renderMode: RenderMode) { + 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) - renderAnnotationsForNode(node) when (node.kind) { DocumentationNode.Kind.Property, DocumentationNode.Kind.CompanionObjectProperty -> keyword("${node.getPropertyKeyword()} ") else -> throw IllegalArgumentException("Node $node is not a property") } - renderTypeParametersForNode(node) + renderTypeParametersForNode(node, renderMode) 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)) - symbol(".") - } + + renderReceiver(node, renderMode, signatureMapper) identifierOrDeprecated(node) symbol(": ") - renderType(node.detail(DocumentationNode.Kind.Type)) + renderType(node.detail(DocumentationNode.Kind.Type), renderMode) } fun DocumentationNode.getPropertyKeyword() = |