From ded569657d1f16905180313593336963c467f242 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Fri, 30 Oct 2015 19:33:29 +0100 Subject: load KDoc comments when building javadoc --- src/Java/JavaDocumentationBuilder.kt | 103 ++++++++++++++----------- src/Kotlin/DocumentationBuilder.kt | 13 ++-- src/Kotlin/KotlinAsJavaDocumentationBuilder.kt | 37 +++++++-- test/src/TestAPI.kt | 1 + test/src/model/KotlinAsJavaTest.kt | 9 +++ 5 files changed, 103 insertions(+), 60 deletions(-) diff --git a/src/Java/JavaDocumentationBuilder.kt b/src/Java/JavaDocumentationBuilder.kt index 660ac4a8..e0cc53a0 100644 --- a/src/Java/JavaDocumentationBuilder.kt +++ b/src/Java/JavaDocumentationBuilder.kt @@ -1,31 +1,30 @@ package org.jetbrains.dokka import com.intellij.psi.* -import com.intellij.psi.javadoc.* +import com.intellij.psi.javadoc.PsiDocTag +import com.intellij.psi.javadoc.PsiDocTagValue +import com.intellij.psi.javadoc.PsiDocToken +import com.intellij.psi.javadoc.PsiInlineDocTag import org.jetbrains.dokka.DocumentationNode.Kind import org.jsoup.Jsoup import org.jsoup.nodes.Element import org.jsoup.nodes.Node import org.jsoup.nodes.TextNode -public class JavaDocumentationBuilder(private val options: DocumentationOptions, - private val refGraph: NodeReferenceGraph) { - fun appendFile(file: PsiJavaFile, module: DocumentationModule) { - if (file.classes.all { skipElement(it) }) { - return - } - val packageNode = module.findOrCreatePackageNode(file.packageName, emptyMap()) - appendClasses(packageNode, file.classes) - } - - fun appendClasses(packageNode: DocumentationNode, classes: Array) { - packageNode.appendChildren(classes) { build() } +data class JavadocParseResult(val content: Content, val deprecatedContent: Content?) { + companion object { + val Empty = JavadocParseResult(Content.Empty, null) } +} - data class JavadocParseResult(val content: Content, val deprecatedContent: Content?) +interface JavaDocumentationParser { + fun parseDocumentation(element: PsiNamedElement): JavadocParseResult +} - fun parseDocumentation(docComment: PsiDocComment?): JavadocParseResult { - if (docComment == null) return JavadocParseResult(Content.Empty, null) +class JavadocParser(private val refGraph: NodeReferenceGraph) : JavaDocumentationParser { + override fun parseDocumentation(element: PsiNamedElement): JavadocParseResult { + val docComment = (element as? PsiDocCommentOwner)?.docComment + if (docComment == null) return JavadocParseResult.Empty val result = MutableContent() var deprecatedContent: Content? = null val para = ContentParagraph() @@ -169,6 +168,49 @@ public class JavaDocumentationBuilder(private val options: DocumentationOptions, } return null } +} + + +private fun getSignature(element: PsiElement?) = when(element) { + is PsiClass -> element.qualifiedName + is PsiField -> element.containingClass!!.qualifiedName + "#" + element.name + is PsiMethod -> + element.containingClass!!.qualifiedName + "#" + element.name + "(" + + element.parameterList.parameters.map { it.type.typeSignature() }.joinToString(",") + ")" + else -> null +} + +private fun PsiType.typeSignature(): String = when(this) { + is PsiArrayType -> "Array<${componentType.typeSignature()}>" + else -> mapTypeName(this) +} + +private fun mapTypeName(psiType: PsiType): String = when (psiType) { + PsiType.VOID -> "Unit" + is PsiPrimitiveType -> psiType.canonicalText.capitalize() + is PsiClassType -> { + val psiClass = psiType.resolve() + if (psiClass?.qualifiedName == "java.lang.Object") "Any" else psiType.className + } + is PsiEllipsisType -> mapTypeName(psiType.componentType) + is PsiArrayType -> "Array" + else -> psiType.canonicalText +} + +class JavaDocumentationBuilder(private val options: DocumentationOptions, + private val refGraph: NodeReferenceGraph, + private val docParser: JavaDocumentationParser = JavadocParser(refGraph)) { + fun appendFile(file: PsiJavaFile, module: DocumentationModule) { + if (file.classes.all { skipElement(it) }) { + return + } + val packageNode = module.findOrCreatePackageNode(file.packageName, emptyMap()) + appendClasses(packageNode, file.classes) + } + + fun appendClasses(packageNode: DocumentationNode, classes: Array) { + packageNode.appendChildren(classes) { build() } + } fun register(element: PsiElement, node: DocumentationNode) { val signature = getSignature(element) @@ -190,25 +232,10 @@ public class JavaDocumentationBuilder(private val options: DocumentationOptions, refGraph.link(qualifiedName, node, kind) } } - - private fun getSignature(element: PsiElement?) = when(element) { - is PsiClass -> element.qualifiedName - is PsiField -> element.containingClass!!.qualifiedName + "#" + element.name - is PsiMethod -> - element.containingClass!!.qualifiedName + "#" + element.name + "(" + - element.parameterList.parameters.map { it.type.typeSignature() }.joinToString(",") + ")" - else -> null - } - - private fun PsiType.typeSignature(): String = when(this) { - is PsiArrayType -> "Array<${componentType.typeSignature()}>" - else -> mapTypeName(this) - } - fun DocumentationNode(element: PsiNamedElement, kind: Kind, name: String = element.name ?: ""): DocumentationNode { - val (docComment, deprecatedContent) = parseDocumentation((element as? PsiDocCommentOwner)?.docComment) + val (docComment, deprecatedContent) = docParser.parseDocumentation(element) val node = DocumentationNode(name, docComment, kind) if (element is PsiModifierListOwner) { node.appendModifiers(element) @@ -378,18 +405,6 @@ public class JavaDocumentationBuilder(private val options: DocumentationOptions, return node } - private fun mapTypeName(psiType: PsiType): String = when (psiType) { - PsiType.VOID -> "Unit" - is PsiPrimitiveType -> psiType.canonicalText.capitalize() - is PsiClassType -> { - val psiClass = psiType.resolve() - if (psiClass?.qualifiedName == "java.lang.Object") "Any" else psiType.className - } - is PsiEllipsisType -> mapTypeName(psiType.componentType) - is PsiArrayType -> "Array" - else -> psiType.canonicalText - } - fun PsiAnnotation.build(): DocumentationNode { val node = DocumentationNode(nameReferenceElement?.text ?: "", Content.Empty, DocumentationNode.Kind.Annotation) parameterList.attributes.forEach { diff --git a/src/Kotlin/DocumentationBuilder.kt b/src/Kotlin/DocumentationBuilder.kt index 375ca4bd..b5b079de 100644 --- a/src/Kotlin/DocumentationBuilder.kt +++ b/src/Kotlin/DocumentationBuilder.kt @@ -45,11 +45,10 @@ private fun isSamePackage(descriptor1: DeclarationDescriptor, descriptor2: Decla } interface PackageDocumentationBuilder { - fun buildPackageDocumentation(project: Project, + fun buildPackageDocumentation(documentationBuilder: DocumentationBuilder, packageName: FqName, packageNode: DocumentationNode, - declarations: List, - options: DocumentationOptions, refGraph: NodeReferenceGraph, logger: DokkaLogger) + declarations: List) } class DocumentationBuilder(val resolutionFacade: ResolutionFacade, @@ -426,7 +425,7 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, if (options.skipEmptyPackages && declarations.none { it.isDocumented() }) continue logger.info(" package $packageName: ${declarations.count()} declarations") val packageNode = findOrCreatePackageNode(packageName.asString(), packageContent) - packageDocumentationBuilder.buildPackageDocumentation(resolutionFacade.project, packageName, packageNode, declarations, options, refGraph, logger) + packageDocumentationBuilder.buildPackageDocumentation(this@DocumentationBuilder, packageName, packageNode, declarations) } } @@ -666,12 +665,10 @@ class DocumentationBuilder(val resolutionFacade: ResolutionFacade, } inner class KotlinPackageDocumentationBuilder : PackageDocumentationBuilder { - override fun buildPackageDocumentation(project: Project, + override fun buildPackageDocumentation(documentationBuilder: DocumentationBuilder, packageName: FqName, packageNode: DocumentationNode, - declarations: List, - options: DocumentationOptions, - refGraph: NodeReferenceGraph, logger: DokkaLogger) { + declarations: List) { val externalClassNodes = hashMapOf() declarations.forEach { descriptor -> if (descriptor.isDocumented()) { diff --git a/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt b/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt index ae295769..a32e9a1f 100644 --- a/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt +++ b/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt @@ -1,27 +1,48 @@ package org.jetbrains.dokka -import com.intellij.openapi.project.Project import com.intellij.psi.JavaPsiFacade +import com.intellij.psi.PsiNamedElement import org.jetbrains.kotlin.asJava.KotlinLightElement import org.jetbrains.kotlin.descriptors.DeclarationDescriptor import org.jetbrains.kotlin.name.FqName +import org.jetbrains.kotlin.psi.KtParameter +import org.jetbrains.kotlin.psi.KtPropertyAccessor class KotlinAsJavaDocumentationBuilder() : PackageDocumentationBuilder { - override fun buildPackageDocumentation(project: Project, + override fun buildPackageDocumentation(documentationBuilder: DocumentationBuilder, packageName: FqName, packageNode: DocumentationNode, - declarations: List, - options: DocumentationOptions, - refGraph: NodeReferenceGraph, - logger: DokkaLogger) { + declarations: List) { + val project = documentationBuilder.resolutionFacade.project val psiPackage = JavaPsiFacade.getInstance(project).findPackage(packageName.asString()) if (psiPackage == null) { - logger.error("Cannot find Java package by qualified name: ${packageName.asString()}") + documentationBuilder.logger.error("Cannot find Java package by qualified name: ${packageName.asString()}") return } - val javaDocumentationBuilder = JavaDocumentationBuilder(options, refGraph) + + val javaDocumentationBuilder = JavaDocumentationBuilder(documentationBuilder.options, + documentationBuilder.refGraph, + KotlinAsJavaDocumentationParser(documentationBuilder)) + psiPackage.classes.filter { it is KotlinLightElement<*, *> }.forEach { javaDocumentationBuilder.appendClasses(packageNode, arrayOf(it)) } } } + +class KotlinAsJavaDocumentationParser(val documentationBuilder: DocumentationBuilder) : JavaDocumentationParser { + override fun parseDocumentation(element: PsiNamedElement): JavadocParseResult { + val kotlinLightElement = element as? KotlinLightElement<*, *> ?: return JavadocParseResult.Empty + val origin = kotlinLightElement.getOrigin() ?: return JavadocParseResult.Empty + if (origin is KtParameter) { + // LazyDeclarationResolver does not support setter parameters + val grandFather = origin.parent?.parent + if (grandFather is KtPropertyAccessor) { + return JavadocParseResult.Empty + } + } + val descriptor = documentationBuilder.session.resolveToDescriptor(origin) + val content = documentationBuilder.parseDocumentation(descriptor) + return JavadocParseResult(content, null) + } +} diff --git a/test/src/TestAPI.kt b/test/src/TestAPI.kt index c4be6623..4a0e4fb4 100644 --- a/test/src/TestAPI.kt +++ b/test/src/TestAPI.kt @@ -133,6 +133,7 @@ fun StringBuilder.appendNode(node: ContentNode): StringBuilder { is ContentBlock -> { appendChildren(node) } + is ContentEmpty -> { /* nothing */ } else -> throw IllegalStateException("Don't know how to format node $node") } return this diff --git a/test/src/model/KotlinAsJavaTest.kt b/test/src/model/KotlinAsJavaTest.kt index 25ee5fad..0b609a2b 100644 --- a/test/src/model/KotlinAsJavaTest.kt +++ b/test/src/model/KotlinAsJavaTest.kt @@ -19,6 +19,15 @@ class KotlinAsJavaTest { assertEquals(DocumentationNode.Kind.CompanionObjectFunction, fn.kind) } } + + @Test fun propertyWithComment() { + verifyModelAsJava("test/data/comments/oneLineDoc.kt") { model -> + val facadeClass = model.members.single().members.single { it.name == "OneLineDocKt" } + val getter = facadeClass.members.single { it.name == "getProperty" } + assertEquals(DocumentationNode.Kind.CompanionObjectFunction, getter.kind) + assertEquals("doc", getter.content.summary.toTestString()) + } + } } fun verifyModelAsJava(source: String, -- cgit