diff options
Diffstat (limited to 'src/Kotlin')
-rw-r--r-- | src/Kotlin/DocumentationBuilder.kt | 101 | ||||
-rw-r--r-- | src/Kotlin/KotlinLanguageService.kt | 54 |
2 files changed, 137 insertions, 18 deletions
diff --git a/src/Kotlin/DocumentationBuilder.kt b/src/Kotlin/DocumentationBuilder.kt index aeea4a55..baa17b5c 100644 --- a/src/Kotlin/DocumentationBuilder.kt +++ b/src/Kotlin/DocumentationBuilder.kt @@ -6,9 +6,21 @@ import org.jetbrains.jet.lang.types.* import org.jetbrains.jet.lang.types.lang.* import org.jetbrains.jet.lang.resolve.name.* import org.jetbrains.jet.lang.resolve.lazy.* +import org.jetbrains.jet.lang.descriptors.annotations.Annotated +import org.jetbrains.jet.lang.descriptors.annotations.AnnotationDescriptor +import org.jetbrains.jet.lang.resolve.DescriptorUtils +import org.jetbrains.jet.lang.resolve.constants.CompileTimeConstant +import com.intellij.openapi.util.text.StringUtil +import org.jetbrains.jet.lang.descriptors.impl.EnumEntrySyntheticClassDescriptor public data class DocumentationOptions(val includeNonPublic: Boolean = false) +private fun isSamePackage(descriptor1: DeclarationDescriptor, descriptor2: DeclarationDescriptor): Boolean { + val package1 = DescriptorUtils.getParentOfType(descriptor1, javaClass<PackageFragmentDescriptor>()) + val package2 = DescriptorUtils.getParentOfType(descriptor2, javaClass<PackageFragmentDescriptor>()) + return package1 != null && package2 != null && package1.fqName == package2.fqName +} + class DocumentationBuilder(val session: ResolveSession, val options: DocumentationOptions) { val visibleToDocumentation = setOf(Visibilities.INTERNAL, Visibilities.PROTECTED, Visibilities.PUBLIC) val descriptorToNode = hashMapOf<DeclarationDescriptor, DocumentationNode>() @@ -73,11 +85,20 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati fun DocumentationNode.appendSupertypes(descriptor: ClassDescriptor) { val superTypes = descriptor.getTypeConstructor().getSupertypes() for (superType in superTypes) { - if (superType.toString() != "Any") + if (!ignoreSupertype(superType)) appendType(superType, DocumentationNode.Kind.Supertype) } } + private fun ignoreSupertype(superType: JetType): Boolean { + val superClass = superType.getConstructor()?.getDeclarationDescriptor() as? ClassDescriptor + if (superClass != null) { + val fqName = DescriptorUtils.getFqNameSafe(superClass).asString() + return fqName == "kotlin.Annotation" || fqName == "kotlin.Enum" || fqName == "kotlin.Any" + } + return false + } + fun DocumentationNode.appendProjection(projection: TypeProjection, kind: DocumentationNode.Kind = DocumentationNode.Kind.Type) { val prefix = when (projection.getProjectionKind()) { Variance.IN_VARIANCE -> "in " @@ -104,6 +125,16 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati node.appendProjection(typeArgument) } + fun DocumentationNode.appendAnnotations(annotated: Annotated) { + annotated.getAnnotations().forEach { + val annotationNode = it.build() + if (annotationNode != null) { + append(annotationNode, + if (annotationNode.name == "deprecated") DocumentationReference.Kind.Deprecation else DocumentationReference.Kind.Annotation) + } + } + } + fun DocumentationNode.appendChild(descriptor: DeclarationDescriptor, kind: DocumentationReference.Kind) { // do not include generated code if (descriptor is CallableMemberDescriptor && descriptor.getKind() != CallableMemberDescriptor.Kind.DECLARATION) @@ -120,15 +151,35 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati descriptors.forEach { descriptor -> appendChild(descriptor, kind) } } + fun DocumentationNode.getParentForPackageMember(descriptor: DeclarationDescriptor, + externalClassNodes: MutableMap<FqName, DocumentationNode>): DocumentationNode { + if (descriptor is CallableMemberDescriptor) { + val extensionClassDescriptor = descriptor.getExtensionClassDescriptor() + if (extensionClassDescriptor != null && !isSamePackage(descriptor, extensionClassDescriptor)) { + val fqName = DescriptorUtils.getFqNameFromTopLevelClass(extensionClassDescriptor) + return externalClassNodes.getOrPut(fqName, { + val newNode = DocumentationNode(fqName.asString(), Content.Empty, Kind.ExternalClass) + append(newNode, DocumentationReference.Kind.Member) + newNode + }) + } + } + return this + } + fun DocumentationNode.appendFragments(fragments: Collection<PackageFragmentDescriptor>) { val descriptors = hashMapOf<String, List<DeclarationDescriptor>>() for ((name, parts) in fragments.groupBy { it.fqName }) { descriptors.put(name.asString(), parts.flatMap { it.getMemberScope().getAllDescriptors() }) } for ((packageName, declarations) in descriptors) { - println(" package $packageName: ${declarations.count()} nodes") + println(" package $packageName: ${declarations.count()} declarations") val packageNode = DocumentationNode(packageName, Content.Empty, Kind.Package) - packageNode.appendChildren(declarations, DocumentationReference.Kind.Member) + val externalClassNodes = hashMapOf<FqName, DocumentationNode>() + declarations.forEach { descriptor -> + val parent = packageNode.getParentForPackageMember(descriptor, externalClassNodes) + parent.appendChild(descriptor, DocumentationReference.Kind.Member) + } append(packageNode, DocumentationReference.Kind.Member) } } @@ -153,6 +204,7 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati ClassKind.CLASS_OBJECT -> Kind.Object ClassKind.TRAIT -> Kind.Interface ClassKind.ENUM_CLASS -> Kind.Enum + ClassKind.ANNOTATION_CLASS -> Kind.AnnotationClass ClassKind.ENUM_ENTRY -> Kind.EnumItem else -> Kind.Class } @@ -168,6 +220,7 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati node.appendChildren(classObjectDescriptor.getDefaultType().getMemberScope().getAllDescriptors(), DocumentationReference.Kind.Member) } + node.appendAnnotations(this) register(this, node) return node } @@ -182,6 +235,15 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati private fun DeclarationDescriptor.inClassObject() = getContainingDeclaration().let { it is ClassDescriptor && it.getKind() == ClassKind.CLASS_OBJECT } + fun CallableMemberDescriptor.getExtensionClassDescriptor(): ClassifierDescriptor? { + val extensionReceiver = getExtensionReceiverParameter() + if (extensionReceiver != null) { + val type = extensionReceiver.getType() + return type.getConstructor().getDeclarationDescriptor() as? ClassDescriptor + } + return null + } + fun FunctionDescriptor.build(): DocumentationNode { val node = DocumentationNode(this, if (inClassObject()) Kind.ClassObjectFunction else Kind.Function) @@ -189,6 +251,7 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati getExtensionReceiverParameter()?.let { node.appendChild(it, DocumentationReference.Kind.Detail) } node.appendChildren(getValueParameters(), DocumentationReference.Kind.Detail) node.appendType(getReturnType()) + node.appendAnnotations(this) register(this, node) return node @@ -210,6 +273,7 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati node.appendChildren(getTypeParameters(), DocumentationReference.Kind.Detail) getExtensionReceiverParameter()?.let { node.appendChild(it, DocumentationReference.Kind.Detail) } node.appendType(getReturnType()) + node.appendAnnotations(this) getGetter()?.let { if (!it.isDefault()) node.appendChild(it, DocumentationReference.Kind.Member) @@ -226,6 +290,7 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati fun ValueParameterDescriptor.build(): DocumentationNode { val node = DocumentationNode(this, Kind.Parameter) node.appendType(getType()) + node.appendAnnotations(this) register(this, node) return node } @@ -264,6 +329,36 @@ class DocumentationBuilder(val session: ResolveSession, val options: Documentati return node } + fun AnnotationDescriptor.build(): DocumentationNode? { + val annotationClass = getType().getConstructor().getDeclarationDescriptor() + if (ErrorUtils.isError(annotationClass)) { + return null + } + val node = DocumentationNode(annotationClass.getName().asString(), Content.Empty, DocumentationNode.Kind.Annotation) + val arguments = getAllValueArguments().toList().sortBy { it.first.getIndex() } + arguments.forEach { + val valueNode = it.second.build() + if (valueNode != null) { + val paramNode = DocumentationNode(it.first.getName().asString(), Content.Empty, DocumentationNode.Kind.Parameter) + paramNode.append(valueNode, DocumentationReference.Kind.Detail) + node.append(paramNode, DocumentationReference.Kind.Detail) + } + } + return node + } + + fun CompileTimeConstant<out Any?>.build(): DocumentationNode? { + val value = getValue() + val valueString = when(value) { + is String -> + "\"" + StringUtil.escapeStringCharacters(value) + "\"" + is EnumEntrySyntheticClassDescriptor -> + value.getContainingDeclaration().getName().asString() + "." + value.getName() + else -> value?.toString() + } + return if (valueString != null) DocumentationNode(valueString, Content.Empty, DocumentationNode.Kind.Value) else null + } + /** * Generates cross-references for documentation such as extensions for a type, inheritors, etc * diff --git a/src/Kotlin/KotlinLanguageService.kt b/src/Kotlin/KotlinLanguageService.kt index a4016849..40f47639 100644 --- a/src/Kotlin/KotlinLanguageService.kt +++ b/src/Kotlin/KotlinLanguageService.kt @@ -1,28 +1,18 @@ package org.jetbrains.dokka -import org.jetbrains.dokka.symbol -import org.jetbrains.dokka.text -import org.jetbrains.dokka.identifier -import org.jetbrains.dokka.link -import org.jetbrains.dokka.keyword -import org.jetbrains.dokka.LanguageService -import org.jetbrains.dokka.DocumentationNode -import org.jetbrains.dokka.ContentNode -import org.jetbrains.dokka -import org.jetbrains.dokka.ContentText - /** * Implements [LanguageService] and provides rendering of symbols in Kotlin language */ class KotlinLanguageService : LanguageService { override fun render(node: DocumentationNode): ContentNode { - return dokka.content { + return content { when (node.kind) { DocumentationNode.Kind.Package -> renderPackage(node) DocumentationNode.Kind.Class, DocumentationNode.Kind.Interface, DocumentationNode.Kind.Enum, DocumentationNode.Kind.EnumItem, + DocumentationNode.Kind.AnnotationClass, DocumentationNode.Kind.Object -> renderClass(node) DocumentationNode.Kind.TypeParameter -> renderTypeParameter(node) @@ -135,6 +125,7 @@ class KotlinLanguageService : LanguageService { } private fun ContentNode.renderParameter(node: DocumentationNode) { + renderAnnotationsForNode(node) identifier(node.name) symbol(": ") val parameterType = node.detail(DocumentationNode.Kind.Type) @@ -171,24 +162,46 @@ class KotlinLanguageService : LanguageService { } } + private fun ContentNode.renderAnnotationsForNode(node: DocumentationNode) { + node.annotations.forEach { + renderAnnotation(it) + } + } + + private fun ContentNode.renderAnnotation(node: DocumentationNode) { + identifier(node.name) + val parameters = node.details(DocumentationNode.Kind.Parameter) + if (!parameters.isEmpty()) { + symbol("(") + renderList(parameters) { + text(it.detail(DocumentationNode.Kind.Value).name) + } + symbol(")") + } + text(" ") + } + private fun ContentNode.renderClass(node: DocumentationNode) { renderModifiersForNode(node) + renderAnnotationsForNode(node) when (node.kind) { DocumentationNode.Kind.Class -> keyword("class ") DocumentationNode.Kind.Interface -> keyword("trait ") 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") } - identifier(node.name) + identifierOrDeprecated(node) renderTypeParametersForNode(node) renderSupertypesForNode(node) } private fun ContentNode.renderFunction(node: DocumentationNode) { renderModifiersForNode(node) + renderAnnotationsForNode(node) when (node.kind) { DocumentationNode.Kind.Constructor -> identifier(node.owner!!.name) DocumentationNode.Kind.Function, @@ -203,7 +216,7 @@ class KotlinLanguageService : LanguageService { } if (node.kind != org.jetbrains.dokka.DocumentationNode.Kind.Constructor) - identifier(node.name) + identifierOrDeprecated(node) symbol("(") renderList(node.details(DocumentationNode.Kind.Parameter)) { @@ -218,6 +231,7 @@ class KotlinLanguageService : LanguageService { private fun ContentNode.renderProperty(node: DocumentationNode) { renderModifiersForNode(node) + renderAnnotationsForNode(node) when (node.kind) { DocumentationNode.Kind.Property, DocumentationNode.Kind.ClassObjectProperty -> keyword("val ") @@ -230,8 +244,18 @@ class KotlinLanguageService : LanguageService { symbol(".") } - identifier(node.name) + identifierOrDeprecated(node) symbol(": ") renderType(node.detail(DocumentationNode.Kind.Type)) } + + fun ContentNode.identifierOrDeprecated(node: DocumentationNode) { + if (node.deprecation != null) { + val strike = ContentStrikethrough() + strike.identifier(node.name) + append(strike) + } else { + identifier(node.name) + } + } }
\ No newline at end of file |