From cb9d0bcfe4a2ba8b874f69f9ab208629a44a5a76 Mon Sep 17 00:00:00 2001 From: Dmitry Jemerov Date: Wed, 4 Nov 2015 16:46:10 +0100 Subject: extract DescriptorDocumentationParser out of DocumentationBuilder --- src/Kotlin/ContentBuilder.kt | 70 --------- src/Kotlin/DescriptorDocumentationParser.kt | 194 +++++++++++++++++++++++++ src/Kotlin/DocumentationBuilder.kt | 120 +-------------- src/Kotlin/KotlinAsJavaDocumentationBuilder.kt | 17 ++- 4 files changed, 212 insertions(+), 189 deletions(-) create mode 100644 src/Kotlin/DescriptorDocumentationParser.kt (limited to 'src/Kotlin') diff --git a/src/Kotlin/ContentBuilder.kt b/src/Kotlin/ContentBuilder.kt index c981eb42..9fe0db6e 100644 --- a/src/Kotlin/ContentBuilder.kt +++ b/src/Kotlin/ContentBuilder.kt @@ -3,16 +3,6 @@ package org.jetbrains.dokka 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.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 { @@ -135,63 +125,3 @@ public fun buildInlineContentTo(tree: MarkdownNode, target: ContentBlock, linkRe } } -fun DocumentationBuilder.functionBody(descriptor: DeclarationDescriptor, functionName: String?): ContentNode { - if (functionName == null) { - 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).asJetScope() - val rootPackage = resolutionFacade.moduleDescriptor.getPackage(FqName.ROOT) - val rootScope = rootPackage.memberScope - val symbol = resolveInScope(functionName, scope) ?: resolveInScope(functionName, rootScope) - if (symbol == null) { - logger.warn("Unresolved function $functionName in @sample in ${descriptor.signature()}") - return ContentBlockCode().let() { it.append(ContentText("Unresolved: $functionName")); it } - } - val psiElement = DescriptorToSourceUtils.descriptorToDeclaration(symbol) - if (psiElement == null) { - logger.warn("Can't find source for function $functionName in @sample in ${descriptor.signature()}") - return ContentBlockCode().let() { it.append(ContentText("Source not found: $functionName")); it } - } - - val text = when (psiElement) { - is KtDeclarationWithBody -> ContentBlockCode().let() { - val bodyExpression = psiElement.bodyExpression - when (bodyExpression) { - is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}") - else -> bodyExpression!!.text - } - } - else -> psiElement.text - } - - 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) }.joinToString("\n") - return ContentBlockCode("kotlin").let() { it.append(ContentText(finalText)); it } -} - -private fun DocumentationBuilder.resolveInScope(functionName: String, scope: KtScope): DeclarationDescriptor? { - var currentScope = scope - val parts = functionName.split('.') - - var symbol: DeclarationDescriptor? = null - - for (part in parts) { - // short name - val symbolName = Name.guess(part) - val partSymbol = currentScope.getAllDescriptors().filter { it.name == symbolName }.firstOrNull() - - if (partSymbol == null) { - symbol = null - break - } - currentScope = if (partSymbol is ClassDescriptor) - partSymbol.defaultType.memberScope - else - getResolutionScope(resolutionFacade, partSymbol).asJetScope() - symbol = partSymbol - } - - return symbol -} diff --git a/src/Kotlin/DescriptorDocumentationParser.kt b/src/Kotlin/DescriptorDocumentationParser.kt new file mode 100644 index 00000000..839c3710 --- /dev/null +++ b/src/Kotlin/DescriptorDocumentationParser.kt @@ -0,0 +1,194 @@ +package org.jetbrains.dokka.Kotlin + +import com.google.inject.Inject +import com.intellij.psi.PsiDocCommentOwner +import com.intellij.psi.PsiNamedElement +import com.intellij.psi.util.PsiTreeUtil +import org.jetbrains.dokka.* +import org.jetbrains.kotlin.descriptors.* +import org.jetbrains.kotlin.idea.kdoc.KDocFinder +import org.jetbrains.kotlin.idea.kdoc.getResolutionScope +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.load.java.descriptors.JavaCallableMemberDescriptor +import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor +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.DescriptorUtils +import org.jetbrains.kotlin.resolve.scopes.KtScope +import org.jetbrains.kotlin.resolve.scopes.utils.asJetScope +import org.jetbrains.kotlin.resolve.source.PsiSourceElement + +class DescriptorDocumentationParser + @Inject constructor(val options: DocumentationOptions, + val logger: DokkaLogger, + val linkResolver: DeclarationLinkResolver, + val resolutionFacade: DokkaResolutionFacade, + val refGraph: NodeReferenceGraph) +{ + fun parseDocumentation(descriptor: DeclarationDescriptor): Content = parseDocumentationAndDetails(descriptor).first + + fun parseDocumentationAndDetails(descriptor: DeclarationDescriptor): Pair Unit> { + if (descriptor is JavaClassDescriptor || descriptor is JavaCallableMemberDescriptor) { + return parseJavadoc(descriptor) + } + + val kdoc = KDocFinder.findKDoc(descriptor) ?: findStdlibKDoc(descriptor) + if (kdoc == null) { + if (options.reportUndocumented && !descriptor.isDeprecated() && + descriptor !is ValueParameterDescriptor && descriptor !is TypeParameterDescriptor && + descriptor !is PropertyAccessorDescriptor) { + logger.warn("No documentation for ${descriptor.signatureWithSourceLocation()}") + } + return Content.Empty to { node -> } + } + var kdocText = kdoc.getContent() + // workaround for code fence parsing problem in IJ markdown parser + if (kdocText.endsWith("```") || kdocText.endsWith("~~~")) { + kdocText += "\n" + } + val tree = parseMarkdown(kdocText) + //println(tree.toTestString()) + val content = buildContent(tree, { href -> linkResolver.resolveContentLink(descriptor, href) }) + if (kdoc is KDocSection) { + val tags = kdoc.getTags() + tags.forEach { + when (it.name) { + "sample" -> + content.append(functionBody(descriptor, it.getSubjectName())) + "see" -> + content.addTagToSeeAlso(descriptor, it) + else -> { + val section = content.addSection(javadocSectionDisplayName(it.name), it.getSubjectName()) + val sectionContent = it.getContent() + val markdownNode = parseMarkdown(sectionContent) + buildInlineContentTo(markdownNode, section, { href -> linkResolver.resolveContentLink(descriptor, href) }) + } + } + } + } + return content to { node -> } + } + + /** + * Special case for generating stdlib documentation (the Any class to which the override chain will resolve + * is not the same one as the Any class included in the source scope). + */ + fun findStdlibKDoc(descriptor: DeclarationDescriptor): KDocTag? { + if (descriptor !is CallableMemberDescriptor) { + return null + } + val name = descriptor.name.asString() + if (name == "equals" || name == "hashCode" || name == "toString") { + var deepestDescriptor: CallableMemberDescriptor = descriptor + while (!deepestDescriptor.overriddenDescriptors.isEmpty()) { + deepestDescriptor = deepestDescriptor.overriddenDescriptors.first() + } + if (DescriptorUtils.getFqName(deepestDescriptor.containingDeclaration).asString() == "kotlin.Any") { + val anyClassDescriptors = resolutionFacade.resolveSession.getTopLevelClassDescriptors( + FqName.fromSegments(listOf("kotlin", "Any")), NoLookupLocation.UNSORTED) + anyClassDescriptors.forEach { + val anyMethod = it.getMemberScope(listOf()).getFunctions(descriptor.name, NoLookupLocation.UNSORTED).single() + val kdoc = KDocFinder.findKDoc(anyMethod) + if (kdoc != null) { + return kdoc + } + } + } + } + return null + } + + fun parseJavadoc(descriptor: DeclarationDescriptor): Pair Unit> { + val psi = ((descriptor as? DeclarationDescriptorWithSource)?.source as? PsiSourceElement)?.psi + if (psi is PsiDocCommentOwner) { + val parseResult = JavadocParser(refGraph).parseDocumentation(psi as PsiNamedElement) + return parseResult.content to { node -> + parseResult.deprecatedContent?.let { + val deprecationNode = DocumentationNode("", it, DocumentationNode.Kind.Modifier) + node.append(deprecationNode, DocumentationReference.Kind.Deprecation) + } + } + } + return Content.Empty to { node -> } + } + + fun KDocSection.getTags(): Array = 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("See Also") ?: addSection("See Also", null) + val link = linkResolver.resolveContentLink(descriptor, subjectName) + link.append(ContentText(subjectName)) + val para = ContentParagraph() + para.append(link) + seeSection.append(para) + } + } + + private fun functionBody(descriptor: DeclarationDescriptor, functionName: String?): ContentNode { + if (functionName == null) { + 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).asJetScope() + val rootPackage = resolutionFacade.moduleDescriptor.getPackage(FqName.ROOT) + val rootScope = rootPackage.memberScope + val symbol = resolveInScope(functionName, scope) ?: resolveInScope(functionName, rootScope) + if (symbol == null) { + logger.warn("Unresolved function $functionName in @sample in ${descriptor.signature()}") + return ContentBlockCode().let() { it.append(ContentText("Unresolved: $functionName")); it } + } + val psiElement = DescriptorToSourceUtils.descriptorToDeclaration(symbol) + if (psiElement == null) { + logger.warn("Can't find source for function $functionName in @sample in ${descriptor.signature()}") + return ContentBlockCode().let() { it.append(ContentText("Source not found: $functionName")); it } + } + + val text = when (psiElement) { + is KtDeclarationWithBody -> ContentBlockCode().let() { + val bodyExpression = psiElement.bodyExpression + when (bodyExpression) { + is KtBlockExpression -> bodyExpression.text.removeSurrounding("{", "}") + else -> bodyExpression!!.text + } + } + else -> psiElement.text + } + + 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) }.joinToString("\n") + return ContentBlockCode("kotlin").let() { it.append(ContentText(finalText)); it } + } + + private fun resolveInScope(functionName: String, scope: KtScope): DeclarationDescriptor? { + var currentScope = scope + val parts = functionName.split('.') + + var symbol: DeclarationDescriptor? = null + + for (part in parts) { + // short name + val symbolName = Name.guess(part) + val partSymbol = currentScope.getAllDescriptors().filter { it.name == symbolName }.firstOrNull() + + if (partSymbol == null) { + symbol = null + break + } + currentScope = if (partSymbol is ClassDescriptor) + partSymbol.defaultType.memberScope + else + getResolutionScope(resolutionFacade, partSymbol).asJetScope() + symbol = partSymbol + } + + return symbol + } +} diff --git a/src/Kotlin/DocumentationBuilder.kt b/src/Kotlin/DocumentationBuilder.kt index a68f6bb5..cdbff877 100644 --- a/src/Kotlin/DocumentationBuilder.kt +++ b/src/Kotlin/DocumentationBuilder.kt @@ -2,11 +2,9 @@ package org.jetbrains.dokka import com.google.inject.Inject import com.intellij.openapi.util.text.StringUtil -import com.intellij.psi.PsiDocCommentOwner import com.intellij.psi.PsiJavaFile -import com.intellij.psi.PsiNamedElement -import com.intellij.psi.util.PsiTreeUtil import org.jetbrains.dokka.DocumentationNode.Kind +import org.jetbrains.dokka.Kotlin.DescriptorDocumentationParser import org.jetbrains.kotlin.builtins.KotlinBuiltIns import org.jetbrains.kotlin.descriptors.* import org.jetbrains.kotlin.descriptors.annotations.Annotated @@ -15,12 +13,8 @@ import org.jetbrains.kotlin.descriptors.impl.EnumEntrySyntheticClassDescriptor import org.jetbrains.kotlin.idea.caches.resolve.KotlinCacheService import org.jetbrains.kotlin.idea.caches.resolve.getModuleInfo import org.jetbrains.kotlin.idea.kdoc.KDocFinder -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.KtTokens -import org.jetbrains.kotlin.load.java.descriptors.JavaCallableMemberDescriptor -import org.jetbrains.kotlin.load.java.descriptors.JavaClassDescriptor import org.jetbrains.kotlin.load.java.structure.impl.JavaClassImpl import org.jetbrains.kotlin.name.FqName import org.jetbrains.kotlin.psi.KtModifierListOwner @@ -62,7 +56,7 @@ interface PackageDocumentationBuilder { class DocumentationBuilder @Inject constructor(val resolutionFacade: DokkaResolutionFacade, - val linkResolver: DeclarationLinkResolver, + val descriptorDocumentationParser: DescriptorDocumentationParser, val options: DocumentationOptions, val refGraph: NodeReferenceGraph, val logger: DokkaLogger) @@ -76,108 +70,6 @@ class DocumentationBuilder KtTokens.OPEN_KEYWORD, KtTokens.FINAL_KEYWORD, KtTokens.ABSTRACT_KEYWORD, KtTokens.SEALED_KEYWORD, KtTokens.OVERRIDE_KEYWORD) - - fun parseDocumentation(descriptor: DeclarationDescriptor): Content = parseDocumentationAndDetails(descriptor).first - - fun parseDocumentationAndDetails(descriptor: DeclarationDescriptor): Pair Unit> { - if (descriptor is JavaClassDescriptor || descriptor is JavaCallableMemberDescriptor) { - return parseJavadoc(descriptor) - } - - val kdoc = KDocFinder.findKDoc(descriptor) ?: findStdlibKDoc(descriptor) - if (kdoc == null) { - if (options.reportUndocumented && !descriptor.isDeprecated() && - descriptor !is ValueParameterDescriptor && descriptor !is TypeParameterDescriptor && - descriptor !is PropertyAccessorDescriptor) { - logger.warn("No documentation for ${descriptor.signatureWithSourceLocation()}") - } - return Content.Empty to { node -> } - } - var kdocText = kdoc.getContent() - // workaround for code fence parsing problem in IJ markdown parser - if (kdocText.endsWith("```") || kdocText.endsWith("~~~")) { - kdocText += "\n" - } - val tree = parseMarkdown(kdocText) - //println(tree.toTestString()) - val content = buildContent(tree, { href -> linkResolver.resolveContentLink(descriptor, href) }) - if (kdoc is KDocSection) { - val tags = kdoc.getTags() - tags.forEach { - when (it.name) { - "sample" -> - content.append(functionBody(descriptor, it.getSubjectName())) - "see" -> - content.addTagToSeeAlso(descriptor, it) - else -> { - val section = content.addSection(javadocSectionDisplayName(it.name), it.getSubjectName()) - val sectionContent = it.getContent() - val markdownNode = parseMarkdown(sectionContent) - buildInlineContentTo(markdownNode, section, { href -> linkResolver.resolveContentLink(descriptor, href) }) - } - } - } - } - return content to { node -> } - } - - /** - * Special case for generating stdlib documentation (the Any class to which the override chain will resolve - * is not the same one as the Any class included in the source scope). - */ - fun findStdlibKDoc(descriptor: DeclarationDescriptor): KDocTag? { - if (descriptor !is CallableMemberDescriptor) { - return null - } - val name = descriptor.name.asString() - if (name == "equals" || name == "hashCode" || name == "toString") { - var deepestDescriptor: CallableMemberDescriptor = descriptor - while (!deepestDescriptor.overriddenDescriptors.isEmpty()) { - deepestDescriptor = deepestDescriptor.overriddenDescriptors.first() - } - if (DescriptorUtils.getFqName(deepestDescriptor.containingDeclaration).asString() == "kotlin.Any") { - val anyClassDescriptors = resolutionFacade.resolveSession.getTopLevelClassDescriptors( - FqName.fromSegments(listOf("kotlin", "Any")), NoLookupLocation.UNSORTED) - anyClassDescriptors.forEach { - val anyMethod = it.getMemberScope(listOf()).getFunctions(descriptor.name, NoLookupLocation.UNSORTED).single() - val kdoc = KDocFinder.findKDoc(anyMethod) - if (kdoc != null) { - return kdoc - } - } - } - } - return null - } - - fun parseJavadoc(descriptor: DeclarationDescriptor): Pair Unit> { - val psi = ((descriptor as? DeclarationDescriptorWithSource)?.source as? PsiSourceElement)?.psi - if (psi is PsiDocCommentOwner) { - val parseResult = JavadocParser(refGraph).parseDocumentation(psi as PsiNamedElement) - return parseResult.content to { node -> - parseResult.deprecatedContent?.let { - val deprecationNode = DocumentationNode("", it, DocumentationNode.Kind.Modifier) - node.append(deprecationNode, DocumentationReference.Kind.Deprecation) - } - } - } - return Content.Empty to { node -> } - } - - fun KDocSection.getTags(): Array = 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("See Also") ?: addSection("See Also", null) - val link = linkResolver.resolveContentLink(descriptor, subjectName) - link.append(ContentText(subjectName)) - val para = ContentParagraph() - para.append(link) - seeSection.append(para) - } - } - fun link(node: DocumentationNode, descriptor: DeclarationDescriptor, kind: DocumentationReference.Kind) { refGraph.link(node, descriptor.signature(), kind) } @@ -193,7 +85,7 @@ class DocumentationBuilder } fun DocumentationNode(descriptor: T, kind: Kind): DocumentationNode where T : DeclarationDescriptor, T : Named { - val (doc, callback) = parseDocumentationAndDetails(descriptor) + val (doc, callback) = descriptorDocumentationParser.parseDocumentationAndDetails(descriptor) val node = DocumentationNode(descriptor.name.asString(), doc, kind).withModifiers(descriptor) callback(node) return node @@ -491,12 +383,12 @@ class DocumentationBuilder } getter?.let { if (!it.isDefault) { - node.addAccessorDocumentation(parseDocumentation(it), "Getter") + node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Getter") } } setter?.let { if (!it.isDefault) { - node.addAccessorDocumentation(parseDocumentation(it), "Setter") + node.addAccessorDocumentation(descriptorDocumentationParser.parseDocumentation(it), "Setter") } } @@ -544,7 +436,7 @@ class DocumentationBuilder } fun TypeParameterDescriptor.build(): DocumentationNode { - val doc = parseDocumentation(this) + val doc = descriptorDocumentationParser.parseDocumentation(this) val name = name.asString() val prefix = variance.label diff --git a/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt b/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt index 8c6e5701..3d3f0b00 100644 --- a/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt +++ b/src/Kotlin/KotlinAsJavaDocumentationBuilder.kt @@ -1,14 +1,18 @@ package org.jetbrains.dokka +import com.google.inject.Inject import com.intellij.psi.JavaPsiFacade import com.intellij.psi.PsiNamedElement +import org.jetbrains.dokka.Kotlin.DescriptorDocumentationParser 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 { +class KotlinAsJavaDocumentationBuilder + @Inject constructor(val kotlinAsJavaDocumentationParser: KotlinAsJavaDocumentationParser) : PackageDocumentationBuilder +{ override fun buildPackageDocumentation(documentationBuilder: DocumentationBuilder, packageName: FqName, packageNode: DocumentationNode, @@ -22,7 +26,7 @@ class KotlinAsJavaDocumentationBuilder() : PackageDocumentationBuilder { val javaDocumentationBuilder = JavaPsiDocumentationBuilder(documentationBuilder.options, documentationBuilder.refGraph, - KotlinAsJavaDocumentationParser(documentationBuilder)) + kotlinAsJavaDocumentationParser) psiPackage.classes.filter { it is KotlinLightElement<*, *> }.forEach { javaDocumentationBuilder.appendClasses(packageNode, arrayOf(it)) @@ -30,7 +34,10 @@ class KotlinAsJavaDocumentationBuilder() : PackageDocumentationBuilder { } } -class KotlinAsJavaDocumentationParser(val documentationBuilder: DocumentationBuilder) : JavaDocumentationParser { +class KotlinAsJavaDocumentationParser + @Inject constructor(val resolutionFacade: DokkaResolutionFacade, + val descriptorDocumentationParser: DescriptorDocumentationParser) : JavaDocumentationParser +{ override fun parseDocumentation(element: PsiNamedElement): JavadocParseResult { val kotlinLightElement = element as? KotlinLightElement<*, *> ?: return JavadocParseResult.Empty val origin = kotlinLightElement.getOrigin() ?: return JavadocParseResult.Empty @@ -41,8 +48,8 @@ class KotlinAsJavaDocumentationParser(val documentationBuilder: DocumentationBui return JavadocParseResult.Empty } } - val descriptor = documentationBuilder.resolutionFacade.resolveToDescriptor(origin) - val content = documentationBuilder.parseDocumentation(descriptor) + val descriptor = resolutionFacade.resolveToDescriptor(origin) + val content = descriptorDocumentationParser.parseDocumentation(descriptor) return JavadocParseResult(content, null) } } -- cgit